#091
posted on 2023.04.23 (Sun) 2024.03.28 (Thu)

(ネイティブJavaScript版) タイムゾーンを考慮した指定日時までのカウントダウン。

オリンピックの開催日までのカウントダウンを実装することがあったので、動作するデバイスのタイムゾーンに依らず、開催地時刻で指定した日時までの正しい残り時間のカウントダウンがサイト表示されるようにJavaScriptで実装する方法のメモ。(2026年ミラノ・コルティナ冬季オリンピックの開催日2月6日00時00分00秒までのカウントダウンの実装方法。)

※ 時刻を扱うために必要な「UTC」(協定世界時)などの基礎知識は前の記事を参照。

 

 

HTMLのマークアップ

カウントダウンをJavaScriptで動的に挿入表示するための任意のHTML要素を記述。

  1. JavaScript側で取得できるようにid属性を付与して任意のHTML要素を記述。(ここでは「span」要素にid名「ag2cd」を付与。)
  2. この要素の中身をJavaScriptでカウントダウンに置換するので子要素は何も記述しない。
<span id="ag2cd"></span>

 

 

JavaScriptで実装

JavaScriptで時刻を扱うことができる「Date」オブジェクトで、目標日時の「UTC」のタイムスタンプと、動作しているいま現在の「UTC」のタイムスタンプを取得し、その差から残り時間を算出してカウントダウンを表示する。

※ DateオブジェクトのMozillaの公式ドキュメント

 

JavaScriptで日時を扱うときの注意

  • 必ず、絶対的な基準である「UTC」での時刻を目標日時として指定して実装する。
  • 「Date.now()」や「getTime()」系のメソッドは、「UTC」ではなく、動作しているシステムのローカルタイムで返すので、全く同じコードを同じ瞬間に実行しても閲覧しているデバイスに設定されているタイムゾーンによって取得されるタイムスタンプが異なる。
  • タイムゾーンを考慮せず実装すると、同じサイトを同時に閲覧していても、イタリアのタイムゾーン「UTC+01」でカウントダウンが「残り0時間00分00秒」で指定時刻に到達した瞬間に日本のタイムゾーン「UTC+09」では「残り8時間00分00秒」という表示になる。(夏時間の場合のイタリアは「UTC+02」。)

[ 「new Date(“Feb 6, 2026 00:00:00”).getTime()」で実装した場合の例 ]

  • 日本のタイムゾーンでの「2026年2月6日00時00分00秒」は「UTC+09」なので、「UTC」で「2026年2月6日09時00分00秒」のタイムスタンプが取得される。
  • イタリアのタイムゾーンでの「2026年2月6日00時00分00秒」は冬時間で「UTC+01」(夏時間で「UTC+02」)なので、「UTC」で「2026年2月6日01時00分00秒」(夏時間で「2026年2月6日02時00分00秒」)のタイムスタンプが取得される。

 

 

カウントダウンの実装

動作システムのタイムゾーンに依らず、イタリア時間「UTC+01」が「2026年2月6日00時00分00秒」(「UTC」で「2026年2月5日23時00分00秒」)になるまでの残り時間をカウントダウンして表示する方法。

 

汎用性があるように、「目標日時」と「タイムゾーンオフセット」を指定するだけで使い回せるように作成。

  1. 「目標日時」を「ISO 8601」形式で「UTC」(末尾に「Z」を記述)で指定。(処理内で時差補正するので、ここでは純粋な現地時間での目標日時「2026年2月6日00時00分00秒」を指定する。)
  2. 現地の「タイムゾーンオフセット」(時差)を指定。(イタリアの冬時間は「UTC+01」なので、ここでは「1」を指定。)
  3. 「Date.parse()」メソッドで「UTC」での「目標時刻のUNIXタイムスタンプ」を取得し、現地の時差分を補正した値を算出。
  4. 「Date()」コンストラクターで「Date」オブジェクトを生成し、「toISOString()」メソッドで「UTC」での「いま現在のUNIXタイムスタンプ」を取得。
  5. 「目標時刻のUNIXタイムスタンプ」と「いま現在のUNIXタイムスタンプ」の差分から、「残り時間」(ミリ秒)を算出。
  6. 「残り時間」から、「日」、「時間」、「分」、「秒」、「ミリ秒」をそれぞれ算出。
  7. 「日」、「時間」、「分」、「秒」は2桁、「ミリ秒」は3桁になるようにゼロパディング。
  8. 指定のHTML要素の「innerHTML」プロパティーに代入して、算出した「残り時間」を文字列として表示。
  9. 作成した関数を1ミリ秒毎に呼び出して実行してカウントダウンを更新して表示。

