前回の
日付表現を便利にするモジュール
(1)
(2)
DateTime ──日付演算を正確に行うモジュール
日付処理の代表的なモジュールとして、DateTime
DateTime
オブジェクトは、new
やfrom_
メソッドなどで時間を直接指定して作成するか、now
メソッドやtoday
メソッドで実行日時から作ることができます。
use DateTime;
my $dt = DateTime->new(
year => 2021,
month => 1,
day => 1,
hour => 0,
minute => 0,
second => 0,
# nanosecond => 0,
time_zone => 'Asia/Tokyo'
);
my $now = DateTime->now(time_zone => 'Asia/Tokyo');
DateTime
オブジェクトは加算や減算の処理が行えますが、Clone
モジュール
use DateTime;
use Clone qw(clone);
my $dt_1 = DateTime->now(time_zone => 'Asia/Tokyo');
my $dt_2 = clone($dt_1);
$dt_2->add(months => 1, days => 1);
演算結果が月末を超える場合の最後の日の合わせ方は、wrap
、limit
、preserve
のいずれかをend_
に指定できます。wrap
は月末を超えて演算します。limit
は月末を超えたら月末に合わせます。preserve
は月末を超えたら月末に合わせたうえで、wrap
で、preserve
です。
$dt->add(months => 1, end_of_month => 'preserve');
また、DateTime
オブジェクトどうしの減算を行う場合、DateTime
オブジェクトのdelta_
メソッドに、DateTime
オブジェクトを渡すことで、DateTime::Duration
DateTime
オブジェクトのin_
メソッドで取り出したい単位を指定します。
use DateTime;
my $dt_1 = DateTime->new( year => 2021, month => 1, day
=> 1, time_zone => 'Asia/Tokyo' );
my $dt_2 = DateTime->new( year => 2021, month => 2, day
=> 1, time_zone => 'Asia/Tokyo' );
my $duration = $dt_2->delta_days($dt_1);
say $duration->in_units('days'); # 差は31日
異なるタイムゾーンのDateTime
オブジェクトどうしの場合は、set_
メソッドで一度UTCに変換してから演算を行い、
$dt_1->set_time_zone('UTC');
DateTime
モジュールは、
しかし、DateTime
モジュールは、
閏秒を考慮しない場合は、Time::Piece
モジュールや次項のTime::Moment
モジュールなどの比較的軽量なモジュールを検討するとよいです。
Time::Moment ──高速な日付演算モジュール
Time::Moment
DateTime
モジュールと似た感覚で扱え、
use Time::Moment;
my $tm = Time::Moment->new(
year => 2021,
month => 1,
day => 1,
hour => 0,
minute => 0,
second => 0,
# nanosecond => 0,
offset => 60 * 9, # 日本時間
);
my $now = Time::Moment->now;
my $utc = Time::Moment->now_utc;
my $epoch = Time::Moment->from_epoch(1000);
Time::Moment
オブジェクトは、DateTime
オブジェクトの破壊的な加算や減算とは異なり、Time::Moment
オブジェクトが作られる非破壊的な処理を行います。また、plus_
や減算するminus_
は、
$tm2 = $tm1->plus_months(1); # 1ヶ月を加算
DateTime
モジュールは実行速度がかなり遅いです。Benchmark
モジュールDateTime
モジュールよりTime::Piece
モジュールが約25倍速く、Time::Moment
モジュールにいたってはDateTime
モジュールより約140倍速いです。頻繁に日付処理を行う場合は、DateTime
モジュールからTime::Moment
モジュールへの移行を検討してもよいでしょう。
use Benchmark qw(cmpthese);
use Time::Moment;
use Time::Piece;
use DateTime;
cmpthese 0, {
'DateTime' => sub {
DateTime->now(time_zone => 'Asia/Tokyo')
},
'Time::Piece' => sub { Time::Piece->localtime },
'Time::Moment' => sub { Time::Moment->now },
};
特定の時刻フォーマットの読み込みと変換を行うモジュール
時間は、
POSIX::strftime ──任意の時刻フォーマットへの変換モジュール
単に現在時間を任意の時刻フォーマットに変換するためにTime::Piece
モジュールやDatetime
モジュールをuse
することは、
POSIX::strftime
localtime
関数を渡すことで、localtime
関数のリストで返される結果と同じ順で直接指定することでも、
use POSIX qw(strftime);
my $now_string = strftime "%a %b %e %H:%M:%S %Y", localt
ime;
Time::Local::timelocal_posix ──UNIX時間に戻すモジュール
同様に、Time::Piece
モジュールやDatetime
モジュールは、
Time::Local::timelocal_
$month
と$year
は、localtime
関数と同じ0~11の範囲と1900を引いた値をTime::Local::timelocal_
に渡します。
use Time::Local qw(timelocal_posix);
my ($sec, $min, $hour, $mday, $month, $year) = (0, 0, 0, 1, 0, 121);
my $epoch = timelocal_posix($sec, $min, $hour, $mday, $month, $year);
HTTP::Date::parse_date ── 日付変換ルーチンモジュール
文字列の日付は、localtime
関数などさまざまな種類があり、
HTTP::Date::parse_
use HTTP::Date qw(parse_date);
my ($year, $month, $day, $hour, $min, $sec, $tz) = parse_date('Fri, 01 Jan 2021 00:00:00 GMT');
my $parsed_time = parse_date('Fri, 01 Jan 2021 00:00:00 GMT');
say $parsed_time;
同類のモジュールとして、DateTime
オブジェクトにするDateTime::Format::HTTP
モジュールなどもあります。
Time::Piece::MySQL ──Time::Piece用MySQL日付モジュール
MySQLで時間を扱うのは少々大変です。たとえば、
Time::Piece
モジュールの代わりにTime::Piece::MySQL
モジュールuse
することで、Time::Piece
オブジェクトとしてMySQLの時刻フォーマットの相互変換がメソッドで可能になります。
use Time::Piece::MySQL;
my $now = localtime;
# DATETIME型への変換
say $now->mysql_datetime;
my $mysql_datetime = '2021-01-01 00:00:00';
# DATETIME型からの変換
my $time = Time::Piece->from_mysql_datetime($mysql_datetime);
DateTime::Format::MySQL ──DateTime用MySQL日付モジュール
DateTime
モジュールでもMySQLの相互変換が行えます。DateTime
モジュールの代わりにDateTime::Format::MySQL
モジュールuse
することで利用できます。
use DateTime::Format::MySQL;
# DATETIME型からの変換
my $dt = DateTime::Format::MySQL->parse_datetime('2021-01-01 00:00:00');
# DATETIME型への変換
say DateTime::Format::MySQL->format_datetime($dt);
Test::MockTime ──ユニットテストでの時間固定モジュール
ユニットテストでプログラムの実行日時を変えたいときがあります。たとえば特定の時間になるとタイムセールが始まるコードがマシンの時間に依存していた場合、Test::MockTime
モジュールtime
関数が返すUNIX時間を修正してテストを行えます。
set_
関数にUNIX時間を渡すと、time
関数の戻り値が固定化されます。日時を文字列で渡す場合でも、restore_
関数を呼び出すだけです。
use Test::MockTime qw(set_fixed_time restore_time);
set_fixed_time(1609426800);
set_fixed_time('2021-01-01 00:00:00', '%Y-%m-%d %H:%M:%S');
restore_time();
逆に、set_
関数に値を渡すか、set_
関数で現在時間から見た秒数を指定して、
use Test::MockTime qw(set_absolute_time set_relative_time restore_time);
set_absolute_time(0);
restore_time();
set_relative_time(-600); # 600秒前に時間を戻す
restore_time();
まとめ
本稿では、
さて、
本誌最新号をチェック!
WEB+DB PRESS Vol.130
2022年8月24日発売
B5判/
定価1,628円
ISBN978-4-297-13000-8
- 特集1
イミュータブルデータモデルで始める
実践データモデリング
業務の複雑さをシンプルに表現! - 特集2
いまはじめるFlutter
iOS/Android両対応アプリを開発してみよう - 特集3
作って学ぶWeb3
ブロックチェーン、スマートコントラクト、 NFT