datefmt.go 2.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. package main
  2. import (
  3. "strings"
  4. "time"
  5. )
  6. // refTime is the canonical reference instant used to validate Go time layouts.
  7. var refTime = time.Date(2006, 1, 2, 15, 4, 5, 0, time.UTC)
  8. // parseDateLayout accepts either a Go time layout (e.g. "02.01.2006",
  9. // "2006-01-02") or a common placeholder format (e.g. "dd.MM.yyyy",
  10. // "yyyy-MM-dd HH:mm") and returns a Go time layout. An empty input yields
  11. // "" so the caller can apply its own default. Values that are neither a
  12. // valid Go layout nor a recognized placeholder string are returned
  13. // unchanged (the caller's Format call will then produce whatever Go does).
  14. func parseDateLayout(s string) string {
  15. s = strings.TrimSpace(s)
  16. if s == "" {
  17. return ""
  18. }
  19. // If it already behaves like a Go layout, keep it as-is.
  20. if isGoLayout(s) {
  21. return s
  22. }
  23. return placeholdersToGoLayout(s)
  24. }
  25. // isGoLayout reports whether s is a usable Go time layout by round-tripping
  26. // a reference instant through Format and Parse.
  27. func isGoLayout(s string) bool {
  28. formatted := refTime.Format(s)
  29. parsed, err := time.Parse(s, formatted)
  30. if err != nil {
  31. return false
  32. }
  33. return parsed.UTC().Equal(refTime)
  34. }
  35. // placeholdersToGoLayout converts common date/time placeholders into the
  36. // equivalent Go time layout. Tokens are matched case-sensitively, longest
  37. // first, so that "MM" (month) is not confused with "mm" (minute).
  38. func placeholdersToGoLayout(s string) string {
  39. // Ordered longest-first to avoid partial token matches.
  40. replacements := []struct{ from, to string }{
  41. {"yyyy", "2006"},
  42. {"MMMM", "January"},
  43. {"MMM", "Jan"},
  44. {"MM", "01"},
  45. {"dd", "02"},
  46. {"HH", "15"},
  47. {"mm", "04"},
  48. {"ss", "05"},
  49. {"yy", "06"},
  50. {"ZZZ", "MST"},
  51. {"ZZ", "Z07:00"},
  52. {"Z", "Z07:00"},
  53. {"zzz", "MST"},
  54. {"zz", "Z07:00"},
  55. {"z", "MST"},
  56. {"M", "1"},
  57. {"d", "2"},
  58. {"H", "3"},
  59. {"m", "4"},
  60. {"s", "5"},
  61. }
  62. // Walk the string, consuming the longest matching token at each position.
  63. var b strings.Builder
  64. i := 0
  65. for i < len(s) {
  66. matched := false
  67. for _, r := range replacements {
  68. if strings.HasPrefix(s[i:], r.from) {
  69. b.WriteString(r.to)
  70. i += len(r.from)
  71. matched = true
  72. break
  73. }
  74. }
  75. if !matched {
  76. b.WriteByte(s[i])
  77. i++
  78. }
  79. }
  80. return b.String()
  81. }