0
1

More than 5 years have passed since last update.

perlのTime::Localの年号指定はyear-1900は不要!?

Last updated at Posted at 2016-03-12

知らなかったよ...

  • 年月日からUNIX時刻に変換する、Time::Local::timelocal って普通こう書きますよね?
my $year = 2016 - 1900; # オフセットの1900を引く
〜〜
my $time = timelocal( $sec, $min, $hour, $mday, $mon, $year );
  • けど、どうも年指定は違う形式でもできるようなんです...

試す!

  • 先に説明文みろよ!って話ですが、まず試してみます。
localtime.pl
use utf8;
use strict;
use Time::Local 'timelocal';

# モジュールのバージョン確認
print "### Time::Local::VERSION\n\n";
print "+ " . ($Time::Local::VERSION)."\n\n";

# 年号指定毎に判定された年を確かめる。
my $cnt = 0;

print "### timelocal\n\n";
print "|".join("|",qw 'No. $year $year+1900 localtime+1900 isEqual')."|\n";
print "|".join("|",qw ':-: ----: ---------: -------------: :-----:')."|\n";

for my $year (-1000,-999,-100,-10,-1,0,1,2,3,4,5,10,50,60,61,62,63,64,65,66,67,68,69,70,71,80,90,99,100,116,100,200,999,1000,1900,2000,2016,2038) {
    my $epoch_t = timelocal(0,0,9,1,0,$year);
    my @lt = localtime($epoch_t);
    my $syear = ($year + 1900) ; # 引数のオフセットを戻しただけ
    my $lyear = ($lt[5] + 1900) ; # localtimeの年号+オフセット
    print "|".join("|",(++$cnt,$year, $syear, $lyear , $syear == $lyear ? 'O' : 'X'))."|\n";
}

実行結果です。

Time::Local::VERSION

  • 1.2300

timelocal

No. $year $year+1900 localtime+1900 isEqual
1 -1000 900 900 O
2 -999 901 901 O
3 -100 1800 1800 O
4 -10 1890 1890 O
5 -1 1899 1899 O
6 0 1900 2000 X
7 1 1901 2001 X
8 2 1902 2002 X
9 3 1903 2003 X
10 4 1904 2004 X
11 5 1905 2005 X
12 10 1910 2010 X
13 50 1950 2050 X
14 60 1960 2060 X
15 61 1961 2061 X
16 62 1962 2062 X
17 63 1963 2063 X
18 64 1964 2064 X
19 65 1965 2065 X
20 66 1966 2066 X
21 67 1967 1967 O
22 68 1968 1968 O
23 69 1969 1969 O
24 70 1970 1970 O
25 71 1971 1971 O
26 80 1980 1980 O
27 90 1990 1990 O
28 99 1999 1999 O
29 100 2000 2000 O
30 116 2016 2016 O
31 100 2000 2000 O
32 200 2100 2100 O
33 999 2899 2899 O
34 1000 2900 1000 X
35 1900 3800 1900 X
36 2000 3900 2000 X
37 2016 3916 2016 X
38 2038 3938 2038 X

なるほど、なにかルールがありそうです。

  1. \$year がマイナスの時は \$year + 1900 と判定される。 <- 知ってた\(^o^)/
  2. \$year が 0〜66 までは \$year + 2000 と判定される。<- そうだったの!Σ(゚Д゚)
  3. \$year が 67〜999 までは \$year + 1900 と判定される。 <- 知ってた\(^o^)/
  4. \$year が 1000以上のときにはそのまま年号として扱われて \$year と判定される。 <- そうだったの!Σ(゚Д゚)
  • No.2と3の66と67は何か意味があるんでしょうか??

やっと説明文を見ます。

Year Value Interpretation

Strictly speaking, the year should be specified in a form consistent with localtime(), i.e. the offset from 1900. In order to make the interpretation of the year easier for humans, however, who are more accustomed to seeing years as two-digit or four-digit values, the following conventions are followed:
Years greater than 999 are interpreted as being the actual year, rather than the offset from 1900. Thus, 1964 would indicate the year Martin Luther King won the Nobel prize, not the year 3864.
Years in the range 100..999 are interpreted as offset from 1900, so that 112 indicates 2012. This rule also applies to years less than zero (but see note below regarding date range).
Years in the range 0..99 are interpreted as shorthand for years in the rolling "current century," defined as 50 years on either side of the current year. Thus, today, in 1999, 0 would refer to 2000, and 45 to 2045, but 55 would refer to 1955. Twenty years from now, 55 would instead refer to 2055. This is messy, but matches the way people currently think about two digit dates. Whenever possible, use an absolute four digit year instead.

  • なんだかよくわかりません...

ソースコードを見ます。

  • perldoc -m Time::Local
timelocal
〜〜〜
sub timegm {
    my ( $sec, $min, $hour, $mday, $month, $year ) = @_;

    if ( $year >= 1000 ) { ### No.4の挙動 
        $year -= 1900;
    }
    elsif ( $year < 100 and $year >= 0 ) { ### No.2,3の挙動はここっぽい
        $year += ( $year > $Breakpoint ) ? $Century : $NextCentury;
    }
    〜〜〜
  • 実行結果No.4の\$yearが1000以上だったら〜ってのがありました。
  • \$yearが0から99までというのでなんかやってますね。この範囲では\$Breakpointで世紀分を加算してるようです。

Breakpointってなんぞ?

  • 該当のソースを見てみます。
timelocal
# Determine breakpoint for rolling century
my $ThisYear    = ( localtime() )[5];
my $Breakpoint  = ( $ThisYear + 50 ) % 100;
my $NextCentury = $ThisYear - $ThisYear % 100;
$NextCentury += 100 if $Breakpoint < 50;
my $Century = $NextCentury - 100;
  • 現在の年からBreakpointを計算してるようです。

これも試します。

breakpoint.pl
use utf8;
use strict;

# Determine breakpoint for rolling century
my $ThisYear    = ( localtime() )[5];
my $Breakpoint  = ( $ThisYear + 50 ) % 100;
my $NextCentury = $ThisYear - $ThisYear % 100;
$NextCentury += 100 if $Breakpoint < 50;
my $Century = $NextCentury - 100;

print '$ThisYear = '.$ThisYear."\n";
print '$Breakpoint = '.$Breakpoint."\n";
print '$NextCentury = '.$NextCentury."\n";
print '$Century = '.$Century."\n";
結果
% perl breakpoint.pl
$ThisYear = 116
$Breakpoint = 66
$NextCentury = 100
$Century = 0
  1. 今年は2016年ですから、ThisYearは 2016-1900 = 116
  2. BreakpointはThisYearに50年を足して2桁までを採用 116 + 50 = 166 -> 66
  3. NextCenturyはThisYear - ThisYear % 100 ですから、2桁部を排除ですね。 116 - 16 = 100
  4. Breakpointが50より小さかったらNextCentury+100ですが、67なのでそのまま。NextCentury = 100
  5. Centuryは一世紀前なのでNextCentury-100 = 0となってます。
  • しきい値となった66は今年が2016だからなのでした!!

まとめ

  • いまから年月日を扱う時には \$year - 1900 って必要なかったんですね。そのまま2016とか入れてOKです!!
  • 00-99だと判定は実行した年に依存するようです。それって、昔のYYしかないデータの処理だと実行年度で結果変わっちゃいません?
  • 2000年問題みたいになりそうですから0-99ってのは使わないほうがいいし、そもそも\$year - 1900のルールのままの方がいいような気もするんですが...
0
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1