【Go】Go言語で時間を扱う(pkg/time)機能を"ほぼ"まとめてみた

ファイル名にタイムスタンプを入れたいなと思ってtime パッケージを調べてたんですが、便利な機能が意外とあるなと気づいたのでまとめてみました。

ちょくちょく調べることがあるので、この際いっきにまとめてみることにした。

自分でも知らない機能があったりして、面白かったです。

まずはドキュメントを読んでみましょう。
time - The Go Programming Language
あ部分的にDocumentのExampleを参考にしてるところはあります。

基本的な使い方

package main

import (
        "fmt"
        "time"
)

func main() {
        fmt.Println("----- Basic usage -----")
        now := time.Now()
        fmt.Println("Now()        : " + now.String())
        time1 := time.Date(2018, 10, 15, 16, 48, 32, 12345, time.Local)
        fmt.Println("Date()       : " + time1.String())
        fmt.Println()
}
----- Basic usage -----
Now()        : 2018-12-21 18:02:48.2384152 +0900 JST m=+0.024933301
Date()       : 2018-10-15 16:48:32.000012345 +0900 JST

Now()で現在時刻、Date()で特定の時刻が取れます。


ここ以降は出力の関係上 "strconv"もimportしておいてください。
また説明が重複するため、package, import 部分は省略します。

値取得関係の関数

日時などの値を単純に取得する関数です。

fmt.Println("----- Functions -----")
fmt.Println("Year()       : " + strconv.Itoa(now.Year()))
fmt.Println("Month()      : " + now.Month().String())
fmt.Println("int(now.Month()) : " + strconv.Itoa(int(now.Month())))
fmt.Println("Day()        : " + strconv.Itoa(now.Day()))
fmt.Println("Hour()       : " + strconv.Itoa(now.Hour()))
fmt.Println("Minute()     : " + strconv.Itoa(now.Minute()))
fmt.Println("Second()     : " + strconv.Itoa(now.Second()))
fmt.Println("Nanosecond() : " + strconv.Itoa(now.Nanosecond()))
fmt.Println("Weekday()    : " + now.Weekday().String())
fmt.Println("int(Weekday()) : " + strconv.Itoa(int(now.Weekday())))
fmt.Println("Unix()       : " + strconv.FormatInt(now.Unix(), 10))
fmt.Println("UnixNano()   : " + strconv.FormatInt(now.UnixNano(), 10))

iso_year, iso_week := now.ISOWeek()

fmt.Println("ISOWeek() year : " + strconv.Itoa(iso_year))
fmt.Println("ISOWeek() week : " + strconv.Itoa(iso_week))
fmt.Println()
----- Functions -----
Year()       : 2018
Month()      : December
int(now.Month()) : 12
Day()        : 21
Hour()       : 18
Minute()     : 2
Second()     : 48
Nanosecond() : 238415200
Weekday()    : Friday
int(Weekday()) : 5
Unix()       : 1545382968
UnixNano()   : 1545382968238415200
ISOWeek() year : 2018
ISOWeek() week : 51

Month()だと英文字の月名になるのが要注意ですね。なんで数字じゃないんだ。
intでキャストする必要があります。Weekday()、お前は許す。。

またIOSWeek()は年の53週間中何週間目がわかるから便利ですね。

便利な関数

じゃあ年月日を取るために Year(), Month(), Day() を使うか~っと思ったら、ちょっと待って。
下記のDate(), Clock()が便利ですよ。

fmt.Println("----- Simple Functions -----")
fmt.Println("Date()  Clock()")
year, month, day := now.Date()
hour, min, sec   := now.Clock()
fmt.Println(year)
fmt.Println(int(month))
fmt.Println(day)
fmt.Println(hour)
fmt.Println(min)
fmt.Println(sec)
fmt.Println()

fmt.Println("YearDay()    : " + strconv.Itoa(now.YearDay()))
fmt.Println("UTC()        : " + now.UTC().String())
fmt.Println("Local()      : " + now.Local().String())

zoneName, zoneOffset := now.Zone()

fmt.Println("Zone() (name,offset) : " + "( " + zoneName + " , " + strconv.Itoa(zoneOffset) + " )")
fmt.Println()
----- Simple Functions -----
Date()  Clock()
2018
12
21
18
2
48

YearDay()    : 355
UTC()        : 2018-12-21 09:02:48.2384152 +0000 UTC
Local()      : 2018-12-21 18:02:48.2384152 +0900 JST
Zone() (name,offset) : ( JST , 32400 )

Date()、Clock()はいっきに取れるから便利ですね。
また年間で何日目なのか?がわかるYearDay()も便利ですね。
UTC←→JST変換も楽です。zone情報もわかりますね。

加減算

時間の加算減算です。

fmt.Println("----- Add -----")
fmt.Println("Now()           : " + now.String())

