If you have a hammer, everything looks like a nail. If you have a computer programmer, everything looks like a function.
First, a map of ordinals function,
package main
import (
"fmt"
"strconv"
)
// generateOrdinalsMap generates map[ordinal]cardinal to stdout
// for the non-negative interval [min, max]
// as var name = map[string]string{}.
func generateOrdinalsMap(name string, min, max int) {
fmt.Printf("
var " + name + " = map[string]string{ // map[ordinal]cardinal
")
for i := min; i >= 0 && i <= max; i++ {
var o string
switch i % 10 {
case 1:
o = "st"
case 2:
o = "nd"
case 3:
o = "rd"
default:
o = "th"
}
if 11 <= i && i <= 13 {
o = "th"
}
c := strconv.Itoa(i)
o = c + o
fmt.Printf(`"%s": "%s", `, o, c)
if (i)%5 == 0 || i == max {
fmt.Printf("
")
}
}
fmt.Printf("}
")
}
func main() {
// Generate ordinal map for days.
generateOrdinalsMap("dayOrdinals", 1, 31)
}
Playground: https://play.golang.org/p/W7Ad0pjjGu
Output:
var dayOrdinals = map[string]string{ // map[ordinal]cardinal
"1st": "1", "2nd": "2", "3rd": "3", "4th": "4", "5th": "5",
"6th": "6", "7th": "7", "8th": "8", "9th": "9", "10th": "10",
"11th": "11", "12th": "12", "13th": "13", "14th": "14", "15th": "15",
"16th": "16", "17th": "17", "18th": "18", "19th": "19", "20th": "20",
"21st": "21", "22nd": "22", "23rd": "23", "24th": "24", "25th": "25",
"26th": "26", "27th": "27", "28th": "28", "29th": "29", "30th": "30",
"31st": "31",
}
Second, a time.Parse
wrapper function, which converts an ordinal day in the value
to a cardinal day before parsing with a cardinal day layout
,
package main
import (
"fmt"
"strconv"
"strings"
"time"
)
var dayOrdinals = map[string]string{ // map[ordinal]cardinal
"1st": "1", "2nd": "2", "3rd": "3", "4th": "4", "5th": "5",
"6th": "6", "7th": "7", "8th": "8", "9th": "9", "10th": "10",
"11th": "11", "12th": "12", "13th": "13", "14th": "14", "15th": "15",
"16th": "16", "17th": "17", "18th": "18", "19th": "19", "20th": "20",
"21st": "21", "22nd": "22", "23rd": "23", "24th": "24", "25th": "25",
"26th": "26", "27th": "27", "28th": "28", "29th": "29", "30th": "30",
"31st": "31",
}
// parseOrdinalDate parses a string time value using an ordinary package time layout.
// Before parsing, an ordinal day, [1st, 31st], is converted to a cardinal day, [1, 31].
// For example, "1st August 2017" is converted to "1 August 2017" before parsing, and
// "August 1st, 2017" is converted to "August 1, 2017" before parsing.
func parseOrdinalDate(layout, value string) (time.Time, error) {
const ( // day number
cardMinLen = len("1")
cardMaxLen = len("31")
ordSfxLen = len("th")
ordMinLen = cardMinLen + ordSfxLen
)
for k := 0; k < len(value)-ordMinLen; {
// i number start
for ; k < len(value) && (value[k] > '9' || value[k] < '0'); k++ {
}
i := k
// j cardinal end
for ; k < len(value) && (value[k] <= '9' && value[k] >= '0'); k++ {
}
j := k
if j-i > cardMaxLen || j-i < cardMinLen {
continue
}
// k ordinal end
// ASCII Latin (uppercase | 0x20) = lowercase
for ; k < len(value) && (value[k]|0x20 >= 'a' && value[k]|0x20 <= 'z'); k++ {
}
if k-j != ordSfxLen {
continue
}
// day ordinal to cardinal
for ; i < j-1 && (value[i] == '0'); i++ {
}
o := strings.ToLower(value[i:k])
c, ok := dayOrdinals[o]
if ok {
value = value[:i] + c + value[k:]
break
}
}
return time.ParseInLocation(layout, value, defaultLocation)
}
// Times without a timezone are Hong Kong times.
var defaultLocation = func(name string) *time.Location {
loc, err := time.LoadLocation(name)
if err != nil {
loc = time.UTC
}
return loc
}(`Asia/Hong_Kong`)
func main() {
var dates = []struct {
layout, value string
}{
{"Monday 02 January 2006 15:04:05 PM", "Friday 24th November 2017 13:14:07 PM"},
{"2 January 2006", "1st August 2017"}, // ISO
{"January 2, 2006", "August 1st, 2017"}, // USA
}
fmt.Println()
for _, d := range dates {
fmt.Printf("Layout: %q
", d.layout)
fmt.Printf("Value: %q
", d.value)
t, err := parseOrdinalDate(d.layout, d.value)
fmt.Printf("Time: %v Error: %v
", t, err)
fmt.Printf("Unix: %v Error: %v
", t, err)
fmt.Println()
}
}
Playground: https://play.golang.org/p/5INR83e66T
Output:
Layout: "Monday 02 January 2006 15:04:05 PM"
Value: "Friday 24th November 2017 13:14:07 PM"
Time: 2017-11-24 13:14:07 +0800 HKT Error: <nil>
Unix: 2017-11-24 13:14:07 +0800 HKT Error: <nil>
Layout: "2 January 2006"
Value: "1st August 2017"
Time: 2017-08-01 00:00:00 +0800 HKT Error: <nil>
Unix: 2017-08-01 00:00:00 +0800 HKT Error: <nil>
Layout: "January 2, 2006"
Value: "August 1st, 2017"
Time: 2017-08-01 00:00:00 +0800 HKT Error: <nil>
Unix: 2017-08-01 00:00:00 +0800 HKT Error: <nil>
Third, complete and run your date_practice
tests,
{
"wombat",
"Sunday 23rd January 2033 04:38:25 AM",
"Monday 2 January 2006 03:04:05 PM",
1990039105,
parseOrdinalDate,
nil,
},
{
"kangaroo",
"Tuesday 7th November 2017 03:18:25 PM",
"Monday 2 January 2006 03:04:05 PM",
1510039105,
parseOrdinalDate,
nil,
},