はじめに
JavaScriptの標準Dateオブジェクトを使っていて、こんな経験はありませんか。
setDate()を呼んだつもりが元のオブジェクトそのものを書き換えてしまい、別の場所でバグが出た- タイムゾーンをまたぐ日時計算をしようとしたら、UTCとローカル時刻の変換で頭がこんがらがった
- 日付を「2026年8月25日」のような自然な形式で表示したいだけなのに、無駄に長い自前関数を書いてしまった
こうした悩みの多くは、長年デファクトスタンダードだったMoment.jsが解決してくれていました。しかしMoment.jsは現在メンテナンスモードに入っており、公式ドキュメントでも後継候補としていくつかのライブラリを挙げています。その中の1つが、今回紹介するLuxonです。
Luxonは、Moment.jsのコア開発者自身が「イミュータブル」「モダンなJavaScript」という設計方針のもとで作り直した日時ライブラリです。DateTime・Duration・Intervalという3つの型を軸に、日時の生成・計算・フォーマット・タイムゾーン変換を一貫したAPIで扱えます。
とはいえ、読むより触った方が早いと思います。タイムゾーンを切り替えて日時がどう変わるかをその場で確認できるサンプルを用意したので、先に挙動を見たい方はこちらからどうぞ。
Luxonとは
Luxonは、ネイティブのDateオブジェクトを直接いじる代わりに、イミュータブル(不変)なDateTimeオブジェクトを介して日時を扱うJavaScriptライブラリです。GitHub上で16,000以上のスターを獲得しており、現在も活発にメンテナンスされています。
主な特徴
- イミュータブルでチェーン可能なAPI -
plus()やsetZone()は新しいインスタンスを返すため、元の値を書き換える心配がありません - Intl APIとのネイティブ統合 - ロケールやタイムゾーンのデータファイルを別途バンドルする必要がなく、ブラウザ・Node.js標準の
Intl機能をそのまま利用します - 柔軟なパース・フォーマット - ISO 8601、RFC 2822、任意のトークン形式など、多様な入力・出力形式に対応します
- TypeScript型定義を同梱 - パッケージ自体に型定義が含まれており、追加のインストールなしで型補完が効きます
インストール
npm、yarn、pnpmのいずれからでも導入できます。
npm install luxon
yarn add luxon
pnpm add luxon
TypeScriptプロジェクトでも型定義がパッケージに同梱されているため、そのままインポートするだけで型補完が効きます。
Luxonのサンプルを動かす
下のサンプルは、DateTime.now()で現在時刻を取得し、setZone()でタイムゾーンを切り替えたうえで、toFormat()とtoLocaleString()でフォーマットするデモです。セレクトボックスでタイムゾーンを変更すると、同じ瞬間の時刻が各地でどう表示されるかがその場で切り替わります。
要点となる部分だけを抜き出すと、次のようになります。
import { DateTime } from 'luxon'
const now = DateTime.now()
const tokyo = now.setZone('Asia/Tokyo')
const nextWeek = tokyo.plus({ weeks: 1 })
console.log(nextWeek.toFormat('yyyy-MM-dd HH:mm'))
console.log(nextWeek.toLocaleString(DateTime.DATETIME_FULL))
実際に動かせるものが以下です。タイムゾーンを変えたり、日数を加算するボタンを押したりして反応を確かめてみてください。
タイムゾーンをAmerica/New_Yorkに切り替えると、同じ瞬間でも表示される時刻とUTCとの差が変わるのが分かります。setZone()は元のDateTimeインスタンスを書き換えず新しいインスタンスを返すため、base変数はいつでも「元の時刻」を指したまま安全に使い回せます。
基本的な使い方
Luxonの中心となるのはDateTimeクラスです。生成・パース・演算・フォーマットの一連の流れをコードで見てみましょう。
import { DateTime } from 'luxon'
// 現在時刻を取得
const now = DateTime.now()
// ISO文字列からパース
const parsed = DateTime.fromISO('2026-08-25T09:00:00')
// 独自フォーマットからパース
const custom = DateTime.fromFormat('2026/08/25', 'yyyy/MM/dd')
// 加減算(元のインスタンスは変化しない)
const nextMonth = now.plus({ months: 1 })
const lastYear = now.minus({ years: 1 })
// フォーマット
console.log(now.toISO()) // 2026-08-25T09:00:00.000+09:00
console.log(now.toFormat('yyyy年MM月dd日')) // 2026年08月25日
console.log(now.toLocaleString(DateTime.DATE_MED)) // 2026年8月25日
// 2つの日時の差分
const diff = nextMonth.diff(now, ['days']).toObject()
console.log(diff) // { days: 31 } のような結果
plus()やminus()は新しいDateTimeインスタンスを返すため、now自体はどれだけ演算しても変化しません。この不変性が、Moment.jsで頻発していた「意図せず元の値を書き換えてしまう」バグを防いでくれます。
実践的なユースケース
ロケールに応じた日時表示
多言語対応のアプリでは、同じ日時でもユーザーの言語設定に応じて表示形式を変えたくなります。LuxonはsetLocale()とtoLocaleString()を組み合わせることで、ブラウザ標準のIntl.DateTimeFormatをそのまま活用したロケール別フォーマットができます。
import { DateTime } from 'luxon'
const dt = DateTime.fromISO('2026-08-25T15:30:00')
console.log(dt.setLocale('ja').toLocaleString(DateTime.DATE_FULL))
// 2026年8月25日
console.log(dt.setLocale('en-US').toLocaleString(DateTime.DATE_FULL))
// August 25, 2026
console.log(dt.setLocale('fr').toLocaleString(DateTime.DATETIME_MED))
// 25 août 2026, 15:30
下のサンプルではロケールをセレクトボックスで切り替えられます。DATE_FULLやDATETIME_MEDといったプリセットを変えると、同じ日時でも表示のされ方がどう変わるかを見比べられます。
ロケールをfrやdeに変えると、月名や曜日名だけでなく区切り文字の位置まで自動的に切り替わります。これはLuxonが独自の翻訳データを持たず、実行環境のIntlをそのまま使っているためです。
タイムゾーンをまたぐ日時変換
海外拠点とのミーティング調整のように、複数のタイムゾーンを同時に扱う場面では、基準となる時刻をどこか1つに固定してからsetZone()で変換するのが安全です。
import { DateTime } from 'luxon'
// UTCを基準に会議時刻を決める
const meetingUtc = DateTime.fromISO('2026-08-25T05:00:00Z')
const tokyo = meetingUtc.setZone('Asia/Tokyo')
const newYork = meetingUtc.setZone('America/New_York')
const london = meetingUtc.setZone('Europe/London')
console.log(tokyo.toFormat('MM/dd HH:mm')) // 08/25 14:00
console.log(newYork.toFormat('MM/dd HH:mm')) // 08/25 01:00
console.log(london.toFormat('MM/dd HH:mm')) // 08/25 06:00
以下のサンプルでは、時刻をずらすスライダーを動かすと、各拠点の現地時刻が同時に更新されます。setZone()は同じ瞬間を指したまま表示だけを変換するため、どこか1都市を基準に会議時間を決めるような用途に向いています。
スライダーを動かすと、3都市の時刻が常に同じ瞬間を指しながら現地時間に変換され続けます。タイムゾーンデータをアプリ側でバンドルする必要がなく、実行環境が持つIANAタイムゾーンデータベースをそのまま参照しているのがLuxonの利点です。
DurationとIntervalで期間を扱う
予約システムのように「期間の重なりをチェックしたい」「残り時間を人間が読める形式で表示したい」といった場面では、DurationとIntervalが役立ちます。
import { DateTime, Duration, Interval } from 'luxon'
// 残り時間を読みやすい形式に変換
const remaining = Duration.fromObject({ hours: 26, minutes: 15 })
console.log(remaining.toFormat('d日 h時間 m分')) // 1日 2時間 15分
// 2つの予約期間が重なっているか判定
const bookingA = Interval.fromDateTimes(
DateTime.fromISO('2026-08-25T10:00:00'),
DateTime.fromISO('2026-08-25T12:00:00')
)
const bookingB = Interval.fromDateTimes(
DateTime.fromISO('2026-08-25T11:00:00'),
DateTime.fromISO('2026-08-25T13:00:00')
)
console.log(bookingA.overlaps(bookingB)) // true
下のサンプルでは、開始・終了時刻を入力すると、既存の予約枠と重なっているかをInterval.overlaps()でリアルタイムに判定します。
終了時刻を12:30のように既存予約と1分でも重なる値に変えると、判定が即座に「重複」へ切り替わります。Intervalは開始・終了を持つ期間そのものをオブジェクトとして扱えるため、重なり判定だけでなく、length()での期間長取得やsplitBy()での分割にも応用できます。
まとめ
Luxonは、DateTime・Duration・Intervalという3つの型と、イミュータブルでチェーン可能なAPIによって、日時操作にまつわる典型的なバグを設計レベルで防いでくれるライブラリです。ブラウザ・Node.js標準のIntl機能を活用しているため、ロケールやタイムゾーンのデータを別途持ち歩く必要がないのも大きな利点です。
Moment.jsからの移行先を探している方はもちろん、これから新しくプロジェクトを始める方も、まずはDateTime.now()から触ってみてください。サンプルで見たように、タイムゾーンやロケールをまたぐ処理ほど、Luxonの設計のありがたみを実感できるはずです。
