世界時計(米国サマータイム対応)

[NC4.5/MSIE4.0対応]
前回の世界時計では夏時間には対応していませんでした。 調べてみるとサマータイムのルールは、そんなに難しくありません。 まずはアメリカのサマータイムに対応してみることにします。
東京
ニューヨーク

実際にJavaScriptのソースを説明していく前に、サマータイムについて 少し知識を蓄えておきましょう。

日本では「サマータイム」として呼ばれることの多いですが、英語では どちらかというとDaylight Saving Time(DST)と書かれることが多いようです。 夏の期間、時計を1時間進めて昼間の時間を長くします。

世界約70ヶ国で実施されていて、その地域にはアメリカ、ヨーロッパ諸国に 加えてオーストラリア、ニュージーランド、 ブラジル、チリ、モンゴル、エジプト、リビア、イラク、イラン、トルコなど が含まれます。しかし、サマータイムの実施期間は国によってバラバラです。

今回対応したのはアメリカのサマータイムは1986年から実施されている方式で、 4月の最初の日曜日の午前2:00からサマータイムを開始し、時計を1時間進めます。 そして、10月の最終日曜日の午前2:00にサマータイムを終了し、時計を1時間 戻します。

たとえば、1999年のサマータイムは、1996年4月4日(日)に始まり、1999年10月31日(日)に 終わります。

蛇足ですが、アメリカでサマータイムが実施されているといっても、アメリカ 全体が対象地域なわけではなく、ハワイやアラスカは対象外です。また、 インディアナ州はサマータイムを実施していません。さらに見ていくと、 インディアナ州全部がサマータイムを実施していないわけではなく、隣の州と 隣接するカウンティの一部はサマータイムを実施していたりします。 面白いですねぇ。

1999年4月4日のGMT-5の時刻 1:551:561:571:581:592:002:012:02
1999年4月4日のニューヨークの現地時刻 1:551:561:571:581:593:003:013:02

1999年10月31日のGMT-5の時刻 0:550:560:570:580:591:001:011:02
1999年10月31日のニューヨークの現地時刻 1:551:561:571:581:591:001:011:02

上の表は、サマータイムの切り替え日の時刻がどのように変っていくのかを 示したものです。サマータイム開始日には、2:00になった瞬間に時計を1時間 進め3時にします。そういうわけで2:00台は存在しません。そして、サマータイム が終わる日には、サマータイムを実施している時計が2:00になった時に 1時間時計を戻します。なので、1:00台が2回繰り返されます。

タイムゾーンから割り出した時刻は表の1段目の時刻ですから、その時間で 開始終了時刻を表すと、サマータイム開始は、2:00からで、終了は1:00と いうことになります。

ここまででわかったことをJavaScriptで実現すると以下のようになります。

まず、サマータイム開始日、終了日の計算に用いる年(今日は何年か)を取得し、 変数yearに取っておきます。

today = new Date();
year = today.getYear();
if (year < 100) {
	year += 1900;
}
次にサマータイム開始日を取得します。この機能はまとまりがいいので、 関数GetDstStart()として切り出します。 サマータイム開始日は、4月の第1日曜日なので、4月1日から7日までの間になるはずです(8日以降なら1度は日曜日になっているはず)。 4月1日2:00に対応するDateオブジェクトを作り、それからsetDate(i)で日付だけを 変更し、4月2日2:00、4月3日2:00、4月4日2:00…としていきます。ここで、 その曜日をgetDay()を使って取得し、それが日曜日(0 ゼロ)の場合はループを 打ち切ります。そうすると、dst_startは4月第1日曜日2:00amを表す時刻が 入っていますから、getTime()で内部表現の数値に変換して返します。

結果は変数pos_startに取っておきます。

pos_start = GetDstStart(year); // 呼び出す方

// サマータイム開始日時の内部表現を取得する関数
function GetDstStart(arg_year) {
	var dst_start = new Date(arg_year, 3, 1, 2, 0, 0); // 第2引数の3は、4月を表す。
	for (var i = 1; i <= 7; i++) {
		dst_start.setDate(i);
		if (0 == dst_start.getDay()) {
			break;
		}
	}
	return dst_start.getTime();
}
同様にサマータイム終了日時を取得する関数を作っておき、それを 呼び出します。今度は10月の最終日曜日を調べなければなりません。 10月31日から1日ずつ減らしながら、日曜日かどうかを確認し、 その日の1:00の内部表現を返します。 この関数を呼び出して、結果をpos_endに入れておきます。
pos_end = GetDstEnd(year);

// 引数arg_yearで指定された年のサマータイム終了時刻を内部表現で返す。
//
function GetDstEnd(arg_year) {
	var dst_end = new Date(arg_year, 9, 31, 1, 0, 0);
	for (var i = 31; i > 24; i--) {
		dst_end.setDate(i);
		if (0 == dst_end.getDay()) {
			break;
		}
	}
	return dst_end.getTime();
}
ここまでで準備はOKです。サマータイムの処理を行うのは、次の条件が 成り立つときです。
  1. そもそもサマータイム処理が必要な場所であること。
  2. 現在の日時が、今年のサマータイム開始日時よりもあとであること。
  3. 現在の日時が、今年のサマータイム終了日時より前であること。
