順番に読み進めながら学べます

タイマーと日付 — setTimeout / Date / Intl

時間をおいて関数を呼ぶ setTimeout、繰り返し呼ぶ setInterval の止め方、Date の作り方と月が 0 から始まる数え方、Intl.DateTimeFormat による日付の表示、日数の差の求め方を扱います。

通知を 3 秒後に閉じる、残り秒数を 1 秒ごとに減らす処理は、上の行から順に進むだけでは書けません。公開日に曜日を付けて表示したり、期限までの日数を数えたりする処理も、日付が文字列のままではできません。

この記事では、タイマーを作る setTimeoutsetInterval、日時を表す DateIntl.DateTimeFormat を扱います。

予約して後から呼ぶ — setTimeout と clearTimeout

写真の共有サイトで「アップロードしました」の通知を出し、3 秒後に閉じたいとします。上の行から順に進むコードには「3 秒たってから次を動かす」書き方が無く、for を空回りさせて時間をつぶすと、その間はクリックも画面の更新も止まります。

setTimeout(関数, ミリ秒) は関数を呼ぶ時刻を予約するだけで、すぐに次の行へ進みます。戻り値は fetch のタイムアウトで使ったタイマー ID で、clearTimeout に渡すと予約を取り消せます。

// ms ミリ秒後に fulfilled になる Promise を返す(Promise の記事の delay から value を省いた形)
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

// 100ms 後に通知を閉じるよう予約し、すぐ次の行へ進む(実際の画面では 3000ms など)
setTimeout(() => console.log("通知を閉じました"), 100);   // 通知を閉じました(2 番目)
console.log("アップロードしました");                        // アップロードしました(1 番目)

// 画面を離れたので、おすすめの表示の予約を取り消す。100ms たっても呼ばれない
const recommendTimer = setTimeout(() => console.log("おすすめの写真を表示しました"), 100);
clearTimeout(recommendTimer);

// 200ms たつまで、この行から先へ進まない
await delay(200);
console.log("200ms 待ちました");                            // 200ms 待ちました(3 番目)
書いた順と表示される順がずれる
0msアップロードしました1 番目に表示予約の行は何も出ない0msおすすめを取り消す100ms たっても呼ばれない100ms通知を閉じましたawait で待つ間に2 番目に表示200ms200ms 待ちましたawait の次の行が3 番目に表示
setTimeout の行では何も表示されず、取り消した予約は呼ばれません。予約した関数は、await で待つ間の 100ms の時点で動きます

予約した関数は、コードが最後の行まで進んだ後にも動きます。予約した表示を練習問題のコンソールで確かめるときは、Promise の記事と同じく、最後に await delay(…) で待つ行を置きます。

メールアプリで、送信を遅らせて取り消せるようにします。delay は宣言済みです。

① 「1 通目を送信しました」の表示を 150ms 後に予約し、50ms 待って取り消してください。

② 「1 通目の送信を取り消しました」と表示してください。

③ 「2 通目を送信しました」の表示を 100ms 後に予約し、150ms 待って取り消してください。

④ 「2 通目は取り消せませんでした」と表示してください。

(正しく実行できれば解説が表示されます)

JavaScript / TypeScript エディタ

コードを実行してください

繰り返しを止める — setInterval と clearInterval

セールの終了まで、残り秒数を 1 秒ごとに減らして表示したいとします。setTimeout は予約した関数を 1 回しか呼ばないので、繰り返すには呼ばれた関数の中で毎回予約し直すことになり、止める条件もその中に散らばります。

setInterval(指定したミリ秒ごとに、同じ関数を繰り返し呼ぶ関数)は、戻り値のタイマー ID を clearInterval に渡すまで呼び続けます。

繰り返しが終わってから次の行を動かすには、関数を Promise で包み、clearInterval を呼ぶ if の中で続けて resolve を呼びます。

// 残り秒数を 100ms ごとに 1 つ減らす(実際の画面では 1000ms ごと)
function startCountdown(seconds) {
  return new Promise((resolve) => {
    let remaining = seconds;                  // 呼ばれる関数の外に置き、呼び出しをまたいで残す
    const timerId = setInterval(() => {
      remaining -= 1;
      console.log(`残り ${remaining} 秒`);     // 残り 2 秒 → 残り 1 秒 → 残り 0 秒
      if (remaining === 0) {
        clearInterval(timerId);               // 0 になった回で止める(呼ばれる時点で timerId は代入済み)
        resolve();                            // 止めたことを await の行へ知らせる
      }
    }, 100);
  });
}

