package main
import (
"fmt"
"strings"
"time"
)
/*
Architecture notes:
- Uses only Go’s standard library: time.Parse, time.Date, and string utilities.
- Strict validation: after parsing, the resulting date is checked against the expected
year, month, and day to prevent silent auto-correction (e.g., Feb 30 → Mar 1).
- Modular design: normalizeDate handles all parsing attempts; strictDate constructs
and validates dates; parseWithLayout tries specific layouts.
- Complexity: O(n) per date, where n = number of layouts attempted.
- Memory: minimal; only small structs created.
- Security: avoids unsafe parsing; relies on trusted standard library.
- Pitfalls: ambiguous formats (e.g., 7/5/2026 could be MM/DD or DD/MM). Here we assume MM/DD.
- Error handling: invalid or impossible dates return an error.
- Tests in main cover valid, invalid, edge cases, and mixed formats.
*/
// strictDate ensures the constructed date matches exactly.
func strictDate(year, month, day int) (time.Time, error) {
dt := time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.UTC)
if dt.Year() != year || int(dt.Month()) != month || dt.Day() != day {
return time.Time{}, fmt.Errorf("invalid date")
}
return dt, nil
}
// parseWithLayout tries a specific layout and validates strictly.
func parseWithLayout(input, layout string) (time.Time, error) {
dt, err := time.Parse(layout, input)
if err != nil {
return time.Time{}, err
}
return strictDate(dt.Year(), int(dt.Month()), dt.Day())
}
// normalizeDate attempts multiple formats and returns YYYY-MM-DD.
func normalizeDate(raw string) (string, error) {
clean := strings.TrimSpace(raw)
clean = strings.Trim(clean, `"'`)
layouts := []string{
"01/02/2006", // zero-padded MM/DD/YYYY
"1/2/2006", // non-zero-padded MM/DD/YYYY (fix for 7/5/2026)
"01-02-2006",
"2006.01.02",
"January 2, 2006",
"2006/01/02",
"2006-01-02",
"2 Jan 2006",
"Jan 2, 2006",
"2 January 2006",
}
for _, layout := range layouts {
if dt, err := parseWithLayout(clean, layout); err == nil {
return dt.Format("2006-01-02"), nil
}
}
// Fallback: RFC3339, still strictly validated.
if dt, err := time.Parse(time.RFC3339, clean); err == nil {
if strict, err := strictDate(dt.Year(), int(dt.Month()), dt.Day()); err == nil {
return strict.Format("2006-01-02"), nil
}
}
return "", fmt.Errorf("unsupported or invalid format")
}
func main() {
tests := []string{
"05/01/2024",
"12-31-2023",
"2024.02.09",
"January 5, 2024",
"2024/09/17",
"2024-09-17",
"25 Dec 2025",
"6 Jan 2025",
"Jan 7, 2025",
"8 January 2025",
"7/5/2026",
"BadFormat",
"Feb 30, 2024",
"April 31, 2024",
}
for _, t := range tests {
result, err := normalizeDate(t)
if err != nil {
fmt.Printf("Input: %-15s => Output: INVALID\n", t)
} else {
fmt.Printf("Input: %-15s => Output: %s\n", t, result)
}
}
}
/*
run:
Input: 05/01/2024 => Output: 2024-05-01
Input: 12-31-2023 => Output: 2023-12-31
Input: 2024.02.09 => Output: 2024-02-09
Input: January 5, 2024 => Output: 2024-01-05
Input: 2024/09/17 => Output: 2024-09-17
Input: 2024-09-17 => Output: 2024-09-17
Input: 25 Dec 2025 => Output: 2025-12-25
Input: 6 Jan 2025 => Output: 2025-01-06
Input: Jan 7, 2025 => Output: 2025-01-07
Input: 8 January 2025 => Output: 2025-01-08
Input: 7/5/2026 => Output: 2026-07-05
Input: BadFormat => Output: INVALID
Input: Feb 30, 2024 => Output: INVALID
Input: April 31, 2024 => Output: INVALID
*/