新しいことにはウェルカム

技術 | 電子工作 | ガジェット | ゲーム のメモ書き

Go言語の時刻演算パッケージ「time」の使い方メモ

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

差分

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みたいのが良かったなぁ…。

関連カテゴリー(Go言語)記事

www.kwbtblog.com

www.kwbtblog.com

www.kwbtblog.com

www.kwbtblog.com

www.kwbtblog.com

www.kwbtblog.com

www.kwbtblog.com

www.kwbtblog.com

www.kwbtblog.com

www.kwbtblog.com

www.kwbtblog.com

www.kwbtblog.com

www.kwbtblog.com