【Go入門】timeパッケージ ~ 日時データの取得と演算・比較

シェアする

こんにちは。Go入門ブログの第22回です。

今回は、Goにおいて日付や時刻を取り扱うための機能を提供するtimeパッケージについて解説します。

time.Time型の取得

timeパッケージの機能の中核となるデータ型が、time.Time型です。
time.Time型は、日時を表現するための構造体型です。
Goで日時を取り扱うには、まずtime.Time型のデータを生成し、それに付随するメソッドで参照や演算などの操作を行うことになります。

したがって、まずはtime.Time型のデータを生成する方法からみていきましょう。

現在日時から取得する

time.Time型を取得するもっとも簡単な方法は、timeパッケージに含まれるtime.Now関数を使用することです。
time.Now関数を使用すると、現在の日時を値に持つtime.Time型変数を取得することができます。

package main

import (
    "fmt"
    "time"
)

func main() {
    t := time.Now()
    fmt.Println(t)
}

このサンプルを実行すると、以下のような書式で実行時点の日時が出力されます。

$ go run main.go
2019-08-28 22:19:44.5600144 +0900 JST m=+0.002000201
指定した日時から取得する

現在日時ではなく、指定した日時のtime.Time型を取得したい場合、time.Date関数を使用します。

time.Date関数は、パラメータにナノ秒ロケーションを指定して実行することで、任意の時刻のtime.Time型を返します。
年~ナノ秒のパラメータは数値で指定できます。

ロケーションには、timeパッケージに定義された定数time.Localまたはtime.UTCを指定します。time.Localを指定した場合、実行環境のタイムゾーンと同じ日時が得られます。
time.UTCを指定した場合、タイムゾーンが協定世界時の日時を得ることができます。

それではサンプルを見てみましょう。

package main

import (
    "fmt"
    "time"
)

func main() {

    // ローカルタイムとUTCで2019年8月28日 23時のtime.Time型を取得
    t_loc := time.Date(2019, 8, 28, 23, 0, 0, 0, time.Local)
    t_utc := time.Date(2019, 8, 28, 23, 0, 0, 0, time.UTC)

    // 出力
    fmt.Println(t_loc)
    fmt.Println(t_utc)
}

このサンプルの実行結果は以下のようになります。

$ go run main.go
2019-08-28 23:00:00 +0900 JST
2019-08-28 23:00:00 +0000 UTC
文字列から取得する

任意の文字列で表現された日時からtime.Time型を取得することもできます。
文字列からtime.Time型を生成するにはtime.Parse関数を使用します

ただし、Goにおいて日時の書式指定の方法は、他の言語と比べてややわかりにくいかもしれません。
先にサンプルを見てから解説したいと思います。

package main

import (
    "fmt"
    "time"
)

func main() {

    // 書式指定
    const format1 = "2006-01-02 15:04:05"  // YYYY-MM-DD hh:mm:ss表記
    const format2 = "2006年01月02日"       // 漢字表記/年月日のみ
    const format3 = "15時04分"             // 漢字表記/時分のみ

    t1, _ := time.Parse(format1, "2019-08-28 22:30:00")
    t2, _ := time.Parse(format2, "2019年08月28日")
    t3, _ := time.Parse(format3, "22時30分")

    fmt.Println(t1)
    fmt.Println(t2)
    fmt.Println(t3)
}

このサンプルの実行結果は以下のようになります。

$ go run main.go
2019-08-28 22:30:00 +0000 UTC
2019-08-28 00:00:00 +0000 UTC
0000-01-01 22:30:00 +0000 UTC

お解りいただけたでしょうか。
Goでは、他の言語でみられるYYYYMMDD%Y-%m-%dのような、日時を表すフォーマット用の文字列を持ちません。
代わりに、2006年1月2日 15時4分5秒という特定の日時をフォーマットに使用します。

やや違和感を感じるかもしれませんが、そういう仕様ですので割り切ってください。
上記の日時で指定さえすれば、それ以外は他言語と同じように使用できます。
区切り文字に任意の記号を使用できますし、漢字を用いて日本語表記にすることも可能です。

time.Time型から文字列への変換

なお、これとは逆に、取得したtime.Time型を文字列に変換することも可能です。
文字列への変換にはtime.Time型のFormatメソッドを使用します。

Formatメソッドは、パラメータに任意の文字列書式を指定できます。
書式の指定方法は、time.Parseで文字列から日時を取得した際と同様です。

package main

import (
    "fmt"
    "time"
)

func main() {

    // 書式指定
    const format = "2006-01-02 15:04:05"

    // 日時の取得
    t := time.Date(2019, 8, 28, 23, 12, 44, 1, time.Local)

    // 文字列に変換して出力
    fmt.Println(t.Format(format))
}

このサンプルの実行結果は以下のようになります。

2019-08-28 23:12:44

time.Time型の参照

