Menu

Go言語のtimeパッケージ:Now、Sleep、Format、Parse、Duration

Goで日付と時刻を扱う方法を解説します。time.Nowとtime.Sleep、Durationの計算、FormatとParseで使う2006-01-02 15:04:05という基準レイアウト、タイムゾーン、Unixタイムスタンプ、そしてタイマーとティッカーまで。

このページのコードはエディタで実行できます - 編集してすぐに結果を確認できます。

1つのプログラムで見る基本

time.Now() は現在のローカル時刻を返し、time.Sleep は現在のゴルーチンを一時停止し、time.Since は経過時間を測り、time.Date は特定の瞬間を組み立てます。このページの残りでは、それぞれを順に取り上げます。例は固定の日付とUTCを使っているので、どこで実行しても出力は同じです。

SleepとDuration

time.Duration はナノ秒を数える int64 です。パッケージには、読みやすい値を作るための定数が定義されています。

定数
time.Nanosecond1
time.Microsecond1000 ns
time.Millisecond1000 µs
time.Second1000 ms
time.Minute60 s
time.Hour60 min

タイムゾーンが夏時間で切り替わるときは1日が24時間とは限らないので、time.Day はありません。暦の日数には AddDate を使います(後述)。

Duration が整数であることから、よくあるバグが2つ生まれます。

  • time.Sleep(5) は5 ナノ秒 スリープします。必ず単位を掛けます:time.Sleep(5 * time.Second)
  • nint の変数のとき、time.Sleep(n * time.Second) はコンパイルできません。変換します:time.Duration(n) * time.Second5 のような定数は、型なし定数がDuration型に合わせて変わるので、変換なしで使えます。

ParseDurationnsus(または µs)、mssmh を受け付け、"2h45m""1.5s" のように組み合わせられます。日の単位はありません。

フォーマット:2006-01-02という基準レイアウト

Goは %Y-%m-%dyyyy-MM-dd も使いません。レイアウトとは、基準時刻

Mon Jan 2 15:04:05 MST 2006

を、出力したい形で書いたものです。値はそれぞれが一意になるよう選ばれています。月が 1、日が 2、時が 3(または 15)、分が 4、秒が 5、年が 62006)、タイムゾーンのオフセットが -7-0700)です。01/02 03:04:05PM '06 -0700 と読むと覚えやすいでしょう。

最もよく使うレイアウトのトークンは次のとおりです。

トークン意味
2006 / 06年、4桁または2桁2026 / 26
01 / 1 / Jan / January03 / 3 / Mar / March
02 / 2 / _2日(ゼロ埋め、そのまま、空白埋め)05 / 5 / " 5"
Mon / Monday曜日Thu / Thursday
15時、24時間制09
03 / 3時、12時間制09 / 9
04 / 407 / 7
05 / 503 / 3
PM / pm午前・午後の表示AM
.000 / .999秒の小数部(固定桁 / 末尾のゼロを削る).250 / .25
MSTタイムゾーンの略称UTC
-0700 / -07:00数値のタイムゾーンオフセット+0000 / +00:00
Z07:00-07:00 と同様だが、UTCでは Z と表示Z

定義済みのレイアウトは次のとおりです。

定数レイアウト
time.RFC33392006-01-02T15:04:05Z07:00(APIとJSONにはこれを使う)
time.RFC3339Nano2006-01-02T15:04:05.999999999Z07:00
time.DateTime(Go 1.20)2006-01-02 15:04:05
time.DateOnly(Go 1.20)2006-01-02
time.TimeOnly(Go 1.20)15:04:05
time.Kitchen3:04PM
time.RFC1123Mon, 02 Jan 2006 15:04:05 MST(HTTPの日付には代わりに http.TimeFormat を使う)

