Fix lỗi Go 'parsing time as "2006-01-02": cannot parse' khi parse thời gian

beginner🔷 Go2026-06-30| Go 1.13+, mọi hệ điều hành (Linux, macOS, Windows), mọi dự án dùng time.Parse() hoặc time.ParseInLocation()

Error Message

parsing time "2023/10/27" as "2006-01-02": cannot parse
#go#golang#time#time-parse#datetime#format#layout

Lỗi Gặp Phải

Bạn gọi time.Parse() và nhận được lỗi như sau:

parsing time "2023/10/27" as "2006-01-02": cannot parse "/10/27" as "-"

Hoặc có thể:

parsing time "27-10-2023 14:30:00" as "2006-01-02 15:04:05": cannot parse "27-10-2023" as "2006"

Chuỗi thời gian hoàn toàn hợp lệ. Code trông cũng ổn. Nhưng Go vẫn từ chối parse nó.

Nguyên Nhân Gốc Rễ

Cách Go định dạng thời gian khác hoàn toàn so với các ngôn ngữ khác. Không có YYYY-MM-DD. Không có %d/%m/%Y. Thay vào đó, Go dùng một mốc thời gian tham chiếu cố định làm template định dạng:

Mon Jan 2 15:04:05 MST 2006

Mỗi thành phần tương ứng với một giá trị tham chiếu cố định:

  • Năm: 2006
  • Tháng: 01 (hoặc Jan)
  • Ngày: 02
  • Giờ: 15 (24h) hoặc 03 (12h)
  • Phút: 04
  • Giây: 05
  • Múi giờ: MST hoặc -0700

Vì vậy khi input là "2023/10/27" nhưng layout lại là "2006-01-02", Go cố khớp dấu gạch chéo với dấu gạch ngang — và thất bại ngay lập tức.

// Đoạn này THẤT BẠI — dấu phân cách không khớp
t, err := time.Parse("2006-01-02", "2023/10/27")
// parsing time "2023/10/27" as "2006-01-02": cannot parse "/10/27" as "-"

Cách Sửa: Khớp Layout Với Input

Mọi dấu phân cách trong layout phải khớp chính xác với những gì có trong chuỗi input.

Input dùng dấu gạch chéo: YYYY/MM/DD

t, err := time.Parse("2006/01/02", "2023/10/27")
if err != nil {
    log.Fatalf("parse error: %v", err)
}
fmt.Println(t) // 2023-10-27 00:00:00 +0000 UTC

Input dùng dấu chấm: DD.MM.YYYY

t, err := time.Parse("02.01.2006", "27.10.2023")
// Lưu ý: ngày đứng trước, rồi đến tháng, rồi năm — phải khớp đúng thứ tự dữ liệu thực tế

Input theo định dạng châu Âu: DD-MM-YYYY

t, err := time.Parse("02-01-2006", "27-10-2023")

Input có thêm giờ: YYYY/MM/DD HH:MM:SS

t, err := time.Parse("2006/01/02 15:04:05", "2023/10/27 14:30:00")

Bảng Tham Chiếu Nhanh Các Layout Phổ Biến

// Chuẩn ISO 8601
time.Parse("2006-01-02", "2023-10-27")

// Phân cách bằng dấu gạch chéo
time.Parse("2006/01/02", "2023/10/27")

// Định dạng Mỹ MM/DD/YYYY
time.Parse("01/02/2006", "10/27/2023")

// Có thêm giờ
time.Parse("2006-01-02 15:04:05", "2023-10-27 14:30:00")

// RFC3339 (ISO 8601 có múi giờ) — dùng hằng số có sẵn
time.Parse(time.RFC3339, "2023-10-27T14:30:00Z")

// RFC1123 — ngày tháng HTTP
time.Parse(time.RFC1123, "Fri, 27 Oct 2023 14:30:00 UTC")

// Unix timestamp — không dùng Parse
t := time.Unix(1698416200, 0)

