Time::Moment - 高速で正確な日時処理
Perlで日時を扱う際、長年 DateTime モジュールが定番として使われてきました。しかし、パフォーマンスが重要な場面では Time::Moment という選択肢があります。Time::Moment は C 言語で実装されており、DateTime よりも圧倒的に高速で、メモリ使用量も少ないという特徴があります。
Time::Momentの特徴
Time::Moment の主な特徴は以下の通りです:
- 高速: C言語で実装され、DateTime の数十倍から数百倍の速度
- イミュータブル: すべての操作で新しいオブジェクトを返すため、予期しない副作用がない
- 正確: マイクロ秒精度の時刻を扱える
- 軽量: メモリ使用量が少ない
- 完全なタイムゾーンサポート: Olson タイムゾーンデータベースに対応
インストール
基本的な使い方
Time::Momentオブジェクトの作成
Time::Moment オブジェクトを作成する方法はいくつかあります:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| use Time::Moment;
# 現在時刻(UTC)
my $now = Time::Moment->now_utc;
print $now, "\n"; # 2025-12-12T10:30:00Z
# 現在時刻(ローカルタイムゾーン)
my $local_now = Time::Moment->now;
# 特定の日時を指定
my $moment = Time::Moment->new(
year => 2025,
month => 12,
day => 12,
hour => 15,
minute => 30,
second => 45,
);
# エポック秒から作成
my $from_epoch = Time::Moment->from_epoch(1702383045);
# ISO 8601 形式の文字列から作成
my $from_string = Time::Moment->from_string('2025-12-12T15:30:45Z');
|
日時の操作
Time::Moment はイミュータブルなので、すべての操作は新しいオブジェクトを返します:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| use Time::Moment;
my $moment = Time::Moment->new(
year => 2025,
month => 12,
day => 12,
);
# 日数を加算
my $next_week = $moment->plus_days(7);
print $next_week->to_string, "\n"; # 2025-12-19T00:00:00Z
# 時間を加算
my $later = $moment->plus_hours(3)->plus_minutes(30);
print $later->to_string, "\n"; # 2025-12-12T03:30:00Z
# 減算も可能
my $yesterday = $moment->minus_days(1);
print $yesterday->to_string, "\n"; # 2025-12-11T00:00:00Z
# 月の操作(月末を考慮)
my $next_month = $moment->plus_months(1);
print $next_month->to_string, "\n"; # 2025-01-12T00:00:00Z
# with_* メソッドで特定のフィールドを変更
my $new_year = $moment->with_year(2025);
my $noon = $moment->with_hour(12)->with_minute(0);
|
フォーマット出力
Time::Moment は柔軟なフォーマット出力をサポートしています:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| use Time::Moment;
my $moment = Time::Moment->new(
year => 2025,
month => 12,
day => 12,
hour => 15,
minute => 30,
second => 45,
);
# ISO 8601 形式(デフォルト)
print $moment->to_string, "\n";
# 2025-12-12T15:30:45Z
# strftime 形式
print $moment->strftime('%Y年%m月%d日 %H:%M:%S'), "\n";
# 2025年12月12日 15:30:45
# 各種コンポーネントの取得
printf "年: %d, 月: %d, 日: %d\n",
$moment->year, $moment->month, $moment->day;
# 年: 2025, 月: 12, 日: 12
# 曜日(1=月曜日, 7=日曜日)
print "曜日: ", $moment->day_of_week, "\n"; # 4 (木曜日)
# 年間通算日
print "年間通算日: ", $moment->day_of_year, "\n"; # 347
# エポック秒
print "エポック秒: ", $moment->epoch, "\n";
|
タイムゾーン処理
Time::Moment はタイムゾーンの変換を簡単に行えます:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| use Time::Moment;
# UTC で作成
my $utc = Time::Moment->new(
year => 2025,
month => 12,
day => 12,
hour => 10, # UTC 10:00
);
# 日本時間(JST: UTC+9)に変換
my $jst = $utc->with_offset_same_instant(9 * 60); # オフセットは分単位
print $jst->to_string, "\n"; # 2025-12-12T19:00:00+09:00
# 逆にJSTからUTCへ
my $back_to_utc = $jst->with_offset_same_instant(0);
print $back_to_utc->to_string, "\n"; # 2025-12-12T10:00:00Z
# オフセット情報を保持したまま時刻のみ変更
my $same_local = $jst->with_offset_same_local(0);
print $same_local->to_string, "\n"; # 2025-12-12T19:00:00Z (19:00のまま)
|
DateTime との比較
DateTime と Time::Moment の主な違いを見てみましょう:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
| use Benchmark qw(cmpthese);
use DateTime;
use Time::Moment;
print "=== パフォーマンス比較 ===\n\n";
# オブジェクト作成の比較
cmpthese(100000, {
'DateTime' => sub {
DateTime->new(
year => 2025,
month => 12,
day => 12,
);
},
'Time::Moment' => sub {
Time::Moment->new(
year => 2025,
month => 12,
day => 12,
);
},
});
# 日付操作の比較
my $dt = DateTime->new(year => 2025, month => 12, day => 12);
my $tm = Time::Moment->new(year => 2025, month => 12, day => 12);
cmpthese(100000, {
'DateTime add' => sub {
$dt->clone->add(days => 7);
},
'Time::Moment add' => sub {
$tm->plus_days(7);
},
});
|
主な違い:
| 項目 | DateTime | Time::Moment |
|---|
| 速度 | 遅い | 非常に高速 |
| メモリ | 多い | 少ない |
| イミュータブル | いいえ | はい |
| タイムゾーン | DateTime::TimeZone | オフセットベース |
| 精度 | マイクロ秒 | マイクロ秒 |
| 拡張性 | 高い | 限定的 |
実用例
ログファイルの解析と集計
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| use Time::Moment;
use feature qw(say);
# ログファイルから時刻を抽出して集計
my @log_lines = (
'2025-12-12T10:15:30Z INFO: User login',
'2025-12-12T10:16:45Z ERROR: Connection failed',
'2025-12-12T10:17:20Z INFO: User logout',
'2025-12-12T11:30:15Z INFO: User login',
);
my %hourly_count;
for my $line (@log_lines) {
if ($line =~ /^(\S+)/) {
my $timestamp = $1;
my $moment = Time::Moment->from_string($timestamp);
# 時間単位で集計
my $hour_key = $moment->strftime('%Y-%m-%d %H:00');
$hourly_count{$hour_key}++;
}
}
say "=== 時間別ログ件数 ===";
for my $hour (sort keys %hourly_count) {
say "$hour: $hourly_count{$hour}件";
}
|
営業日計算
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| use Time::Moment;
use feature qw(say);
sub add_business_days {
my ($start_moment, $days) = @_;
my $current = $start_moment;
my $added = 0;
while ($added < $days) {
$current = $current->plus_days(1);
# 土曜日(6)と日曜日(7)をスキップ
my $dow = $current->day_of_week;
next if $dow == 6 || $dow == 7;
$added++;
}
return $current;
}
my $today = Time::Moment->new(
year => 2025,
month => 12,
day => 12, # 木曜日
);
my $deadline = add_business_days($today, 5);
say "5営業日後: ", $deadline->strftime('%Y-%m-%d (%a)');
# 2025-12-19 (Thu)
|
期間の計算と比較
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| use Time::Moment;
use feature qw(say);
my $start = Time::Moment->new(
year => 2025,
month => 12,
day => 1,
);
my $end = Time::Moment->new(
year => 2025,
month => 12,
day => 25,
);
# 期間の計算(秒単位)
my $duration_seconds = $end->epoch - $start->epoch;
my $duration_days = int($duration_seconds / 86400);
say "期間: $duration_days 日";
# 比較
if ($start < $end) {
say "start は end より前です";
}
# 2つの日時が同じかチェック
my $same = Time::Moment->new(year => 2025, month => 12, day => 1);
if ($start == $same) {
say "同じ日時です";
}
|
タイムゾーンを跨ぐ会議時刻の調整
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| use Time::Moment;
use feature qw(say);
# 東京での会議時刻(JST: UTC+9)
my $tokyo_meeting = Time::Moment->new(
year => 2025,
month => 12,
day => 12,
hour => 14, # 14:00 JST
minute => 0,
offset => 9 * 60, # JST は UTC+9
);
say "東京: ", $tokyo_meeting->strftime('%Y-%m-%d %H:%M %z');
# ニューヨーク(EST: UTC-5)での時刻
my $newyork = $tokyo_meeting->with_offset_same_instant(-5 * 60);
say "ニューヨーク: ", $newyork->strftime('%Y-%m-%d %H:%M %z');
# ロンドン(GMT: UTC+0)での時刻
my $london = $tokyo_meeting->with_offset_same_instant(0);
say "ロンドン: ", $london->strftime('%Y-%m-%d %H:%M %z');
# UTC での時刻
my $utc = $tokyo_meeting->with_offset_same_instant(0);
say "UTC: ", $utc->strftime('%Y-%m-%d %H:%M %z');
|
まとめ
Time::Moment は以下のような場合に特に有効です:
- 大量の日時データを処理する場合: ログ解析、データ集計など
- パフォーマンスが重要な場合: Web API、リアルタイム処理など
- イミュータブルなオブジェクトが必要な場合: 関数型プログラミング、マルチスレッド処理など
一方、DateTime の方が適している場合もあります:
- 複雑なタイムゾーン処理が必要: 夏時間の自動処理など
- DateTime エコシステムのモジュールを使う: DateTime::Format::* など
- カレンダー計算が必要: DateTime::Event::* など
用途に応じて適切なモジュールを選択することで、効率的な日時処理を実現できます。Time::Moment の高速性とシンプルさは、多くの実用的なユースケースで強力な武器となるでしょう。