Go言語は、シンプルな関数を組み合わせてプログラミングをすることが多く、また、関数毎にエラーチェックが発生するので、全般的にコードが長くなりがちです。
ちょっとした事をしたい時でも、コードをそれなりに書く必要があるのですが、毎回ゼロから書くのは面倒なので、よくやる処理を自分コピペ用にメモしておこうと思います。
ここでは、Go言語で、時刻を扱うtime
パッケージの使い方自分用メモを記載しました。
ロケーション
はじめに
時刻は「JST」で扱うことが多いのですが、昨今は実行環境がローカルPCだけでなく、サーバー、Docker、Lambdaなど、様々な場所が想定されます。 実行場所のローカルタイムが「JST」でない場合もあるので、まずは、明示的にロケーションを指定する方法を記します。
Time
時刻はTime
構造体に格納されます。Time
構造体はロケーション情報を持ちます。
time
で定義済みロケーションは、「UTC(time.UTC
)」と「ローカル(time.Local
)」の2つです。
「JST」などの他のロケーションを利用するには、time.LoadLocation()
で実行環境に登録されているタイムゾーンを読み込むか、
time.FixedZone()
で独自に作成する必要があります。
あるロケーションのTime
を、別のロケーションのTime
を生成するにはTime.In()
を使います。
t := time.Now().UTC() // 現在時刻をUTCで取得 fmt.Println(t) // 2020-05-12 20:14:31.2928348 +0000 UTC // タイムゾーンからJSTを読み込み tokyo, err := time.LoadLocation("Asia/Tokyo") if err != nil { return err } timeTokyo := t.In(tokyo) fmt.Println(timeTokyo) // 2020-05-13 05:14:31.2928348 +0900 JST // JSTを独自に作成(名前は任意) jst := time.FixedZone("JST", +9*60*60) timeJst := t.In(jst) fmt.Println(timeJst) // 2020-05-13 05:14:31.2928348 +0900 JST
現在時刻を取得したり、時刻を作成する際は、どのロケーションで作成するかを意識した方がいいかも知れませんね。
time.Local
の設定にならって、デフォルトのロケーションを取得する関数を用意しておくのもありかと思います。
var jstLocation *time.Location var jstOnce sync.Once func jst() *time.Location { if jstLocation == nil { jstOnce.Do(func() { l, err := time.LoadLocation("Asia/Tokyo") if err != nil { l = time.FixedZone("JST2", +9*60*60) } jstLocation = l }) } return jstLocation } func test() { t := time.Now().In(jst()) fmt.Println(t) }
時刻の生成と取得
現在時刻の取得
現在時刻はtime.Now()
で取得できます。ロケーションはtime.Local
です。
値を指定して生成
年からナノセコンドまでの値を指定して生成します。
各値はTime
のメソッドで取得できます。
月は1始まりのint値なのですが、String()
メソッドが定義されていて、英語表記の出力も可能です。
週は日曜が0始まりのint値なのですが、String()
メソッドが定義されていて、英語表記の出力も可能です。
「年月日」はTime.Date()
でまとめて取得することもできます。
t := time.Date(2020, 1, 2, 3, 4, 5, 123456789, time.Local) fmt.Println(t) //2020-01-02 03:04:05.123456789 +0900 JST fmt.Println(t.Year()) // 2020 fmt.Println(t.Month()) // January fmt.Printf("%d\n", t.Month()) // 1 fmt.Println(t.Day()) // 2 fmt.Println(t.Weekday()) // Thursday fmt.Printf("%d\n", t.Weekday()) // 4 fmt.Println(t.Hour()) // 3 fmt.Println(t.Minute()) // 4 fmt.Println(t.Second()) // 5 fmt.Println(t.Nanosecond()) // 123456789 year, month, day := t.Date() fmt.Println(year) // 2020 fmt.Println(month) // January fmt.Println(day) // 3
文字列フォーマット
文字列から時刻生成ができます。また、時刻を指定したフォーマットの文字列に出力できます。
その際、どのようなフォーマットなのかを指定するのですが、よくあるYYYY-MM-DD HH:mm:ss
みたいな記法ではなく、かなり特殊なので、まずフォーマットの説明を記します。
フォーマット指定方法
01/02 03:04:05PM '06 -0700
という時刻が、フォーマットではどう表示されるかをもって、フォーマットを指定します。
例えば、YYYY/MM/DD HH:mm:ss
の場合は下記のようになります。
t := time.Date(2020, 1, 2, 3, 4, 5, 123456789, time.Local) fmt.Println(t) // 2020-01-02 03:04:05.123456789 +0900 JST fmt.Println(t.Format("2006/01/02 15:04:05")) // 2020/01/02 03:04:05
フォーマット注意点
- ゼロ埋めする時は前に0を置きます
- ナノセコンドを表示する時は小数点以下を0で埋めます
- 元の時刻が午後3時なので、
hour
の12h表記は「3」、24h表示は「15」になります hour
の24h表示は15で2桁なので、1桁の時のゼロ埋め無しはできません- タイムゾーンは「-7」の「MST」となり、タイムゾーンを表示する時は
MST
を記載します - 午前午後は英語表記が使え、
PM
を記載します - 月の英語表記が使え、2020/1/2は1月なので
January
またはJan
を記載します - 曜日の英語表記が使え、2020/1/2は月曜なので
Monday
またはMon
を記載します
t := time.Date(2020, 1, 2, 3, 4, 5, 123456789, time.Local) fmt.Println(t) // 2020-01-02 03:04:05.123456789 +0900 JST // 2020/1/2(Thu) 03:04:05(.123)AM JST +09:00 fmt.Println(t.Format("2006/1/2(Mon) 03:04:05(.000)PM MST -07:00"))
何でこんな特別な日付がフォーマットのベースになるかと言うと、ベースの日付が01/02 03:04:05PM '06 -0700
から分かるように、
時刻が英語表記で1・2・3...と連番になっているのです。
とは言え、とっさに出てくるものでもないので、下記のようなフォーマットをメモしておいて、メモを見ながら使うことになるかと思います。
2006-01-02 [12h]03:04:05PM [24h]15:04:05 -07:00 MST January Monday
文字列から時刻生成
文字列から時刻生成はtime.ParseInLocation()
で行います。
時刻がどういったフォーマットで記載されているかを、前述の方法で指定します。
文字列にロケーション情報が記載されていない場合は、2番目の引数のロケーションが適用されます。
t, err := time.ParseInLocation("2006-01-02", "2020-12-31", time.Local) if err != nil { return err } fmt.Println(t) // 2020-12-31 00:00:00 +0900 JST
文字列にロケーション情報が記載されている場合は、2番目の引数で指定したロケーションと同じならば、そのロケーションで適用されるのですが、 それ以外の場合は設定されません。
つまり、UTCかローカル以外のロケーションを文字列で設定してもうまく判別されません。
時間の長さの単位
時刻の演算を行う際、時間の長さを扱うのですが、最小単位はナノセカンドで整数値です。
ただ、整数をそのまま扱うと桁数が多すぎて分かりづらいので、Duration
という別の型名を割り当てていて、時間の長さはDuration
介して扱うようになっています。
Duration
には、Hour・Minute・Second・Millisecond...
の定数が定義されていて、それらを使ってDuration
を書くことにより、時間の長さの可読性を上げています。
例えば、現在時刻に「2時間3分」足した時刻を算出するには下記のようになります。
t := time.Now().Add(time.Hour * 2 + time.Minute * 3)
演算
加減算
Time.Add()
で時間の加減算ができます。時間の長さの単位は前述のDuration
です。
t := time.Date(2020, 1, 2, 3, 4, 5, 123456789, time.Local) tAdd := t.Add(time.Hour) // 2020-01-02 04:04:05.123456789 +0900 JST tSub := t.Add(-time.Hour) // 2020-01-02 02:04:05.123456789 +0900 JST
差分(diff)
Time.Sub()
で2つの時刻の時間差を求めることができます。時間の長さの単位はDuration
です。
並びは計算式と同じ「a.Sub(b)」なら「a - b」です。
a := time.Date(2020, 1, 2, 3, 4, 5, 123456789, time.Local) b := a.Add(-time.Hour) fmt.Println(a.Sub(b)) // 1h0m0s
日付の加減算
「1年後」とか「3日前」など、日付のDuration
を計算するのは面倒なので、日付加減用の専用メソッド(Time.AddDate()
)が用意されています。
日付の加減数を引数で「年月日」の順で指定します。
t := time.Date(2020, 1, 2, 3, 4, 5, 123456789, time.Local) tt := t.AddDate(1, 0, -3) // 1年後の3日前 fmt.Println(t) // 2020-01-02 03:04:05.123456789 +0900 JST fmt.Println(tt) // 2020-12-30 03:04:05.123456789 +0900 JST
演算後の時刻以下の値は、そのまま引き継がれます。
日付の差分を求める方法は、残念ながら提供されていません。
比較
Time
には、時刻を比較するメソッドがいくつかあります。
Time.Sub()
で2つの時刻の絶対差分を求めて調べれば、覚えるメソッドは1つだけで済みます。
Time
は構造体なので==
演算子が使えるのですが、ロケーションも比較するので、同じ時刻かを調べる方法としては使用しない方がいいです。
diff := a.Sub(b) if diff == 0 { fmt.Println("同じ") } else if diff > 0 { fmt.Println("aが後") } else { fmt.Println("aが前") }
日付の比較
Time.Sub()
で2つの時刻の日付を比較するには、時以下をゼロにした上で比較する必要があります。
それらを良しなにやってくれる手段は提供されていないので、愚直に、比較したい時刻から、時以下をゼロにした新しい時刻を生成して、それらを比較します。
aZero := time.Date(a.Year(), a.Month(), a.Day(), 0, 0, 0, 0, time.Local) bZero := time.Date(b.Year(), b.Month(), b.Day(), 0, 0, 0, 0, time.Local) diff := aZero.Sub(bZero) if diff == 0 { fmt.Println("同じ") } else if diff > 0 { fmt.Println("aが後") } else { fmt.Println("aが前") }
Sleep
指定したDuraion
だけ待つ「Sleep」関数もtime
で定義されています。
time.Sleep(time.Duration.Second * 3)
参考記事
感想など
フォーマットの指定がぱっと見フォーマットに見えなくて…。YYYY-MM-DD HH:mm:ss
みたいのが良かったなぁ…。