Featured image of post Time::Moment — 高速で正確な日時処理

Time::Moment — 高速で正確な日時処理

Perl の高速で軽量な日時ライブラリ Time::Moment を紹介します。性能比較、基本操作、タイムゾーン処理、実用例をわかりやすく解説します。

Time::Moment - 高速で正確な日時処理

Perlで日時を扱う際、長年 DateTime モジュールが定番として使われてきました。しかし、パフォーマンスが重要な場面では Time::Moment という選択肢があります。Time::Moment は C 言語で実装されており、DateTime よりも圧倒的に高速で、メモリ使用量も少ないという特徴があります。

Time::Momentの特徴

Time::Moment の主な特徴は以下の通りです:

  • 高速: C言語で実装され、DateTime の数十倍から数百倍の速度
  • イミュータブル: すべての操作で新しいオブジェクトを返すため、予期しない副作用がない
  • 正確: マイクロ秒精度の時刻を扱える
  • 軽量: メモリ使用量が少ない
  • 完全なタイムゾーンサポート: Olson タイムゾーンデータベースに対応

インストール

1
cpanm Time::Moment

基本的な使い方

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);
    },
});

主な違い:

項目DateTimeTime::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 の高速性とシンプルさは、多くの実用的なユースケースで強力な武器となるでしょう。

comments powered by Disqus
Hugo で構築されています。
テーマ StackJimmy によって設計されています。