await startCountdown(3);
console.log("セールは終了しました");          // セールは終了しました
resolve まで届くと await が進む
呼び出し側 — await startCountdown(3) の行
  • 返された Promise が fulfilled になるまで、次の行へ進まない
  • セールは終了しましたresolve の後に表示される
startCountdown(3) の中 — 1 回だけ動く
  • let remaining = seconds — ここで 1 回だけ作られ、3 が入る
  • const timerId = setInterval(...) — 止めるときに渡す ID
100ms ごとに呼ばれる関数 — 3 回動く
  • remaining -= 1 — 外側の変数を減らす: 2 → 1 → 0
  • 0 になった回で clearInterval(timerId)resolve()
外側の remaining は 1 回だけ作られ、呼ばれるたびに減ります。0 になった回の resolve で、呼び出し側の await が進みます

let remaining = seconds を呼ばれる関数の中へ移すと、呼ばれるたびに 3 から作り直されて 0 にならず、clearIntervalresolve も呼ばれません。進まないまま 15 秒たつと、練習問題のコンソールは実行がタイムアウトしたと表示します。

止め忘れた繰り返しが後の実行に混ざる

練習問題のコンソールは、この記事のすべての実行で同じページを使います。clearInterval を呼ばずに実行を終えると、その関数はページを再読み込みするまで呼ばれ続け、後から実行した演習のうち await で待つものの結果に表示が混ざって、正しいコードでも不正解になります。混ざったときは、記事のページを再読み込みしてください。

倉庫の在庫数を 100ms ごとに読み取り、3 回で止めます。watchStock の枠、在庫数の配列 readingsdelay は宣言済みです。

① 100ms ごとに readings を順に読み、「1 回目: 在庫 12 個」の形で表示してください。

② 3 回目で止め、Promise を fulfilled にしてください。

③ 完了を待ち、4 回目が出ないか 200ms 待って確かめてください。

④ 「在庫の確認を終えました」と表示してください。

JavaScript / TypeScript エディタ

コードを実行してください

日付を表示用の文字列にする — getMonth と Intl

動画の一覧に、公開日時を「2026年9月2日(水)」の形で表示したいとします。API から届く "2026-09-02T09:00:00" のような文字列から slice で月を切り出すと "09" の文字列になり、曜日はどこにも書かれていないので、文字列のままでは求められません。

Date(日時を表す組み込みのオブジェクト)は、"2026-09-02T09:00:00" を端末のタイムゾーン(UTC という基準の時刻との時差で決まる地域の時刻)の 9 時として読み、get で始まるメソッドで月や曜日を数値で返します。

Intl.DateTimeFormat(言語ごとの表記で日時を文字列にするクラス)は、"ja-JP"(日本語)を渡して作ります。

const publishedAt = new Date("2026-09-02T09:00:00");

// get で始まるメソッドは数値を返す。月は 1 月が 0、曜日は日曜が 0
console.log(publishedAt.getMonth(), publishedAt.getDate(), publishedAt.getDay());   // 8 2 3

// 言語と、表示する項目ごとの形を渡して書式を作る
const dateFormat = new Intl.DateTimeFormat("ja-JP", {
  year: "numeric",     // 年を数字で
  month: "long",       // 月を「9月」の形で
  day: "numeric",      // 日を数字で
  weekday: "short",    // 曜日を短い名前で
});
console.log(dateFormat.format(publishedAt));   // 2026年9月2日(水)
console.log(new Intl.DateTimeFormat("ja-JP", { hour: "numeric", minute: "2-digit" }).format(publishedAt));   // 9:00

// 時刻を省いた文字列は、UTC の 0 時として読まれる
const dateOnly = new Date("2026-09-02");
console.log(dateOnly.getDate());   // 日本では 2、ニューヨークでは 1(前日の 20 時)
時刻を省くと UTC の 0 時になる
"2026-09-02T09:00:00"端末のタイムゾーンの 9 時として読むニューヨークでも9 月 2 日 9:00getDate() は 2東京と同じ日"2026-09-02"時刻を省くUTC の0 時として読むニューヨークでは9 月 1 日 20:00getDate() は 1前日になる
時刻まで書いた文字列は、どのタイムゾーンでも 9 月 2 日の 9 時です。時刻を省くと UTC で読まれ、時差によっては前日になります

