Perlで日付/時刻/時間を扱う
2023/11/25
すぐ忘れるのでまとめておこう
現在日付/時刻取得
  • Time::Pieceモジュールを使用します。localtime関数はオーバーライドされますが、組込のlocaltime関数と挙動が変わるのでネームスペース明示がお勧め(*1)です。
  • #!perl -w
    use Time::Piece;
    use strict;
    
    {
      # 現時刻のTime::Pieceオブジェクト取得
        my $o_tp = Time::Piece::localtime();
        
      # 年月日と時刻に関する各要素はメソッドで取得
        printf("Case0: %04d/%02d/%02d, %02d:%02d:%02d\n",
                $o_tp->year,   # 西暦年(year)
                $o_tp->mon,    # 月(mon)
                $o_tp->mday,   # 日(day) day of the month
                $o_tp->hour,   # 時
                $o_tp->minute, # 分
                $o_tp->sec     # 秒
        );
        
      # 表示のみならymd, hmsメソッドを使用
      # ymdのデフォルトデリミタは '-'なので、好みに応じて '/'に変更
        printf("Case1: %s, %s # default delimiter\n",  $o_tp->ymd,      $o_tp->hms     );
        printf("Case2: %s, %s # modified delimiter\n", $o_tp->ymd('/'), $o_tp->hms('.'));
    }
    >sample0a.pl
    Case0: 2023/11/25, 12:31:59
    Case1: 2023-11-25, 12:31:59 # default delimiter
    Case2: 2023/11/25, 12.31.59 # modified delimiter
  • 尚標準時(UTC/GMT)でTime::Pieceオブジェクトを取得するにはgmtime()関数を使用します。

ファイルタイムスタンプ取得
  • ファイルのタイムスタンプには下記の3種類があります。
    • ctime: 作成日時
    • utime: 更新日時
    • atime: アクセス日時

  • タイムスタンプ取得にはstat関数を使用します。情報はリストで[8],[9],[10]が該当します。ただし値はエポック秒(*2)のため、表示等の必要に応じて、先と同じようにTime::Piece::localtime関数でローカルタイムに変換します。
  • #!perl
    use Time::Piece;
    use strict;
    
    {
      # 指定ファイルの情報取得
        my @list = stat('sample0.pl'); # statは組込関数
        
        my $e_ct = $list[10]; # 作成日時(エポック秒)
        my $e_ut = $list[9];  # 更新日時(エポック秒)
        my $e_at = $list[8];  # アクセス日時(エポック秒)
        
      # エポック秒をTime::Pieceオブジェクトに変換
        my $o_ct = Time::Piece::localtime($e_ct);
        my $o_ut = Time::Piece::localtime($e_ut);
        my $o_at = Time::Piece::localtime($e_at);
        
      # 日付,時刻表示
        printf("ctime=%s, %s\n", $o_ct->ymd('/'), $o_ct->hms);
        printf("utime=%s, %s\n", $o_ut->ymd('/'), $o_ut->hms);
        printf("atime=%s, %s\n", $o_at->ymd('/'), $o_at->hms);
    }
    >sample1.pl
    ctime=2023/11/23, 10:37:17
    utime=2023/11/23, 14:26:48
    atime=2023/11/23, 14:26:48

ファイルタイムスタンプ設定
  • utime関数で、atimeとutimeの値を設定することができます。エポック秒で設定します。下記サンプルはファイル変更後にutimeとatimeを変更前時刻へ戻すコードです。ちょっと長いですが見て欲しいのはutimeのところだけです。
  • #!perl
    use Time::Piece;
    use strict;
    
    {
      # 対象ファイル
        my $fname = 'sample0a.pl';
        
      # 初期情報取得
        my @inf0 = stat($fname);
        my %e_t0; ($e_t0{at}, $e_t0{ut}, $e_t0{ct}) = @inf0[8..10]; # 初期[auc]timeキープ
        print "0: ", conv_lc(%e_t0), "\n";
        
      # 少しファイルを変更
        my $fh; open($fh, ">> $fname"); print $fh "1;\n"; close($fh);
    
      # 変更後情報取得
        my @inf1 = stat($fname);
        my %e_t1; ($e_t1{at}, $e_t1{ut}, $e_t1{ct}) = @inf1[8..10];
        print "1: ", conv_lc(%e_t1), "\n";
      
      # utime,atime設定(変更前時刻に戻すという設定)
      #       atime      utime      file_name
        utime($e_t0{at}, $e_t0{ut}, $fname); # 引数1:atime, 引数2:utime, 引数3:ファイル名
        
      # 設定後情報取得
        my @inf2 = stat($fname);
        my %e_t2; ($e_t2{at}, $e_t2{ut}, $e_t2{ct}) = @inf2[8..10];
        print "2: ", conv_lc(%e_t2), "\n";
    }
    
    sub conv_lc { # エポック秒をローカルタイムへ変換
        my (%e_t) = @_;
        
        my @ret;
        foreach my $t_key ('ct','ut','at') {
            my $o_tp = Time::Piece::localtime($e_t{$t_key});
            push(@ret, sprintf("[%s]=%s,%s", $t_key, $o_tp->ymd('/'), $o_tp->hms));
        }
        return join(' ',@ret);
    }
    >sample2.pl
    0: [ct]=2023/11/23,10:37:17 [ut]=2023/11/23,16:20:57 [at]=2023/11/25,12:14:39 # 初期
    1: [ct]=2023/11/23,10:37:17 [ut]=2023/11/25,12:47:26 [at]=2023/11/25,12:47:26 # 変更
    2: [ct]=2023/11/23,10:37:17 [ut]=2023/11/23,16:20:57 [at]=2023/11/25,12:14:39 # 初期に戻る