典型的な間違いは、"2023-01-01" のように違う数字でレイアウトを書くことです。Goはそれを拒否しません。認識しない文字はそのままコピーし、認識するものは置き換えます。それぞれの 2 は日、3 は12時間制の時、2つの 01 は月なので、3月5日9時07分は 5059-03-03 と整形されます。"YYYY-MM-DD" のようなレイアウトにはトークンがまったくないので、そのまま表示されます。整形された日付がおかしく見えたら、レイアウトが基準の値を正確に使っているか確認してください。

文字列を時刻にパースする

time.Parse(layout, value) は同じレイアウトを使い、チェックが必要なエラーを返します。

入力にタイムゾーンがなければ、Parse はUTCを返します。同じ壁時計の文字列でもタイムゾーンによって別の瞬間を意味しうるので、ParseInLocation があります。エラーメッセージは一致しなかった部分を示すので、レイアウトのデバッグに役立ちます。

タイムゾーン

time.Time は、瞬間と、表示に使うロケーションの組です。In でロケーションを変えると表示の仕方が変わるだけで、どの瞬間かは変わりません。

  • time.UTC は常に使えます。時刻はUTC(またはオフセット付きのRFC 3339)で保存・送信し、表示のときだけ変換します。
  • time.Local はマシンのタイムゾーンです。サーバーやコンテナではUTCであることが多く、ノートPCではそうでないので、それに依存するコードは場所によって違う振る舞いをします。
  • time.LoadLocation("Asia/Tokyo") はOSからIANAのデータベースを読みます。最小限のコンテナイメージにはそれがないことが多く、その場合この呼び出しはエラーを返します。ブランクインポート _ "time/tzdata" はデータベースをバイナリに埋め込む(約450KB)ので、常に動くようになります。エラーは必ず処理してください。
  • time.FixedZone(name, offsetSeconds) は一定のオフセットを持つタイムゾーンを作ります。夏時間のルールを持たないので、名前付きの地域ではなく、受け取ったオフセットに使います。

計算と比較

出力で注目すべき点は次のとおりです。

  • 1月31日に対する AddDate(0, 1, 0) は、2月28日ではなく 3月3日 になります。Goは1か月を足して「2月31日」を得てから、あふれた分を正規化します。「翌月の同じ日、ただし月末で止める」が必要なら、その処理は自分で書きます。
  • SubDuration を返し、その上限は約292年です。暦の日数の差には、UTCの0時に揃えた日付を比較し、24時間で割ります。
  • 1日の始まりを得るには、Date() から時刻を組み立て直します。t.Truncate(24 * time.Hour) はUTCのゼロ時刻を基準に丸めるので、UTC以外のタイムゾーンでは間違った答えになります。

比較には EqualBeforeAfter を使い、== は決して使いません。 time.Now() はモノトニッククロックの読み取り値を含んでおり、壁時計が調整されても time.Since が正しくなるよう使われます。== はその読み取り値とロケーションも比較するので、同じ瞬間を表す2つの Time の値が等しくならないことがあります。同じ理由で、time.Time を正規化せずにマップのキーに使ってはいけません(t.UTC().Round(0) でモノトニックの読み取り値を取り除けます)。

ゼロ値の time.Time は、UTCの1年1月1日0時0分です。t.IsZero() でチェックします。

経過時間を測る

start := time.Now()
doWork()
log.Printf("doWork took %v", time.Since(start))

time.Since(start)time.Now().Sub(start) で、time.Until(deadline)deadline.Sub(time.Now()) です。どちらも使えるときはモノトニッククロックを使うので、システムの時計が変わっても安全です。コードのベンチマークには、手で計測するのではなくtestingパッケージのベンチマーク機能を使います。

タイマーとティッカー

time.After(d) は、d の後に一度受信できるチャネルを返します。time.Timer は同じものに Stop メソッドが付いたものです。time.Ticker は止めるまで周期ごとに値を届けます。

