Go言語で日付を扱う

Category:
Last Updated: 2022/01/04 09:40:45

Go言語での日時の扱いがなかなか慣れないのでまとめておく。


# time.Time変数を生成する

# 現在時刻から生成

現在時刻の time.Time を生成するには time.Now 関数を利用する。

import "time"

// Write redundantly to show the type
var t time.Time
t = time.Now()

fmt.Print(t) // 2022-01-31 13:45:56 +0000 UTC
1
2
3
4
5
6
7

# 日時を指定して生成

時刻を指定して time.Time を生成するには time.Date 関数を利用する。
第二引数は time.Month 型を指定するが、time.Month(1) のようにすることでintからのキャストが可能。


var t time.Time

// 2022-01-31 13:45:56.000000 +00:00
year := 2022
month := time.January // Jatuary - December
// month := time.Month(1) // cast int into time.Month
day := 31
hour := 13
min := 45
sec := 56
nanosec := 0
t = time.Date(year, month, day, hour, min, sec, nanosec, time.UTC)

fmt.Print(t) // 2022-01-31 13:45:56 +0000 UTC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 特定のタイムゾーンを指定して生成

time.Time型の時刻のタイムゾーンはデフォルトではUTCなので、time.LoadLocation でタイムゾーンを読み込んで指定する。日本の場合は Asia/Tokyo


var t time.Time

// 2022-01-31 13:45:56.000000 +00:00
year := 2022
month := time.January // Jatuary - December
day := 31
hour := 13
min := 45
sec := 56
nanosec := 0
loc, _ := time.LoadLocation("Asia/Tokyo")
t = time.Date(year, month, day, hour, min, sec, nanosec, loc)

fmt.Print(t) // 2022-01-31 13:45:56 +0900 JST

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# Unixtimeから生成

UNIXタイムを指定して time.Time を生成するには time.Unix 関数を利用する。
第二引数はナノ秒(0 - 999,999,999)。

t = time.Unix(1640769840, 0)
fmt.Print(t) // 2021-12-29 18:24:00 +0900 JST
1
2

タイムゾーンはローカルに設定されているタイムゾーンとなる。
私のPCは日本時間に設定しているので、JSTとなっている。


# time.Time変数から情報を取得する

# 日時の情報を取得する

下記のように必要な情報が取得できる。戻り値は基本的に int なのだが、Month()Weekday() に関してはそれぞれtime.Month型とtime.Weekday型という特殊な型になっており、基本的にintと同様に扱うことができる。

両方とも、文字列にキャストされるかString()関数が呼び出されると、月や曜日の英語名となる。


t := time.Now()

/*
Year(): 2022
Month(): 1 (string: January)
Day(): 3
Hour(): 15
Minute(): 11
Second(): 40
Weekday(): 1 (string: Monday)
*/
fmt.Printf("Year(): %d\n", t.Year() )
fmt.Printf("Month(): %d (.String(): %s)\n", t.Month(), t.Month().String() )
fmt.Printf("Day(): %d\n", t.Day() )
fmt.Printf("Hour(): %d\n", t.Hour() )
fmt.Printf("Minute(): %d\n", t.Minute() )
fmt.Printf("Second(): %d\n", t.Second() )
fmt.Printf("Weekday(): %d (.String(): %s)\n", t.Weekday(), t.Weekday().String() )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 必要な情報をまとめて取得する

AddDate関数のソースコード (opens new window)を見て知ったが、下記のようにまとめて取得することができる。

year, month, date := t.Date()
hour, min, sec := t.Clock()
nsec := t.Nanosecond()
1
2
3

# タイムゾーンを取得する

Time.Zone()関数でタイムゾーンの情報を取得できる。offsetの単位は秒。

t := time.Now()
name, offset := t.Zone()

// name=JST offset=32400
fmt.Printf("name=%s offset=%d", name, offset)
1
2
3
4
5

# 時刻の計算

# 時刻を進める・戻す

時刻を進めたり戻したりする(時間を足したり引いたりする)には Time.Add関数を使う。
日にちを進めたり戻したりする場合は後述のTime.AddDateを使う。(Time.Addでもできるが)

t = time.Date(2022, time.Month(1), 3, 12, 23, 34,123, time.UTC)
fmt.Print(t,"\n") // 2022-01-03 12:23:34.000000123 +0000 UTC

// add 20 sec
t = t.Add(time.Duration(20) * time.Second) 
fmt.Print(t,"\n") // 2022-01-03 12:23:54.000000123 +0000 UTC
1
2
3
4
5
6

時間を戻すにはTime.Add関数の引数に負の値を指定する。(間違いやすい関数としてTime.Subがあるが、Time.Sub関数はTime.Add関数の逆ではなく、time.Time同士の差分を求める関数である。)

// sub 30 hours
t = t.Add(time.Duration(-30) * time.Hour) 
fmt.Print(t,"\n") // 2022-01-02 06:23:54.000000123 +0000 UTC
1
2
3

# 日にちを進める・戻す

日にちを進めたり戻したりする場合はTime.AddDateを使う。

t = time.Date(2022, time.Month(1), 3, 0, 0, 0, 0, time.UTC)
t = t.AddDate(1,1,1)
fmt.Print(t,"\n") // 2023-02-04 00:00:00 +0000 UTC
1
2
3

こちらも過去の日付に戻す場合は負の値を指定する。

t = time.Date(2022, time.Month(1), 3, 0, 0, 0, 0, time.UTC)
t = t.AddDate(-1,-2,-6)
fmt.Print(t,"\n") // 2020-10-28 00:00:00 +0000 UTC
1
2
3

