diff --git a/soap/types.go b/soap/types.go index 46889f3..4faf73c 100644 --- a/soap/types.go +++ b/soap/types.go @@ -3,6 +3,7 @@ package soap import ( "errors" "fmt" + "regexp" "strconv" "time" "unicode/utf8" @@ -52,9 +53,43 @@ var dateFmts = []string{"2006-01-02", "20060102"} func UnmarshalDate(s string) (time.Time, error) { for _, f := range dateFmts { - if t, err := time.Parse(f, s); err == nil { + if t, err := time.ParseInLocation(f, s, time.Local); err == nil { return t, nil } } return time.Time{}, fmt.Errorf("soap date: value %q is not in a recognized date format", s) } + +// TimeOfDay is used in cases where SOAP "time" or "time.tz" is used. +type TimeOfDay struct { + // Duration of time since midnight. + FromMidnight time.Duration + + // TimeZone is present only if time.tz is used. It is otherwise ignored. + TimeZone *time.Location +} + +func MarshalTimeOfDay(v TimeOfDay) (string, error) { + d := int64(v.FromMidnight / time.Second) + hour := d / 3600 + d = d % 3600 + minute := d / 60 + second := d % 60 + + return fmt.Sprintf("%02d:%02d:%02d", hour, minute, second), nil +} + +var timeRegexp = regexp.MustCompile(`^(\d\d)(?::?(\d\d)(?::?(\d\d))?)?$`) + +func UnmarshalTimeOfDay(s string) (TimeOfDay, error) { + parts := timeRegexp.FindStringSubmatch(s) + if len(parts) < 2 { + return TimeOfDay{}, fmt.Errorf("soap time: value %q is not in ISO8601 time format", s) + } + parts = parts[1:] + var iParts [3]int64 + for i, pStr := range parts { + iParts[i], _ = strconv.ParseInt(pStr, 10, 64) + } + return TimeOfDay{time.Duration(iParts[0]*3600+iParts[1]*60+iParts[2]) * time.Second, nil}, nil +} diff --git a/soap/types_test.go b/soap/types_test.go index db30cba..78c6bd9 100644 --- a/soap/types_test.go +++ b/soap/types_test.go @@ -57,6 +57,20 @@ func (v DateTest) Equal(result interface{}) bool { return v.Time.Equal(result.(time.Time)) } +type TimeOfDayTest struct { + TimeOfDay +} + +func (v TimeOfDayTest) Marshal() (string, error) { + return MarshalTimeOfDay(v.TimeOfDay) +} +func (v TimeOfDayTest) Unmarshal(s string) (interface{}, error) { + return UnmarshalTimeOfDay(s) +} +func (v TimeOfDayTest) Equal(result interface{}) bool { + return v.TimeOfDay == result.(TimeOfDay) +} + func Test(t *testing.T) { tests := []testCase{ // Fixed14_4 @@ -78,11 +92,27 @@ func Test(t *testing.T) { {str: "", value: CharTest(0), wantMarshalErr: true, wantUnmarshalErr: true}, // Date - {str: "2013-10-08", value: DateTest{time.Date(2013, 10, 8, 0, 0, 0, 0, time.UTC)}}, - {str: "20131008", value: DateTest{time.Date(2013, 10, 8, 0, 0, 0, 0, time.UTC)}, noMarshal: true}, + {str: "2013-10-08", value: DateTest{time.Date(2013, 10, 8, 0, 0, 0, 0, time.Local)}}, + {str: "20131008", value: DateTest{time.Date(2013, 10, 8, 0, 0, 0, 0, time.Local)}, noMarshal: true}, {str: "2013-10-08T10:30:50", value: DateTest{}, wantUnmarshalErr: true, noMarshal: true}, {str: "", value: DateTest{}, wantMarshalErr: true, wantUnmarshalErr: true, noMarshal: true}, {str: "-1", value: DateTest{}, wantUnmarshalErr: true, noMarshal: true}, + + // Time + {str: "00:00:00", value: TimeOfDayTest{TimeOfDay{0, nil}}}, + {str: "000000", value: TimeOfDayTest{TimeOfDay{0, nil}}, noMarshal: true}, + {str: "01:02:03", value: TimeOfDayTest{TimeOfDay{(1*3600 + 2*60 + 3) * time.Second, nil}}}, + {str: "010203", value: TimeOfDayTest{TimeOfDay{(1*3600 + 2*60 + 3) * time.Second, nil}}, noMarshal: true}, + {str: "23:59:59", value: TimeOfDayTest{TimeOfDay{(23*3600 + 59*60 + 59) * time.Second, nil}}}, + {str: "235959", value: TimeOfDayTest{TimeOfDay{(23*3600 + 59*60 + 59) * time.Second, nil}}, noMarshal: true}, + {str: "01:02", value: TimeOfDayTest{TimeOfDay{(1*3600 + 2*60) * time.Second, nil}}, noMarshal: true}, + {str: "0102", value: TimeOfDayTest{TimeOfDay{(1*3600 + 2*60) * time.Second, nil}}, noMarshal: true}, + {str: "01", value: TimeOfDayTest{TimeOfDay{(1 * 3600) * time.Second, nil}}, noMarshal: true}, + {str: "01", value: TimeOfDayTest{TimeOfDay{(1 * 3600) * time.Second, nil}}, noMarshal: true}, + {str: "foo 01:02:03", value: TimeOfDayTest{}, wantUnmarshalErr: true, noMarshal: true}, + {str: "foo\n01:02:03", value: TimeOfDayTest{}, wantUnmarshalErr: true, noMarshal: true}, + {str: "01:02:03 foo", value: TimeOfDayTest{}, wantUnmarshalErr: true, noMarshal: true}, + {str: "01:02:03\nfoo", value: TimeOfDayTest{}, wantUnmarshalErr: true, noMarshal: true}, } for _, test := range tests {