From 807db97af83ff61b022cbc8af80b9dc9cdb8dd43 Mon Sep 17 00:00:00 2001 From: Cameron Moore Date: Tue, 20 Oct 2020 20:07:11 -0500 Subject: [PATCH] tpl: Refactor time.AsTime location implementation --- docs/content/en/functions/time.md | 12 ++-- tpl/time/time.go | 94 +++++++++++++++---------------- 2 files changed, 51 insertions(+), 55 deletions(-) diff --git a/docs/content/en/functions/time.md b/docs/content/en/functions/time.md index 57d5f65f8..c4f74215b 100644 --- a/docs/content/en/functions/time.md +++ b/docs/content/en/functions/time.md @@ -13,13 +13,13 @@ menu: keywords: [dates,time,location] signature: ["time INPUT [LOCATION]"] workson: [] -hugoversion: +hugoversion: "v0.77.0" relatedfuncs: [] deprecated: false aliases: [] --- -`time` converts a timestamp string with an optional timezone into a [`time.Time`](https://godoc.org/time#Time) structure so you can access its fields: +`time` converts a timestamp string with an optional default location into a [`time.Time`](https://godoc.org/time#Time) structure so you can access its fields: ``` {{ time "2016-05-28" }} → "2016-05-28T00:00:00Z" @@ -27,9 +27,11 @@ aliases: [] {{ mul 1000 (time "2016-05-28T10:30:00.00+10:00").Unix }} → 1464395400000, or Unix time in milliseconds ``` -## Using Timezone +## Using Locations -The optional 2nd parameter [LOCATION] argument is a string that references a timezone that is associated with the specified time value. If the time value has an explicit timezone or offset specified, it will take precedence over an explicit [LOCATION]. +The optional `LOCATION` parameter is a string that sets a default location that is associated with the specified time value. If the time value has an explicit timezone or offset specified, it will take precedence over the `LOCATION` parameter. + +The list of valid locations may be system dependent, but should include `UTC`, `Local`, or any location in the [IANA Time Zone database](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones). ``` {{ time "2020-10-20" }} → 2020-10-20 00:00:00 +0000 UTC @@ -37,8 +39,6 @@ The optional 2nd parameter [LOCATION] argument is a string that references a tim {{ time "2020-01-20" "America/Los_Angeles" }} → 2020-01-20 00:00:00 -0800 PST ``` -> **Note**: Timezone support via the [LOCATION] parameter is included with Hugo `0.77`. - ## Example: Using `time` to get Month Index The following example takes a UNIX timestamp---set as `utimestamp: "1489276800"` in a content's front matter---converts the timestamp (string) to an integer using the [`int` function][int], and then uses [`printf`][] to convert the `Month` property of `time` into an index. diff --git a/tpl/time/time.go b/tpl/time/time.go index c3a01003a..8ed4606d6 100644 --- a/tpl/time/time.go +++ b/tpl/time/time.go @@ -21,6 +21,33 @@ import ( "github.com/spf13/cast" ) +var timeFormats = []string{ + _time.RFC3339, + "2006-01-02T15:04:05", // iso8601 without timezone + _time.RFC1123Z, + _time.RFC1123, + _time.RFC822Z, + _time.RFC822, + _time.RFC850, + _time.ANSIC, + _time.UnixDate, + _time.RubyDate, + "2006-01-02 15:04:05.999999999 -0700 MST", // Time.String() + "2006-01-02", + "02 Jan 2006", + "2006-01-02T15:04:05-0700", // RFC3339 without timezone hh:mm colon + "2006-01-02 15:04:05 -07:00", + "2006-01-02 15:04:05 -0700", + "2006-01-02 15:04:05Z07:00", // RFC3339 without T + "2006-01-02 15:04:05Z0700", // RFC3339 without T or timezone hh:mm colon + "2006-01-02 15:04:05", + _time.Kitchen, + _time.Stamp, + _time.StampMilli, + _time.StampMicro, + _time.StampNano, +} + // New returns a new instance of the time-namespaced template functions. func New() *Namespace { return &Namespace{} @@ -32,30 +59,26 @@ type Namespace struct{} // AsTime converts the textual representation of the datetime string into // a time.Time interface. func (ns *Namespace) AsTime(v interface{}, args ...interface{}) (interface{}, error) { - t, err := cast.ToTimeE(v) + if len(args) == 0 { + t, err := cast.ToTimeE(v) + if err != nil { + return nil, err + } + + return t, nil + } + + timeStr, err := cast.ToStringE(v) if err != nil { return nil, err } - if len(args) == 0 { - return t, nil + locStr, err := cast.ToStringE(args[0]) + if err != nil { + return nil, err } - // Otherwise, if a location is specified, attempt to parse the time using the location specified. - // Note: In this case, we require the input variable to be a string for proper parsing. - // Note: We can't convert an existing parsed time by using the `Time.In()` as this CONVERTS/MODIFIES - // the resulting time. - - switch givenType := v.(type) { - case string: - // Good, we only support strings - break - - default: - return nil, fmt.Errorf("Creating a time instance with location requires a value of type String. Given type: %s", givenType) - } - - location, err := _time.LoadLocation(args[0].(string)) + loc, err := _time.LoadLocation(locStr) if err != nil { return nil, err } @@ -63,41 +86,14 @@ func (ns *Namespace) AsTime(v interface{}, args ...interface{}) (interface{}, er // Note: Cast currently doesn't support time with non-default locations. For now, just inlining this. // Reference: https://github.com/spf13/cast/pull/80 - fmts := []string{ - _time.RFC3339, - "2006-01-02T15:04:05", // iso8601 without timezone - _time.RFC1123Z, - _time.RFC1123, - _time.RFC822Z, - _time.RFC822, - _time.RFC850, - _time.ANSIC, - _time.UnixDate, - _time.RubyDate, - "2006-01-02 15:04:05.999999999 -0700 MST", // Time.String() - "2006-01-02", - "02 Jan 2006", - "2006-01-02T15:04:05-0700", // RFC3339 without timezone hh:mm colon - "2006-01-02 15:04:05 -07:00", - "2006-01-02 15:04:05 -0700", - "2006-01-02 15:04:05Z07:00", // RFC3339 without T - "2006-01-02 15:04:05Z0700", // RFC3339 without T or timezone hh:mm colon - "2006-01-02 15:04:05", - _time.Kitchen, - _time.Stamp, - _time.StampMilli, - _time.StampMicro, - _time.StampNano, - } - - for _, dateType := range fmts { - t, err := _time.ParseInLocation(dateType, v.(string), location) - if err == nil { + for _, dateType := range timeFormats { + t, err2 := _time.ParseInLocation(dateType, timeStr, loc) + if err2 == nil { return t, nil } } - return nil, fmt.Errorf("Unable to ParseInLocation using date \"%s\" with timezone \"%s\"", v, location) + return nil, fmt.Errorf("Unable to ParseInLocation using date %q with timezone %q", v, loc) } // Format converts the textual representation of the datetime string into