20、40、60ミリ秒の3回のティックは110ミリ秒のタイマーよりずっと前に届くので、プログラムは3回目のティックの後に止まります。time.AfterFunc(d, f)d の後に専用のゴルーチンで f を実行し、一度きりの遅延処理に便利です。

Go 1.23以降、参照されなくなったタイマーとティッカーは Stop を呼ばなくてもガベージコレクションされ、止めたりリセットしたりしたタイマーのチャネルが古い値を届けることもなくなりました。それでも、deferStop を呼ぶのが、ティッカーの役目が終わったことを示すわかりやすい方法です。時間制限が1回の待機ではなく操作全体にかかるなら、たいていはタイマーより context.WithTimeout のほうが読みやすくなります。

JSONの中の時刻

time.Time は、JSONではナノ秒の精度でRFC 3339の文字列と自動的に相互変換されます。

type Event struct {
	Name string    `json:"name"`
	At   time.Time `json:"at"`
}
// {"name":"deploy","at":"2026-09-23T14:30:00Z"}

別の形式のJSON文字列はアンマーシャルに失敗します。Unixタイムスタンプや独自の形式には、int64 か文字列で保存して変換するか、独自の UnmarshalJSON を持つ型を定義します。

よくある間違い

  • time.Sleep(1)time.Sleep(n) に数値だけを渡す。 それはナノ秒です。
  • 間違った数字のレイアウト。 "2023-01-01""YYYY-MM-DD" はレイアウトではありません。"2006-01-02" を使います。
  • 分と月を取り違える。 分は 04、月は 01 です。"15:01" は時の後に分ではなく月を表示します。
  • == で比較する。 Equal を使います。
  • time.Local に頼る。 マシンによって違います。time.UTC か読み込んだロケーションで明示します。
  • LoadLocationParse のエラーを無視する。 どちらも実際の入力で失敗します。失敗した Parse はゼロの時刻を返し、それは1年として表示されます。失敗した LoadLocationnil のロケーションを返し、t.In(loc) をpanicさせます。

よくある質問

Goでスリープするには?

time.Duration を渡して time.Sleep を呼びます:time.Sleep(2 * time.Second)time.Sleep(500 * time.Millisecond)time.Sleep(2) のように数値だけを書いてもコンパイルできますが、Durationはナノ秒を数えるので2ナノ秒しかスリープしません。Sleepがブロックするのは現在のゴルーチンだけです。

なぜGoの日付フォーマットは2006-01-02 15:04:05なのですか?

Goは例を示すことで日付を整形します。レイアウトは、基準時刻 Mon Jan 2 15:04:05 MST 2006 を出力したい形で書いたものです。その各部分はアメリカ式の順で数が増えていきます。月が1、日が2、時が3(24時間制では15)、分が4、秒が5、年が6(2006)、タイムゾーンのオフセットが7(-0700)です。ですから "2006-01-02" は年-月-日、"02/01/2006" は日/月/年を意味します。

Goで日付の文字列をパースするには?

基準時刻で書いたレイアウトとともに time.Parse(layout, value) を使います:t, err := time.Parse("2006-01-02", "2026-09-23")err は必ずチェックします。文字列にタイムゾーンの情報がなければ結果はUTCになります。別のタイムゾーンとして解釈するには time.ParseInLocation を使います。

GoでUnixタイムスタンプを取得するには?

time.Now().Unix() は、UTCの1970年1月1日からの秒数を int64 で返します。UnixMilli()UnixMicro()UnixNano() はより細かい単位を返します。逆方向には time.Unix(sec, 0)time.UnixMilli(ms) を使います。

Goで2つの時刻を比較するには?

t1.Before(t2)t1.After(t2)t1.Equal(t2) を使います。== は使ってはいけません。ロケーションとモノトニッククロックの読み取り値も比較するので、同じ瞬間を表す2つの値が等しくならないことがあります。t2.Sub(t1) で差をDurationとして得られます。

Coddy programming languages illustration

Coddyでコードを学ぼう

始める