Skip to content

Commit

Permalink
Date ParseISO & AutoParse now accept a date-time input (time is ignored)
Browse files Browse the repository at this point in the history
  • Loading branch information
rickb777 committed Jul 24, 2024
1 parent 082ed93 commit b4113cc
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 28 deletions.
27 changes: 22 additions & 5 deletions parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package date
import (
"errors"
"fmt"
"regexp"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -142,6 +143,9 @@ func MustParseISO(value string) Date {
// minimum. A leading plus '+' sign is allowed and ignored. Basic format (without '-'
// separators) is allowed.
//
// If a time field is present, it is ignored. For example, "2018-02-03T00:00:00Z" is parsed as
// 3rd February 2018.
//
// For ordinal dates, the extended format (including '-') is supported, but the basic format
// (without '-') is not supported because it could not be distinguished from the YYYYMMDD format.
//
Expand All @@ -168,6 +172,14 @@ func parseISO(input, value string) (Date, error) {
}
}

tee := strings.IndexByte(abs, 'T')
if tee == 8 || tee == 10 {
if !timeRegex1.MatchString(abs[tee:]) && !timeRegex2.MatchString(abs[tee:]) {
return 0, fmt.Errorf("date.ParseISO: date-time %q: not a time", value)
}
abs = abs[:tee]
}

dash1 := strings.IndexByte(abs, '-')
dash2 := strings.LastIndexByte(abs, '-')

Expand All @@ -177,7 +189,7 @@ func parseISO(input, value string) (Date, error) {
fm := ln - 4
fd := ln - 2
if fm < 0 || fd < 0 {
return 0, fmt.Errorf("Date.ParseISO: cannot parse %q: too short", input)
return 0, fmt.Errorf("date.ParseISO: cannot parse %q: too short", input)
}

return parseYYYYMMDD(input, abs[:fm], abs[fm:fd], abs[fd:], sign)
Expand All @@ -191,7 +203,7 @@ func parseISO(input, value string) (Date, error) {
fd1 := dash2 + 1

if abs[fm2] != '-' {
return 0, fmt.Errorf("Date.ParseISO: cannot parse %q: incorrect syntax for date yyyy-mm-dd", input)
return 0, fmt.Errorf("date.ParseISO: cannot parse %q: incorrect syntax for date yyyy-mm-dd", input)
}

return parseYYYYMMDD(input, abs[:fy1], abs[fm1:fm2], abs[fd1:], sign)
Expand All @@ -202,7 +214,7 @@ func parseISO(input, value string) (Date, error) {
fo1 := dash1 + 1

if len(abs) != fo1+3 {
return 0, fmt.Errorf("Date.ParseISO: cannot parse %q: incorrect length for ordinal date yyyy-ooo", input)
return 0, fmt.Errorf("date.ParseISO: cannot parse %q: incorrect length for ordinal date yyyy-ooo", input)
}

return parseYYYYOOO(input, abs[:fy1], abs[fo1:], sign)
Expand All @@ -215,7 +227,7 @@ func parseYYYYMMDD(input, yyyy, mm, dd string, sign int) (Date, error) {

err := errors.Join(e1, e2, e3)
if err != nil {
return 0, fmt.Errorf("Date.ParseISO: cannot parse %q: %w", input, err)
return 0, fmt.Errorf("date.ParseISO: cannot parse %q: %w", input, err)
}

t := time.Date(sign*year, time.Month(month), day, 0, 0, 0, 0, time.UTC)
Expand All @@ -229,14 +241,19 @@ func parseYYYYOOO(input, yyyy, ooo string, sign int) (Date, error) {

err := errors.Join(e1, e2)
if err != nil {
return 0, fmt.Errorf("Date.ParseISO: cannot parse ordinal date %q: %w", input, err)
return 0, fmt.Errorf("date.ParseISO: cannot parse ordinal date %q: %w", input, err)
}

t := time.Date(sign*year, time.January, ordinal, 0, 0, 0, 0, time.UTC)

return encode(t), nil
}

var (
timeRegex1 = regexp.MustCompile("^T[0-9][0-9].[0-9][0-9].[0-9][0-9]")
timeRegex2 = regexp.MustCompile("^T[0-9]{2,6}")
)

func parseField(field, name string, minLength, requiredLength int) (int, error) {
if (minLength > 0 && len(field) < minLength) || (requiredLength > 0 && len(field) != requiredLength) {
return 0, fmt.Errorf("%s has wrong length", name)
Expand Down
52 changes: 29 additions & 23 deletions parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ func TestAutoParse_errors(t *testing.T) {
"+10-11-12",
"+100-02-03",
"-123-05-06",
"20210506T0Z",
"2021-05-06T0:0:0Z",
"--",
"",
" ",
Expand Down Expand Up @@ -161,6 +163,7 @@ func TestParseISO(t *testing.T) {
{value: "+1970-01-01", year: 1970, month: time.January, day: 1},
{value: "+01970-01-02", year: 1970, month: time.January, day: 2},
{value: "2000-02-28", year: 2000, month: time.February, day: 28},
{value: "2018-02-03T00:00:00Z", year: 2018, month: time.February, day: 3},
{value: "+2000-02-29", year: 2000, month: time.February, day: 29},
{value: "+02000-03-01", year: 2000, month: time.March, day: 1},
{value: "+002004-02-28", year: 2004, month: time.February, day: 28},
Expand Down Expand Up @@ -190,6 +193,7 @@ func TestParseISO(t *testing.T) {
{value: "12340506", year: 1234, month: time.May, day: 6},
{value: "+12340506", year: 1234, month: time.May, day: 6},
{value: "-00191012", year: -19, month: time.October, day: 12},
{value: "20210506T010203Z", year: 2021, month: time.May, day: 6},
}
for i, c := range cases {
t.Run(fmt.Sprintf("%d %s", i, c.value), func(t *testing.T) {
Expand All @@ -207,29 +211,31 @@ func TestParseISO_errors(t *testing.T) {
value string
want string
}{
{value: ``, want: `Date.ParseISO: cannot parse "": ` + "too short"},
{value: `-`, want: `Date.ParseISO: cannot parse "-": ` + "too short"},
{value: `z`, want: `Date.ParseISO: cannot parse "z": ` + "too short"},
{value: `z--`, want: `Date.ParseISO: cannot parse "z--": ` + "year has wrong length\nmonth has wrong length\nday has wrong length"},
{value: `not-a-date`, want: `Date.ParseISO: cannot parse "not-a-date": ` + "year has wrong length\nmonth has wrong length\nday has wrong length"},
{value: `foot-of-og`, want: `Date.ParseISO: cannot parse "foot-of-og": ` + "invalid year\ninvalid month\ninvalid day"},
{value: `215-08-15`, want: `Date.ParseISO: cannot parse "215-08-15": year has wrong length`},
{value: "1234-05", want: `Date.ParseISO: cannot parse "1234-05": incorrect length for ordinal date yyyy-ooo`},
{value: "1234-5-6", want: `Date.ParseISO: cannot parse "1234-5-6": ` + "month has wrong length\nday has wrong length"},
{value: "1234-05-6", want: `Date.ParseISO: cannot parse "1234-05-6": day has wrong length`},
{value: "1234-5-06", want: `Date.ParseISO: cannot parse "1234-5-06": month has wrong length`},
{value: "1234/05/06", want: `Date.ParseISO: cannot parse "1234/05/06": ` + "invalid year\ninvalid month"},
{value: "1234-0A-06", want: `Date.ParseISO: cannot parse "1234-0A-06": invalid month`},
{value: "1234-05-0B", want: `Date.ParseISO: cannot parse "1234-05-0B": invalid day`},
{value: "1234-05-06trailing", want: `Date.ParseISO: cannot parse "1234-05-06trailing": day has wrong length`},
{value: "padding1234-05-06", want: `Date.ParseISO: cannot parse "padding1234-05-06": invalid year`},
{value: "1-02-03", want: `Date.ParseISO: cannot parse "1-02-03": year has wrong length`},
{value: "10-11-12", want: `Date.ParseISO: cannot parse "10-11-12": year has wrong length`},
{value: "100-02-03", want: `Date.ParseISO: cannot parse "100-02-03": year has wrong length`},
{value: "+1-02-03", want: `Date.ParseISO: cannot parse "+1-02-03": year has wrong length`},
{value: "+10-11-12", want: `Date.ParseISO: cannot parse "+10-11-12": year has wrong length`},
{value: "+100-02-03", want: `Date.ParseISO: cannot parse "+100-02-03": year has wrong length`},
{value: "-123-05-06", want: `Date.ParseISO: cannot parse "-123-05-06": year has wrong length`},
{value: ``, want: `date.ParseISO: cannot parse "": ` + "too short"},
{value: `-`, want: `date.ParseISO: cannot parse "-": ` + "too short"},
{value: `z`, want: `date.ParseISO: cannot parse "z": ` + "too short"},
{value: `z--`, want: `date.ParseISO: cannot parse "z--": ` + "year has wrong length\nmonth has wrong length\nday has wrong length"},
{value: `not-a-date`, want: `date.ParseISO: cannot parse "not-a-date": ` + "year has wrong length\nmonth has wrong length\nday has wrong length"},
{value: `foot-of-og`, want: `date.ParseISO: cannot parse "foot-of-og": ` + "invalid year\ninvalid month\ninvalid day"},
{value: `215-08-15`, want: `date.ParseISO: cannot parse "215-08-15": year has wrong length`},
{value: "1234-05", want: `date.ParseISO: cannot parse "1234-05": incorrect length for ordinal date yyyy-ooo`},
{value: "1234-5-6", want: `date.ParseISO: cannot parse "1234-5-6": ` + "month has wrong length\nday has wrong length"},
{value: "1234-05-6", want: `date.ParseISO: cannot parse "1234-05-6": day has wrong length`},
{value: "1234-5-06", want: `date.ParseISO: cannot parse "1234-5-06": month has wrong length`},
{value: "1234/05/06", want: `date.ParseISO: cannot parse "1234/05/06": ` + "invalid year\ninvalid month"},
{value: "1234-0A-06", want: `date.ParseISO: cannot parse "1234-0A-06": invalid month`},
{value: "1234-05-0B", want: `date.ParseISO: cannot parse "1234-05-0B": invalid day`},
{value: "1234-05-06trailing", want: `date.ParseISO: cannot parse "1234-05-06trailing": day has wrong length`},
{value: "padding1234-05-06", want: `date.ParseISO: cannot parse "padding1234-05-06": invalid year`},
{value: "1-02-03", want: `date.ParseISO: cannot parse "1-02-03": year has wrong length`},
{value: "10-11-12", want: `date.ParseISO: cannot parse "10-11-12": year has wrong length`},
{value: "100-02-03", want: `date.ParseISO: cannot parse "100-02-03": year has wrong length`},
{value: "+1-02-03", want: `date.ParseISO: cannot parse "+1-02-03": year has wrong length`},
{value: "+10-11-12", want: `date.ParseISO: cannot parse "+10-11-12": year has wrong length`},
{value: "+100-02-03", want: `date.ParseISO: cannot parse "+100-02-03": year has wrong length`},
{value: "-123-05-06", want: `date.ParseISO: cannot parse "-123-05-06": year has wrong length`},
{value: "2018-02-03T0:0:0Z", want: `date.ParseISO: date-time "2018-02-03T0:0:0Z": not a time`},
{value: "2018-02-03T0Z", want: `date.ParseISO: date-time "2018-02-03T0Z": not a time`},
}
for i, c := range cases {
t.Run(fmt.Sprintf("%d %s", i, c.value), func(t *testing.T) {
Expand Down

0 comments on commit b4113cc

Please sign in to comment.