Debug Khi Không Biết Định Dạng Input

Đang nhận dữ liệu từ API bên ngoài và không chắc họ gửi định dạng gì? Hãy in chuỗi thô ra trước khi xử lý:

rawDate := response["created_at"].(string)
fmt.Printf("raw date: %q\n", rawDate)
// raw date: "2023/10/27"
// Giờ bạn thấy rõ cần viết layout như thế nào

Sau đó ánh xạ từng ký tự sang các giá trị tham chiếu của Go:

Input:  2023/10/27
Layout: 2006/01/02
        ^^^^ ^^ ^^
        năm  th ngày

Xử Lý Nhiều Định Dạng Có Thể Có

Dữ liệu từ nhiều nguồn hiếm khi dùng cùng một định dạng. Cách đơn giản nhất: thử từng layout cho đến khi có một cái thành công.

func parseDate(s string) (time.Time, error) {
    layouts := []string{
        "2006-01-02",
        "2006/01/02",
        "01/02/2006",
        "02-01-2006",
        "2006-01-02T15:04:05Z07:00",
        time.RFC3339,
    }
    for _, layout := range layouts {
        if t, err := time.Parse(layout, s); err == nil {
            return t, nil
        }
    }
    return time.Time{}, fmt.Errorf("unrecognized date format: %q", s)
}

Bẫy Múi Giờ

Nếu input có chứa offset múi giờ nhưng layout không xử lý nó, bạn sẽ gặp lỗi khác. Hãy thêm token múi giờ vào layout, hoặc dùng time.ParseInLocation:

// Input có offset: "2023/10/27 14:30:00 +0900"
t, err := time.Parse("2006/01/02 15:04:05 -0700", "2023/10/27 14:30:00 +0900")

// Hoặc nếu bạn biết múi giờ nhưng input không có:
loc, _ := time.LoadLocation("Asia/Tokyo")
t, err := time.ParseInLocation("2006/01/02", "2023/10/27", loc)

Kiểm Tra Lại Kết Quả

Sau khi sửa layout, hãy kiểm tra nhanh. Cách nhanh nhất là round-trip: parse chuỗi, rồi format lại và so sánh:

t, err := time.Parse("2006/01/02", "2023/10/27")
if err != nil {
    log.Fatalf("still failing: %v", err)
}

// Kiểm tra từng trường
fmt.Println(t.Year())  // 2023
fmt.Println(t.Month()) // October
fmt.Println(t.Day())   // 27

// Round-trip: format lại về chuỗi ban đầu
fmt.Println(t.Format("2006/01/02")) // 2023/10/27

Nếu output khớp chính xác với input ban đầu, layout đã đúng.

Phòng Tránh

  • Khai báo các layout ngày tháng là hằng số có tên ở cấp package — chuỗi inline rải rác khắp nơi sẽ nhanh chóng trở thành vấn đề bảo trì.
  • Có layout mới? Hãy kiểm tra round-trip trước. Parse một ngày đã biết, format lại, và xác nhận kết quả khớp.
  • Với các API do bạn kiểm soát, hãy chuẩn hóa theo RFC3339 (time.RFC3339) và dùng time.Time với JSON marshaling. Go tự xử lý serialization.
  • Viết unit test cho mỗi lần gọi time.Parse mới — dùng cặp input/output thực tế, không chỉ kiểm tra "không có lỗi".
// Thực hành tốt: dùng hằng số có tên
const (
    DateLayout     = "2006/01/02"
    DateTimeLayout = "2006/01/02 15:04:05"
)

// Unit test
func TestParseDate(t *testing.T) {
    got, err := time.Parse(DateLayout, "2023/10/27")
    if err != nil {
        t.Fatal(err)
    }
    if got.Day() != 27 || got.Month() != time.October || got.Year() != 2023 {
        t.Errorf("unexpected date: %v", got)
    }
}

Related Error Notes