Go言語 日付チェック:様々なフォーマットに対応する判定テクニック

Go言語で文字列が日付形式か判定!time パッケージを使いこなす

Go言語でユーザー入力やファイルから読み込んだ文字列が、特定の日付形式であるかどうかを判定したい場面は少なくありません。例えば、API から受け取った日付文字列のバリデーションや、ログファイルの日付情報の解析などが挙げられます。

この記事では、Go言語の標準パッケージである time パッケージを利用して、文字列が日付形式かどうかを判定する基本的な方法と、様々な日付フォーマットに対応するためのテクニックを解説します。

1. time.Parse() 関数を使った基本的な判定

time パッケージの Parse() 関数は、指定されたレイアウト(日付フォーマット)に基づいて文字列を time.Time 型の値に変換しようと試みます。変換に成功すれば time.Time オブジェクトと nil のエラーが返り、失敗すればエラーが返ります。このエラーの有無を利用して、文字列が指定された日付形式であるかどうかを判定できます。

package main

import (
    "fmt"
    "time"
)

func isDate(layout, value string) bool {
    _, err := time.Parse(layout, value)
    return err == nil
}

func main() {
    dateString1 := "2025-04-18"
    layout1 := "2006-01-02" // Go のリファレンスレイアウト

    dateString2 := "18/04/2025"
    layout2 := "02/01/2006"

    dateString3 := "2025/04/18 17:54:00"
    layout3 := "2006/01/02 15:04:05"

    notDateString := "not a date"

    fmt.Printf("%q は %q 形式の日付ですか?: %t\n", dateString1, layout1, isDate(layout1, dateString1))
    fmt.Printf("%q は %q 形式の日付ですか?: %t\n", dateString2, layout2, isDate(layout2, dateString2))
    fmt.Printf("%q は %q 形式の日付ですか?: %t\n", dateString3, layout3, isDate(layout3, dateString3))
    fmt.Printf("%q は %q 形式の日付ですか?: %t\n", notDateString, layout1, isDate(layout1, notDateString))
}

ポイント:

  • time.Parse(layout, value) は、指定された layoutvalue が合致するかどうかを厳密にチェックします。
  • layout は、Go 言語特有の リファレンスレイアウト を使用します。これは、特定の日付(2006年1月2日 午後3時4分5秒 MST)の各要素を特定の数値で表現したものです。
    • 年: 2006
    • 月: 01 (または 1 for non-padded), Jan, January
    • 日: 02 (または 2 for non-padded)
    • 時: 15 (24時間形式), 03 (12時間形式)
    • 分: 04
    • 秒: 05
    • タイムゾーン: MST, -0700, Z0700
  • isDate 関数は、time.Parse のエラーが nil であれば true(日付形式である)、そうでなければ false を返します。

2. 複数の日付フォーマットに対応する

現実のシステムでは、様々な形式の日付文字列を受け取る可能性があります。複数のフォーマットに対応するためには、それぞれのフォーマットで time.Parse を試行し、最初に成功したものを採用するなどの方法が考えられます。

package main

import (
    "fmt"
    "time"
)

func parseDateMultiFormat(value string, layouts ...string) (time.Time, error) {
    for _, layout := range layouts {
        t, err := time.Parse(layout, value)
        if err == nil {
            return t, nil
        }
    }
    return time.Time{}, fmt.Errorf("日付形式が一致しません: %q", value)
}

func main() {
    dateString := "18/04/2025"
    formats := []string{"2006-01-02", "02/01/2006", "2006/01/02"}

    parsedTime, err := parseDateMultiFormat(dateString, formats...)
    if err != nil {
        fmt.Println("エラー:", err)
    } else {
        fmt.Println("解析された日時:", parsedTime)
    }

    invalidDateString := "2025年4月18日"
    parsedTimeInvalid, errInvalid := parseDateMultiFormat(invalidDateString, formats...)
    if errInvalid != nil {
        fmt.Println("エラー:", errInvalid)
    } else {
        fmt.Println("解析された日時:", parsedTimeInvalid)
    }
}

