今川館

都内勤務の地味OLです

Goの日付フォーマットの謎

日付や日時を文字列に変換する方法を調べていて、Goは%Y-%m-%dではなく、"2006-01-02"というフォーマットを採用していることを知る。

年は「2006」、月は「01」、日は「02」と決まっている。
これは、「2016」、「12」、「31」ではいけないのである。

import time

fmt.Println(time.Now()) // 2016-11-17 11:46:44.630877067 +0900 JST

// わかる。
fmt.Println(time.Now().Format("2006/01/02")) // => 2016/11/17

// なぜなのか?
fmt.Println(time.Now().Format("2016/12/31")) // => 17116/1117/1111
fmt.Println(time.Now().Format("2005/01/02")) // => 17044/11/17

ここに各種トークンの意味が定義されている
https://golang.org/src/time/format.go

const (
	_                        = iota
	stdLongMonth             = iota + stdNeedDate  // "January"
	stdMonth                                       // "Jan"
	stdNumMonth                                    // "1"
	stdZeroMonth                                   // "01"
	stdLongWeekDay                                 // "Monday"
	stdWeekDay                                     // "Mon"
	stdDay                                         // "2"
	stdUnderDay                                    // "_2"
	stdZeroDay                                     // "02"
	stdHour                  = iota + stdNeedClock // "15"
	stdHour12                                      // "3"
	stdZeroHour12                                  // "03"
	stdMinute                                      // "4"
	stdZeroMinute                                  // "04"
	stdSecond                                      // "5"
	stdZeroSecond                                  // "05"
	stdLongYear              = iota + stdNeedDate  // "2006"
	stdYear                                        // "06"
	stdPM                    = iota + stdNeedClock // "PM"
	stdpm                                          // "pm"
	stdTZ                    = iota                // "MST"
	stdISO8601TZ                                   // "Z0700"  // prints Z for UTC
	stdISO8601SecondsTZ                            // "Z070000"
	stdISO8601ShortTZ                              // "Z07"
	stdISO8601ColonTZ                              // "Z07:00" // prints Z for UTC
	stdISO8601ColonSecondsTZ                       // "Z07:00:00"
	stdNumTZ                                       // "-0700"  // always numeric
	stdNumSecondsTz                                // "-070000"
	stdNumShortTZ                                  // "-07"    // always numeric
	stdNumColonTZ                                  // "-07:00" // always numeric
	stdNumColonSecondsTZ                           // "-07:00:00"
	stdFracSecond0                                 // ".0", ".00", ... , trailing zeros included
	stdFracSecond9                                 // ".9", ".99", ..., trailing zeros omitted

	stdNeedDate  = 1 << 8             // need month, day, year
	stdNeedClock = 2 << 8             // need hour, minute, second
	stdArgShift  = 16                 // extra argument in high bits, above low stdArgShift
	stdMask      = 1<<stdArgShift - 1 // mask out argument
)

非常に不運なことに、わたしはこのサンプルコードを11月17日の午前11時に動かしていたため、「11」と「17」が不規則に並んで出力され(!)、ことさら混乱を招いた。
ちなみに、11月11日は日本を代表する歌手である吉幾三さんの誕生日である。

"2005"を指定すると、「2:日」+「0:無視」+「05:秒」と解釈される。
よって、「17(日)」+「(空文字列)」+「044(秒)」=「17044」となる。

同様に、"2016"は「2:日」+「01:月」+「6(末尾文字はそのまま結合)」解釈され、「17」+「11」+「6」=「17116」となるのである。

はっきり言って、%Y-%m-%dの方が慣れているのでわかりやすい*1
なまじ具体的な日時の値に即したフォーマットなので、任意の日時を書式指定するとうまく解釈してくれるものと誤解してしまった。

*1:所詮、わかりやすい理由は「慣れているから」にすぎないから、新たに慣れれば良いだけなのだが。