指定時間の実行停止
  • sleep関数を使用します。秒単位で待ち時間を指定できます。
  • #!perl -w
    use Time::Piece;
    use strict;
    
    {
        my $o_tp0 = Time::Piece::localtime(); # 時刻取得
        print $o_tp0->hms,"\n";  # 時刻表示
        
        sleep(10); # 10秒停止
        
        my $o_tp1 = Time::Piece::localtime(); # 時刻取得
        print $o_tp1->hms,"\n";  # 時刻表示
    }
    >sample3.pl
    10:20:08
    10:20:18

経過時間の計測
  • Time::Pieceオブジェクト同士で差を計算(*3)することができます。戻り値はTime::Secondsのオブジェクトです。経過/差分時間を秒/分/時間等の単位で返すメソッドを持ちます。
  • #!perl -w
    use Time::Piece;
    use Time::Seconds;
    use strict;
    
    {
        my $o_tp0 = Time::Piece::localtime(); # 時刻取得(初期)
        print $o_tp0->hms,"\n";
        
        sleep(10); # 10秒待ち
        
        my $o_tp1 = Time::Piece::localtime(); # 時刻取得(10秒後)
        print $o_tp1->hms,"\n";
        
        my $o_ts = $o_tp1 - $o_tp0; # 経過時間計算(Time::Secondsオブジェクトが戻る)
        
        printf("diff[s] = %02d\n", $o_ts->seconds); # [秒]単位表示
        printf("diff[m] = %.4f\n", $o_ts->minutes); # [分]単位表示
        printf("diff[h] = %.4f\n", $o_ts->hours);   # [時]単位表示
    }
    >sample4.pl
    14:11:34
    14:11:44
    diff[s] = 10
    diff[m] = 0.1667
    diff[h] = 0.0028

停止及び計測時間をus単位にする
  • あくまで単位であって精度ではありませんがTime::HiResモジュールで停止時間や経過時間の計測に適用する時刻の単位をus(マイクロ秒)単位にすることができます。

  • time関数はオーバーライドされますが、出力小数点桁数が「5桁」のため、Time::HiResのgettimeofdayメソッドの使用をお勧めします。
  • #!perl -w
    use Time::HiRes;
    use strict;
    
    {
        my @e_t0 = Time::HiRes::gettimeofday; # gettimeofdayメソッド: 時刻取得(浮動小数点エポック秒)
        # 戻り値が2要素のリスト
        print "0: $e_t0[0].$e_t0[1]\n";
        #         |        |
        #         |        +--要素[1]小数部
        #         +--要素[0]整数部
        
        Time::HiRes::usleep(256*1000); # usleepメソッド: 256*1000[us]=256[ms]待ち
        
        my @e_t1 = Time::HiRes::gettimeofday; # 時刻取得(浮動小数点エポック秒)
        print "1: $e_t1[0].$e_t1[1]\n";
        
        printf("diff=%f[sec]\n",
                Time::HiRes::tv_interval(\@e_t0, \@e_t1)); # tv_intervalメソッド: 時間差取得
                                       # 引数にgettimeofday戻り値配列の"参照"を渡す
    }
    >sample5.pl
    0: 1700899914.546883
    1: 1700899914.816538
    diff=0.269655[sec]

ローカルタイムをエポック秒に変換
  • エポック秒への変換はTime::Localモジュールtimelocal関数を使います。ただしローカルタイムの与え方が少し面倒です。
  • #!perl -w
    use Time::Local;
    use Time::Piece;
    use strict;
    
    {
      # 2023/11/25, 11:10:15
        my $year = 2023 - 1900; # 年は1900年からの経過年数で与える ※ミスりやすい
        my $mon  = 11 - 1;      # 月は0から開始 ※ミスりやすい
        my $mday = 25;          # day of the month
        my $hour = 11;          # 時
        my $min  = 10;          # 分
        my $sec  = 15;          # 秒
        
      # エポック秒に変換
        my $e_time = Time::Local::timelocal($sec, $min, $hour, $mday, $mon, $year);
        print "epoch=$e_time\n";
      
      # 確認のためTime::Pieceオブジェクトに変換
        my $o_tp = Time::Piece::localtime($e_time);
        printf("local=%s,%s\n", $o_tp->ymd, $o_tp->hms);
    }
    >sample6.pl
    epoch=1700878215
    local=2023-11-25,11:10:15
  • ちなみに標準時(UTC/GMT)をエポック秒に変換する場合はtimegm関数を使います。

  • Time::Piece::strptime()を使えば? と言われそうですが、オブジェクト生成がgmtimeで解釈されるので、それをlocaltimeへ変更させるために、もう一つTime::Pieceオブジェクトを作るなど不自然な面倒さがあるため私は避けています。

実は複数のタイムゾーンには向かない
  • 日付と時刻を扱うモジュールとしてTime::Pieceを中心に説明してきました。

  • しかし、Time::Pieceは明示的にタイムゾーンを指定する機能を持たないため、例えば日本/インド/アメリカ等の複数のタイムゾーンで時刻をケアするツールには向かないという側面があります。

  • そんな場合にはDateTimeモジュールを使うことになりますが、大きなモジュールです。なので不必要な場で使うのもどうかと。普段は軽量のTime::Pieceを使い、必要な場合にDateTimeモジュールを検討した方が良いと考えています。
Notes
  • 戻り値の期待型(スカラー/リスト)でコンテキストスイッチしていると思われます。
  • 1970年1月1日0時0分0秒からの経過秒です。これによると私は紀元前生まれの人ってことに。
  • 普通に '-' の演算子で算出できます。オブジェクトなので演算子オーバーライドしていますね。
2023-11-26: 一部サンプルコード変更とエポック秒変換追加
2023-11-25: 初版
Copyright(C) 2023 Altmo
本HPについて