ポイント:

  • parseDateMultiFormat 関数は、可変長引数 layouts で複数の日付フォーマットを受け取ります。
  • 各フォーマットで time.Parse を試し、エラーが nil であれば解析成功として time.Time オブジェクトを返します。
  • すべてのフォーマットでの解析に失敗した場合、エラーを返します。

3. より厳密な判定のために:ParseStrictLayout()

Go 1.18 以降では、time パッケージに ParseStrictLayout() 関数が導入されました。これは Parse() と似ていますが、より厳密なレイアウトマッチングを行います。例えば、レイアウトにない余分な文字が含まれている場合や、数値がレイアウトの範囲を超えている場合にエラーを返します。

package main

import (
    "fmt"
    "time"
)

func main() {
    layout := "2006-01-02"
    validDate := "2025-04-18"
    invalidDateExtraChars := "2025-04-18 extra"
    invalidDateWrongDay := "2025-04-32"

    _, errValid := time.ParseStrictLayout(layout, validDate)
    fmt.Printf("%q は厳密な %q 形式ですか?: %t (エラー: %v)\n", validDate, layout, errValid == nil, errValid)

    _, errExtra := time.ParseStrictLayout(layout, invalidDateExtraChars)
    fmt.Printf("%q は厳密な %q 形式ですか?: %t (エラー: %v)\n", invalidDateExtraChars, layout, errExtra == nil, errExtra)

    _, errWrongDay := time.ParseStrictLayout(layout, invalidDateWrongDay)
    fmt.Printf("%q は厳密な %q 形式ですか?: %t (エラー: %v)\n", invalidDateWrongDay, layout, errWrongDay == nil, errWrongDay)
}

ポイント:

  • time.ParseStrictLayout() は、フォーマットだけでなく、値の妥当性もより厳しくチェックしたい場合に有効です。

4. タイムゾーンを考慮した判定

日付文字列にタイムゾーン情報が含まれている場合、time.Parse()time.ParseStrictLayout() はそれを解析して time.Time オブジェクトに格納します。タイムゾーンの指定がない場合は、UTC として扱われることに注意が必要です。

特定のタイムゾーンを前提とした判定を行いたい場合は、レイアウトにタイムゾーン情報 (MST, -0700, Z0700) を含める必要があります。

package main

import (
    "fmt"
    "time"
)

func main() {
    layoutWithZone := "2006-01-02 15:04:05 MST"
    dateStringWithZone := "2025-04-18 17:54:00 JST" // JST は Parse では認識されない

    parsedTimeWithZone, err := time.Parse(layoutWithZone, dateStringWithZone)
    fmt.Printf("%q を %q で解析 (タイムゾーン考慮): %v (エラー: %v)\n", dateStringWithZone, layoutWithZone, parsedTimeWithZone, err)

    layoutUTC := "2006-01-02T15:04:05Z"
    dateStringUTC := "2025-04-18T08:54:00Z"

    parsedTimeUTC, errUTC := time.Parse(layoutUTC, dateStringUTC)
    fmt.Printf("%q を %q で解析 (UTC): %v (エラー: %v)\n", dateStringUTC, layoutUTC, parsedTimeUTC, errUTC)
}

ポイント:

  • time.Parse() は、レイアウトに指定されたタイムゾーンに基づいて解析を試みます。
  • レイアウトにないタイムゾーン文字列は認識されません。
  • UTC を扱う場合は、レイアウトに Z を含めます。

まとめ

Go言語で文字列が日付形式であるかを判定するには、time パッケージの Parse() 関数(Go 1.18 以降では ParseStrictLayout()) を利用するのが基本です。

  • 判定したい日付形式に合わせて適切な リファレンスレイアウト を指定することが重要です。
  • 複数のフォーマットに対応する必要がある場合は、複数のレイアウトで解析を試みる関数を作成しましょう。
  • より厳密な判定を行いたい場合は ParseStrictLayout() の利用を検討してください。
  • タイムゾーンを考慮した判定も、レイアウトにタイムゾーン情報を加えることで可能です。