条件1は、都市によって違うので、関数nowat()に引数を1つ追加して、 第3引数が0以外のとき(1を想定)に、サマータイム処理が必要とします。 次に条件2,3は、先ほど変数pos_start, pos_endに取っておいたサマータイム 開始、終了日時の内部表現と、現在時刻の内部表現を比較することで 実現します。

以上の3点の条件が満たされたときに、現地の時刻に1時間足せば、サマータイム に対応できるというわけです。

function nowat(now_t, tz, st) {
	var hour, min, sec;

	var pos_t = now_t + (tz_offset + tz) * 60 * 1000;

	if (0 != st) {
		// サマータイム処理
		if ((pos_start <= pos_t) && (pos_end > pos_t)) {
			pos_t += 60 * 60 * 1000;
		}
	}
	var t = new Date();
	t.setTime(pos_t);

	hour = t.getHours();
	min = t.getMinutes();
	sec = t.getSeconds();
	if (hour < 10) {
		hour = "0" + hour;
	}
	if (min < 10) {
		min = "0" + min;
	}
	if (sec < 10) {
		sec = "0" + sec;
	}
	return hour + ":" + min + ":" + sec; 
}

毎秒ごとに時計表示を書き換えるときには前の世界時計と同様に nowat()を呼び出します。サマータイム処理が必要のない東京のような場合は 第3引数に0を指定し、サマータイムが必要な都市の場合は第3引数に1を指定します。

function update_watch() {
	now = new Date();
	n_t = now.getTime();
	jp = nowat(n_t, tz_jp, 0);
	ny = nowat(n_t, tz_ny, 1);

	document.forms[0].elements[0].value = jp;
	document.forms[0].elements[1].value = ny;

	setTimeout('update_watch()', 999); // 1000msec = 1sec
}

世界時計スクリプト全ソース
<FORM METHOD="post">
<BLOCKQUOTE>
<TABLE>
<TR><TD>東京<TD><INPUT NAME="tokyo" SIZE=8>
<TR><TD>ニューヨーク<TD><INPUT NAME="newyork" SIZE=8> 
</TABLE>
</BLOCKQUOTE>
</FORM>

<SCRIPT LANGUAGE="JavaScript">
// 引数arg_yearで指定された年のサマータイム開始時刻を内部表現で返す。
//
function GetDstStart(arg_year) {
	var dst_start = new Date(arg_year, 3, 1, 2, 0, 0); // 第2引数の3は、4月を表す。
	for (var i = 1; i <= 7; i++) {
		dst_start.setDate(i);
		if (0 == dst_start.getDay()) {
			break;
		}
	}
	return dst_start.getTime();
}

// 引数arg_yearで指定された年のサマータイム終了時刻を内部表現で返す。
//
function GetDstEnd(arg_year) {
	var dst_end = new Date(arg_year, 9, 31, 1, 0, 0);
	for (var i = 31; i > 24; i--) {
		dst_end.setDate(i);
		if (0 == dst_end.getDay()) {
			break;
		}
	}
	return dst_end.getTime();
}


// まず今が何年か調べます。
today = new Date();
year = today.getYear();
if (year < 100) {
	year += 1900;
}
// ここまでで、yearに今年の年(1998など)が入っています。

var tz_offset = today.getTimezoneOffset();



pos_start = GetDstStart(year);
pos_end = GetDstEnd(year);

tz_jp = 9 * 60; // GMT+9
tz_ny = -5 * 60; // GMT-0500


function nowat(now_t, tz, st) {
	var hour, min, sec;

	var pos_t = now_t + (tz_offset + tz) * 60 * 1000;

	if (0 != st) {
		// サマータイム処理
		if ((pos_start <= pos_t) && (pos_end > pos_t)) {
			pos_t += 60 * 60 * 1000;
		}
	}			
	var t = new Date();
	t.setTime(pos_t);

	hour = t.getHours();
	min = t.getMinutes();
	sec = t.getSeconds();
	if (hour < 10) {
		hour = "0" + hour;
	}
	if (min < 10) {
		min = "0" + min;
	}
	if (sec < 10) {
		sec = "0" + sec;
	}
	return hour + ":" + min + ":" + sec; 
}

function update_watch() {
	now = new Date();
	n_t = now.getTime();
	jp = nowat(n_t, tz_jp, 0);
	ny = nowat(n_t, tz_ny, 1);

	document.forms[0].elements[0].value = jp;
	document.forms[0].elements[1].value = ny;

	setTimeout('update_watch()', 999); // 1000msec = 1sec
}

update_watch();
</SCRIPT>