diff --git a/pkg/schema/schema.go b/pkg/schema/schema.go index 9123a9357..6e1bc572a 100644 --- a/pkg/schema/schema.go +++ b/pkg/schema/schema.go @@ -828,14 +828,40 @@ func NewDeleteClaim(target blob.Ref) *Builder { // This is the "send a link to a friend" access model. const ShareHaveRef = "haveref" -// RFC3339FromTime returns an RFC3339-formatted time in UTC. +// UnknownLocation is a magic timezone value used when the actual location +// of a time is unknown. For instance, EXIF files commonly have a time without +// a corresponding location or timezone offset. +var UnknownLocation = time.FixedZone("Unknown", -60) // 1 minute west + +// IsZoneKnown reports whether t is in a known timezone. +// Camlistore uses the magic timezone offset of 1 minute west of UTC +// to mean that the timezone wasn't known. +func IsZoneKnown(t time.Time) bool { + if t.Location() == UnknownLocation { + return false + } + if _, off := t.Zone(); off == -60 { + return false + } + return true +} + +// RFC3339FromTime returns an RFC3339-formatted time. +// +// If the timezone is known, the time will be converted to UTC and +// returned with a "Z" suffix. For unknown zones, the timezone will be +// "-00:01" (1 minute west of UTC). +// // Fractional seconds are only included if the time has fractional // seconds. func RFC3339FromTime(t time.Time) string { - if t.UnixNano()%1e9 == 0 { - return t.UTC().Format(time.RFC3339) + if IsZoneKnown(t) { + t = t.UTC() } - return t.UTC().Format(time.RFC3339Nano) + if t.UnixNano()%1e9 == 0 { + return t.Format(time.RFC3339) + } + return t.Format(time.RFC3339Nano) } var bytesCamliVersion = []byte("camliVersion") diff --git a/pkg/schema/schema_test.go b/pkg/schema/schema_test.go index 9436db9e6..1c59f2ad8 100644 --- a/pkg/schema/schema_test.go +++ b/pkg/schema/schema_test.go @@ -158,11 +158,27 @@ func TestStringFromMixedArray(t *testing.T) { } } +func TestIsZoneKnown(t *testing.T) { + if !IsZoneKnown(time.Now()) { + t.Errorf("should know Now's zone") + } + if !IsZoneKnown(time.Now().UTC()) { + t.Errorf("UTC should be known") + } + if IsZoneKnown(time.Now().In(UnknownLocation)) { + t.Errorf("with explicit unknown location, should be false") + } + if IsZoneKnown(time.Now().In(time.FixedZone("xx", -60))) { + t.Errorf("with other fixed zone at -60, should be false") + } +} + func TestRFC3339(t *testing.T) { tests := []string{ "2012-05-13T15:02:47Z", "2012-05-13T15:02:47.1234Z", "2012-05-13T15:02:47.123456789Z", + "2012-05-13T15:02:47-00:01", } for _, in := range tests { tm, err := time.Parse(time.RFC3339, in) @@ -170,9 +186,19 @@ func TestRFC3339(t *testing.T) { t.Errorf("error parsing %q", in) continue } - if out := RFC3339FromTime(tm); in != out { + knownZone := IsZoneKnown(tm) + out := RFC3339FromTime(tm) + if in != out { t.Errorf("RFC3339FromTime(%q) = %q; want %q", in, out, in) } + + sub := "Z" + if !knownZone { + sub = "-00:01" + } + if !strings.Contains(out, sub) { + t.Errorf("expected substring %q in %q", sub, out) + } } }