Go 言語の日付処理
今回は日付処理の話。 特にフォーマットの定義の仕方はよく忘れるので覚え書きとして記しておく。
Go 言語で日付処理を行うには time パッケージを使う。
よく使う型としては
が挙げられるだろう。
time.Time は時刻を, time.Duration は2時点間の時間を,そして time.Location は地球上の時差を表す型である。
たとえば
package main
import (
"fmt"
"os"
"time"
)
func main() {
tz, err := time.LoadLocation("Asia/Tokyo")
if err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
tm := time.Date(2006, 1, 2, 15, 4, 5, 0, tz)
fmt.Println(tm)
now := time.Now()
fmt.Println(now)
d := now.Sub(tm)
fmt.Println(d)
}
と書くと
2006-01-02 15:04:05 +0900 JST
2009-11-10 23:00:00 +0000 UTC
33808h55m55s
てな感じになる1。
時刻を任意のフォーマットで表示する場合は少し特殊な方法を使う。 たとえば RFC 3339 フォーマットに出力するなら
package main
import (
"fmt"
"os"
"time"
)
func main() {
tz, err := time.LoadLocation("Asia/Tokyo")
if err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
tm := time.Date(2015, 12, 31, 23, 59, 59, 0, tz)
fmt.Println(tm.Format("2006-01-02T15:04:05Z07:00"))
}
とすれば
2015-12-31T23:59:59+09:00
と出力される。
テンプレート文字列が %Y とか YYYY のような形式ではないのだ。
これは適当な文字列ではなく一応法則があって
- 月は
1(または01,Jan,January) - 日は
2(または02,_2) - 時は
3(または03,15) ※ 午後3時を指す - 分は
4(または04) - 秒は
5(または05) - 年は
06(または2006) - 時差は
-07(または-0700,-07:00,Z07:00,MST2 など) - 曜日は
Mon(またはMonday) - AM/PM は
PM(またはpm)
という感じに 1 からの連番になっている(曜日等は例外だけど)ので,まぁ覚えられるかな? でもよく忘れるんだよなぁ。
%Y みたいなのとどちらがいいかは微妙な気がするが,慣れの問題かもしれない。
典型的なフォーマットは定数化されている。
const (
ANSIC = "Mon Jan _2 15:04:05 2006"
UnixDate = "Mon Jan _2 15:04:05 MST 2006"
RubyDate = "Mon Jan 02 15:04:05 -0700 2006"
RFC822 = "02 Jan 06 15:04 MST"
RFC822Z = "02 Jan 06 15:04 -0700" // RFC822 with numeric zone
RFC850 = "Monday, 02-Jan-06 15:04:05 MST"
RFC1123 = "Mon, 02 Jan 2006 15:04:05 MST"
RFC1123Z = "Mon, 02 Jan 2006 15:04:05 -0700" // RFC1123 with numeric zone
RFC3339 = "2006-01-02T15:04:05Z07:00"
RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00"
Kitchen = "3:04PM"
// Handy time stamps.
Stamp = "Jan _2 15:04:05"
StampMilli = "Jan _2 15:04:05.000"
StampMicro = "Jan _2 15:04:05.000000"
StampNano = "Jan _2 15:04:05.000000000"
)
なので,先ほどのコードも出力部分を
fmt.Println(tm.Format(time.RFC3339))
とすれば同じ結果が得られる。 時刻フォーマットは,いったんシステムの中で決めてしまえば同じものを使い回すことになると思うので,定数化してしまえば「フォーマットどうだっけ?」と煩わされることも少ないだろう。
ところでバージョン 1.5 系の time.Parse() 関数は日付の解釈が寛容で,各月の末日を31日まで許容している。
たとえば閏年でない2月29日でも
package main
import (
"fmt"
"os"
"time"
)
func main() {
tm, err := time.Parse(time.RFC3339, "2015-02-29T23:59:59+09:00")
if err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
fmt.Println(tm)
}
2015-03-01 23:59:59 +0900 +0900
となり,エラーとならずいい感じ(?)に加減してくれるのだが,バージョン 1.6 からは少し解釈が厳密になりエラーを返すようだ。
Thetimepackage'sParsefunction has always rejected any day of month larger than 31, such as January 32. In Go 1.6, Parse now also rejects February 29 in non-leap years, February 30, February 31, April 31, June 31, September 31, and November 31.
実際に 1.6 で上のコードを実行すると
parsing time "2015-02-29T23:59:59+09:00": day out of range
とエラーが返ってくる。
ちなみに time.Date() 関数は更に寛容である。
package main
import (
"fmt"
"os"
"time"
)
func main() {
tz, err := time.LoadLocation("Asia/Tokyo")
if err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
tm := time.Date(2015, 13, 32, 25, 60, 00, 0, tz)
fmt.Println(tm)
}
2016-02-02 02:00:00 +0900 JST