now = now.Add(time.Duration(-1) * time.Hour)
fmt.Println("time 1 hour ago : " + now.String())
fmt.Println()
----- Add -----
Now()           : 2018-12-21 18:02:48.2384152 +0900 JST m=+0.024933301
time 1 hour ago : 2018-12-21 17:02:48.2384152 +0900 JST m=-3599.975066699

もちろんHour以外にもDayでもMinuteでも可能です。

フォーマット変換

指定のフォーマットから文字列時刻を読み取ります。

fmt.Println("----- Parse() Format()-----")
var timeParse01 = time.Time{}
timeParse01, _  = time.Parse("2006/01/02 15:04:05 (MST)", "2018/12/21 13:45:38 (JST)")

fmt.Println("Parse()          : " + timeParse01.String())
fmt.Println("Format(ANSIC)    : " + timeParse01.Format(time.ANSIC))
fmt.Println("Format(RFC1123Z) : " + timeParse01.Format(time.RFC1123Z))
fmt.Println()
----- Parse() Format()-----
Parse()          : 2018-12-21 13:45:38 +0900 JST
Format(ANSIC)    : Fri Dec 21 13:45:38 2018
Format(RFC1123Z) : Fri, 21 Dec 2018 13:45:38 +0900

パースするところで、2006とか01とか02ってなんなんだ?と思ってましたが、そういうフォーマットの指定方法なんですね。
ここを見るとわかります。
src/time/format.go - The Go Programming Language

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"
)

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
)

時間単位の変更

fmt.Println("----- ParseDuration() -----")
duration, _ := time.ParseDuration("2h35m10s")
fmt.Println("Duration String      : " + duration.String())
fmt.Println("Duration in minutes  : " + strconv.FormatFloat(duration.Minutes(), 'f', 0, 64))
fmt.Println("Duration in seconds  : " + strconv.FormatFloat(duration.Seconds(), 'f', 0, 64))
fmt.Println()
----- ParseDuration() -----
Duration String      : 2h35m10s
Duration in minutes  : 155
Duration in seconds  : 9310

X時間は何分/何秒なのか?とかの単位変換計算が楽ですね。

どれくらいの時間の隔たりがあるか

fmt.Println("----- Sub() Since() Until() -----")
time01 := time.Date(2018, 10, 10, 15,  0,  0, 0, time.Local)
time02 := time.Date(2018, 10, 25, 15, 30, 10, 0, time.Local)

fmt.Println("time01   : " + time01.String())
fmt.Println("time02   : " + time02.String())

time01Subtime02 := time01.Sub(time02)
time02Subtime01 := time02.Sub(time01)

fmt.Println("time01.Sub(time02)   : " + time01Subtime02.String())
fmt.Println("time02.Sub(time01)   : " + time02Subtime01.String())
fmt.Println()

time03 := time.Date(2018, 12, 10, 0, 0, 0, 0, time.Local)
time04 := time.Date(2018, 12, 25, 0, 0, 0, 0, time.Local)

fmt.Println("Now()    : " + now.String())
fmt.Println("time03   : " + time03.String())
fmt.Println("time04   : " + time04.String())

fmt.Println("time.Since(time03)   : " + time.Since(time03).String())
fmt.Println("time.Until(time04)   : " + time.Until(time04).String())
fmt.Println()
----- Sub() Since() Until() -----
time01   : 2018-10-10 15:00:00 +0900 JST
time02   : 2018-10-25 15:30:10 +0900 JST
time01.Sub(time02)   : -360h30m10s
time02.Sub(time01)   : 360h30m10s

Now()    : 2018-12-21 17:02:48.2384152 +0900 JST m=-3599.975066699
time03   : 2018-12-10 00:00:00 +0900 JST
time04   : 2018-12-25 00:00:00 +0900 JST
time.Since(time03)   : 282h2m48.3541336s
time.Until(time04)   : 77h57m11.6458664s

二つの時間の差分がSub()、その時間から今までがSince()、今から未来の時間までがUntil()。
このへんも便利ですね。

どっちが新しい?古い?

自分はUnixタイムで比較してましたが、こんな関数もあるんですね。

fmt.Println("----- After() Before() -----")
date11m := time.Date(2018, 11, 1, 0, 0, 0, 0, time.Local)
date12m := time.Date(2018, 12, 1, 0, 0, 0, 0, time.Local)

fmt.Println(date11m)
fmt.Println(date12m)

isDate11mAfterDate12m := date11m.After(date12m)
isDate12mAfterDate11m := date12m.After(date11m)

fmt.Printf("date11m.After(date12m)  = %v\n", isDate11mAfterDate12m)
fmt.Printf("date12m.After(date11m)  = %v\n", isDate12mAfterDate11m)

isDate11mBeforeDate12m := date11m.Before(date12m)
isDate12mBeforeDate11m := date12m.Before(date11m)

