⌚
こういうのでいいんだよ、な JavaScript日付時刻ライブラリ Qrono
作った。
Qronoの特徴 🎨
- タイムゾーンとロケールの対応を捨てた軽い日付時刻ライブラリ。
- 他のライブラリはタイムゾーンとロケールに対応するためにコードベースが巨大になったり使い方が複雑になったりしがち。
- ロケール対応はECMAScript® Internationalization APIを使うだけで良い。
- ほとんどの場合、クライアント環境のタイムゾーンにだけ対応できれば十分。
Luxonによる説明は、まさにその核心をついている。- Don't make servers think about local times. Configure them to use UTC and write your server's code to work in UTC. Times can often be thought of as a simple count of epoch milliseconds; what you would call that time (e.g. 9:30) in what zone doesn't (again, often) matter.
- Communicate times between systems in ISO 8601, like 2017-05-15T13:30:34Z where possible (it doesn't matter if you use Z or some local offset; the point is that it precisely identifies the millisecond on the global timeline).
- Where possible, only think of time zones as a formatting concern; your application ideally never knows that the time it's working with is called "9:00" until it's being rendered to the user.
- Barring 3, do as much manipulation of the time (say, adding an hour to the current time) in the client code that's already running in the time zone where the results will matter.
All those things will make it less likely you ever need to work explicitly with time zones and may also save you plenty of other headaches.
- デフォルトはUTCとして時刻を扱う。
- デフォルトをローカルタイムに切り替えることもできる。
- 厳密に夏時間を扱う。
- 他のライブラリは、夏時間開始・終了時のいわゆる「あいまいな時間」を自在には扱えない。
- 夏時間を厳密に扱うことは、複数タイムゾーンの対応を捨てたからこそできる芸当。
他のライブラリなど
Moment.js
ミュータブルオブジェクトとしての挙動がバグを生みやすいという根本的な問題を抱えており、以下に紹介する後発の日付時刻ライブラリは全てイミュータブルな設計となっている。
Luxon
Moment.jsのメンテナが作成したイミュータブルでリッチなライブラリ。洗練されていて機能も豊富。コードが読みやすい。
デフォルトはローカルタイムで時刻を扱い、あいまいな時間を厳密には扱えない。
ただし、あいまいな時間に対してどのような挙動となるかを明確にドキュメントに示している点で、他のライブラリとは一線を画している。
Day.js
Moment.js互換のライブラリ。GitHubスター数が多く、デファクトスタンダードになりつつある。コードが読みにくい。
タイムゾーンとロケール対応のためにコードベースは非常に大きくなっている(2021-11-02時点でソースファイル数は178)が、必要なプラグインを宣言して使うスタイルのためtree-shakingが有効であれば実効サイズは小さくなる。
ただデフォルトで実装していてもよさそうな機能も細かくプラグインになっているので、都度プラグインの宣言をするのが面倒。
大量のIssueを消化しきれない中、メジャーバージョンアップを計画している。
バグを見つけたが、まだPull requestしていない。
date-fns
JavaScriptのDateオブジェクトが主役になるため、ミュータブル、月が0始まりなどといった問題はそのまま継承されてしまう。
The ECMA TC39 Temporal Proposal
java.timeにインスパイアされた仕様になっている。
将来標準となるかもしれないECMAScript® API提案。厳密かつ壮大でQronoの導入 📥
ブラウザ
<script src="path/to/qrono.min.js"></script>
<!-- from UNPKG -->
<script src="https://unpkg.com/qrono/dist/qrono.min.js"></script>
Node.jsなど
npm install qrono
// as module
import { qrono } from 'qrono'
// or CommonJS
const { qrono } = require('qrono')
使用例 🚀
コンストラクタ
// now
qrono()
// from various type of arguments
qrono('2022-12-31') // => 2022-12-31T00:00:00.000Z
qrono(new Date())
// followings are same 2022-12-31T15:23:11.321Z
qrono('2022-12-31 15:23:11.321')
qrono(1672500191321)
qrono(2022, 12, 31, 15, 23, 11, 321)
qrono([2022, 12, 31, 15, 23, 11, 321])
qrono({ year: 2022, month: 12, day: 31, hour: 15, minute: 23, second: 11, millisecond: 321 })
アクセサ
const time = qrono(2022, 12, 31, 15, 23, 11, 321)
time.year() // => 2022
time.month() // => 12
time.day() // => 31
time.hour() // => 15
time.minute() // => 23
time.second() // => 11
time.millisecond() // => 321
time.second(0) // => returns new Qrono instance
タイムゾーン
// UTC as default
qrono('2022-12-31 15:23:11.321').toString() // => "2022-12-31T15:23:11.321Z"
// set default to local time
qrono.asLocaltime()
qrono('2022-12-31 15:23:11.321').toString() // => "2022-12-31T15:23:11.321-04:00"
qrono('2022-12-31 15:23:11.321').asUtc().hour() // => 11 as UTC
qrono('2022-12-31 15:23:11.321').hour() // => 15 as local time
変換
qrono('2000-01-01').numeric() // => 946,684,800,000 milliseconds from UNIX epoch
=== +qrono('2000-01-01') // => true
const time = qrono('2000-01-02 03:04:05.006')
time.toObject() // => { year: 2000, month: 1, day: 2, hour: 3, minute: 4, second: 5, millisecond: 6 }
time.toArray() // => [2000, 1, 2, 3, 4, 5, 6]
time.nativeDate() // => JavaScript native Date instance
計算
qrono('2000-01-01 01:00:00.000') - qrono('2000-01-01') // => 3,600,000 milliseconds = 1 hour
qrono('2000-01-01 01:00:00.000') < qrono('2000-01-01') // => false
qrono('2000-01-01').plus(7200000).minus(3600000) // => 2000-01-01T01:00:00.000Z
qrono('2000-01-01').minus({ hour: 1, minute: 30 }) // => 1999-12-31T22:30:00.000Z
const today = qrono()
const yesterday = today.minus({ day: 1 })
const tommorrow = today.plus({ day: 1 })
today.isBetween(yesterday, tommorrow) // => true
ショートハンド
const time = qrono('2000-01-02 03:04:05.006')
time.startOfYear() // => 2000-01-01T00:00:00.000Z
time.startOfMonth() // => 2000-01-01T00:00:00.000Z
time.startOfDay() // => 2000-01-02T00:00:00.000Z
time.startOfHour() // => 2000-01-02T03:00:00.000Z
time.startOfMinute() // => 2000-01-02T03:04:00.000Z
time.startOfSecond() // => 2000-01-02T03:04:05.000Z
time.dayOfWeek() // => 7 === qrono.sunday
time.dayOfYear() // => 2
time.isLeapYear() // => true
time.daysInMonth() // => 31
time.daysInYear() // => 366
// ISO week number. See https://en.wikipedia.org/wiki/ISO_week_date
time.weeksInYear() // => 52
time.weekOfYear() // => 52
time.yearOfWeek() // => 1999
// Daylight saving time stuff that is meaningful in case of local time
const localtime = time.asLocaltime()
localtime.hasDstInYear()
localtime.isInDst()
localtime.isDstTransitionDay()
localtime.minutesInDay()
QronoDate
日付型 qrono.date(...)
は Qrono
とほぼ同じメソッド群を持つ QronoDate
インスタンスを返す。
qrono.date('2000-01-02').toString() // => "2000-01-02"
qrono('2000-01-02 23:04:05.006').toDate() // => QronoDate instance 2000-01-02
qrono.date('2000-01-02').numeric() // => 10958 days from UNIX epoch
最後に
要望・バグなどあればIssueをお願いします。
Discussion