date関数で一部書式を使ったときに不審な動きがあったので修正する (PyukiWiki Classic v0.1.7)
PyukiWikiを改造する場合,日付書式を変えるというのは結構ありがちなテーマだ.
その時には,wiki.cgi(index.cgi)のdate関数のお世話になるのだが,
一部の書式で予期せぬ結果が返ってきてしまった.
そこで,修正を試みた.(PyukiWiki Classic v0.1.7)
1.現象
5月の日付に対して,
M (Jan-Dec)
の書式指定を使うと,例えば以下のような変な結果が得られる.("D, d M Y H:i:s"の例)
Sat, 05 Mpmy 2007 23:01:33
これは,5月→Mayが得られた後に,Mayに含まれる「a」が後続の処理で別の書式指定
a (am or pm)
と解釈されて,am/pmに置換されるためである
このように数値ではなく文字列が返るような書式の場合,
現状だとその返った文字列が後続の処理で書式指定として解釈されることで,変な結果が出てしまう.
2.修正
以下のように修正した.
やってることとしては,書式を1文字ずつに分解して,
さらに置換結果が書式として解釈されないように配慮しているという感じ.
既存のコードをあまりいじらずに目的を達成しようとしているので,かなり汚い...
修正前
sub date
{
my ($format, $tm) = @_;
# yday:0-365 $isdst Summertime:1/not:0
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = ((@_ > 1) ? localtime($tm) : localtime);
$year += 1900; #
my ($hr12, $ampm) = $hour >= 12 ? ($hour - 12,'pm') : ($hour, 'am');
# year
$format =~ s/Y/$year/ge; # Y:4char ex)1999 or 2003
$year = $year % 100;
$year = "0" . $year if ($year < 10);
$format =~ s/y/$year/ge; # y:2char ex)99 or 03
# month
my $month = ('January','February','March','April','May','June','July','August','September','October','November','December')[$mon];
$mon++; # mon is 0 to 11 add 1
$format =~ s/n/$mon/ge; # n:1-12
$mon = "0" . $mon if ($mon < 10);
$format =~ s/m/$mon/ge; # m:01-12
$format =~ s/M/substr($month,0,3)/ge; # M:Jan-Dec
$format =~ s/F/$month/ge; # F:January-December
# day
$format =~ s/j/$mday/ge; # j:1-31
$mday = "0" . $mday if ($mday < 10);
$format =~ s/d/$mday/ge; # d:01-31
# hour
$format =~ s/g/$hr12/ge; # g:1-12
$format =~ s/G/$hour/ge; # G:0-23
$hr12 = "0" . $hr12 if ($hr12 < 10);
$hour = "0" . $hour if ($hour < 10);
$format =~ s/h/$hr12/ge; # h:01-12
$format =~ s/H/$hour/ge; # H:00-23
# minutes
$min = "0" . $min if ($min < 10);
$format =~ s/i/$min/ge; # i:00-59
# second
$sec = "0" . $sec if ($sec < 10);
$format =~ s/s/$sec/ge; # s:00-59
$format =~ s/a/$ampm/ge; # a:am or pm
$format =~ s/A/uc $ampm/ge; # A:AM or PM
$format =~ s/w/$wday/ge; # w:0(Sunday)-6(Saturday)
my $weekday = ('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday')[$wday];
$format =~ s/l/$weekday/ge; # l(lower L):Sunday-Saturday
$format =~ s/D/substr($weekday,0,3)/ge; # D:Mon-Sun
$format =~ s/I/$isdst/ge; # I(Upper i):1 Summertime/0:Not
# Not Allowed
# L 閏年であるかどうかを表す論理値。 1なら閏年。0なら閏年ではない。
# O グリニッジ標準時(GMT)との時間差 Example: +0200
# r RFC 822 フォーマットされた日付 Example: Thu, 21 Dec 2000 16:01:07 +0200
# S 英語形式の序数を表すサフィックス。2 文字。 st, nd, rd or th. Works well with j
# T このマシーンのタイムゾーンの設定。 Examples: EST, MDT ...
# U Unix 時(1970年1月1日0時0分0秒)からの秒数 See also time()
# W ISO-8601 月曜日に始まる年単位の週番号 (PHP 4.1.0で追加) Example: 42 (the 42nd week in the year)
$format =~ s/z/$yday/ge; # z:days/year 0-366
return $format;
}
修正後
sub date {
my($format, $tm) = @_;
my(@f) = split(//, $format);
my(@fa) = ();
foreach (@f) {
push(@fa, date_inner($_, $tm));
}
return join('', @fa);
}
sub date_inner
{
my ($format, $tm) = @_;
my($org_format) = $format;
# yday:0-365 $isdst Summertime:1/not:0
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = ((@_ > 1) ? localtime($tm) : localtime);
$year += 1900; #
my ($hr12, $ampm) = $hour >= 12 ? ($hour - 12,'pm') : ($hour, 'am');
# year
$format =~ s/Y/$year/ge; # Y:4char ex)1999 or 2003
$year = $year % 100;
$year = "0" . $year if ($year < 10);
$format =~ s/y/$year/ge; # y:2char ex)99 or 03
# month
my $month = ('January','February','March','April','May','June','July','August','September','October','November','December')[$mon];
$mon++; # mon is 0 to 11 add 1
$format =~ s/n/$mon/ge; # n:1-12
$mon = "0" . $mon if ($mon < 10);
$format =~ s/m/$mon/ge; # m:01-12
$format =~ s/M/substr($month,0,3)/ge; # M:Jan-Dec
$format =~ s/F/$month/ge; # F:January-December
# day
$format =~ s/j/$mday/ge; # j:1-31
$mday = "0" . $mday if ($mday < 10);
$format =~ s/d/$mday/ge; # d:01-31
# hour
if ($org_format =~ /g/) {
$format =~ s/g/$hr12/ge; # g:1-12
}
$format =~ s/G/$hour/ge; # G:0-23
$hr12 = "0" . $hr12 if ($hr12 < 10);
$hour = "0" . $hour if ($hour < 10);
$format =~ s/h/$hr12/ge; # h:01-12
$format =~ s/H/$hour/ge; # H:00-23
# minutes
$min = "0" . $min if ($min < 10);
if ($org_format =~ /i/) {
$format =~ s/i/$min/ge; # i:00-59
}
# second
$sec = "0" . $sec if ($sec < 10);
if ($org_format =~ /s/) {
$format =~ s/s/$sec/ge; # s:00-59
}
if ($org_format =~ /a/) {
$format =~ s/a/$ampm/ge; # a:am or pm
}
if ($org_format =~ /A/) {
$format =~ s/A/uc $ampm/ge; # A:AM or PM
}
$format =~ s/w/$wday/ge; # w:0(Sunday)-6(Saturday)
my $weekday = ('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday')[$wday];
if ($org_format =~ /l/) {
$format =~ s/l/$weekday/ge; # l(lower L):Sunday-Saturday
}
if ($org_format =~ /D/) {
$format =~ s/D/substr($weekday,0,3)/ge; # D:Mon-Sun
}
$format =~ s/I/$isdst/ge; # I(Upper i):1 Summertime/0:Not
# Not Allowed
# L 閏年であるかどうかを表す論理値。 1なら閏年。0なら閏年ではない。
# O グリニッジ標準時(GMT)との時間差 Example: +0200
# r RFC 822 フォーマットされた日付 Example: Thu, 21 Dec 2000 16:01:07 +0200
# S 英語形式の序数を表すサフィックス。2 文字。 st, nd, rd or th. Works well with j
# T このマシーンのタイムゾーンの設定。 Examples: EST, MDT ...
# U Unix 時(1970年1月1日0時0分0秒)からの秒数 See also time()
# W ISO-8601 月曜日に始まる年単位の週番号 (PHP 4.1.0で追加) Example: 42 (the 42nd week in the year)
$format =~ s/z/$yday/ge; # z:days/year 0-366
return $format;
}
ぱっと見た感じでは,date_inner()で与えられた書式指定と変換結果を分けているので,date()で行っている文字分割は不要に見えるかも知れないが,
文字分解をやらない場合は,
"D, d M Y H:i:s"
は大丈夫なのだが,
"D, d M Y a H:i:s"
でアボンである.こんな感じに誤出力してしまう.
Sat, 05 Mpmy 2007 pm 23:40:21
なので,必要なのだ.