投稿日:2025年1月15日

「○○まであと何日」と表示させたい。

という要望をいただくときがあります。

ググってみると「○○まで、9000日 11時間21分21秒
とか表示するものばかり目につきます。
9000日って何年と何日なの??? と首を傾げてしまいます。

バニラJSで作ってみました。

仕様

現在の年月日、時分秒を表示してカウントします。
表示されている現在時刻は input要素datetime-local を使用しています。
表示されている「現在時刻」で日時を指定します。
現在日時、または過去の日時を設定し「日時設定」をクリックするとエラーを表示しリセット(リロード)します。
日時のため、表示されている「現在時刻」をクリックするとカウントが停止します。
「日時設定」ボタンをクリックすると「設定した日時」と「設定した日時」までのカウントダウンが表示されます。
今回の課題に則し「残り y年 dd日 hh:mm:ss」と表示されます。
うるう年の計算ロジックも実装してあるので、残りの年もうるう年を反映した数値になります。
以下がソースコードです。

<div class="compWrap">
  <div class="setWrap">
    <h1>カウントダウン</h1>
    <!-- ▼「クリア」ボタン -->
    <input id="inputDate" type="datetime-local" step="1">
    <div class="btnWrap">
       <!-- ▼「日時設定」ボタン -->
       <p class="setBtn">日時設定</p>
       <!-- ▼「クリア」ボタン -->
       <p class="clearBtn">クリア</p>
    </div>
    <div class="setDateWrap">
       <p>設定した日時</p>
       <!-- ▼「設定した日時」表示欄 -->
       <p id="setDate"></p>
       <p>まで、残り</p>
       <!-- 「残りの年月日、時分秒」表示欄 -->
       <p id="outputDate"></p>
    </div>
  </div>
</div>

▲5行目、input要素type="datetime-local" を使用しているので日時の指定ができます。
step="1" と記述することで秒までの入力が可能になります。

// ▼▼▼ ゼロ埋め関数 2桁
const zeroPad = (zeroNum) => {
  zeroNum = ("00" + zeroNum).slice(-2);
  return zeroNum;
};

// ▼▼▼ カレンダー要素を取得
const inputDate = document.querySelector("#inputDate");

// ▼▼▼ カレンダーのカウント関数(現在の年月日、時刻を取得)関数
const putDate = () => {
  current = new Date();
  (currentYear = current.getFullYear()), // 年
    (currentMonth = current.getMonth() + 1), // 月
    (currentDay = current.getDate()), // 日
    (currentHour = current.getHours()), // 時
    (currentMinute = current.getMinutes()), // 分
    (currentSecond = current.getSeconds()); // 病
  // ▼▼▼ カレンダーに表示
  inputDate.value = `${currentYear}-${zeroPad(currentMonth)}-${zeroPad(currentDay)} ${zeroPad(currentHour)}:${zeroPad(
    currentMinute
  )}:${zeroPad(currentSecond)}`;
};

// ▼▼▼ カレンダーのカウント関数実行
putDate();

// ▼▼▼ タイマーをストップするために変数に格納
const timeInterval = setInterval(putDate, 1000);

// ▼▼▼ 日付が変更(クリック)されたらタイマーをストップ
inputDate.addEventListener("click", () => {
  clearInterval(timeInterval);
});

