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


## 知らなかったよ...

+ 年月日からUNIX時刻に変換する、Time::Local::timelocal って普通こう書きますよね？

```perl
my $year = 2016 - 1900; # オフセットの1900を引く
〜〜
my $time = timelocal( $sec, $min, $hour, $mday, $mon, $year );
```

+  けど、どうも年指定は違う形式でもできるようなんです...

## 試す！

+ 先に説明文みろよ！って話ですが、まず試してみます。

```perl: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は何か意味があるんでしょうか？？

## やっと説明文を見ます。

+ ここあたりですかね？
+ http://perldoc.perl.org/Time/Local.html#timelocal()-and-timegm()

> ### 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

```perl: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ってなんぞ？

+ 該当のソースを見てみます。

```perl: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を計算してるようです。

### これも試します。

```perl: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";
```

```shell:結果
% perl breakpoint.pl
$ThisYear = 116
$Breakpoint = 66
$NextCentury = 100
$Century = 0
```

1. 今年は2016年ですから、ThisYearは 2016-1900 = 116
2. BreakpointはThisYearに50年を足して２桁までを採用 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のルールのままの方がいいような気もするんですが...