fmt.Printf("date11m.Before(date12m) = %v\n", isDate11mBeforeDate12m)
fmt.Printf("date12m.Before(date11m) = %v\n", isDate12mBeforeDate11m)
fmt.Println()
----- After() Before() -----
2018-11-01 00:00:00 +0900 JST
2018-12-01 00:00:00 +0900 JST
date11m.After(date12m)  = false
date12m.After(date11m)  = true
date11m.Before(date12m) = true
date12m.Before(date11m) = false

After()、Before() の方がわかりやすい!可読性がよくなります。これまで時間の比較はエポックタイムで比較してたんですが、これならぱっと見わかりやすくなりますね。

同じ時間か?

時差があっても同じ時刻か比較してくれます。

fmt.Println("----- Equal() -----")
//時差が8時間の "Beijing Time" を作ります。
secondsEastOfUTC := int((8 * time.Hour).Seconds())
beijing := time.FixedZone("Beijing Time", secondsEastOfUTC)

timeBeijin := time.Date(2018, 2, 1, 20, 15, 45, 0, beijing)
timeUtc    := time.Date(2018, 2, 1, 12, 15, 45, 0, time.UTC)

fmt.Println(timeBeijin)
fmt.Println(timeUtc)

datesEqualUsingEqualOperator := timeBeijin == timeUtc
datesEqualUsingFunction      := timeBeijin.Equal(timeUtc)

fmt.Printf("datesEqualUsingEqualOperator = %v\n", datesEqualUsingEqualOperator)
fmt.Printf("datesEqualUsingFunction = %v\n", datesEqualUsingFunction)
fmt.Println()
----- Equal() -----
2018-02-01 20:15:45 +0800 Beijing Time
2018-02-01 12:15:45 +0000 UTC
datesEqualUsingEqualOperator = false
datesEqualUsingFunction = true

timeBeijinとtimeUtcは時刻は違いますが、地球上の上では時差を換算すると同じ時刻ですからTrueになりますね。
(UTC)12:15 + (Beijin時差)8hr = (Beijin Time)20:15

それにしても上記のように書けば時差ロケーションが作れるんですね。

四捨五入や切り下げ

時間の四捨五入ってめんどくさいですよね。でもこれなら簡単です。

fmt.Println("----- Round() Truncate() -----")
time10    := time.Date(2018, 12, 10, 14, 15, 45, 987654321, time.Local)

fmt.Println("time10                      : " + time10.String())
fmt.Println("time10.Round(time.Hour)     : " + time10.Round(time.Hour).String())
fmt.Println("time10.Round(time.Minute)   : " + time10.Round(time.Minute).String())
fmt.Println("time10.Round(time.Second)   : " + time10.Round(time.Second).String())
fmt.Println("time10.Truncate(time.Hour)  : " + time10.Truncate(time.Hour).String())
fmt.Println("time10.Truncate(time.Minute): " + time10.Truncate(time.Minute).String())
fmt.Println("time10.Truncate(time.Second): " + time10.Truncate(time.Second).String())
fmt.Println()
----- Round() Truncate() -----
time10                      : 2018-12-10 14:15:45.987654321 +0900 JST
time10.Round(time.Hour)     : 2018-12-10 14:00:00 +0900 JST
time10.Round(time.Minute)   : 2018-12-10 14:16:00 +0900 JST
time10.Round(time.Second)   : 2018-12-10 14:15:46 +0900 JST
time10.Truncate(time.Hour)  : 2018-12-10 14:00:00 +0900 JST
time10.Truncate(time.Minute): 2018-12-10 14:15:00 +0900 JST
time10.Truncate(time.Second): 2018-12-10 14:15:45 +0900 JST

Round()は四捨五入みたいな感じで、Truncate()は切り捨てですね。

Parse処理うまくいったかな?

IsZero() ってなんのためにあるのかな?と思ってましたが、確認のためのようですね。

fmt.Println("----- IsZero() -----")
timeRight, _  := time.Parse("2006/01/02 15:04", "2018/12/21 13:45")
// Parse処理で不適切な形式をしてして失敗させます。
timeFail, _   := time.Parse("2006/01/02 15:04", "2018/12/21 hoge 13:45")

fmt.Println("timeRight   : " + timeRight.String())
fmt.Println("timeFail    : " + timeFail.String())
fmt.Println("timeRight.IsZero()   : " + strconv.FormatBool(timeRight.IsZero()))
fmt.Println("timeFail.IsZero()    : " + strconv.FormatBool(timeFail.IsZero()))
fmt.Println()
----- IsZero() -----
timeRight   : 2018-12-21 13:45:00 +0000 UTC
timeFail    : 0001-01-01 00:00:00 +0000 UTC
timeRight.IsZero()   : false
timeFail.IsZero()    : true

なるほど。パースに失敗すると0001-01-01....になってしまうんだな。

サンプルソース全文

こちらです

Tips of Tim pkg

(2018/12/26)追記

qiitaにも書いてみました。
qiita.com