延長機能付きカウントダウン


■Q&A

■Question

作って欲しいですー。 2002.5.11.Sat No.3445

もしお暇であれば、作って頂きたいJAVAがあります・・・。

カウントダウンJAVAなのですが、普通のカウントダウンは特定の日にちを指定してその日までカウントするタイプだと思います。
例えば「2000年まで後 45 日」の様な表示になると思います。

そうではなくて30日間のカウントダウンをして、1日の次に0日ではなくて、再び30日のカウントダウンをし直して欲しいのです。
つまり特定の日にちを指定し、その日までのカウントダウンではなくて、30日間を純粋にカウントし、また再び永遠に30日間をカウントし続けるプログラムです。

いくら探しても特定の日を指定してのカウントダウンJAVAしか無く、もうここにお願いするしか無いと思い、お願いに上がりました・・・。
簡単な物だとは思いますが如何せん知識不足でして・・・どうかお願いいたします。

■Answer

Re:作って欲しいですー。 ぴろあき 2002.5.11.Sat No.3449

一見簡単そうに思えて、実はえらい難しかったりするのが「プログラム」の妙。

カウントダウンは「目標日までの日数を求める」ことで計算しますが、それを過ぎたら、新しい目標日(つまり30日後)を設定しないといけません。「2000年まであとx日」も、2000年を過ぎたらおしまいですよね。

ここでの課題は、「目標日を過ぎたら新しい目標日を自動で設定する」ことです。
「次の目標日まであと何日か?」「目標日を過ぎたか?」を計算するためには、比較するための基準値(たとえば「前の目標日」など)が必要になるわけですが、ここで JavaScript の弱点が出てきます。

基準値は可変であるため、どこかに記録しておかないといけないのですが、JavaScript はその仕様上、cookie 以外にデータを保存する手段がありません。しかし、cookie に記録してしまっては、閲覧者ごとに基準値が異なってしまいます。カレンダーが30日周期なら問題なかったんですけどね。(笑)

そういうわけで、JavaScript では全自動30日カウントダウンプログラムは無理なのです。「毎月15日」なら簡単なんですけど…。可能だとすれば「30日ごとに基準値を手動で設定する」しかなく、でもそれじゃ全然意味ないですしね。

 ※自分もまだまだ未熟であり、あくまでも己の知識内で回答しています。
  もしかしたら可能かもしれないので、信じこむと馬鹿を見るかも…。

■Question

Re:Re:作って欲しいですー。 2002.5.12.Sun No.3454

