Perl Hackers Hub

第66回 モジュールによる時間の多様な取り扱い(1)

この記事を読むのに必要な時間:およそ 3 分

時間の標準モジュール

localtime関数やgmtime関数で時間をある程度は自由に表現できるものの,時間の加算や減算などの演算が煩雑であることや,ミリ秒の取り扱いができないなどの問題があります。

本節では,時間演算を便利に行う標準モジュールと,ミリ秒まで時間を扱う標準モジュールを見ていきます。

Time::Piece ─⁠─高速な時間演算モジュール

localtime関数は時間を任意の単位で取り出せるため加工するのに便利ですが,使いたい形にするためには関数化する必要があります。日付や時刻情報はISO8601などで出力フォーマットが決められていることが多く,その形に毎回変換するのは煩雑です。

Time::Pieceモジュール(執筆時点のバージョンは1.3401)は,localtime関数で得られていた分や日など単体の時間はもちろん,日付フォーマットの設定を行うメソッドを持っています。localtime関数とgmtime関数をオーバーライドして,Time::Pieceオブジェクトとして結果が返却されます。

use Time::Piece;
my $t = localtime;
# $t->ymd      2021-01-01 # 年月日
# $t->mdy      01-01-2021 # 月日年
# $t->mdy("/") 01/01/2021
# $t->dmy      01-01-2021 # 日月年
# $t->datetime 2021-01-01T00:00:00 (ISO 8601)
# $t->cdate    Fri Jan 01 00:00:00 2021

Time::PieceモジュールではUNIX時間から任意の時間を得るのではなく,文字列からTime::Pieceオブジェクトに変換できます。

use Time::Piece;
my $parse_time = Time::Piece->strptime('2021-01-01T00:00
:00', '%Y-%m-%dT%H:%M:%S');
say $parse_time->ymd; # 2021-01-01

localtime関数では,時間の加算や減算はUNIX時間を直接渡して出力を得ていました。

my $t = time;
# 1週間分の秒数を減らす
my $one_week_ago = localtime($t - 60 * 60 * 24 * 7);
say $one_week_ago;

それに対してTime::Pieceモジュールでは,Time::Pieceオブジェクトに対してTime::Secondsオブジェクトを加算や減算できます。このとき得られるのはTime::Pieceオブジェクトです。

use Time::Piece;
use Time::Seconds;
my $now = localtime;
# ONE_DAYはTime::Secondsで定義されている1日の定数
my $tomorrow = $now + ONE_DAY;
say $tomorrow->ymd; # 2021-01-02

また,Time::Pieceオブジェクトどうしを減算することで時間の差を得られます。このとき得られるのはTime::Secondsオブジェクトです。

use Time::Piece;
use Time::Seconds;
my $now = localtime;
my $tomorrow = $now + ONE_DAY;
my $diff = $now - $tomorrow;
say $diff->days; # -1

さらに,Time::Pieceオブジェクトどうしで比較できます。

use Time::Piece;
use Time::Seconds;
my $now = localtime;
my $next_year = $now + ONE_YEAR;
if ($now < $next_year) {
    say '$next_yearのほうが未来です';
}
Time::Secondsモジュールの定数の罠

Time::Secondsモジュールでは,1年の定数と1ヵ月の定数は次の内容で定義されています。

  • ONE_YEAR:365.24225日
  • ONE_MONTH:ONE_YEAR / 12

ONE_YEARは365日と定義されていません。これは,閏年を考慮して400年の平均の値となっているためです。その影響を受けて,ONE_MONTHは30日よりも10時間ほど多い値になっています。

閏年を考慮せず常に30日として扱いたい場合はONE_FINANCIAL_MONTHを,閏年として扱いたい場合はLEAP_YEARを,閏年以外として扱いたい場合はNON_LEAP_YEARを使います。

Time::HiRes ─⁠─ミリ秒まで時間を扱うモジュール

UNIX時間は秒数を単位にしているため,小数点以下の時間,つまりミリ秒やマイクロ秒をそのままでは扱えません。

Time::HiResモジュール(執筆時点のバージョンは1.9764)は,マイクロ秒まで扱うことができ,精度が求められる場合に有用です。現在時間をマイクロ秒までほしい場合には,このモジュールのgettimeofday関数を使うと,UNIX時間と小数点以下の秒数が配列で返ってきます。

use Time::HiRes qw(gettimeofday);
my ($now, $micro) = gettimeofday;
say scalar gettimeofday; # 1609426800.76897

マイクロ秒までの時間の差は,tv_interval関数にgettimeofday関数で得られた時間を渡すことで得られます。第二引数を省略した場合は現在時間を参照します。

use Time::HiRes qw(gettimeofday tv_interval);

my $t0 = [gettimeofday];
sleep 1;
my $diff = tv_interval($t0);
say scalar $diff; # 1.001211

sleep関数では秒数で実行を停止しましたが,Time::HiResモジュールではusleep関数にマイクロ秒を渡すことで浮動小数点の秒数でプログラムの実行を停止できます。

use Time::HiRes qw(usleep);
usleep 2_550_000; # 2.55秒停止

<続きの(2)こちら。>

WEB+DB PRESS

本誌最新号をチェック!
WEB+DB PRESS Vol.125

2021年10月23日発売
B5判/168ページ
定価1,628円
(本体1,480円+税10%)
ISBN978-4-297-12435-9

  • 特集1
    作って学ぶプログラミング言語のしくみ
    インタプリタ,構文解析器,文法
  • 特集2
    GraphQL完全ガイド
    RESTの先へ! フロントエンドに最適化されたAPI
  • 特集3
    速習DynamoDB
    AWSフルマネージドNoSQLの探求

著者プロフィール

koluku

北海道生まれ。

2020年に面白法人カヤックに入社し,現在はサーバーサイドアプリケーションを開発している。Webとゲームが好きなひよっこエンジニア。

Twitter:@koluku
URL:https://koluku.com