※ 「Date.parse()」メソッドに具体的な日時の指定する場合、JavaScriptで規定のある「ISO 8601」形式で記述する。(それ以外の日付の記述形式では動作や返り値が保証されていない。「ISO 8601」については前の記事を参照。)

※ サマータイム制度がある場合、現地時間で目標日時のときのタイムゾーンオフセットを指定する。(「UTC」の時系列での日時は普遍なので、到達した瞬間のオフセットのみが必要。)

※ タイムゾーンの一覧(Wikipedia)。

//目標日時と現地のタイムゾーンオフセットを指定
const ag2settings = {
  date: '2026-02-06T00:00:00Z', //目標日時
  offset: 1, //タイムゾーンオフセット
  ele: 'ag2cd' //カウントダウンを表示するHTML要素に付与してあるid名
};

//カウントダウンを表示するHTML要素を取得
const ag2cdEle = document.getElementById(ag2settings.ele);
//時差分を補正した「UTC」での目標時刻のUNIXタイムスタンプを取得
const ag2dateTrgt = Date.parse(ag2settings.date) - (ag2settings.offset*60*60*1000);

let ag2cdTimerId;
//残り時間を算出する関数を作成
const ag2cdFn = function(){
  //「UTC」の現在時刻のタイムスタンプを取得
  let crrntUtc = Date.parse(new Date().toISOString());

  //現在の残り時間を取得
  let leftTime = ag2dateTrgt - crrntUtc;

  //カウントダウンが終了した場合
  if(leftTime <= 0){
    clearInterval(ag2cdTimerId);
    leftTime = '残り0日00時間00分00秒000ミリ秒';
    ag2cdEle.innerHTML = leftTime;
    return;
  }

  //現在の残り時間から各値を算出
  let d = Math.floor(leftTime / (1000 * 60 * 60 * 24));
  let h = Math.floor((leftTime % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
  let m = Math.floor((leftTime % (1000 * 60 * 60)) / (1000 * 60));
  let s = Math.floor((leftTime % (1000 * 60)) / 1000);
  let ms = leftTime % 1000;
  //ゼロパディング
  h = h.toString().padStart(2, '0');
  m = m.toString().padStart(2, '0');
  s = s.toString().padStart(2, '0');
  ms = ms.toString().padStart(3, '0');

  leftTime = '残り'+d+'日'+h+'時'+m+'分'+s+'秒'+ms+'ミリ秒';
  ag2cdEle.innerHTML = leftTime;
};

//初回の実行
ag2cdFn();

//1ミリ秒毎に実行
ag2cdTimerId = setInterval(function(){
  ag2cdFn();
}, 1);
この記事のURL

https://memo.ag2works.tokyo/post-5464/

Copyコピー
この記事のタイトル

(ネイティブJavaScript版) タイムゾーンを考慮した指定日時までのカウントダウン。 | memo メモ [AG2WORKS]

Copyコピー
この記事のリンクタグ

<a href="https://memo.ag2works.tokyo/post-5464/" target="_blank" rel="noopener">(ネイティブJavaScript版) タイムゾーンを考慮した指定日時までのカウントダウン。 | memo メモ [AG2WORKS]</a>

Copyコピー
※ フィールドをクリックでコピーするテキストの編集ができます。

この記事へのコメント

コメントの書き込みはまだありません。

  • コメント内のタグはエスケープ処理され、文字列として出力されます。
  • セキュリティーのため、投稿者のIPアドレスは取得されます。
  • 管理者が内容を不適切と判断したコメントは削除されます。
  • このフォームにはスパム対策として、Googleの提供するreCAPTCHAシステムが導入されています。
    (Google Privacy Policy and Terms of Service.)