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期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?