m_shige1979のときどきITブログ

プログラムの勉強をしながら学習したことや経験したことをぼそぼそと書いていきます

Github(変なおっさんの顔でるので気をつけてね)

https://github.com/mshige1979

Perl入学式#5の最終問題2

問題は以下

以下の機能があるYAPCモジュールを実装してください(上級編)
YAPC::is_yet(<日付の文字列>)で, 開催前か開催後かを真か偽で返します(テストをBの人が, コードをAの人が書きましょう)
「8月28日以前」ならば開催前(真), それ以降なら開催後(偽)として扱うことにします
日付のフォーマットは, 「4桁の年/2桁の月/2桁の日」という形にします
例えば, 2014年1月1日は, 「2014/01/01」です.
この上級編はテストもコードも結構難しいです! わからない所があれば, サポーターの人に「どうすればXXXXが出来る?」と聞きいてみましょう

なんですけど

ワンランク上の実装へ...
YAPC::is_yetは, 引数として日付の文字列を受け取るのではなく, 「プログラムを実行した, 現在の時間」を利用して実装することもできます
日付をうまく操作するにはTime::Pieceというモジュールを使います
テスト内の時間を操作するにはTest::MockTimeを使ってみてください

って書いてあったのをちょっと調査

必要な機能

  • 現在日付を取得する
  • 指定した文字列の日付(2014/11/23 11:20:30とか)を日付情報として扱う
  • 日付データを比較して開催前、以降を判定する方法

モジュール

日付制御用モジュール

資料にもあったけど「Time::Piece」で対応できるよう。

#!/usr/bin/env perl

use strict;
use warnings;

use Time::Piece;

# システム日付の情報を取得
my $t = localtime;

# 日時単位に習得
my ($y, $m, $d, $h, $i, $s, $ss);

# それぞれの値を取得
$y = $t->year;
$m = $t->mon;
$d = $t->mday;
$h = $t->hour;
$i = $t->min;
$s = $t->second;
$ss = $t->epoch;

# 出力
print "$y/$m/$d $h:$i:$s\n";
print "$ss\n";

# 日付とする文字列
my $date_string = "2014/08/28 00:00:00";
my $t2 = Time::Piece->strptime($date_string, '%Y/%m/%d %H:%M:%S');

# それぞれの値を取得
$y = $t2->year;
$m = $t2->mon;
$d = $t2->mday;
$h = $t2->hour;
$i = $t2->min;
$s = $t2->second;
$ss = $t2->epoch;

# 出力
print "$y/$m/$d $h:$i:$s\n";
print "$ss\n";
結果
[root@localhost last2]# perl sample1.perl
2014/2/8 10:43:8
1391823788
2014/8/28 0:0:0
1409184000
[root@localhost last2]#
テスト用

「Test::MovkTime」を使用する
モジュールがない場合はインストールする

[root@localhost last2]# cpanm Test::MockTime
--> Working on Test::MockTime
Fetching http://www.cpan.org/authors/id/D/DD/DDICK/Test-MockTime-0.12.tar.gz ... OK
Configuring Test-MockTime-0.12 ... OK
Building and testing Test-MockTime-0.12 ... OK
Successfully installed Test-MockTime-0.12
1 distribution installed
[root@localhost last2]#

モジュールを改修

YAPC.pm
package YAPC;

# 年を返す
sub year {
    return 2014;
}

# 月を返す
sub month {
    return 8;
}

# 日付を返す()
sub day {
    return 28;
}

# 開催前か開催後を返すモジュール
# 最終問題の仕様だと日付ではない場合などが記載されていないので
# 日付ではない場合はありえないということを前提にして考える
sub is_yet{
    # 引数を取得
    my $string = shift;
    my $res = 0;

    if($string =~ /^(\d+)\/(\d+)\/(\d+)$/){
        my $date1 = "$1$2$3";
        if("20140828" gt $date1){
            $res = 1;
        }
    }

    return $res;

}

# 現在時刻を取得して、その日付が開始前か開始以降で判定する
# 開始前:真、以降:0
sub is_yet_now{
    use Time::Piece;

    # システム時刻を取得
    my $t = localtime;

    # 文字列より日付を取得
    my $date_string = "2014/08/28 00:00:00";
    my $t2 = Time::Piece->strptime($date_string, '%Y/%m/%d %H:%M:%S');

    return ($t->epoch < $t2->epoch);

}

1;

※エポックデータ?で判定する

test2.t
#!/usr/bin/env perl

use strict;
use warnings;
use lib 'lib';

use Test::More;
use Test::MockTime qw(set_fixed_time);
use YAPC;

is YAPC::year(), 2014;
is YAPC::month(), 8;
is YAPC::day(), 28;

subtest 'is_yet' => sub {
    ok YAPC::is_yet("2014/08/27");
    ok !YAPC::is_yet("2014/08/28");
    ok !YAPC::is_yet("2014/08/29");
};

subtest 'is_yet_now' => sub {
    use Time::Piece;
    my ($date_string, $t1, $t2, $t3);

    # 開催前
    $date_string = "2014/08/27 23:59:59";
    $t1 = Time::Piece->strptime($date_string, '%Y/%m/%d %H:%M:%S');
    set_fixed_time($t1->epoch);
    ok YAPC::is_yet_now();

    # 開催後
    $date_string = "2014/08/28 00:00:00";
    $t2 = Time::Piece->strptime($date_string, '%Y/%m/%d %H:%M:%S');
    set_fixed_time($t2->epoch);
    ok !YAPC::is_yet_now();

    # 日付エラー
    $date_string = "2014/02/29 00:00:00";
    $t3 = Time::Piece->strptime($date_string, '%Y/%m/%d %H:%M:%S');
    set_fixed_time($t3->epoch);
    ok YAPC::is_yet_now();

};

done_testing();
結果
[root@localhost last2]# prove test2.t -v
test2.t ..
ok 1
ok 2
ok 3
    ok 1
    ok 2
    ok 3
    1..3
ok 4 - is_yet
    ok 1
    ok 2
    ok 3
    1..3
ok 5 - is_yet_now
1..5
ok
All tests successful.
Files=1, Tests=5,  0 wallclock secs ( 0.03 usr  0.01 sys +  0.05 cusr  0.02 csys =  0.11 CPU)
Result: PASS
[root@localhost last2]#

※なんか日付でありえないものを指定したらエラーにならずに真扱いされた。
なんかちょっと調整いるかも

まとめ

  • 日付を制御する場合は、「Time::Piece」と「Test::MockTime」
  • いくつかのテストをまとめたい場合は「subtest」でくくる