📅

具体例でDay.jsと標準Dateクラスを比較する

2024/11/21に公開

Day.js

Day.jsはJavascriptの軽量な日付操作ライブラリです。
シンプルなAPI・Immutable性・i18nを備えており、githubでのスター数は47,000を超えています。

今回は日付関連のよくありそうな仕様をDay.jsとJavascriptの標準Dateクラスの両方で実装し、比較します。

簡単な例だと差がわかりにくいので、ある程度現実にありそうな例にします。

今年の残り営業日

与えられた日付に対して、その年の残った平日の日数を返します。簡単のため、祝日などは考えません。

Day.js

const getRemainingBusinessDaysDayjs = (date) => {
  const startDate = dayjs(date);
  const endDate = dayjs(date).endOf("year");
  const totalDays = endDate.diff(startDate, "day") + 1;

  return new Array(totalDays)
    .fill()
    .map((_, index) => startDate.add(index, "day"))
    .filter((date) => {
      const dayOfWeek = date.day();
      return dayOfWeek !== 0 && dayOfWeek !== 6;
    }).length;
};

console.log(getRemainingBusinessDaysDayjs("2000-06-01"));
// 152

Date

const getRemainingBusinessDaysDate = (date) => {
  const startDate = new Date(date);
  const endDate = new Date(startDate.getFullYear(), 11, 31);
  const totalDays =
    Math.floor((endDate - startDate) / (1000 * 60 * 60 * 24)) + 1;

  return Array.from({ length: totalDays })
    .map((_, index) => {
      return new Date(
        startDate.getFullYear(),
        startDate.getMonth(),
        startDate.getDate() + index
      );
    })
    .filter((date) => {
      const dayOfWeek = date.getDay();
      return dayOfWeek !== 0 && dayOfWeek !== 6;
    }).length;
};

どちらも今年の残っている日付の配列を作成し、それぞれの要素で平日かどうか判定しています。

記述量という点だと両者にさほど大きな差はないのですが、Day.jsのendOf, diff, addなどの地味なユーティリティが便利です。
全体的に、Dateクラスは日付の減算・加算がつらい印象があります。

ただ、これだけならまだDateクラスを使ってもさほど問題はないのかなと思います。

国際フライトの出発/到着時刻

Day.js

タイムゾーンの異なる国から国へと飛行機でフライトするようなケースで、発着時刻をUTCと現地時間で取得します。

// dayjs
const dayjs = require("dayjs");
const timezone = require("dayjs/plugin/timezone");
const utc = require("dayjs/plugin/utc");

dayjs.extend(timezone);
dayjs.extend(utc);

const getFlightInformationDayjs = (
  fromTz,
  toTz,
  departureLocalTime,
  flightTimeHours
) => {
  const departure = dayjs.tz(departureLocalTime, fromTz);
  const arrival = departure.add(flightTimeHours, "hours").tz(toTz);

  return {
    departureLocalTime: departure.format("YYYY-MM-DD HH:mm"),
    departureUtcTime: departure.utc().format("YYYY-MM-DD HH:mm"),
    arrivalLocalTime: arrival.format("YYYY-MM-DD HH:mm"),
    arrivalUtcTime: arrival.utc().format("YYYY-MM-DD HH:mm"),
  };
};

console.log(
  getFlightInformationDayjs(
    "Asia/Tokyo",
    "America/New_York",
    "2000-01-01 00:00",
    12
  )
);
// {
//   departureLocalTime: '2000-01-01 00:00',
//   departureUtcTime: '1999-12-31 15:00',
//   arrivalLocalTime: '1999-12-31 22:00',
//   arrivalUtcTime: '2000-01-01 03:00'
// }

dayjs.tz()でタイムゾーンを持った日付のオブジェクトを生成、そこからutc()を呼び出してUTCへの変換しています。

タイムゾーンに関連するパッケージはプラグイン化されているので別途インポートする必要がありますが、実装はかなりシンプルでそのほか特筆すべき点はありません。

Date

標準Dateクラスでの実装例を紹介しますが、それなりに長いです。

Dateクラスでの実装
const getFlightInformationDate = (
  fromTz,
  toTz,
  departureLocalTime,
  flightTimeHours
) => {
  const getTimezoneOffset = (date, timeZone) => {
    // 12:00:00 PM GMT+9という形式のstringが出力される
    const tz = date.toLocaleString("en-US", {
      timeZone,
      timeZoneName: "shortOffset",
    });
    // 数値部分を取得する
    return parseInt(tz.split("GMT")[1], 10);
  };

  const format = (date) => {
    const year = date.getFullYear();
    const month = String(date.getMonth() + 1).padStart(2, "0");
    const day = String(date.getDate()).padStart(2, "0");
    const hours = String(date.getHours()).padStart(2, "0");
    const minutes = String(date.getMinutes()).padStart(2, "0");

    return `${year}-${month}-${day} ${hours}:${minutes}`;
  };

  const departure = new Date(departureLocalTime);
  const departureUtc = new Date(
    departure.getTime() - getTimezoneOffset(departure, fromTz) * 60 * 60 * 1000
  );

  const arrivalUtc = new Date(
    departureUtc.getTime() + flightTimeHours * 60 * 60 * 1000
  );
  const arrival = new Date(
    arrivalUtc.getTime() + getTimezoneOffset(arrivalUtc, toTz) * 60 * 60 * 1000
  );

  return {
    departureLocalTime: format(departure),
    departureUtcTime: format(departureUtc),
    arrivalLocalTime: format(arrival),
    arrivalUtcTime: format(arrivalUtc),
  };
};

Dateクラスはタイムゾーンを保持しておらず、toLocaleStringでタイムゾーンを指定することではじめてタイムゾーンに絡めた実装が可能になります。
ここでは、タイムゾーンを指定して出力されたstringからUTCとのオフセットをどうにか取得してUTCへの変換をしています。
若干ハックな印象は受けますが、Dateクラスのみを使う場合妥当なアプローチだと思います。

また日付のフォーマットについても記述量が多いのはそうですが、悪名高いgetMonth() + 1[1]などが気になってくるところです。

複雑なタイムゾーンを扱う場合だとDay.jsはかなり検討の余地がありそうです。

おわりに

他にちょうどいい例が思いつかなかったのでおわりです。

Dateクラスはどこでも使えてかつ慣れているので引力が強いですが、多少複雑なことをしようと思うと可読性の低さやイミュータブルではないことが問題になることがあり、そういった場面では積極的にDay.jsを採用していきたいです。

ありがとうございました。
X (Twitter): @koyo_k0
https://github.com/koyo221/dayjs-comparison

脚注
  1. getMonthは0を起点とした月を返すため、1月の場合0になる ↩︎

Discussion