日付変更線(?)
日付の変わり目が、0時だと困ることがあります。
たとえば、この「はてなダイアリー」は、深夜にエントリを書く人(オイラみたいなコンピュータ屋さんに多いだろうね)を想定して、日付の変わり目がデフォルトで 5時(だっけ?)に設定されています。ほかにも、テレビ局なんかも、25時とか26時とか良く分からないタイムテーブルを持ってたりするよね?
オイラは工場勤務です。工場の人は交代制で働いています。なので、朝の交代の時間が日付の変わり目になるらしいです。
まあ日付の変わり目がいつも0時とは限らないよ、というのがこれでお分かりいただけたかと思います。
で、これをプログラミングでどう実現するか、というお話。
今日は 2/21(はてなダイアリー的には前述の理由で2/20だけど、一般人的には日付変わったから2/21だよね)ですが、交代制勤務で、交代の時間が仮に9:00としましょう。つまり「2/22 の 8:59 までは 2/21 として扱う」ということになります。
で、本日の日付をどう求めるかを考えていきます。
まずは、日付変更を無視して、一番簡単なケースを考えます。こんな感じ。
#!/usr/bin/perl use strict; use warnings; use Test::More "no_plan"; sub today { my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); return sprintf("%04d/%02d/%02d", $year+1900, $mon+1, $mday); } is(today(), "2009/02/21");
実行します。
tsucchi@appears[108]% prove a.pl a.......ok All tests successful. Files=1, Tests=1, 1 wallclock secs ( 0.00 usr 0.00 sys + 0.12 cusr 0.05 csys = 0.17 CPU) Result: PASS
OK ですね。これだと明日になるとダメなので、Time::Fake を使っていつ実行しても結果が変わらんようにします。(see also: Time::Fake 可愛いよ、Time::Fake、 - tsucchiの日記)
#!/usr/bin/perl use strict; use warnings; use Test::More "no_plan"; use Time::Local; use Time::Fake; sub today { my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); return sprintf("%04d/%02d/%02d", $year+1900, $mon+1, $mday); } Time::Fake->offset(timelocal(56, 34, 12, 21, 2-1, 2009));# 2009/2/21 12:34 56秒; is(today(), "2009/02/21");
実行します。
tsucchi@appears[109]% prove a.pl a.......ok All tests successful. Files=1, Tests=1, 0 wallclock secs ( 0.00 usr 0.02 sys + 0.15 cusr 0.00 csys = 0.17 CPU) Result: PASS
まあ OK ですな。
では、日付の変わり目が 9:00 になるとします。テストはこんな感じかな。
#!/usr/bin/perl use strict; use warnings; use Test::More "no_plan"; use Time::Local; use Time::Fake; sub today { my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); return sprintf("%04d/%02d/%02d", $year+1900, $mon+1, $mday); } Time::Fake->offset(timelocal(59, 59, 8, 21, 2-1, 2009));# 2009/2/21 8:59 59秒; is(today(), "2009/02/20"); Time::Fake->offset(timelocal(0, 0, 9, 21, 2-1, 2009));# 2009/2/21 9:00 00秒; is(today(), "2009/02/21");
8:59:59 では前日、9:00:00 では当日になってくれれば良い訳です。では実行します。
% prove a.pl a.......1/? # Failed test at a.pl line 14. # got: '2009/02/21' # expected: '2009/02/20' # Looks like you failed 1 test of 2. a....... Dubious, test returned 1 (wstat 256, 0x100) Failed 1/2 subtests Test Summary Report ------------------- a.pl (Wstat: 256 Tests: 2 Failed: 1) Failed test: 1 Non-zero exit status: 1 Files=1, Tests=2, 0 wallclock secs ( 0.00 usr 0.02 sys + 0.14 cusr 0.01 csys = 0.17 CPU) Result: FAIL
today() は現在日付を返しているので、当然失敗します。
で、ここからが難問。9:00という時間の境界をどう実現するか、を考えなければなりません。「localtime の戻り値の時間を見る」というのが一番最初に思いつきますが、どう考えてもダメダメです。仮に時間を見て日付を戻すにしても、月末月初の処理やうるう年の処理を考えると、悪夢以外の何者でもありません。
で、オイラはたまたま today() 側の処理に色々書いていたこともあって、ちょっとひらめきがありました。(全然たいしたことないんだけどさ)
「9:00開始ってことは、0:00が9:00にずれるってことじゃね?オフセット取ればよくね?」ということです。つーわけで、「オフセット」というアイデアを反映させます。
#!/usr/bin/perl use strict; use warnings; use Test::More "no_plan"; use Time::Local; use Time::Fake; sub today { my ($offset) = @_; my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); return sprintf("%04d/%02d/%02d", $year+1900, $mon+1, $mday); } Time::Fake->offset(timelocal(59, 59, 8, 21, 2-1, 2009));# 2009/2/21 8:59 59秒; is(today(9), "2009/02/20"); Time::Fake->offset(timelocal(0, 0, 9, 21, 2-1, 2009));# 2009/2/21 9:00 01秒; is(today(9), "2009/02/21");
today()にオフセットとして、9時間を渡します。
tsucchi@appears[112]% prove a.pl a.......1/? # Failed test at a.pl line 15. # got: '2009/02/21' # expected: '2009/02/20' # Looks like you failed 1 test of 2. a....... Dubious, test returned 1 (wstat 256, 0x100) Failed 1/2 subtests Test Summary Report ------------------- a.pl (Wstat: 256 Tests: 2 Failed: 1) Failed test: 1 Non-zero exit status: 1 Files=1, Tests=2, 0 wallclock secs ( 0.00 usr 0.00 sys + 0.12 cusr 0.08 csys = 0.20 CPU) Result: FAIL
渡したオフセットに対して、today() はまだ何もしていないから、当然テストは失敗します。
today() 側では、オフセット値を引き算するようにします。time() は秒単位なので、秒に変換する必要があります。こんな感じ。
sub today { my ($offset) = @_; my $offset_sec = $offset * 60 * 60; my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time - $offset_sec); return sprintf("%04d/%02d/%02d", $year+1900, $mon+1, $mday); }
現在時刻( time() )から、$offset_sec を引き算すればよいわけです。で、実行します。
tsucchi@appears[113]% prove a.pl a.......ok All tests successful. Files=1, Tests=2, 0 wallclock secs ( 0.00 usr 0.02 sys + 0.12 cusr 0.05 csys = 0.19 CPU) Result: PASS
テストも無事通りました。というわけで終了です。