さて、ここまで日時データをtime.Time型として取得する方法をみてきました。
ここからは、取得した日時データの取り扱いについて説明していきます。

まず、time.Time型の構造体データに対し、必要な情報を参照するメソッドを紹介します。
time.Time型には、年のみや曜日のみなど、任意の情報にアクセスする為のメソッドが定義されています。

メソッド 取得する値 戻り値の型
time.Time.Year() int
time.Time.Month() time.Month
time.Time.Day() int
time.Time.Weekday() 曜日 time.Weekday
time.Time.YearDay() 年内の通算日(1-366) int
time.Time.Hour() int
time.Time.Minute() int
time.Time.Second() int
time.Time.Nanosecond() ナノ秒 int
time.Time.Zone() タイムゾーン名とオフセット秒 string,int

それでは実際に出力を確認してみましょう。

package main

import (
    "fmt"
    "time"
)

func main() {

    // time.Time型の生成
    t := time.Date(2019, 8, 28, 23, 12, 44, 1, time.Local)

    // 出力
    fmt.Println(t.Year())       // 年
    fmt.Println(t.Month())      // 月
    fmt.Println(t.Day())        // 日
    fmt.Println(t.Weekday())    // 曜日
    fmt.Println(t.YearDay())    // 年内の通算日
    fmt.Println(t.Hour())       // 時
    fmt.Println(t.Minute())     // 分
    fmt.Println(t.Second())     // 秒
    fmt.Println(t.Nanosecond()) // ナノ秒
    fmt.Println(t.Zone())       // タイムゾーン

}

このサンプルの実行結果は以下のようになります。

$ go run main.go
2019
August
28
Wednesday
240
23
12
44
1
JST 32400
time.Month型とtime.Weekday型

上のサンプルで見たとおり、月と曜日はintではなく、time.Monthとtime.Weekdayという独自の型で取得されます。
ただし、これらは実体としてはint型へのエイリアスであり、time.Augustは8というように、月や曜日を表す定数が英語名によって定義されているに過ぎません。

月や曜日を数字で出力したい場合、以下のようにint型へキャストします。
なお、曜日は日曜日を0、月曜日を1・・・土曜日を6、のように定義されています。

package main

import (
    "fmt"
    "time"
)

func main() {

    // time.Time型の生成
    t := time.Date(2019, 8, 28, 23, 12, 44, 1, time.Local)

    fmt.Println(int(t.Month()))   // 月を数字で出力
    fmt.Println(int(t.Weekday())) // 曜日を数字で出力

}
$ go run main.go
8
3

time.Time型の演算

続いて、取得した日時を加算/減算したり、日時同士の前後関係を比較する方法を解説していきます。

日時の間隔を表すtime.Duration型

日時演算のために必要となるデータ型が、timeパッケージに含まれるtime.Duration型です。
time.Duration型は、2つの時点間の時間を表す型で、実体はint64型です。

timeパッケージには、ナノ秒・マイクロ秒・ミリ秒・秒・分・時の各単位を表すtime.Duration型の定数があらかじめ用意されています。

定数 出力表現 実際の値
time.Nanosecond “1ns” 1
time.Microsecond “1μs” 1000 * Nanosecond
time.Millisecond “1ms” 1000 * Microsecond
time.Second “1s” 1000 * Millisecond
time.Minute “1m0s” 60 * Second
time.Hour “1h0m0s” 60 * Minute

実際に出力を確認してみましょう。

package main

import (
    "fmt"
    "time"
)

func main() {

    fmt.Println(time.Nanosecond, ":",int(time.Nanosecond),"ナノ秒")
    fmt.Println(time.Microsecond, ":",int(time.Microsecond),"ナノ秒")
    fmt.Println(time.Millisecond, ":",int(time.Millisecond),"ナノ秒")
    fmt.Println(time.Second, ":",int(time.Second),"ナノ秒")
    fmt.Println(time.Minute, ":",int(time.Minute),"ナノ秒")
    fmt.Println(time.Hour, ":",int(time.Hour),"ナノ秒")

}
1ns : 1 ナノ秒
1μs : 1000 ナノ秒
1ms : 1000000 ナノ秒
1s : 1000000000 ナノ秒
1m0s : 60000000000 ナノ秒
1h0m0s : 3600000000000 ナノ秒

このようにtime.Duration型の定数は単位付きのわかりやすいフォーマットで出力されますが、int型にキャストするとナノ秒を最小単位とした数値であることがわかります。
時刻を操作したり差分を確認する場合、このtime.Duration型を取り扱うことになります。

日時の加算/減算

それでは最も基本的な日時演算として、日時の加算/減算の方法を見ていきましょう。

時間の加算/減算には、time.Time型のAddメソッドを使用します。
Addメソッドは、引数に指定されたtime.Durationの時間だけ、元の時間を増減します。

time.Time.Add(時間の増減値 time.Duration)