# Nヶ月後 / Nヶ月前を求める際に末日を丸める

前述のように時間を進めたり戻したりするにはGo言語ではTime.Add関数を利用するが、月末が正しく計算されない場合がある。

t = time.Date(2022, time.Month(1), 31, 0, 0, 0, 0, time.UTC)
t = t.AddDate(0,1,0)
fmt.Print(t,"\n") // 2022-03-03 00:00:00 +0000 UTC
1
2
3

timeモジュールのnormという関数のソースコード (opens new window)を見ると、どうやら time.Date 全般、規定の範囲をはみ出している数値は都度、正規化されるようだ。つまり「1/31の1ヶ月後」はGo言語においては「2/31」→正規化されて「3/3」という扱いになる。

月末を意識してNヶ月後/Nヶ月前を計算するには、自前で関数を作るしかないので下記のような関数を作成する。


func main() {
	t := time.Date(2022, time.Month(1), 31, 0, 0, 0, 0, time.UTC)
	t = addMonth(t, 13)
	fmt.Print(t,"\n") // 2023-02-28 00:00:00 +0000 UTC
}

func addMonth(t time.Time, months int) time.Time {
	y, m_, d := t.Date()
	m := int(m_)

	if months > 0 {
		m += months
		if m > 12 {
			y_ := int((m - 1) / 12)

			m -= y_ * 12
			y += y_
		}
	} else if months < 0 {
		m -= months
		if m < 1 {
			y_ := int(-m / 12) + 1
			
			m += y_ * 12
			y -= y_
		}
	}
	last_d := getLastDay(y, m)
	if d > last_d {
		d = last_d
	}
	print(y,"\n",m,"\n", d, "\n")

	h, min, sec := t.Clock()

	return time.Date(y, time.Month(m), d, h, min, sec, t.Nanosecond(), t.Location())
}
// get the last day of the month
func getLastDay(y int, m int) int {
	if m == 4 || m == 6 || m == 9 || m == 11 {
		return 30
	}
	if m == 2 {
		if y % 4 == 0 && (y % 100 != 0 || y % 400 == 0) {
			return 29
		} else {
			return 28
		}
	}
	return 31
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

# 2つの時刻の差を求める

2つの時刻の差を求める場合はTime.Subを使う。
戻り値はtime.Duration

t1 := time.Date(2022, time.Month(1), 31, 12, 23, 34, 5678, time.UTC)
t2 := time.Date(2023, time.Month(2), 3, 23, 34, 56, 7890, time.UTC)

var duration time.Duration
duration = t2.Sub(t1)

// int(duration)=31835482000002212 
fmt.Printf("int(duration)=%d\n",int(duration))

// string(duration)=8843h11m22.000002212s
fmt.Printf("string(duration)=%s\n",duration.String())

// methods
/*
days: 368
hours: 8843
minutes: 530591
seconds: 31835482
milliseconds: 31835482111
microseconds: 31835482111111
nanoseconds: 31835482111111102
*/
fmt.Print("days: ", int(duration.Hours() / 24), "\n")
fmt.Print("hours: ", int(duration.Hours()), "\n")
fmt.Print("minutes: ", int(duration.Minutes()), "\n")
fmt.Print("seconds: ", int(duration.Seconds()), "\n")
fmt.Print("milliseconds: ", duration.Milliseconds(), "\n")
fmt.Print("microseconds: ", duration.Microseconds(), "\n")
fmt.Print("nanoseconds: ", duration.Nanoseconds(), "\n")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

time.Duration型はintにキャストするとナノ秒となるが、String()メソッドを呼び出すと上記のソースのような文字列となる。

Hours()Minutes()Seconds()Nanoseconds()メソッドでそれぞれ、時間、分、秒、ナノ秒を取得できる。(time.Timeのメソッド名がHour()など単数系なのに対してこちらは複数形であることに注意)

戻り値はNanoseconds()Microseconds()Milliseconds()ではint64型だが、Hours()Minutes()Seconds()ではfloat64型なので小数点以下が不要な場合は必要に応じてキャストする。また、日数を求めるメソッドは存在しないので、Hours()の戻り値を24で割ることで日数を求めることができる。


# うるう年かどうかの判定

該当の年がうるう年かどうかを判定するメソッドはデフォルトでは用意されていないため、自分で関数を作成する必要がある。

うるう年は下記のすべての条件を満たす年かどうかを調べることで判定できる。
下に行くほど優先順位が高い。

  • 4の倍数の年はうるう年である。
  • ただし、100の倍数の年はうるう年ではない。
  • ただし、400の倍数の年はうるう年である。

いくつか例を挙げておく。

  • 2004年、2008年、2012年は4の倍数なのでうるう年である。
  • 1900年、1800年、1700年は100の倍数でかつ400の倍数ではないため、うるう年ではない
  • 2000年や1600年は400の倍数なのでうるう年である。

これをプログラムの条件式に落とし込むと下記のようになる。

func isLeapYear(t time.Time) bool {
	year := t.Year()
	return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
}
1
2
3
4

# time.Time変数をコピーする

下記のように行う。

year, month, day := t.Date()
hour, min, sec := t.Clock()
t = time.Date(year, month, day, hour, min, sec, t.Nanosecond(), t.Location())
1
2
3

簡潔に書きたい場合、AddDate()関数ですべてゼロを渡すことでもコピーできる。

t = t.AddDate(0,0,0)
1

Category:
Last Updated: 2022/01/04 09:40:45
Copyright © Web Ninja All Rights Reserved.