../tempo-javascript-library-thoughts

最近気になっている日付操作ライブラリ「Tempo」を触ってみる

はじめに

最近少しずつ話題として見かけるようになった JavaScript 製日付操作ライブラリの Tempo を軽く触ってみたいと思います。

Tempo

Tempo は 日付操作型のライブラリで、類似ライブラリとして day.jsdate-fnsluxon が有名です。

Tempo is best thought of as a collection of utilities for working with the native Date object

公式ドキュメントにこのように記載されているように、Tempo は JavaScript の Date object を操作するためのユーティリティ関数を提供しています。

他の日付操作ライブラリではライブラリ独自の日付型を扱っているものもありますが、Tempo は JavaScript ネイティブの Date object を扱う点が特徴となっており、素直な使い心地と軽さと速さを併せ持っていることを売りとしているようです。

ちょっと中身を読んでみる

Tempo が Date object 操作のためのユーティリティ関数を提供しているということだったのでどんな感じなのか中身を少しみてみます。

addDay

ひとまず日付を操作する処理の一つである addDay からみてみます。 公式ドキュメントには次のように説明されています。

Returns a new Date object with a positive or negative number of days applied to date argument. To subtract days, use a negative number.

引数で渡した date に正、もしくは負の日付を適用した新しい Date オブジェクトを返してくれるようです。

import { addDay, format } from "@formkit/tempo";

// 1日足す
const day1 = addDay("2024-03-09");

// 2日足す 
const day2 = addDay("2024-03-09", 2);

// 2日引く
const day3 = addDay("2024-03-09", -2);

console.log(format(day1, "full", "ja")); // 2024年3月10日日曜日
console.log(format(day2, "full", "ja")); // 2024年3月11日月曜日
console.log(format(day3, "full", "ja")); // 2024年3月7日木曜日

足したり引いたりできています。処理はどうなっているかというと

import { date } from "./date"
import type { DateInput } from "./types"

/**
 * Returns a new date object 1/n days after the original one.
 * @param inputDate - A date to increment by 1 day.
 */
export function addDay(inputDate: DateInput, count = 1) {
  const d = date(inputDate)
  d.setDate(d.getDate() + count)
  return d
}

コード: src/addDay.ts

シンプルですね。引数の inputDateDateInput 型となっており定義は次のようになっています。

/**
 * The date format used as an input value. Either a date or an ISO8601 string.
 */
export type DateInput = Date | string

コード: src/types.ts

Date object か ISO8601 文字列を受け取るようです。addDay の処理の中でもう一つ date という関数がインポートされているのでそれも確認します。

import { iso8601, iso8601Match } from "./iso8601"
import type { DateInput } from "./types"

/**
 * Normalizes a "short" date like 2012-01-01 to 2012-01-01T00:00:00 to prevent
 * automatic coercion to UTC.
 * @param date - A string representation of the date.
 */
function normalize(date: string) {
  const matches = date.match(iso8601Match)
  if (matches && typeof matches[4] === "undefined") {
    return (date += "T00:00:00")
  }
  return date
}

/**
 * A date to parse.
 * @param date - A Date object or an ISO 8601 date.
 */
export function date(date?: DateInput): Date {
  if (!date) {
    date = new Date()
  }
  if (date instanceof Date) {
    const d = new Date(date)
    d.setMilliseconds(0)
    return d
  }
  date = date.trim()
  if (iso8601(date)) {
    return new Date(normalize(date))
  }
  throw new Error(`Non ISO 8601 compliant date (${date}).`)
}

コード: src/date.ts

この date 関数で引数で受け取った値から整形した Date object を作っているようです。そして addDay の処理に戻ると

export function addDay(inputDate: DateInput, count = 1) {
  const d = date(inputDate) // d には Date object が代入されている
  d.setDate(d.getDate() + count)
  return d
}

d.setDate(d.getDate() + count) の部分で日付の計算をしており、Date.prototype.getDate()Date.prototype.setDate() という Date object に生えているメソッドを使って日付の操作をしています。

他の addHouraddMinuteaddMonth などを見ても Date object にあるメソッドを駆使して操作していることがわかります。これを自前で書くこともできると思いますが、このようにライブラリとして整理されていると直感的に使いやすくて良いですね。

ちょっと触ってみる

