最近マヨネーズのCMが流れるたびに、子ども達からパパだ!パパだ!と言われています。
さて、去年の年末に改修したサイトを今年の年初に見ていた時の話。
ふとサイトを眺めていると、去年12月のデータのはずが今年12月のデータとして表示されている。
(2014/12/29のデータが、2015/12/29のデータとして表示されている。時空を越えている。)
目が飛び出たけど、自分たちが改修した部分ではなかったので然るべき所に連絡だけをしてとりあえず放置。
結局何が原因だったかというと、smartyの日付フォーマットでdate_format:"%G/%m/%d"
と、年のフォーマットでG
を使っていたから。との事だった。
何か、G
を使うと週番号を見るからウンタラカンタラで意図しない日付が表示される事があるんだと。
ほんまかいな。なんでやねん。
と思いながらちょっと試してみると、確かに、
{strtotime("2014/12/27")|date_format:"%G/%m/%d"} // 2014/12/27 => OK {strtotime("2014/12/28")|date_format:"%G/%m/%d"} // 2014/12/28 => OK {strtotime("2014/12/29")|date_format:"%G/%m/%d"} // 2015/12/29 => ファッ? {strtotime("2014/12/30")|date_format:"%G/%m/%d"} // 2015/12/30 => ファッ?? {strtotime("2014/12/31")|date_format:"%G/%m/%d"} // 2015/12/31 => ファッ??? {strtotime("2015/01/01")|date_format:"%G/%m/%d"} // 2015/01/01 => OK {strtotime("2015/01/02")|date_format:"%G/%m/%d"} // 2015/01/02 => OK
ちょっとまって ちょっとまって おにぃさん 状態。
(時代に乗ります)
自分としては、年の変換指定子(こう言うらしい)にG
を使ったこと無いし、いつもY
しか使ったことなかったし、週番号を見てウンタラカンタラがよく分からなかったし、なんだし、なので何故こうなるのか調べてみました。
はじめに
smartyのdate_format
まず、smartyのdate_formatの内部で何が行われているのかを見ます。
Smarty-3.1.21/libs/plugins/modifier.date_format.php
※smartyのバージョンは3.1.12
引数を多く取るみたいですが、フォーマットしたい日付と変換指定子を引数に取るパターン(これが一番普通)だと結局以下の様にstrftime関数を使っているようです。
// {strtotime("2014/12/29")|date_format:"%G/%m/%d"} だとこんな感じ return strftime($format /* "%G/%m/%d" */, $timestamp /* strtotime("2014/12/29") */);
phpのstrftime
じゃあ、strftimeで何されるのっていう話です。
http://php.net/manual/ja/function.strftime.php によると、
- 変換指定子 %G
- %g の 4 桁完全版
- 変換指定子 %g
- 2 桁であらわした年。ISO-8601:1988 標準形式 (%V を参照)
- 変換指定子 %V
- ISO-8601:1988 で規定された、指定した年の週番号。 週の開始日は月曜日で、第 1 週は少なくとも 4 日はあることになる
との事。
。。なにこのたらい回し感。しかも、イマイチよく分からん。
(あと、dateと何が違うのって思ったけどdateだとGの扱いが違った)
やっぱり週番号を見てウンタラカンタラなのか。
うん、週番号を見てウンタラカンタラなんだねっ!
って納得しかけた時、ISO 週番号
でググってみたらこの記事を見つけました。
ISO週番号の決め方とその背景 – Casual Startup – MBA/プログラマの起業日記
謎が解かれはじめる
この記事で一番最初に飛び込んできた言葉。
「年をまたぐ週は、日数が多い方の年に属するものとする」
うぉぉ。これや。何かピンときた。
上で確認したこれを、
{strtotime("2014/12/27")|date_format:"%G/%m/%d"} // 2014/12/27 => OK {strtotime("2014/12/28")|date_format:"%G/%m/%d"} // 2014/12/28 => OK {strtotime("2014/12/29")|date_format:"%G/%m/%d"} // 2015/12/29 => ファッ? {strtotime("2014/12/30")|date_format:"%G/%m/%d"} // 2015/12/30 => ファッ?? {strtotime("2014/12/31")|date_format:"%G/%m/%d"} // 2015/12/31 => ファッ??? {strtotime("2015/01/01")|date_format:"%G/%m/%d"} // 2015/01/01 => OK {strtotime("2015/01/02")|date_format:"%G/%m/%d"} // 2015/01/02 => OK
カレンダーで見てみると、12月最終週の月曜日から始まる日付だけ狂っている。
うぉぉ。これや。やっぱりピンときた。
日付が狂う条件
自分なりに以下の条件がたった。
- 意図しない日付が表示されるのは12月の最終週
- smartyのdate_formatで”G”を使い日付をフォーマットする
- 内部ではstrftimeが呼ばれている
- 12月の最終週で12月と翌年1月の日付が存在する
- 当年12月の日付が翌年1月の日付の数よりも少ない(3日しかない)
- 単純に12月の最終週に1月4日があるかないかの話でもある
- この時意図しない日付(月曜以降が対象)が表示される
date_format:"%Y/%m/%d"
だったら問題ない(気がする)
検証してみる
んで、上記を確認する為に簡単なプログラムを書いてみた。
- 12月(2014年〜2034年)の最終週を羅列
- 日付に対し、
date_format:"%Y/%m/%d"
したものとdate_format:"%G/%m/%d"
したものを比較- 違っていたら背景色を赤にする
- 月曜日開始と日曜日開始を用意
- 日曜日基準が普通だと思うけど、ISO週番号においては月曜日始まりと決められているっぽ
smarty date_format (php strftime) 変換指定子”G”のテスト
これで目視頑張る。
結果
- 12月の最終週に1月4日がある場合(翌年に属される週) の12月の日付
- 狂う(翌年になる)
- 12月の最終週に1月4日が無い場合(翌年に属されない週)の1月の日付
- 狂う(前年になる) ※年が変わらない
- date_format:”%Y/%m/%d”は?
- 狂っていない
の様に、
12月の日付が狂うのは想定通りだったけど、1月も狂ってるのあるじゃね〜か!!
という結果になりました。
まとめ
単純な日付のフォーマットにGは使うな
※大体、Y(y)でいいと思います。G(g)をどんな時に使うのかわかりません。
検証用ソース
検証用ソースをここに置きました。
※テスト書いてないし、間違っていたらごめんなさい
smarty_date_format_php_strftime_G