前回の(1)はこちらから。
日付表現を便利にするモジュール
(1)では時刻に関わるモジュールを見てきました。しかし、長い未来を見ていく場合、日付のほうが重要です。たとえば2つの異なるタイムゾーンでの日付の比較は、タイムゾーンの差の計算と時間の計算、そしてそれに伴う日付の移動の発生が起こり得ます。これらの計算は、日付計算に優れたモジュールを使用すると便利になります。
(2)では、日付処理に関わるモジュールを見ていきます。
DateTime ──日付演算を正確に行うモジュール
日付処理の代表的なモジュールとして、DateTime
(執筆時点のバージョンは1.54)があります。DateTime
オブジェクトは、new
やfrom_epoch
メソッドなどで時間を直接指定して作成するか、now
メソッドやtoday
メソッドで実行日時から作ることができます。
DateTime
オブジェクトは加算や減算の処理が行えますが、オブジェクトに破壊的な変更を加えるため、もとの情報は失われます。演算前の情報を残しておきたい場合は、Clone
モジュール(執筆時点のバージョンは0.45)などであらかじめオブジェクトを複製する必要があります。
演算結果が月末を超える場合の最後の日の合わせ方は、wrap
、limit
、preserve
のいずれかをend_of_month
に指定できます。wrap
は月末を超えて演算します。limit
は月末を超えたら月末に合わせます。preserve
は月末を超えたら月末に合わせたうえで、もとの日付が月末の場合は超えた月の月末に合わせます。値が正の場合の初期値はwrap
で、負の場合の初期値はpreserve
です。
また、DateTime
オブジェクトどうしの減算を行う場合、基点にしたいDateTime
オブジェクトのdelta_days
メソッドに、引きたいDateTime
オブジェクトを渡すことで、DateTime::Duration
(執筆時点のバージョンは1.54)オブジェクトが作られます。差の結果を取り出すには、DateTime
オブジェクトのin_units
メソッドで取り出したい単位を指定します。
異なるタイムゾーンのDateTime
オブジェクトどうしの場合は、set_time_zone
メソッドで一度UTCに変換してから演算を行い、もとのタイムゾーンに戻します。
DateTime
モジュールは、タイムゾーンを指定しなかった場合、フローティングタイムゾーンとして扱い、閏秒を考慮しなくなります。指定した場合はそのタイムゾーンの閏秒を扱えます。
しかし、閏秒がいつ挿入されるかは半年前にならないとわかりません。DateTime
モジュールは、閏秒の挿入が決定されるとモジュールのバージョンアップで対応しているため、閏秒に対応していくためにはそのたびにモジュールを更新する必要があります。
閏秒を考慮しない場合は、Time::Piece
モジュールや次項のTime::Moment
モジュールなどの比較的軽量なモジュールを検討するとよいです。
Time::Moment ──高速な日付演算モジュール
Time::Moment
(執筆時点のバージョンは0.44)は、日付演算の軽量モジュールです。DateTime
モジュールと似た感覚で扱え、タイムゾーンの代わりにUTCからの時間の差をオフセットとして分で指定することで表現します。
Time::Moment
オブジェクトは、DateTime
オブジェクトの破壊的な加算や減算とは異なり、演算後に新たにTime::Moment
オブジェクトが作られる非破壊的な処理を行います。また、月を加算するplus_months
や減算するminus_months
は、その月に日が存在しないならその月の最終日になり、存在する場合は日付が維持されます。
DateTime
モジュールは実行速度がかなり遅いです。Benchmark
モジュール(執筆時点のバージョンは1.23)を使って各モジュールの現在時刻を取得するコードの実行速度を筆者のマシンで計測すると、DateTime
モジュールよりTime::Piece
モジュールが約25倍速く、Time::Moment
モジュールにいたってはDateTime
モジュールより約140倍速いです。頻繁に日付処理を行う場合は、DateTime
モジュールからTime::Moment
モジュールへの移行を検討してもよいでしょう。
特定の時刻フォーマットの読み込みと変換を行うモジュール
時間は、年月日や時刻だけでなく、曜日やフォーマットなども自由に表現できるため、読み込みや変換が独自の実装だけでは足りなくなりがちです。ここでは個々のケースに対応するモジュールを見ていきます。
POSIX::strftime ──任意の時刻フォーマットへの変換モジュール
単に現在時間を任意の時刻フォーマットに変換するためにTime::Piece
モジュールやDatetime
モジュールをuse
することは、目的に対してモジュールが大きすぎます。
POSIX::strftime
(執筆時点のバージョンは1.94)に変換したいフォーマットとlocaltime
関数を渡すことで、任意の時刻文字列に変換できます。localtime
関数のリストで返される結果と同じ順で直接指定することでも、任意の変換が行えます。
Time::Local::timelocal_posix ──UNIX時間に戻すモジュール
同様に、Time::Piece
モジュールやDatetime
モジュールは、単に任意の時間をUNIX時間に変換する目的では大げさです。
Time::Local::timelocal_posix
(執筆時点のバージョンは1.30)に変換したい時間のリストを渡すことで、UNIX時間に変換できます。$month
と$year
は、localtime
関数と同じ0~11の範囲と1900を引いた値をTime::Local::timelocal_posix
に渡します。
HTTP::Date::parse_date ── 日付変換ルーチンモジュール
文字列の日付は、HTTPフォーマットやスカラコンテキストのlocaltime
関数などさまざまな種類があり、変換が複雑になりがちです。
HTTP::Date::parse_date
(執筆時点のバージョンは6.05)に変換したい日付文字列を渡すことで、時間情報をリストに変換できます。また、スカラコンテキストの場合はタイムゾーンの情報を含めた「YYYY-MMDD hh:mm:ss TZ」形式で返されます。
同類のモジュールとして、変換結果をDateTime
オブジェクトにするDateTime::Format::HTTP
モジュールなどもあります。
Time::Piece::MySQL ──Time::Piece用MySQL日付モジュール
MySQLで時間を扱うのは少々大変です。たとえば、DATETIME型とTIMESTAMP型は、同じフォーマットでもサポートされている時間の範囲が異なります。また、DATE型やDATETIME型、TIMESTAMP型は、無効な値を入力された場合は年月日に00を含むゼロ値に変換されます。
Time::Piece
モジュールの代わりにTime::Piece::MySQL
モジュール(執筆時点のバージョンは0.06)をuse
することで、Time::Piece
オブジェクトとしてMySQLの時刻フォーマットの相互変換がメソッドで可能になります。
DateTime::Format::MySQL ──DateTime用MySQL日付モジュール
DateTime
モジュールでもMySQLの相互変換が行えます。DateTime
モジュールの代わりにDateTime::Format::MySQL
モジュール(執筆時点のバージョンは0.06)をuse
することで利用できます。
Test::MockTime ──ユニットテストでの時間固定モジュール
ユニットテストでプログラムの実行日時を変えたいときがあります。たとえば特定の時間になるとタイムセールが始まるコードがマシンの時間に依存していた場合、テストをするたびにマシンの時間を変えるわけにもいきません。Test::MockTime
モジュール(執筆時点のバージョンは0.17)を使うと、time
関数が返すUNIX時間を修正してテストを行えます。
set_fixed_time
関数にUNIX時間を渡すと、time
関数の戻り値が固定化されます。日時を文字列で渡す場合でも、フォーマットを指定することでその時間に固定化されます。もとの時間に戻す場合にはrestore_time
関数を呼び出すだけです。
逆に、経過時間を記録するなどテストの実行中は時間が動き続けてほしいケースもあります。この場合は、set_absolute_time
関数に値を渡すか、set_relative_time
関数で現在時間から見た秒数を指定して、過去や未来に時間を移動できます。
まとめ
本稿では、時間を取り扱うさまざまなPerlモジュールを見てきました。もしPerlでの時間の取り扱いに悩んでいる場合は、本稿がモジュール選択の指針になれば幸いです。
さて、次回の執筆者は菅井茂樹さんで、テーマは「GitHub APIを使ってみよう」です。お楽しみに。
- 特集1
イミュータブルデータモデルで始める
実践データモデリング
業務の複雑さをシンプルに表現!
- 特集2
いまはじめるFlutter
iOS/Android両対応アプリを開発してみよう
- 特集3
作って学ぶWeb3
ブロックチェーン、スマートコントラクト、NFT