フォーマット

Tempo の日付フォーマットは2種類あります。

import { format } from "@formkit/tempo";

const date = new Date();

// 下記二つは同じ
console.log(format(date, "full", "ja")); // 2024年3月9日土曜日
console.log(format(date, {date: "full"}, "ja")); // 2024年3月9日土曜日

console.log(format(date, {date: "long"}, "ja")); // 2024年3月9日
console.log(format(date, {date: "medium"}, "ja")); // 2024/03/09
console.log(format(date, {date: "short"}, "ja")); // 2024/03/09

console.log(format(date, {time: "full"}, "ja")); // 19時43分09秒 +0900
console.log(format(date, {time: "long"}, "ja")); // 19:43:09 +0900
console.log(format(date, {time: "medium"}, "ja")); // 19:43:09
console.log(format(date, {time: "short"}, "ja")); // 19:43

// 組み合わせ
console.log(format(date, {date: "full", time: "full"}, "ja")); // 2024年3月9日土曜日 19時43分09秒 +0900 
console.log(format(date, {date: "full", time: "long"}, "ja")); // 2024年3月9日土曜日 19:43:09 +0900
console.log(format(date, {date: "full", time: "medium"}, "ja")); // 2024年3月9日土曜日 19:43:09
console.log(format(date, {date: "full", time: "short"}, "ja")); // 2024年3月9日土曜日 19:43

console.log(format(date, {date: "long", time: "full"}, "ja")); // 2024年3月9日 19時43分09秒 +0900
console.log(format(date, {date: "long", time: "long"}, "ja")); // 2024年3月9日 19:43:09 +0900
console.log(format(date, {date: "long", time: "medium"}, "ja")); // 2024年3月9日 19:43:09
console.log(format(date, {date: "long", time: "short"}, "ja")); // 2024年3月9日 19:43

console.log(format(date, {date: "medium", time: "full"}, "ja")); // 2024/03/09 19時43分09秒 +0900
console.log(format(date, {date: "medium", time: "long"}, "ja")); // 2024/03/09 19:43:09 +0900
console.log(format(date, {date: "medium", time: "medium"}, "ja")); // 2024/03/09 19:43:09
console.log(format(date, {date: "medium", time: "short"}, "ja")); // 2024/03/09 19:43

console.log(format(date, {date: "short", time: "full"}, "ja")); // 2024/03/09 19時43分09秒 +0900
console.log(format(date, {date: "short", time: "long"}, "ja")); // 2024/03/09 19:43:09 +0900
console.log(format(date, {date: "short", time: "medium"}, "ja")); // 2024/03/09 19:43:09
console.log(format(date, {date: "short", time: "short"}, "ja")); // 2024/03/09 19:43

// トークン
console.log(format(date, "YYYY-MM-DD", "ja")); // 2024-03-09
console.log(format(date, "hh:mm:ss", "ja")); // 07:43:09

ミリセカンドは現状対応していないようです。先述した date.tsd.setMilliseconds(0) 部分で切り捨てているようなのでミリセカンドまで必要なプロダクトでは注意が必要です。

タイムゾーンの指定も追加のパッケージなども必要なく対応可能です。

import { format } from "@formkit/tempo";

const date = new Date();

const tz1 = format({
  date,
  format: {date: "full", time: "medium"},
  locale: "ja",
});

const tz2 = format({
  date,
  format: {date: "full", time: "medium"},
  locale: "ja",
  tz: "America/Los_Angeles", // ← タイムゾーンの指定
});

console.log(tz1) // 2024年3月9日土曜日 20:06:18
console.log(tz2) // 2024年3月9日土曜日 3:06:18

オプションに渡すだけでタイムゾーン設定できるのはシンプルでとてもいいなと思います。

まとめ

今回は最近気になっていた日付操作ライブラリの Tempo を触ってみました。単に触ってみた系の話だとドキュメントを読めば良い感じになってしまうので元のコードも少し読んでみました。まだバージョンも若いパッケージではありますが、今後の発展に期待したいと思います。紹介したもの以外にも機能がありますのでぜひ公式ドキュメントをご確認ください。

タイムゾーンって概念そのものがとても難解1ですよね....


1

どれくらいめんどくさいのかはタイムゾーン呪いの書 (知識編)にまとまっています