// ▼▼▼ 日時設定ボタンクリック
document.querySelector(".setBtn").addEventListener("click", (getYears) => {
  // ▼▼▼ 再度タイマー作動。
  setInterval(putDate, 1000);

  // ▼▼▼ カレンダーの入力値を取得
  const inputDateVal = inputDate.value;

  // ▼▼▼ カレンダーの入力値から日時設定
  const setting = new Date(inputDateVal),
    settingYear = setting.getFullYear(),
    settingMonth = setting.getMonth() + 1,
    settingday = setting.getDate(),
    settingHour = setting.getHours(),
    settingMinute = setting.getMinutes(),
    settingSecond = setting.getSeconds();

  // ▼▼▼ エラー処理。日時を設定せずに「日時設定」をクリックした場合
  if (setting <= current) {
    alert("現在、過去の入力はできません。\n未来の日時を入力してください。");
    location.reload(); // リロードする
  }

  // ▼▼▼ 「設定した日時」
  const settingDate = `${settingYear}年${settingMonth}月${settingday}日 ${zeroPad(settingHour)}:${zeroPad(
    settingMinute
  )}:${zeroPad(settingSecond)}`;

  // ▼▼▼ 「設定した日時」に出力
  document.querySelector("#setDate").innerText = settingDate;

  // ▼▼▼ countDown関数
  const countDown = () => {
    leapYear = 0; // 閏年、初期化
    normalYear = 0; //平年、初期化

    // ▼▼▼ 設定日時と現在日時の差分で閏年、平年を取得。日数の定義
    for (let i = currentYear + 1; i <= settingYear; i++) {
      // ▼▼▼ 閏年、平年の日数定義
      !((i % 4 == 0 && i % 100 != 0) || i % 400 == 0)
        ? (days = 365) //平年だったら365日
        : (days = 366); //閏年だったら366日
      if (days == 366) {
        leapYear++; // 閏年、加算
      } else {
        normalYear++; //平年、加算
      }
    }

    // ▼▼▼ 設定日時と現在日時の差分を取得 ※+1000にしないと1秒ずれる
    restMilliseconds = setting.getTime() - current.getTime() + 1000;

    // ▼▼▼ 年の差を取得
    getYears = settingYear - currentYear;

    // ▼▼▼ 設定した年が閏年か平年かで日数の設定
    !((settingYear % 4 == 0 && settingYear % 100 != 0) || settingYear % 400 == 0)
      ? (days = 365) //平年だったら365日
      : (days = 366); //閏年だったら366日

    // ▼▼▼ 日付を取得
    getDays = Math.floor(restMilliseconds / 1000 / 60 / 60 / 24); // 累計日数
    getDay = getDays - (365 * normalYear + 366 * leapYear); // 経過日数(平年、閏年を累計から減算)

    // ▼▼▼ 経過日数が負になった場合
    if (getDay < 0) {
      getYears = settingYear - currentYear - 1; // 1マイナス
      getDay = days + getDay; //閏年、平年を判定しマイナスの日程を減算
    }

    // ▼▼▼ 時を取得
    hours = Math.floor(restMilliseconds / 1000 / 60 / 60) % 24;
    // ▼▼▼ 分を取得
    minutes = Math.floor(restMilliseconds / 1000 / 60) % 60;
    // ▼▼▼ 秒を取得
    seconds = Math.floor(restMilliseconds / 1000) % 60;
    // ▼▼▼ 出力する要素を表示
    setDateWrap = document.querySelector(".setDateWrap");

    // ▼▼▼ カウントダウン終了時
    if (seconds < 0) {
      // ▼ カウントダウン停止
      clearInterval(countDownInterval);
      alert("指定した日時になりました。\n処理を終了します。");
      location.reload(); // リロードする
    }

    // ▼▼▼ エラー処理。入力した月日が相応しくない場合。例)1/32、2/31など
    if (isNaN(getYears)) {
      alert("指定した年月日時が正しくありません。\nご確認ください。");
      location.reload(); // リロードする
      // ▼▼▼ 現在、過去を入力した場合
    }

    // ▼▼▼ カウントダウン年日時を設定
    contDownText = `${getYears}年 ${getDay}日 ${zeroPad(hours)}時間${zeroPad(minutes)}分${zeroPad(seconds)}秒`;
    document.querySelector("#outputDate").innerText = contDownText;
    return getYears;
  };

  countDown(); // 関数実行
  const countDownInterval = setInterval(countDown, 1000);
});

// ▼▼▼ クリアボタン
const clearBtn = document.querySelector(".clearBtn");
clearBtn.addEventListener("click", () => {
  location.reload(); // リロードする
});

▲少々、冗長になりましたが想定通り動いてくれます。

まとめ

ちょっとダサイ書き方になってしまいました。
Reactで作ってみようと試行錯誤してるところです。
Reactでは日時の取得に、

useEffect(() => {
  setInterval(() => {
    const current = new Intl.DateTimeFormat("ja-JP", {
      year: "numeric",
      month: "2-digit",
      day: "2-digit",
      hour: "2-digit",
      minute: "2-digit",
      second: "2-digit",
    });

    setDate(`${current.format(new Date())}`);
  });
}, []);

のように書くのがモダンのようです。
スマートは書き方を心掛けたいと思います。

完成したら紹介します。

なお、本記事で紹介しているソースコードはご自由にお使いいただいても構いませんが、支障が生じても責任は負いませんのでご了承いただきますようお願いいたします。

最後まで読んでいただき、ありがとうございました。

関連記事
Pocket