日時の加算/減算には、同じくtime.Time型のAddDateメソッドを使用すると便利です。
AddDateメソッドは、年・月・日それぞれの増減値をパラメータで指定することができるので、より直感的に利用できます。

time.Time.AddDate(年の増減値 int, 月の増減値 int, 日の増減値 int)

それではサンプルを見てみましょう。

package main

import (
    "fmt"
    "time"
)

func main() {

    const format = "2006-01-02 15:04:05"

    // time.Time型の生成
    t := time.Date(2019, 8, 29, 0, 0, 0, 0, time.Local) // 2019-08-29 0:0:0.0
    fmt.Println(t.Format(format))

    // 時刻を加算
    t = t.Add(7*time.Hour + 15*time.Minute) // 7時間15分加算
    fmt.Println(t.Format(format))

    // 時刻を減算
    t = t.Add(-1840 * time.Second) // 1840秒減算
    fmt.Println(t.Format(format))

    // 年月日を加算・減算
    t = t.AddDate(-1, 4, -10) // -1年、+4ヶ月、-10日
    fmt.Println(t.Format(format))

}

このサンプルの実行結果は以下のようになります。

2019-08-29 00:00:00
2019-08-29 07:15:00
2019-08-29 06:44:20
2018-12-19 06:44:20
日時の差分取得

2つの日時の差分を取得したい場合、time.Time型のSubメソッドを使用します。
Subメソッドは、引数で指定されたtime.Time型との差分をtime.Duration型で返します。

time.Time.Sub(差分を得たい日時 time.Time)
package main

import (
    "fmt"
    "time"
)

func main() {

    // 2つのtime.Time型の生成
    t1 := time.Date(2019, 8, 28, 23, 12, 44, 1, time.Local) // 2019-08-28 23:12:44.1
    t2 := time.Date(2019, 8, 29, 12, 0, 0, 0, time.Local)   // 2019-08-29 12:0:0.0

    // 2つの日時の差分を取得
    sub := t2.Sub(t1)
    fmt.Println(sub)

}

このサンプルの実行結果は以下の通りです。

$ go run main.go
12h47m15.999999999s
日時の比較

最後に、日時同士の比較方法を確認しましょう。
2つのtime.Time型の前後関係を比較するメソッドとして、BeforeAfterEqualの3つが用意されています。

Beforeメソッドは、対象のtime.Time型がパラメータで指定された日時よりも前の日時である場合にtrueを返します。
Afterメソッドはその逆で、対象のtime.Time型がパラメータで指定された日時よりも後の日時である場合にtrueを返します。
Equalメソッドは、2つのtime.Time型の日時が等しい場合にtrueを返します。

それではサンプルを見てみましょう。

package main

import (
    "fmt"
    "time"
)

// 2つの時刻を比較する関数
func compareDate(t1, t2 time.Time) {
    if t1.Before(t2) {
        // 第1引数の時刻が第2引数の時刻2より前の場合
        fmt.Println("時刻1 < 時刻2")
    } else if t1.After(t2) {
        // 第1引数の時刻が第2引数の時刻2より後の場合
        fmt.Println("時刻1 > 時刻2")
    } else if t1.Equal(t2) {
        // 2つの時刻が一致する場合
        fmt.Println("時刻1 = 時刻2")
    } else {
        fmt.Println("比較失敗")
    }

}

func main() {

    // time.Time型の生成
    t1 := time.Date(2019, 8, 28, 23, 12, 44, 1, time.Local) // 2019-08-28 23:12:44.1
    t2 := time.Date(2019, 8, 29, 0, 0, 0, 0, time.Local)    // 2019-08-29 0:0:0.0
    t3 := time.Date(2019, 8, 28, 15, 0, 0, 0, time.UTC)     // 2019-08-28 15:0:0.0(UTC)

    // 2つの日時の差分を取得
    compareDate(t1, t2) // t1とt2の比較
    compareDate(t2, t1) // t1とt2の比較(順序入れ替え)
    compareDate(t2, t3) // t2とt3の比較
}

このサンプルの実行結果は以下の通りです。

時刻1 < 時刻2
時刻1 > 時刻2
時刻1 = 時刻2

t2とt3の比較結果に注目してください。
日本/東京と協定世界時の時差は9時間ですので、UTCでの2019-08-28 15:00:00とローカルタイムの2019-08-29 0:0:0は等しいと判定され、Equalメソッドがtrueを返しています。

終わりに

今回は、Goの標準パッケージのひとつであるtimeパッケージを解説しました。
time.Time型に定義された様々なメソッドで日時を便利に取り扱うことができることをお解りいただけたかと思います。

一方で、日時の書式指定方法やtime.Duration型などは、他の言語に慣れているとややとまどってしまうかもしれません。
必要に応じて、この記事を振りかえっていただけますと幸いです。

スポンサーリンク
スポンサーリンク
スポンサーリンク

シェアする

フォローする

スポンサーリンク
スポンサーリンク