なるほど、プログラムの妙ですね・・・(笑
私も作って下さいとお願いしてから一人で色々と考えたのですが、CGIプログラムではないので、カウントダウンの日数を記憶する手段がないですよね・・・良く考えたら。
やはり日にちの指定が一番なのでしょうかねー・・・。

で一つ考えたのですが、指定日をいくつも設定してカウントダウンする事なら可能でしょうか?
例えば「6月11日・7月9日・8月10日」と言ったカンジに月一回を一年間(12個)分程です。つまり6月11日までのカウントダウンが終了すると、自動的に7月9日までのカウントダウンへと移行するワケです。(それが終われば勿論8月10日のカウントへ移行)

使用例としては「月一度の健康診断まで後 x 日」みたいになるでしょうか?

それならば、簡単に出来るでしょうか・・・?
どうぞ宜しくお願いいたします〜。

             >ぴろあき様(お返事ありがとうございます)

■Answer

Re:Re:Re:作って欲しいですー。 ぴろあき 2002.5.12.Sun No.3457

それならバッチリ可能です。ちょっと作ってみますが、ちょっと時間かかりそうなので、2〜3日ほどお待ちください。(これでもまだ簡単にはいかないんです)
できあがったらこの掲示板でお知らせします。

あと、自分も色々考えてみたんですけど、手法の工夫次第で JavaScript でも「一定間隔でカウントダウンを繰り返す」が可能でした。自分が馬鹿見せてどうするんでしょう。
…といってもデータの保存ができない弱点が響いてるのに変わりはなく、「永遠に」とかいかないんですが…。

ついでなので、これも一緒に作ってみますね。

■Res

Re:Re:Re:Re:作って欲しいですー。 2002.5.12.Sun No.3459

ありがとうございまするーー!!
是非とも待たせて頂きますので、お願いいたしますー!(笑

記憶手段が無くても同じ日にちを繰り返しカウントする事が可能なのですか!?
ちょっと見てみたい気がします・・・。

心待ちにしております〜^^
                      >ぴろあき様

■Answer

できました。 ぴろあき 2002.5.14.Tue No.3475

お待たせしました。カウントダウンのサンプルスクリプト×2ができました。
ここに載せるには長すぎるので、別ページに用意しました。
http://faq.creasus.net/02/0514/

さて、ご希望を満たしていれば良いのですが…。

■Res

Re:できました。 2002.5.15.Wed No.3477

ありがとうございます!まさに希望通りです!
ちなみに、「一定間隔でカウントダウン」の方を使わせて頂きました。
PCやサーバーの時間に依存させて、指定日から一定間隔でカウントする方法ですかー・・・。
気が付きませんでした・・・アイデアはいっぱいですね。

何かに色々と使えそうなスクリプトなので、両方保存しておきました。

本当にお世話になりました。感謝します^^


リストを作ってカウントダウン

■リスト式:実行結果


■リスト式:スクリプト概要

あらかじめ日付のリストを作っておき、一番近い目標日までの日数をカウントダウンします。日付はいくらでも指定できますし、間隔も自由です。
「次の目標日」「その次の目標日」などの日付も取得できます。

他に「一定間隔でカウントダウン」するやつも作りましたので、お好きな方をお使いください。


■リスト式:対応ブラウザ

IE3
split() に対応してないのでエラーが起きます。
その他
よほど変なブラウザでもない限りは動くでしょう。(IE4 と Netscape3 で正常動作を確認)

■リスト式:ソース

<head>に入れる関数

以下のスクリプトを <head> の中に入れます。

<script type="text/javascript"><!--
//「リストを作ってカウントダウンする」
function listCountDown() {

  //目標日のリスト(いくつでも可、ただし日付順に並べる)
  var dateList = new Array(
    "2002/1/12", "2002/2/13", "2002/3/14", "2002/4/15", "2002/5/16", "2002/6/17",
    "2002/7/18", "2002/8/19", "2002/9/20", "2002/10/21", "2002/11/22", "2002/12/23",
    "2003/1/24", "2003/2/25", "2003/8/14", "2004/1/9", "2005/1/1", "2005/7/1", "2010/1/1"
  );

  //ary[0][0] = 2002; ary[0][1] = 1; ary[0][2] = 12;
  //のように、日付データを分割して二次元配列に入れる
  var ary = new Array();
  for (var i = 0; i < dateList.length; i++)
    ary[i] = dateList[i].split("/");

  //今日の日付を取得
  var date = new Date();
  var Y = date.getYear(), M = date.getMonth() +1, D = date.getDate();

  //古いブラウザで起きる2000年問題(懐かしい)の対策
  if (Y < 1000)
    Y += 1900;

  //ループ開始
  //1.年が同じ && 月が同じ && 日が目標日「未満」
  //2.年が同じ && 月が目標月未満
  //3.年が目標未満
  //の条件のどれかに合う配列を探す
  //1.の目標日判定を「未満」にすることで、当日になったら次の目標日がセットされる
  //見つかったら比較用の基準値を取得してループ脱出

  var rest;
  for (i = 0; i < dateList.length; i++) {
    if ((Y == ary[i][0] && M == ary[i][1] && D < ary[i][2]) || (Y == ary[i][0] && M < ary[i][1]) || Y < ary[i][0]){
      rest = Date.parse(dateList[i]);
      break;
    }
  }

  //ちゃんと取得できた?
  if (rest) {

    //取得した基準値から「残り日数」を計算
    var date = new Date();
    rest = Math.ceil((rest - date.getTime()) /86400000);

    //表示させる文字列を作る
    var returnText = "あと" + rest + "日です。";


    ////// 以下はお好みに加工してください //////


    //目標日の日付を足してみる(iには参照中のインデックスが入っている)
    var tempText = "";
    if (ary[i]) {
      tempText += "目標日の";
      if (ary[i][0] != Y) //次の目標日が今年以外だったら年も追加
        tempText += ary[i][0] + "年";
      tempText += ary[i][1] + "月" + ary[i][2] + "日まで、";
    }
    returnText = tempText + returnText + "<br>"; //→"目標日の(****年)**月**日まで、あと**日です。<br>"

    //ついでに次の目標日の日付も作ってみる(警告はステータスバーにも表示)
    tempText = "";
    if (ary[i+1]) { //次の目標日がある
      tempText += "その次の目標日は";
      if (ary[i+1][0] > Y) //次の目標日が来年だったら年も追加
        tempText += ary[i+1][0] + "年";
      tempText += ary[i+1][1] + "月" + ary[i+1][2] + "日です。<br>";
    } else { //次の目標日がない
      window.status = "これが最後の目標日です。早めにリストを修正してください。";
      tempText += window.status + "<br>";
    }
    returnText += tempText; //→"その次の目標日は(****年)**月**日です。<br>"

    //呼び出し元に文字列を返す
    return returnText;

  //エラー処理
  } else {
    window.status = "目標日リストが無効です。新しいリストに修正してください。";
    return "<strong>" + window.status + "<\/strong>";
  }
}

■HTMLのソース

以下の3行を、書き出したい場所に入れます。

<script type="text/javascript"><!--
  document.write(listCountDown());//-->
</script>


■リスト式:初期設定

var dateList = new Array(
  "2002/1/12", "2002/2/13", "2002/3/14", "2002/4/15", "2002/5/16", "2002/6/17",
  "2002/7/18", "2002/8/19", "2002/9/20", "2002/10/21", "2002/11/22", "2002/12/23",
  "2003/1/24", "2003/2/25", "2004/1/9", "2005/1/1", "2005/7/1", "2010/1/1"
);

dateList 配列に "年/月/日", "年/月/日", "年/月/日", … の形で指定します。いくつ指定しても構いません。
ただし、必ず日付順に並べてください。

目標日の追加は後ろにどんどん足していってもいいですし、過ぎた目標日はどんどん消していって構いません。目標日のリストは余裕をもたせた数を指定しておくことをおすすめします。
でもあまり多すぎるとループ処理が重くなるので、ほどほどがよろしい感じです。(最近のパソコンだったら100個も200個も変わらないですけど)


一定間隔でカウントダウン


■一定間隔式:実行結果


■一定間隔式:スクリプト概要

「基準日」「日数間隔」を指定すると、一定間隔でカウントダウンを繰り返し続けます。基準日は過去の日付ならいつでも構いません。

ただ、基準日を自動で書き換えられない都合上、時が経てば経つほど効率が下がりますので、1年〜2年に一度は基準日を変更した方がいいでしょう。


■一定間隔式:対応ブラウザ

IE3
while を使ってるのでエラーが起きると思います。
その他
よほど変なブラウザでもない限りは動くでしょう。(IE4 と Netscape3 で正常動作を確認)

■一定間隔式:ソース

<head>に入れる関数

以下のスクリプトを <head> の中に入れます。

<script type="text/javascript"><!--
function termCountDown() {

  var baseTime = "2002/1/1"; //基準日
  var countTerm = 30; //目標日の間隔
  var date;

  //基準日の秒数を取得
  date = new Date(baseTime);
  var baseSec = Math.ceil(date.getTime() /1000);

  //今日の日付の秒数を取得
  date = new Date();
  var todaySec = Math.ceil(date.getTime() /1000);

  //ループ開始
  var breakFlag, i = 0;
  var nextSec = baseSec; //次の目標日

  //基準日に日数間隔を足していく
  //今日より後の秒数になったらループ脱出
  //これで「次の目標日までの秒数」が出る
  while (nextSec <= todaySec) {
    nextSec = 86400 *countTerm *i +baseSec;
    i++;
  }

  //目標日と今日の秒数から「残り日数」を計算
  var next = Math.ceil((nextSec -todaySec) /24 /60 /60);

  //ちゃんと取れた?
  if (next) {

    //呼び出し元に返す文字列を作る
    var returnText = baseTime + "から" + countTerm + "日間隔でカウントダウンしています。<br>";
    returnText += "次の「" + countTerm + "日周期」まで、あと" + next + "日です。<br>";

    return returnText;
  } else
    return "error!";
}//-->
</script>

■HTMLのソース

以下の3行を、書き出したい場所に入れます。

<script type="text/javascript"><!--
  document.write(termCountDown());//-->
</script>


■一定間隔式:初期設定

var baseTime = "2002/1/1";
var countTerm = 30;

設定する部分はこの2ヵ所だけです。

baseTime
基準日です。この日から計算します。構造上、カウントダウンがリセットされるたびにループ内のステップがひとつずつ増えるので、定期的に書き換えてください。カウントダウンの周期にもよりますが、1年〜2年に一度くらいでいいでしょう。
ここに未来の日付を設定すると、その日までのカウントダウンをして、基準日がきたら通常のカウントダウンを繰り返します。
countTerm
カウントダウンをリセットする間隔です。ここにセットした間隔でカウントダウンを繰り返します。単位は日です。

■ちょっとした解説?

基本的な構造としては、すべてを「秒」で数えてます。JavaScript には1970年1月1日午前0時からのミリ秒を返す(当然すごい桁数になります)というメソッドがいくつかあり、これを軸に計算します。
日付を使うと、閏年の計算など、果てしなく面倒くさくなりますので…。

たとえば一定間隔でカウントダウンの場合…

1970年正月        基準日      目標日      目標日      目標日
    |              |          |          |          |
    └───────┴─────┴──┬──┴─────┴───
                                      │ ↑ここの秒数を求める
                                     今日
  1. 基準日を1970年からの秒数に変換
  2. 今日の日付を1970年からの秒数に変換
  3. 基準日の秒数から今日の秒数を引く
  4. マイナスになったらすでに過ぎてるということなので、基準日に日数間隔分の秒数を足す
  5. まだマイナスだったらさらに足す
  6. プラスになるまで繰り返す
  7. 結果がプラスだったら、それが「次の目標日」の1970年からの秒数
  8. 次の目標日から、今日の秒数を引く
  9. 秒数を日数に変換して表示

…という行程になってます。

当然、図上の「今日」はどんどん右へ行きますので、時が経てば経つほど 6. のステップ数が増えます。理論上は永遠に動きますが、同時に効率も無限に悪くなっていきます。
なので、基準日を適度に書き換え、効率を元に戻してやる必要があります。(リストを作ってカウントダウンは、リストの切れ目がプログラムの終わりになりますが)

他言語のようにデータを記録する仕組みがあれば、基準日を書き換えて「効率を落とさず永遠にカウントし続ける」が可能なんですけどね。

ちなみに、以上によって本当の「基準日」とは1970年の正月である、が判明します。(笑)


■その他

なにぶん急ごしらえな上に充分な確認も取れてないので、バグが混じってる可能性は低くないです。あるとしたら、エラーにならず不思議な勘違いをすると思われます。(カウントが1日ずれてる…など)
うまく動かない部分があったらご報告ください。
動作テストは、パソコンの時計を進めてズルしましょう。

表示させる文字列等、細かい部分はお好きにカスタマイズしてください。


■追記:2003.8.14.Thu

HTMLを Strict に校正しながら、リストを作ってカウントダウンの日付判定ループは、配列をひとつずつ Date.parse() して比較した方がはるかに簡単なのでは…と気づく。頭悪。


■ このページについて

このページは、質疑応答掲示板で使用した回答の残骸です。検索エンジンに捕まったりリサイクルしたりすることもあろうので、なんとなく残してあります。広く公開しているページではありませんが、第3者の閲覧を禁止するものでもありません。
恒久的なURIは保証できないものの、よほどのことがない限り削除されることはないでしょう。心配性な人はページの保存をおすすめします。(IEは mht 形式でないと保存できないかも)