- Go言語
Goの日付フォーマット早見表|time.FormatとParse・JST変換
Goの日付フォーマットは、%Y や yyyy のような記号ではなく、「2006年1月2日 15時4分5秒」という決まった基準日時をお手本として書く方式です。たとえば "2006-01-02 15:04:05" と書けば、年-月-日 時:分:秒の形で出力されます。 文字列から日時に戻す time.Parse も同じレイアウトを使います。
この記事では、レイアウト文字の全一覧表と、よく使う書式のコピペ集を go1.24.7 で実行した出力つき でまとめました。time.Parse がタイムゾーンのない文字列を UTC として読むために 9 時間ずれる現象を再現し、ParseInLocation・time.FixedZone・time.LoadLocation で直す手順、タイムゾーンのデータがない環境での time/tzdata の埋め込み、Go 1.20 で追加された time.DateTime / DateOnly / TimeOnly、月と日を取り違えたときのエラー文まで順に確かめます。
日付フォーマットの早見表
まず「これだけ覚えれば用が足りる」書式です。出力は 2026-10-03 09:05:07.123456789 JST(土曜日)を整形した結果です。
| やりたいこと | レイアウト(定数) | 出力 |
|---|---|---|
| 年-月-日 時:分:秒 | time.DateTime("2006-01-02 15:04:05") |
2026-10-03 09:05:07 |
| 年-月-日 | time.DateOnly("2006-01-02") |
2026-10-03 |
| 時:分:秒 | time.TimeOnly("15:04:05") |
09:05:07 |
| RFC3339(API・JSON 向け) | time.RFC3339 |
2026-10-03T09:05:07+09:00 |
| スラッシュ区切り | "2006/01/02 15:04:05" | 2026/10/03 09:05:07 |
| ゼロ埋めなし | "2006/1/2 15:04" | 2026/10/3 09:05 |
| 日本語 | "2006年1月2日 15時04分05秒" | 2026年10月3日 09時05分07秒 |
| ファイル名用 | "20060102_150405" | 20261003_090507 |
| ミリ秒つき | "2006-01-02T15:04:05.000Z07:00" | 2026-10-03T09:05:07.123+09:00 |
| やりたいこと | コード |
|---|---|
| 日時を文字列にする | s := t.Format(time.DateTime) |
| 文字列を日時にする(UTC として読む) | t, err := time.Parse(time.DateTime, s) |
| 文字列を日本時間として読む | t, err := time.ParseInLocation(time.DateTime, s, jst) |
| JST を用意する(固定オフセット) | jst := time.FixedZone("JST", 9*60*60) |
| JST を用意する(タイムゾーン名) | tokyo, err := time.LoadLocation("Asia/Tokyo") |
| UTC の日時を JST の表示にする | t.In(jst) |
| JST の日時を UTC の表示にする | t.UTC() |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Goの日付フォーマット早見表|time.FormatとParse・JST変換」
検証環境
| 項目 | 内容 |
|---|---|
| Go | go1.24.7 linux/amd64 |
| 実行方法 | go run main.go |
| 環境変数 TZ | Asia/Tokyo(略称の確認だけ TZ=UTC で実行) |
| 外部モジュール | なし(標準ライブラリのみ) |
出力が毎回同じになるよう、現在時刻(time.Now)ではなく time.Date で作った固定の日時を使っています。
レイアウト文字の全一覧表
Go のレイアウトは、基準日時 「2006年1月2日(月)15時4分5秒、UTC から 7 時間遅れ(MST)」 の各部分を、出したい形で書きます。公式ドキュメントでは「01/02 03:04:05PM '06 -0700」と紹介されていて、月=1、日=2、時=3(15)、分=4、秒=5、年=6、オフセット=7 と、1 から 7 までが順に並ぶ ように選ばれています。
次のコードで、レイアウト文字を 1 つずつ整形しました。
package main
import (
"fmt"
"time"
)
func main() {
jst := time.FixedZone("JST", 9*60*60)
t := time.Date(2026, 10, 3, 9, 5, 7, 123456789, jst) // 2026-10-03(土) 09:05:07.123456789 JST
pm := time.Date(2026, 10, 3, 21, 5, 7, 120000000, jst)
layouts := []string{
"2006", "06", "01", "1", "Jan", "January", "02", "2", "_2", "002", "__2",
"15", "03", "3", "04", "4", "05", "5", "PM", "pm",
".000", ".000000", ".000000000", ".999", ".999999999", ",000",
"MST", "-0700", "-07:00", "-07", "-070000", "Z0700", "Z07:00",
"Mon", "Monday",
}
for _, l := range layouts {
fmt.Printf("%-12q %-14q %q\n", l, t.Format(l), pm.Format(l))
}
fmt.Println("UTC:", t.UTC().Format("Z07:00"), t.UTC().Format("-07:00"), t.UTC().Format("MST"))
}
出力を表に整理します(午前の列が 09:05:07.123456789、午後の列が 21:05:07.12 の結果です)。
| レイアウト文字 | 意味 | 午前の出力 | 午後の出力 |
|---|---|---|---|
2006 |
年(4桁) | 2026 | 2026 |
06 |
年(下2桁) | 26 | 26 |
01 |
月(ゼロ埋め) | 10 | 10 |
1 |
月(ゼロ埋めなし) | 10 | 10 |
Jan |
月(英語の短縮) | Oct | Oct |
January |
月(英語) | October | October |
02 |
日(ゼロ埋め) | 03 | 03 |
2 |
日(ゼロ埋めなし) | 3 | 3 |
_2 |
日(空白埋め) | (空白)3 | (空白)3 |
002 |
年初からの通算日 | 276 | 276 |
15 |
時(24時間制) | 09 | 21 |
03 |
時(12時間制・ゼロ埋め) | 09 | 09 |
3 |
時(12時間制) | 9 | 9 |
04 |
分(ゼロ埋め) | 05 | 05 |
4 |
分 | 5 | 5 |
05 |
秒(ゼロ埋め) | 07 | 07 |
5 |
秒 | 7 | 7 |
PM |
午前・午後(大文字) | AM | PM |
pm |
午前・午後(小文字) | am | pm |
.000 |
ミリ秒(桁数固定) | .123 | .120 |
.000000 |
マイクロ秒(桁数固定) | .123456 | .120000 |
.000000000 |
ナノ秒(桁数固定) | .123456789 | .120000000 |
.999 |
小数秒(末尾の 0 を省く) | .123 | .12 |
.999999999 |
小数秒(末尾の 0 を省く) | .123456789 | .12 |
,000 |
小数秒(カンマ区切り) | ,123 | ,120 |
MST |
タイムゾーンの略称 | JST | JST |
-0700 |
UTC との差 | +0900 | +0900 |
-07:00 |
UTC との差(コロンつき) | +09:00 | +09:00 |
-07 |
UTC との差(時のみ) | +09 | +09 |
Z0700 |
UTC なら Z、それ以外は差 | +0900 | +0900 |
Z07:00 |
UTC なら Z、それ以外は差(コロンつき) | +09:00 | +09:00 |
Mon |
曜日(英語の短縮) | Sat | Sat |
Monday |
曜日(英語) | Saturday | Saturday |
- 24 時間制は 15、12 時間制は 03 です。「2006-01-02 03:04:05」と書くと、21 時が 09 と表示されます。
.000は桁数を固定、.999は末尾の 0 を省きます。 小数秒がちょうど 0 のとき、.999は小数点ごと出力されません。ログの桁をそろえたいときは.000を使います。- UTC の日時を
Z07:00で整形すると Z、-07:00で整形すると +00:00 になります(コードの最終行で確認。出力は「UTC: Z +00:00 UTC」)。RFC3339 が Z07:00 を使っているのはこのためです。 - 月名と曜日は英語のみです。日本語の曜日は、後述のコピペ集のように自分で対応表を持ちます。
- 一覧にない文字(「年」「T」「/」など)は、そのまま出力されます。
よく使う書式のコピペ集(ISO8601・RFC3339・和暦風・ファイル名用)
time パッケージに用意されている定数と、自分で書くレイアウトをまとめて実行しました。
package main
import (
"fmt"
"time"
)
var youbi = [...]string{"日", "月", "火", "水", "木", "金", "土"}
// Wareki は令和の年月日(2019年5月1日以降が対象)
func Wareki(t time.Time) string {
y := t.Year() - 2018
if y == 1 {
return t.Format("令和元年1月2日")
}
return fmt.Sprintf("令和%d年%s", y, t.Format("1月2日"))
}
func main() {
jst := time.FixedZone("JST", 9*60*60)
t := time.Date(2026, 10, 3, 9, 5, 7, 123456789, jst)
show := func(name, layout string) {
fmt.Printf("%-18s %-38q %s\n", name, layout, t.Format(layout))
}
show("time.DateTime", time.DateTime)
show("time.DateOnly", time.DateOnly)
show("time.TimeOnly", time.TimeOnly)
show("time.RFC3339", time.RFC3339)
show("time.RFC3339Nano", time.RFC3339Nano)
show("time.RFC1123", time.RFC1123)
show("time.RFC1123Z", time.RFC1123Z)
show("time.RFC822", time.RFC822)
show("time.UnixDate", time.UnixDate)
show("time.Kitchen", time.Kitchen)
fmt.Println("----")
show("スラッシュ区切り", "2006/01/02 15:04:05")
show("ゼロなし", "2006/1/2 15:04")
show("ISO8601 基本形式", "20060102T150405-0700")
show("ISO8601 ミリ秒", "2006-01-02T15:04:05.000Z07:00")
show("ファイル名用", "20060102_150405")
show("ファイル名用(ms)", "20060102-150405.000")
show("年月", "2006-01")
show("日本語", "2006年1月2日 15時04分05秒")
show("日本語(ゼロ埋め)", "2006年01月02日")
show("12時間制", "2006-01-02 03:04:05 PM")
show("英語表記", "Jan 2, 2006 (Mon)")
fmt.Println("----")
fmt.Println("UTCのRFC3339 :", t.UTC().Format(time.RFC3339))
fmt.Println("曜日つき日本語 :", t.Format("2006年1月2日")+"("+youbi[t.Weekday()]+")")
fmt.Println("和暦風 :", Wareki(t))
fmt.Println("和暦風(元年) :", Wareki(time.Date(2019, 5, 1, 0, 0, 0, 0, jst)))
fmt.Println("午前/午後 :", map[bool]string{true: "午前", false: "午後"}[t.Hour() < 12]+t.Format("3時4分"))
fmt.Println("Unix秒 :", t.Unix())
fmt.Println("Unixミリ秒 :", t.UnixMilli())
fmt.Println("ファイル名 :", "report_"+t.Format("20060102_150405")+".csv")
fmt.Println("fmt.Println(t) :", t)
fmt.Println("t.String() :", t.String())
}
実行結果:
time.DateTime "2006-01-02 15:04:05" 2026-10-03 09:05:07
time.DateOnly "2006-01-02" 2026-10-03
time.TimeOnly "15:04:05" 09:05:07
time.RFC3339 "2006-01-02T15:04:05Z07:00" 2026-10-03T09:05:07+09:00
time.RFC3339Nano "2006-01-02T15:04:05.999999999Z07:00" 2026-10-03T09:05:07.123456789+09:00
time.RFC1123 "Mon, 02 Jan 2006 15:04:05 MST" Sat, 03 Oct 2026 09:05:07 JST
time.RFC1123Z "Mon, 02 Jan 2006 15:04:05 -0700" Sat, 03 Oct 2026 09:05:07 +0900
time.RFC822 "02 Jan 06 15:04 MST" 03 Oct 26 09:05 JST
time.UnixDate "Mon Jan _2 15:04:05 MST 2006" Sat Oct 3 09:05:07 JST 2026
time.Kitchen "3:04PM" 9:05AM
----
スラッシュ区切り "2006/01/02 15:04:05" 2026/10/03 09:05:07
ゼロなし "2006/1/2 15:04" 2026/10/3 09:05
ISO8601 基本形式 "20060102T150405-0700" 20261003T090507+0900
ISO8601 ミリ秒 "2006-01-02T15:04:05.000Z07:00" 2026-10-03T09:05:07.123+09:00
ファイル名用 "20060102_150405" 20261003_090507
ファイル名用(ms) "20060102-150405.000" 20261003-090507.123
年月 "2006-01" 2026-10
日本語 "2006年1月2日 15時04分05秒" 2026年10月3日 09時05分07秒
日本語(ゼロ埋め) "2006年01月02日" 2026年10月03日
12時間制 "2006-01-02 03:04:05 PM" 2026-10-03 09:05:07 AM
英語表記 "Jan 2, 2006 (Mon)" Oct 3, 2026 (Sat)
----
UTCのRFC3339 : 2026-10-03T00:05:07Z
曜日つき日本語 : 2026年10月3日(土)
和暦風 : 令和8年10月3日
和暦風(元年) : 令和元年5月1日
午前/午後 : 午前9時5分
Unix秒 : 1790985907
Unixミリ秒 : 1790985907123
ファイル名 : report_20261003_090507.csv
fmt.Println(t) : 2026-10-03 09:05:07.123456789 +0900 JST
t.String() : 2026-10-03 09:05:07.123456789 +0900 JST
使い分けの目安
| 用途 | 向いている書式 | 理由 |
|---|---|---|
| API・JSON でやり取りする | time.RFC3339 |
オフセットつきで、受け取る側がずれなく解釈できる |
| データベースや CSV に近い形 | time.DateTime |
多くのツールが読める並び |
| ログ | "2006-01-02T15:04:05.000Z07:00" | 桁がそろい、文字列のままで時刻順に並ぶ |
| ファイル名 | "20060102_150405" | コロンやスラッシュを含まず、名前順が時刻順になる |
| 画面表示(日本語) | "2006年1月2日" と曜日の対応表 | ゼロ埋めなしが読みやすい |
| HTTP ヘッダー | http.TimeFormat |
GMT 表記が決まりになっている(今回は未実行) |
- time.DateTime / DateOnly / TimeOnly は Go 1.20 で追加された定数 です。Go 1.20 のリリースノートに「公開されている Go のソースコードを調べて、よく使われていた 3 つのレイアウトに名前を付けた」と書かれています。"2006-01-02 15:04:05" を手で打つ代わりに使えるので、打ちまちがいを防げます。
- ISO8601 の拡張形式(ハイフンとコロンつき)は RFC3339 とほぼ同じ形で、基本形式(区切りなし)は "20060102T150405-0700" と書けます。
- 和暦は標準ライブラリに用意されていない ので、年から 2018 を引いて「令和」を付ける関数を用意しました。令和より前の日付も扱う場合は、元号の開始日の表を持つ形に広げます。
- 曜日は
t.Weekday()が日曜を 0 とする数値を返すので、配列の添字にそのまま使えます。 fmt.Println(t)の表示は "2006-01-02 15:04:05.999999999 -0700 MST" の形です。この文字列を Parse し直す設計にはせず、Format で形を決めて出力します。
time.Parse で9時間ずれる現象と直し方
「2026-10-03 09:00:00」のように タイムゾーンの情報がない文字列を time.Parse に渡すと、UTC の時刻として読み込まれます。 日本時間の 9 時のつもりで読み込んだ値が、実際には UTC の 9 時(日本時間の 18 時)になり、9 時間のずれが生まれます。再現コードと直し方を 1 つにまとめました。
package main
import (
"fmt"
"time"
)
func main() {
const input = "2026-10-03 09:00:00" // 日本時間のつもりの文字列
// うまくいかない例:time.Parse はタイムゾーンの情報がないと UTC として読む
t1, err := time.Parse(time.DateTime, input)
fmt.Println("Parse :", t1, err)
jst := time.FixedZone("JST", 9*60*60)
fmt.Println(" JST で表示 :", t1.In(jst).Format(time.DateTime), "<- 9時間ずれる")
// 直し方1:ParseInLocation + FixedZone
t2, err := time.ParseInLocation(time.DateTime, input, jst)
fmt.Println("ParseInLocation :", t2, err)
// 直し方2:ParseInLocation + LoadLocation
tokyo, err := time.LoadLocation("Asia/Tokyo")
if err != nil {
fmt.Println("LoadLocation :", err)
return
}
t3, _ := time.ParseInLocation(time.DateTime, input, tokyo)
fmt.Println("LoadLocation :", t3)
// 直し方3:文字列側にオフセットを付けてもらう
t4, _ := time.Parse(time.RFC3339, "2026-10-03T09:00:00+09:00")
fmt.Println("RFC3339 +09:00 :", t4)
fmt.Println("差(t1 - t2) :", t1.Sub(t2))
fmt.Println("t2.Equal(t3) :", t2.Equal(t3), " t2 == t3:", t2 == t3)
fmt.Println("t2.Equal(t4) :", t2.Equal(t4))
fmt.Println("UTC に直す :", t2.UTC().Format(time.RFC3339))
fmt.Println("Unix 秒 :", t1.Unix(), t2.Unix())
// UTC → JST の変換(時刻そのものは同じで、表示だけ変わる)
u := time.Date(2026, 10, 3, 0, 0, 0, 0, time.UTC)
fmt.Println("UTC :", u.Format(time.RFC3339))
fmt.Println("In(JST) :", u.In(jst).Format(time.RFC3339))
fmt.Println("In(Asia/Tokyo) :", u.In(tokyo).Format(time.RFC3339))
ny, _ := time.LoadLocation("America/New_York")
fmt.Println("In(New_York) :", u.In(ny).Format(time.RFC3339), u.In(ny).Format("MST"))
fmt.Println("同じ瞬間か :", u.Equal(u.In(jst)))
}
実行結果:
Parse : 2026-10-03 09:00:00 +0000 UTC <nil>
JST で表示 : 2026-10-03 18:00:00 <- 9時間ずれる
ParseInLocation : 2026-10-03 09:00:00 +0900 JST <nil>
LoadLocation : 2026-10-03 09:00:00 +0900 JST
RFC3339 +09:00 : 2026-10-03 09:00:00 +0900 JST
差(t1 - t2) : 9h0m0s
t2.Equal(t3) : true t2 == t3: false
t2.Equal(t4) : true
UTC に直す : 2026-10-03T00:00:00Z
Unix 秒 : 1791018000 1790985600
UTC : 2026-10-03T00:00:00Z
In(JST) : 2026-10-03T09:00:00+09:00
In(Asia/Tokyo) : 2026-10-03T09:00:00+09:00
In(New_York) : 2026-10-02T20:00:00-04:00 EDT
同じ瞬間か : true
| 方法 | コード | 向いている場面 |
|---|---|---|
| ParseInLocation と FixedZone | time.ParseInLocation(layout, s, time.FixedZone("JST", 9*60*60)) |
日本時間だけ扱う。タイムゾーンのデータが不要で、どの環境でも動く |
| ParseInLocation と LoadLocation | time.ParseInLocation(layout, s, tokyo) |
夏時間のある地域も扱う。タイムゾーンのデータが必要 |
| 文字列にオフセットを付けてもらう | time.Parse(time.RFC3339, "2026-10-03T09:00:00+09:00") |
送る側の形式を決められる。受け取る側の設定に左右されない |
- 「差(t1 - t2)」が 9h0m0s になっているとおり、Parse で読んだ値は 9 時間あとの瞬間を指しています。Unix 秒も 32,400 秒(9 時間)違います。
- In や UTC は「同じ瞬間を別のタイムゾーンで表示し直す」操作 で、時刻そのものは変わりません。出力の「同じ瞬間か: true」がその確認です。ずれた値に In(jst) をかけても直らないので、読み込む段階で ParseInLocation を使います。
- 日時の比較は
==ではなく Equal を使います。FixedZone で読んだ t2 と LoadLocation で読んだ t3 は同じ瞬間ですが、==は場所の情報まで比べるため false になりました。 - 日本には夏時間がないので、JST は FixedZone で足ります。ニューヨークのように季節でオフセットが変わる地域(出力では 10 月が -04:00 の EDT)は LoadLocation を使います。
略称「JST」つきの文字列を Parse するとき
"2026-10-03 09:00:00 JST" のように略称が付いた文字列は、読み込む環境によって結果が変わります。TZ=UTC の環境で実行しました。
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("time.Local :", time.Local)
// 略称 "JST" つきの文字列を Parse する
t, err := time.Parse("2006-01-02 15:04:05 MST", "2026-10-03 09:00:00 JST")
name, off := t.Zone()
fmt.Println("Parse(… JST) :", t, err)
fmt.Println(" Zone() :", name, off)
fmt.Println(" UTC では :", t.UTC().Format(time.DateTime))
// 直し方:場所を渡す
jst := time.FixedZone("JST", 9*60*60)
t2, _ := time.ParseInLocation("2006-01-02 15:04:05 MST", "2026-10-03 09:00:00 JST", jst)
name, off = t2.Zone()
fmt.Println("ParseInLocation :", t2)
fmt.Println(" Zone() :", name, off)
fmt.Println(" UTC では :", t2.UTC().Format(time.DateTime))
}
実行結果(TZ=UTC で実行):
time.Local : UTC
Parse(… JST) : 2026-10-03 09:00:00 +0000 JST <nil>
Zone() : JST 0
UTC では : 2026-10-03 09:00:00
ParseInLocation : 2026-10-03 09:00:00 +0900 JST
Zone() : JST 32400
UTC では : 2026-10-03 00:00:00
time パッケージの公式ドキュメントには、略称が不明な場合は「その略称とオフセット 0 を持つ、仮の場所」として記録する と書かれています。出力でも、名前は JST なのにオフセットが 0 で、UTC に直しても 09:00 のままになっています。同じコードを TZ=Asia/Tokyo の環境で動かすと、略称 JST がその環境のタイムゾーンと一致するため +0900 として読めました。
動かす環境で結果が変わらないようにするには、ParseInLocation に場所を渡すか、"+09:00" のような数値のオフセットを使います。
LoadLocation が読み込めない環境と time/tzdata の埋め込み
time.LoadLocation は、タイムゾーンのデータベース(IANA Time Zone database)を探して読み込みます。公式ドキュメントによると、探す順番は次のとおりです。
| 順番 | 探す場所 |
|---|---|
| 1 | 環境変数 ZONEINFO が指すディレクトリまたは zip ファイル |
| 2 | Unix 系 OS の標準の場所(/usr/share/zoneinfo など) |
| 3 | $GOROOT/lib/time/zoneinfo.zip |
| 4 | time/tzdata パッケージ(import されている場合) |
最小構成のコンテナイメージのように、OS にタイムゾーンのデータが入っていない環境では、1〜3 がすべて見つからずエラーになります。この状態を、実行ファイルだけを置いた空のディレクトリに chroot して再現しました(scratch や alpine などのコンテナイメージでの確認は未実行です)。
package main
import (
"fmt"
"time"
)
func main() {
loc, err := time.LoadLocation("Asia/Tokyo")
if err != nil {
fmt.Println("LoadLocation エラー:", err)
// 代わりに固定オフセットを使う
loc = time.FixedZone("JST", 9*60*60)
}
t := time.Date(2026, 10, 3, 0, 0, 0, 0, time.UTC)
fmt.Println(t.In(loc).Format(time.RFC3339), loc)
}
CGO_ENABLED=0 go build -o jail/app1 ./tz1/main.go
chroot jail /app1
実行結果:
LoadLocation エラー: unknown time zone Asia/Tokyo
2026-10-03T09:00:00+09:00 JST
エラーメッセージは unknown time zone Asia/Tokyo です。このコードでは、読み込めなかったときに FixedZone に切り替えているので、2 行目は正しく +09:00 になっています。
直し方は、time/tzdata を import してデータを実行ファイルに埋め込む ことです。
import (
"fmt"
"time"
_ "time/tzdata" // タイムゾーンのデータを実行ファイルに埋め込む
)
同じ chroot 環境での実行結果:
2026-10-03T09:00:00+09:00 Asia/Tokyo
ソースを変えずに、ビルド時のタグで埋め込むこともできます。
CGO_ENABLED=0 go build -tags timetzdata -o jail/app3 ./tz1/main.go
chroot jail /app3
2026-10-03T09:00:00+09:00 Asia/Tokyo
| ビルド方法 | 実行ファイルの大きさ | データのない環境での LoadLocation |
|---|---|---|
| そのまま | 2,320,941 バイト | unknown time zone Asia/Tokyo |
_ "time/tzdata" を import |
2,736,525 バイト | 読み込める |
-tags timetzdata |
2,736,605 バイト | 読み込める |
埋め込みによる増加は約 415KB でした。名前のつづりが違う場合も同じ形のメッセージになります。
unknown time zone Asia/Tokio
unknown time zone JST
UTC <nil>
Asia/Tokyo <nil>
1 行目は "Asia/Tokio"、2 行目は "JST" を渡した結果です。LoadLocation に渡すのは "JST" ではなく "Asia/Tokyo" です。3 行目は空文字で UTC、4 行目は "Local" でその環境のタイムゾーンが返りました。
どの方法を選ぶかの判断表
| 状況 | 選ぶ方法 |
|---|---|
| 日本時間だけ扱う | time.FixedZone("JST", 9*60*60)。データがなくても動く |
| 夏時間のある地域を扱う、利用者ごとにタイムゾーンが違う | LoadLocation と time/tzdata の埋め込み |
| コンテナイメージを自分で作れる | OS のタイムゾーンデータを入れる、または埋め込む |
| 保存・通信 | UTC または RFC3339 のオフセットつきでそろえ、表示の直前に In で変換する |
Parse のエラー文と、月と日を取り違えたときの動き
time.Parse は、レイアウトと文字列が合わないときに理由つきのエラーを返します。よくあるパターンを集めました。
package main
import (
"errors"
"fmt"
"time"
)
func try(layout, value string) {
t, err := time.Parse(layout, value)
if err != nil {
fmt.Printf("Parse(%q, %q)\n エラー: %v\n", layout, value, err)
return
}
fmt.Printf("Parse(%q, %q)\n OK: %v\n", layout, value, t)
}
func main() {
try("2006-02-01", "2026-10-23") // 月と日を取り違えたレイアウト
try("2006-02-01", "2026-10-03") // 取り違えてもエラーにならない日付
try("2006-01-02", "2026/10/03") // 区切り文字が違う
try("2006-01-02", "2026-10-3") // ゼロ埋めなし
try("2006-1-2", "2026-10-03") // ゼロなしレイアウトはどちらも読める
try("2006-01-02", "2026-10-03 09:00:00") // 余分な文字
try("2006-01-02 15:04:05", "2026-10-03") // 足りない
try("2006-01-02", "2026-02-30") // 存在しない日
try("2006-01-02 15:04:05", "2026-10-03 24:00:00")
try("2006-01-02 15:04:05", "2026-10-03 09:00:00.123") // 小数秒は自動で読める
try("2006-01-02T15:04:05Z07:00", "2026-10-03T09:00:00") // オフセットなし
try("2006-01-02", "")
try("YYYY-MM-DD", "2026-10-03")
try("2006-01-02", " 2026-10-03")
_, err := time.Parse(time.DateOnly, "2026/10/03")
var pe *time.ParseError
if errors.As(err, &pe) {
fmt.Printf("ParseError: Layout=%q Value=%q LayoutElem=%q ValueElem=%q Message=%q\n",
pe.Layout, pe.Value, pe.LayoutElem, pe.ValueElem, pe.Message)
}
fmt.Println("---- Format 側の取り違え ----")
t := time.Date(2026, 10, 3, 21, 5, 7, 0, time.UTC)
for _, l := range []string{
"YYYY-MM-DD hh:mm:ss",
"2006-01-02 03:04:05",
"2006-02-01",
"2006-01-02 15:04:05",
"2016-01-02",
"2006-01-02 15:4:5",
"2006-01-02 15:04:05.000",
"2006-01-02 15:04:05.999",
"2006年01月02日 15時04分",
"Mon Monday Jan January",
} {
fmt.Printf("%-28q -> %s\n", l, t.Format(l))
}
}
実行結果:
Parse("2006-02-01", "2026-10-23")
エラー: parsing time "2026-10-23": month out of range
Parse("2006-02-01", "2026-10-03")
OK: 2026-03-10 00:00:00 +0000 UTC
Parse("2006-01-02", "2026/10/03")
エラー: parsing time "2026/10/03" as "2006-01-02": cannot parse "/10/03" as "-"
Parse("2006-01-02", "2026-10-3")
エラー: parsing time "2026-10-3" as "2006-01-02": cannot parse "3" as "02"
Parse("2006-1-2", "2026-10-03")
OK: 2026-10-03 00:00:00 +0000 UTC
Parse("2006-01-02", "2026-10-03 09:00:00")
エラー: parsing time "2026-10-03 09:00:00": extra text: " 09:00:00"
Parse("2006-01-02 15:04:05", "2026-10-03")
エラー: parsing time "2026-10-03" as "2006-01-02 15:04:05": cannot parse "" as "15"
Parse("2006-01-02", "2026-02-30")
エラー: parsing time "2026-02-30": day out of range
Parse("2006-01-02 15:04:05", "2026-10-03 24:00:00")
エラー: parsing time "2026-10-03 24:00:00": hour out of range
Parse("2006-01-02 15:04:05", "2026-10-03 09:00:00.123")
OK: 2026-10-03 09:00:00.123 +0000 UTC
Parse("2006-01-02T15:04:05Z07:00", "2026-10-03T09:00:00")
エラー: parsing time "2026-10-03T09:00:00" as "2006-01-02T15:04:05Z07:00": cannot parse "" as "Z07:00"
Parse("2006-01-02", "")
エラー: parsing time "" as "2006-01-02": cannot parse "" as "2006"
Parse("YYYY-MM-DD", "2026-10-03")
エラー: parsing time "2026-10-03" as "YYYY-MM-DD": cannot parse "2026-10-03" as "YYYY-MM-DD"
Parse("2006-01-02", " 2026-10-03")
エラー: parsing time " 2026-10-03" as "2006-01-02": cannot parse " 2026-10-03" as "2006"
ParseError: Layout="2006-01-02" Value="2026/10/03" LayoutElem="-" ValueElem="/10/03" Message=""
---- Format 側の取り違え ----
"YYYY-MM-DD hh:mm:ss" -> YYYY-MM-DD hh:mm:ss
"2006-01-02 03:04:05" -> 2026-10-03 09:05:07
"2006-02-01" -> 2026-03-10
"2006-01-02 15:04:05" -> 2026-10-03 21:05:07
"2016-01-02" -> 3106-10-03
"2006-01-02 15:4:5" -> 2026-10-03 21:5:7
"2006-01-02 15:04:05.000" -> 2026-10-03 21:05:07.000
"2006-01-02 15:04:05.999" -> 2026-10-03 21:05:07
"2006年01月02日 15時04分" -> 2026年10月03日 21時05分
"Mon Monday Jan January" -> Sat Saturday Oct October
| 状況 | エラーメッセージ(要点) | 直し方 |
|---|---|---|
| 月と日を逆にしたレイアウトで 23 日を読む | month out of range | "2006-01-02" の順に直す(01 が月、02 が日) |
| 月と日を逆にしたレイアウトで 3 日を読む | エラーにならず 3 月 10 日になる | 12 日以下では気づけないので、13 日以降の日付でテストする |
| 区切り文字が違う | cannot parse "/10/03" as "-" | レイアウトを "2006/01/02" にする |
| ゼロ埋めなしの入力を 02 で読む | cannot parse "3" as "02" | レイアウトを "2006-1-2" にする(ゼロ埋めありも読める) |
| 後ろに余分な文字 | extra text: " 09:00:00" | レイアウトに時刻を足すか、先に切り出す |
| 文字列が足りない | cannot parse "" as "15" | 日付だけなら DateOnly で読む |
| 存在しない日(2 月 30 日) | day out of range | 入力エラーとして返す |
| 24 時 | hour out of range | 同上 |
| オフセットが必要なレイアウトで省略 | cannot parse "" as "Z07:00" | DateTime などオフセットなしのレイアウトで ParseInLocation |
| "YYYY-MM-DD" をレイアウトにした | cannot parse "2026-10-03" as "YYYY-MM-DD" | 基準日時の数字で書く |
| 先頭に空白 | cannot parse " 2026-10-03" as "2006" | strings.TrimSpace を通す |
- 「cannot parse A as B」は「入力の A の部分を、レイアウトの B として読めなかった」 という意味です。A と B を見比べると、どこから合わなくなったかがわかります。
- 月と日の取り違えは、日が 12 以下だとエラーにならず、別の日付として読めてしまいます。 テストデータには 13 日以降の日付を入れておくと早く気づけます。
- "2006-1-2" のようにゼロ埋めなしで書いたレイアウトは、"10-03" のようなゼロ埋めありの入力も読めました。入力の形がそろっていないときに使えます。
- レイアウトに小数秒がなくても、入力の秒の直後に小数(.123)があれば自動で読み込まれました。
- エラーの中身は errors.As で
*time.ParseErrorとして取り出せます。LayoutElem(合わなかったレイアウト側)と ValueElem(入力側)が入っています。
Format 側で取り違えたときの出力
Format はエラーを返さないので、レイアウトの書きまちがいは出力を見て気づくことになります。
| 書いたレイアウト | 出力 | 何が起きたか |
|---|---|---|
| "YYYY-MM-DD hh:mm:ss" | YYYY-MM-DD hh:mm:ss | 基準日時の数字がないので、そのまま出力される |
| "2006-01-02 03:04:05" | 2026-10-03 09:05:07 | 03 は 12 時間制。21 時が 09 になる |
| "2006-02-01" | 2026-03-10 | 02 が日、01 が月なので「年-日-月」になる |
| "2016-01-02" | 3106-10-03 | 2016 は年ではなく、2(日)・01(月)・6(そのまま)と読まれる |
| "2006-01-02 15:4:5" | 2026-10-03 21:5:7 | 4 と 5 はゼロ埋めなし |
レイアウトは手で打たず、time.DateTime などの定数か、コードの 1 か所にまとめた定数を使う と、こうした書きまちがいを減らせます。
いろいろな形の日付を読むコピペ用関数と日付計算
入力の形がそろっていないときは、レイアウトを順に試す関数が便利です。日数の差や月末の求め方も合わせて確認します。
package main
import (
"encoding/json"
"fmt"
"time"
)
// 複数の書式を順に試す
func ParseAny(s string, loc *time.Location) (time.Time, error) {
layouts := []string{
time.RFC3339,
time.DateTime,
"2006/01/02 15:04:05",
"2006/1/2 15:04",
time.DateOnly,
"2006/1/2",
"20060102",
}
for _, l := range layouts {
if t, err := time.ParseInLocation(l, s, loc); err == nil {
return t, nil
}
}
return time.Time{}, fmt.Errorf("日付として読めません: %q", s)
}
func main() {
jst := time.FixedZone("JST", 9*60*60)
for _, s := range []string{"2026-10-03T09:00:00+09:00", "2026-10-03 09:00:00", "2026/10/03 09:00:00", "2026/10/3 9:05", "2026-10-03", "2026/10/3", "20261003", "10/3"} {
t, err := ParseAny(s, jst)
if err != nil {
fmt.Printf("%-28q %v\n", s, err)
continue
}
fmt.Printf("%-28q %s\n", s, t.Format(time.RFC3339))
}
// 日付だけの比較・差
a, _ := time.ParseInLocation(time.DateOnly, "2026-10-03", jst)
b, _ := time.ParseInLocation(time.DateOnly, "2026-12-31", jst)
fmt.Println("日数差:", int(b.Sub(a).Hours()/24), "日")
fmt.Println("30日後:", a.AddDate(0, 0, 30).Format(time.DateOnly))
fmt.Println("月末 :", time.Date(a.Year(), a.Month()+1, 0, 0, 0, 0, 0, jst).Format(time.DateOnly))
fmt.Println("1か月後(1/31):", time.Date(2026, 1, 31, 0, 0, 0, 0, jst).AddDate(0, 1, 0).Format(time.DateOnly))
// JSON では RFC3339 で出入りする
type Event struct {
Name string `json:"name"`
At time.Time `json:"at"`
}
out, _ := json.Marshal(Event{"meeting", time.Date(2026, 10, 3, 9, 0, 0, 0, jst)})
fmt.Println(string(out))
var ev Event
err := json.Unmarshal([]byte(`{"name":"x","at":"2026-10-03 09:00:00"}`), &ev)
fmt.Println("JSON:", err)
// ゼロ値
var z time.Time
fmt.Println(z.Format(time.DateTime), z.IsZero())
}
実行結果:
"2026-10-03T09:00:00+09:00" 2026-10-03T09:00:00+09:00
"2026-10-03 09:00:00" 2026-10-03T09:00:00+09:00
"2026/10/03 09:00:00" 2026-10-03T09:00:00+09:00
"2026/10/3 9:05" 2026-10-03T09:05:00+09:00
"2026-10-03" 2026-10-03T00:00:00+09:00
"2026/10/3" 2026-10-03T00:00:00+09:00
"20261003" 2026-10-03T00:00:00+09:00
"10/3" 日付として読めません: "10/3"
日数差: 89 日
30日後: 2026-11-02
月末 : 2026-10-31
1か月後(1/31): 2026-03-03
{"name":"meeting","at":"2026-10-03T09:00:00+09:00"}
JSON: parsing time "2026-10-03 09:00:00" as "2006-01-02T15:04:05Z07:00": cannot parse " 09:00:00" as "T"
0001-01-01 00:00:00 true
- ParseAny は、厳密な形から順に試し、すべて合わなければ自前のエラーを返します。 受け付ける形を増やしたいときは、スライスにレイアウトを足すだけです。
- 月末は「翌月の 0 日」で求められます。time.Date は範囲外の値を自動で繰り上げ・繰り下げします。
- AddDate(0, 1, 0) も同じ繰り上げをする ため、1 月 31 日の 1 か月後は 2 月 31 日にあたる 3 月 3 日になりました。「翌月の同じ日、なければ月末」としたい場合は、月初に直してから計算します。
- time.Time を JSON にすると RFC3339 の形 になり、読み込むときも RFC3339 が必要です。"2026-10-03 09:00:00" の形を渡すと、出力のとおり cannot parse " 09:00:00" as "T" というエラーになります。
- time.Time のゼロ値は 0001-01-01 00:00:00 で、
IsZero()で判定できます。
日付フォーマットのチェックリスト
| 確認すること | 見るポイント |
|---|---|
| 24 時間制になっているか | 時は 15(03 は 12 時間制) |
| 月と日の順番 | 01 が月、02 が日。13 日以降の日付でテストしたか |
| Parse のタイムゾーン | オフセットのない文字列は ParseInLocation で読んでいるか |
| 略称(JST など)に頼っていないか | 数値のオフセットか、場所を渡す形になっているか |
| LoadLocation のエラー処理 | err を確かめているか。データのない環境では time/tzdata を埋め込んだか |
| 比較 | == ではなく Equal / Before / After を使っているか |
| 小数秒の桁 | 桁をそろえるなら .000、省くなら .999 |
| 保存と表示の分離 | 保存は UTC かオフセットつき、表示の直前に In で変換しているか |
よくある質問
Q. なぜ 2006-01-02 15:04:05 という数字なのですか? A. 基準日時を「01/02 03:04:05PM '06 -0700」と並べると、月=1、日=2、時=3、分=4、秒=5、年=6、オフセット=7 と順番になるように決められています。覚えにくいときは、time.DateTime・time.DateOnly・time.TimeOnly・time.RFC3339 の定数を使うと数字を打たずに済みます。
Q. time.Parse で読んだ時刻が 9 時間ずれます。どう直しますか?
A. タイムゾーンの情報がない文字列は UTC として読まれるためです。time.ParseInLocation(layout, s, time.FixedZone("JST", 9*60*60)) のように、場所を渡して読み込みます。
Q. LoadLocation("Asia/Tokyo") で unknown time zone と出ます。
A. 実行環境にタイムゾーンのデータがない状態です。import _ "time/tzdata" を足すか -tags timetzdata を付けてビルドすると、データが実行ファイルに埋め込まれます。日本時間だけなら FixedZone でも同じ結果になります。
Q. 日本語の曜日や和暦は出せますか?
A. Format が出せる月名と曜日は英語のみです。曜日は t.Weekday() を添字にした配列、和暦は年から計算する関数を用意します(この記事のコピペ集にあります)。
まとめ
- レイアウトは基準日時「2006-01-02 15:04:05」の数字で書く。 月=01、日=02、時=15、分=04、秒=05、年=2006 です。
- time.DateTime / DateOnly / TimeOnly(Go 1.20 以降)と time.RFC3339 を使うと、打ちまちがいを防げます。
- time.Parse はタイムゾーンのない文字列を UTC として読む ため、日本時間の文字列は ParseInLocation で読みます。
- JST は
time.FixedZone("JST", 9*60*60)で用意できる。 LoadLocation を使うなら、データのない環境に備えて time/tzdata を埋め込みます。 - In と UTC は表示を変えるだけ で、時刻そのものは変わりません。比較は Equal を使います。
- 月と日の取り違えは 12 日以下だとエラーにならない ので、13 日以降の日付でテストします。
- 略称「JST」つきの文字列は環境によって読み方が変わるので、数値のオフセット(+09:00)を使います。
参考・出典
- time パッケージ(Go 公式ドキュメント): https://pkg.go.dev/time
- Go 1.20 Release Notes(time): https://go.dev/doc/go1.20
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Goの日付フォーマット早見表|time.FormatとParse・JST変換」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。