日本で実行すると 2 が返るので、この違いには気づきにくいです。API から届く文字列が日付だけのときは "T00:00:00" を付けて渡し、端末のタイムゾーンの 0 時として読みます。下の表は、publishedAt に対する get で始まるメソッドの戻り値をまとめたものです。

メソッドpublishedAt での戻り値表示に使うとき
getFullYear()2026そのまま使える
getMonth()81 月が 0 なので、1 を足して 9 月
getDate()2日は 1 から数えるので、そのまま使える
getDay()3日曜が 0 なので、3 は水曜
getHours()90〜23 の 24 時間表記

歯科医院の予約画面で、予約日時を表示用の文字列にします。予約日時 reservedAt は宣言済みです。

get で始まるメソッドで、予約日を「10月5日」の形で表示してください。

② 月・日・曜日を「10月5日(月)」の形で表示する、日本語の書式を作ってください。

③ ② の書式で、予約日を表示してください。

④ 時と分を「14:30」の形で表示する書式を作り、予約の時刻を表示してください。

JavaScript / TypeScript エディタ

コードを実行してください

期限までの日数を数える — Date の引き算

図書館のサイトで、返却期限まであと何日かを表示したいとします。日の数字だけを引くと、9 月 2 日に借りて期限が 10 月 3 日なら 3 - 2 で 1 日になり、月をまたいだ分の日数が数えられません。

Date は 1970 年 1 月 1 日 0 時(UTC)からのミリ秒を数値で持ち、Date どうしの引き算はその差を返すので、1 日のミリ秒 24 * 60 * 60 * 1000 で割ると日数になります。

その日の 0 時を表す new Date(年, 月, 日) でも、月は 0 から数えます。

// 1 日のミリ秒(24 時間 × 60 分 × 60 秒 × 1000)
const DAY_MS = 24 * 60 * 60 * 1000;

// 貸出日と返却期限。数値で作るときも、月は 0 から数える
const borrowedAt = new Date(2026, 8, 2);     // 2026 年 9 月 2 日
const dueDate = new Date(2026, 9, 3);        // 2026 年 10 月 3 日

// Date どうしの引き算は、ミリ秒の差になる
const diffMs = dueDate - borrowedAt;
console.log(Math.round(diffMs / DAY_MS));    // 31

// 9 月に無い 31 日を渡すと、エラーにならず翌月へ繰り上がる
const overflowDate = new Date(2026, 8, 31);
console.log(overflowDate.getMonth() + 1, overflowDate.getDate());   // 10 1
9 月 30 日のつもりで書いた 3 通り
期限 - new Date(2026,8, 2) を日数にnew Date(2026, 8, 30)new Date(2026, 9, 30)new Date(2026, 8, 31)9 月 30 日が作られる10 月 30 日月の 9 は 10 月10 月 1 日9 月 31 日は無い28 日58 日30 日多い29 日1 日多い
どれも期限を 9 月 30 日にしたつもりの書き方です。月に 9、日に 31 を渡してもエラーにならず、日数だけがずれます

Math.round で丸めるのは、夏時間(夏の間だけ時計を 1 時間進める制度)のある地域では 1 日が 23 時間や 25 時間の日があり、割った結果が 30.958… のように整数にならないことがあるためです。日本で実行すると割り切れるので、丸め忘れには気づきにくくなります。

キャンプ場の予約サイトで、泊数を数えます。toDatecountNights の枠と DAY_MS は宣言済みです。

toDate で、1 から数えた月の日付を Date にして返してください。

countNights で、2 つの Date の差から泊数を返してください。

③ 2026 年 9 月 28 日〜10 月 2 日の泊数を「4 泊」の形で表示してください。

④ 2026 年 12 月 29 日〜翌年 1 月 3 日の泊数も同じ形で表示してください。

JavaScript / TypeScript エディタ

コードを実行してください
QUIZ

理解度チェック

まずは1問ずつ答えてみましょう。

Q1setInterval に渡す関数の中で let count = 0; を宣言し、3 で止めるようにすると?

Q2new Date("2026-09-02T09:00:00").getMonth() の戻り値は?

Q3new Date(2026, 8, 31) で作った Date の getDate() は?