diff --git a/gpx.go b/gpx.go index 5b22296..6157ad7 100644 --- a/gpx.go +++ b/gpx.go @@ -5,6 +5,7 @@ package gpx import ( "encoding/xml" + "errors" "fmt" "io" "strconv" @@ -20,7 +21,10 @@ const ( https = "https://" ) -var timeLayout = time.RFC3339Nano +var timeLayouts = []string{ + time.RFC3339Nano, + "2006-01-02T15:04:05.999999999", +} // StartElement is the XML start element for GPX files. var StartElement = xml.StartElement{ @@ -33,6 +37,8 @@ var copyrightYearLayouts = []string{ "2006-07:00", } +var errNoTimeLayout = errors.New("no time layout") + // A BoundsType is a boundsType. type BoundsType struct { MinLat float64 `xml:"minlat,attr"` @@ -207,7 +213,15 @@ func Read(r io.Reader, options ...ReadOption) (*GPX, error) { // WithTimeLayout applies a custom time layout for the decoding of the GPX source. func WithTimeLayout(layout string) ReadOption { return func() { - timeLayout = layout + timeLayouts = []string{layout} + } +} + +// WithTimeLayouts applies a custom time layouts for the decoding of the GPX +// source. +func WithTimeLayouts(layouts []string) ReadOption { + return func() { + timeLayouts = layouts } } @@ -280,6 +294,43 @@ func (g *GPX) WriteIndent(w io.Writer, prefix, indent string) error { return e.EncodeElement(g, StartElement) } +// UnmarshalXML implements xml.Unmarshaler.UnmarshalXML. +func (m *MetadataType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var e struct { + Name string `xml:"name"` + Desc string `xml:"desc"` + Author *PersonType `xml:"author"` + Copyright *CopyrightType `xml:"copyright"` + Link []*LinkType `xml:"link"` + Time string `xml:"time"` + Keywords string `xml:"keywords"` + Bounds *BoundsType `xml:"bounds"` + Extensions *ExtensionsType `xml:"extensions"` + } + if err := d.DecodeElement(&e, &start); err != nil { + return err + } + mt := MetadataType{ + Name: e.Name, + Desc: e.Desc, + Author: e.Author, + Copyright: e.Copyright, + Link: e.Link, + Keywords: e.Keywords, + Bounds: e.Bounds, + Extensions: e.Extensions, + } + if e.Time != "" { + t, err := parseTime(e.Time) + if err != nil { + return err + } + mt.Time = t + } + *m = mt + return nil +} + // NewRteType returns a new RteType with geometry g. func NewRteType(g *geom.LineString) *RteType { return &RteType{ @@ -386,7 +437,7 @@ func (w *WptType) MarshalXML(e *xml.Encoder, start xml.StartElement) error { return err } if !w.Time.IsZero() { - if err := maybeEmitStringElement(e, "time", w.Time.UTC().Format(timeLayout)); err != nil { + if err := maybeEmitStringElement(e, "time", w.Time.UTC().Format(timeLayouts[0])); err != nil { return err } } @@ -505,7 +556,7 @@ func (w *WptType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { Extensions: e.Extensions, } if e.Time != "" { - t, err := time.ParseInLocation(timeLayout, e.Time, time.UTC) + t, err := parseTime(e.Time) if err != nil { return err } @@ -601,3 +652,16 @@ func newWptTypes(g *geom.LineString) []*WptType { } return wpts } + +func parseTime(value string) (time.Time, error) { + firstErr := errNoTimeLayout + for i, timeLayout := range timeLayouts { + switch t, err := time.Parse(timeLayout, value); { + case err == nil: + return t, nil + case i == 0: + firstErr = err + } + } + return time.Time{}, firstErr +} diff --git a/gpx_test.go b/gpx_test.go index 9053ed5..c4bb946 100644 --- a/gpx_test.go +++ b/gpx_test.go @@ -17,6 +17,42 @@ import ( gpx "github.com/twpayne/go-gpx" ) +func TestMetadata(t *testing.T) { + for _, tc := range []struct { + name string + data string + expected *gpx.MetadataType + }{ + { + name: "rfc3339", + data: ``, + expected: &gpx.MetadataType{ + Time: time.Date(2004, 4, 12, 13, 20, 0, 0, time.UTC), + }, + }, + { + name: "rfc3339_without_timezone", + data: ``, + expected: &gpx.MetadataType{ + Time: time.Date(2004, 4, 12, 13, 20, 0, 0, time.UTC), + }, + }, + { + name: "rfc3339_with_milliseconds_without_timezone", + data: ``, + expected: &gpx.MetadataType{ + Time: time.Date(2004, 4, 12, 13, 20, 0, 123000000, time.UTC), + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + var gotMetadata gpx.MetadataType + assert.NoError(t, xml.Unmarshal([]byte(tc.data), &gotMetadata)) + assert.Equal(t, tc.expected, &gotMetadata) + }) + } +} + func TestWpt(t *testing.T) { for i, tc := range []struct { data string diff --git a/testdata/hhs_77yl9msjcr.gpx b/testdata/hhs_77yl9msjcr.gpx new file mode 100644 index 0000000..8f82309 --- /dev/null +++ b/testdata/hhs_77yl9msjcr.gpx @@ -0,0 +1,3 @@ + + +Harzer-Hexen-Stieghttps://www.openstreetmap.org/copyrightWaymarked TrailsHarzer-Hexen-Stieg \ No newline at end of file