Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 68 additions & 4 deletions gpx.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package gpx

import (
"encoding/xml"
"errors"
"fmt"
"io"
"strconv"
Expand All @@ -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{
Expand All @@ -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"`
Expand Down Expand Up @@ -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
}
}

Expand Down Expand Up @@ -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{
Expand Down Expand Up @@ -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
}
}
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
36 changes: 36 additions & 0 deletions gpx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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: `<metadata><time>2004-04-12T13:20:00Z</time></metadata>`,
expected: &gpx.MetadataType{
Time: time.Date(2004, 4, 12, 13, 20, 0, 0, time.UTC),
},
},
{
name: "rfc3339_without_timezone",
data: `<metadata><time>2004-04-12T13:20:00</time></metadata>`,
expected: &gpx.MetadataType{
Time: time.Date(2004, 4, 12, 13, 20, 0, 0, time.UTC),
},
},
{
name: "rfc3339_with_milliseconds_without_timezone",
data: `<metadata><time>2004-04-12T13:20:00.123</time></metadata>`,
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
Expand Down
3 changes: 3 additions & 0 deletions testdata/hhs_77yl9msjcr.gpx

Large diffs are not rendered by default.