summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDave Henderson <dhenderson@gmail.com>2017-10-14 15:17:23 -0400
committerGitHub <noreply@github.com>2017-10-14 15:17:23 -0400
commit2fce7908c3fb03384c4b8cb4e9bbc2b1a246c2d8 (patch)
treef68c1a5e28fcc3a416c03de41e6400657c1bda28
parentfa0aa0127cdadd6be21c02569a96376f233f00ec (diff)
parentcbd225bc835ad0523012406893a0cd3843956191 (diff)
Merge pull request #211 from hairyhenderson/add-time-funcs
Add time funcs
-rw-r--r--docs/content/functions/time.md164
-rw-r--r--funcs.go1
-rw-r--r--funcs/time.go167
-rw-r--r--funcs/time_test.go60
-rw-r--r--test/integration/time.bats34
-rw-r--r--time/time.go11
6 files changed, 437 insertions, 0 deletions
diff --git a/docs/content/functions/time.md b/docs/content/functions/time.md
new file mode 100644
index 00000000..fc6e0bb0
--- /dev/null
+++ b/docs/content/functions/time.md
@@ -0,0 +1,164 @@
+---
+title: time functions
+menu:
+ main:
+ parent: functions
+---
+
+This namespace wraps Go's [`time` package](https://golang.org/pkg/time/), and a
+few of the functions return a `time.Time` value. All of the
+[`time.Time` functions](https://golang.org/pkg/time/#Time) can then be used to
+convert, adjust, or format the time in your template.
+
+An important difference between this and many other time/date utilities is how
+parsing and formatting is accomplished. Instead of relying solely on pre-defined
+formats, or having a complex system of variables, formatting is accomplished by
+declaring an example of the layout you wish to display.
+
+This uses a _reference time_, which is:
+
+```
+Mon Jan 2 15:04:05 -0700 MST 2006
+```
+
+### Constants
+
+#### format layouts
+
+Some pre-defined layouts have been provided for convenience:
+
+| layout name | value |
+|-------------|-------|
+| `time.ANSIC` | `"Mon Jan _2 15:04:05 2006"` |
+| `time.UnixDate` | `"Mon Jan _2 15:04:05 MST 2006"` |
+| `time.RubyDate` | `"Mon Jan 02 15:04:05 -0700 2006"` |
+| `time.RFC822` | `"02 Jan 06 15:04 MST"` |
+| `time.RFC822Z` | `"02 Jan 06 15:04 -0700"` // RFC822 with numeric zone |
+| `time.RFC850` | `"Monday, 02-Jan-06 15:04:05 MST"` |
+| `time.RFC1123` | `"Mon, 02 Jan 2006 15:04:05 MST"` |
+| `time.RFC1123Z` | `"Mon, 02 Jan 2006 15:04:05 -0700"` // RFC1123 with numeric zone |
+| `time.RFC3339` | `"2006-01-02T15:04:05Z07:00"` |
+| `time.RFC3339Nano` | `"2006-01-02T15:04:05.999999999Z07:00"` |
+| `time.Kitchen` | `"3:04PM"` |
+| `time.Stamp` | `"Jan _2 15:04:05"` |
+| `time.StampMilli` | `"Jan _2 15:04:05.000" `|
+| `time.StampMicro` | `"Jan _2 15:04:05.000000"` |
+| `time.StampNano` | `"Jan _2 15:04:05.000000000"` |
+
+See below for examples of how these layouts can be used.
+
+#### durations
+
+Some operations (such as [`Time.Add`](https://golang.org/pkg/time/#Time.Add) and
+[`Time.Round`](https://golang.org/pkg/time/#Time.Round)) require a
+[`Duration`](https://golang.org/pkg/time/#Duration) value. These can be created
+conveniently with the following functions:
+
+- `time.Nanosecond`
+- `time.Microsecond`
+- `time.Millisecond`
+- `time.Second`
+- `time.Minute`
+- `time.Hour`
+
+For example:
+
+```console
+$ gomplate -i '{{ (time.Now).Format time.Kitchen }}
+{{ ((time.Now).Add (time.Hour 2)).Format time.Kitchen }}'
+9:05AM
+11:05AM
+```
+
+## `time.Now`
+
+Returns the current local time, as a `time.Time`. This wraps [`time.Now`](https://golang.org/pkg/time/#Now).
+
+Usually, further functions are called using the value returned by `Now`.
+
+### Usage
+```go
+time.Now
+```
+
+### Examples
+
+Usage with [`UTC`](https://golang.org/pkg/time/#Time.UTC) and [`Format`](https://golang.org/pkg/time/#Time.Format):
+```console
+$ gomplate -i '{{ (time.Now).UTC.Format "Day 2 of month 1 in year 2006 (timezone MST)" }}'
+Day 14 of month 10 in year 2017 (timezone UTC)
+```
+
+Usage with [`AddDate`](https://golang.org/pkg/time/#Time.AddDate):
+```console
+$ date
+Sat Oct 14 09:57:02 EDT 2017
+$ gomplate -i '{{ ((time.Now).AddDate 0 1 0).Format "Mon Jan 2 15:04:05 MST 2006" }}'
+Tue Nov 14 09:57:02 EST 2017
+```
+
+_(notice how the TZ adjusted for daylight savings!)_
+
+## `time.Parse`
+
+Parses a timestamp defined by the given layout. This wraps [`time.Parse`](https://golang.org/pkg/time/#Parse).
+
+A number of pre-defined layouts are provided as constants, defined
+[here](https://golang.org/pkg/time/#pkg-constants).
+
+Just like [`time.Now`](#time-now), this is usually used in conjunction with
+other functions.
+
+### Usage
+```go
+time.Parse layout timestamp
+```
+
+### Examples
+
+Usage with [`Format`](https://golang.org/pkg/time/#Time.Format):
+```console
+$ gomplate -i '{{ (time.Parse "2006-01-02" "1993-10-23").Format "Monday January 2, 2006" }}'
+Saturday October 23, 1993
+```
+
+## `time.Unix`
+
+Returns the local `Time` corresponding to the given Unix time, in seconds since
+January 1, 1970 UTC. Note that fractional seconds can be used to denote
+milliseconds, but must be specified as a string, not a floating point number.
+
+### Usage
+```go
+time.Unix time
+```
+
+### Example
+
+_with whole seconds:_
+```console
+$ gomplate -i '{{ (time.Unix 42).UTC.Format time.Stamp}}'
+Jan 1, 00:00:42
+```
+
+_with fractional seconds:_
+```console
+$ gomplate -i '{{ (time.Unix "123456.789").UTC.Format time.StampMilli}}'
+Jan 2 10:17:36.789
+```
+
+## `time.ZoneName`
+
+Return the local system's time zone's name.
+
+### Usage
+```go
+time.ZoneName
+```
+
+### Example
+
+```console
+$ gomplate -i '{{time.ZoneName}}'
+EDT
+```
diff --git a/funcs.go b/funcs.go
index f4a5d6fc..28bfc907 100644
--- a/funcs.go
+++ b/funcs.go
@@ -18,5 +18,6 @@ func initFuncs(d *data.Data) template.FuncMap {
funcs.AddStringFuncs(f)
funcs.AddEnvFuncs(f)
funcs.AddConvFuncs(f)
+ funcs.AddTimeFuncs(f)
return f
}
diff --git a/funcs/time.go b/funcs/time.go
new file mode 100644
index 00000000..4515f4e8
--- /dev/null
+++ b/funcs/time.go
@@ -0,0 +1,167 @@
+package funcs
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+ "sync"
+ gotime "time"
+
+ "github.com/hairyhenderson/gomplate/time"
+)
+
+var (
+ timeNS *TimeFuncs
+ timeNSInit sync.Once
+)
+
+// TimeNS -
+func TimeNS() *TimeFuncs {
+ timeNSInit.Do(func() {
+ timeNS = &TimeFuncs{
+ ANSIC: gotime.ANSIC,
+ UnixDate: gotime.UnixDate,
+ RubyDate: gotime.RubyDate,
+ RFC822: gotime.RFC822,
+ RFC822Z: gotime.RFC822Z,
+ RFC850: gotime.RFC850,
+ RFC1123: gotime.RFC1123,
+ RFC1123Z: gotime.RFC1123Z,
+ RFC3339: gotime.RFC3339,
+ RFC3339Nano: gotime.RFC3339Nano,
+ Kitchen: gotime.Kitchen,
+ Stamp: gotime.Stamp,
+ StampMilli: gotime.StampMilli,
+ StampMicro: gotime.StampMicro,
+ StampNano: gotime.StampNano,
+ }
+ })
+ return timeNS
+}
+
+// AddTimeFuncs -
+func AddTimeFuncs(f map[string]interface{}) {
+ f["time"] = TimeNS
+}
+
+// TimeFuncs -
+type TimeFuncs struct {
+ ANSIC string
+ UnixDate string
+ RubyDate string
+ RFC822 string
+ RFC822Z string
+ RFC850 string
+ RFC1123 string
+ RFC1123Z string
+ RFC3339 string
+ RFC3339Nano string
+ Kitchen string
+ Stamp string
+ StampMilli string
+ StampMicro string
+ StampNano string
+}
+
+// ZoneName - return the local system's time zone's name
+func (f *TimeFuncs) ZoneName() string {
+ return time.ZoneName()
+}
+
+// Parse -
+func (f *TimeFuncs) Parse(layout, value string) (gotime.Time, error) {
+ return gotime.Parse(layout, value)
+}
+
+// Now -
+func (f *TimeFuncs) Now() gotime.Time {
+ return gotime.Now()
+}
+
+// Unix - convert UNIX time (in seconds since the UNIX epoch) into a time.Time for further processing
+// Takes a string or number (int or float)
+func (f *TimeFuncs) Unix(in interface{}) (gotime.Time, error) {
+ sec, nsec, err := parseNum(in)
+ if err != nil {
+ return gotime.Time{}, err
+ }
+ return gotime.Unix(sec, nsec), nil
+}
+
+// Nanosecond -
+func (f *TimeFuncs) Nanosecond(n int64) gotime.Duration {
+ return gotime.Nanosecond * gotime.Duration(n)
+}
+
+// Microsecond -
+func (f *TimeFuncs) Microsecond(n int64) gotime.Duration {
+ return gotime.Microsecond * gotime.Duration(n)
+}
+
+// Millisecond -
+func (f *TimeFuncs) Millisecond(n int64) gotime.Duration {
+ return gotime.Millisecond * gotime.Duration(n)
+}
+
+// Second -
+func (f *TimeFuncs) Second(n int64) gotime.Duration {
+ return gotime.Second * gotime.Duration(n)
+}
+
+// Minute -
+func (f *TimeFuncs) Minute(n int64) gotime.Duration {
+ return gotime.Minute * gotime.Duration(n)
+}
+
+// Hour -
+func (f *TimeFuncs) Hour(n int64) gotime.Duration {
+ return gotime.Hour * gotime.Duration(n)
+}
+
+// convert a number input to a pair of int64s, representing the integer portion and the decimal remainder
+// this can handle a string as well as any integer or float type
+// precision is at the "nano" level (i.e. 1e+9)
+func parseNum(in interface{}) (integral int64, fractional int64, err error) {
+ if s, ok := in.(string); ok {
+ ss := strings.Split(s, ".")
+ if len(ss) > 2 {
+ return 0, 0, fmt.Errorf("can not parse '%s' as a number - too many decimal points", s)
+ }
+ if len(ss) == 1 {
+ integral, err := strconv.ParseInt(s, 0, 64)
+ return integral, 0, err
+ }
+ integral, err := strconv.ParseInt(ss[0], 0, 64)
+ if err != nil {
+ return integral, 0, err
+ }
+ fractional, err = strconv.ParseInt(padRight(ss[1], "0", 9), 0, 64)
+ return integral, fractional, err
+ }
+ if s, ok := in.(fmt.Stringer); ok {
+ return parseNum(s.String())
+ }
+ if i, ok := in.(int); ok {
+ return int64(i), 0, nil
+ }
+ if u, ok := in.(uint64); ok {
+ return int64(u), 0, nil
+ }
+ if f, ok := in.(float64); ok {
+ return 0, 0, fmt.Errorf("can not parse floating point number (%f) - use a string instead", f)
+ }
+ if in == nil {
+ return 0, 0, nil
+ }
+ return 0, 0, nil
+}
+
+// pads a number with zeroes
+func padRight(in, pad string, length int) string {
+ for {
+ in += pad
+ if len(in) > length {
+ return in[0:length]
+ }
+ }
+}
diff --git a/funcs/time_test.go b/funcs/time_test.go
new file mode 100644
index 00000000..99ab1e34
--- /dev/null
+++ b/funcs/time_test.go
@@ -0,0 +1,60 @@
+package funcs
+
+import (
+ "math"
+ "math/big"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestParseNum(t *testing.T) {
+ i, f, _ := parseNum("42")
+ assert.Equal(t, int64(42), i)
+ assert.Equal(t, int64(0), f)
+
+ i, f, _ = parseNum(42)
+ assert.Equal(t, int64(42), i)
+ assert.Equal(t, int64(0), f)
+
+ i, f, _ = parseNum(big.NewInt(42))
+ assert.Equal(t, int64(42), i)
+ assert.Equal(t, int64(0), f)
+
+ i, f, _ = parseNum(big.NewFloat(42.0))
+ assert.Equal(t, int64(42), i)
+ assert.Equal(t, int64(0), f)
+
+ i, f, _ = parseNum(uint64(math.MaxInt64))
+ assert.Equal(t, int64(uint64(math.MaxInt64)), i)
+ assert.Equal(t, int64(0), f)
+
+ i, f, _ = parseNum("9223372036854775807.999999999")
+ assert.Equal(t, int64(9223372036854775807), i)
+ assert.Equal(t, int64(999999999), f)
+
+ i, f, _ = parseNum("999999999999999.123456789123")
+ assert.Equal(t, int64(999999999999999), i)
+ assert.Equal(t, int64(123456789), f)
+
+ i, f, _ = parseNum("123456.789")
+ assert.Equal(t, int64(123456), i)
+ assert.Equal(t, int64(789000000), f)
+
+ _, _, err := parseNum("bogus.9223372036854775807")
+ assert.Error(t, err)
+
+ _, _, err = parseNum("bogus")
+ assert.Error(t, err)
+
+ _, _, err = parseNum("1.2.3")
+ assert.Error(t, err)
+
+ _, _, err = parseNum(1.1)
+ assert.Error(t, err)
+
+ i, f, err = parseNum(nil)
+ assert.Zero(t, i)
+ assert.Zero(t, f)
+ assert.NoError(t, err)
+}
diff --git a/test/integration/time.bats b/test/integration/time.bats
new file mode 100644
index 00000000..17fe40da
--- /dev/null
+++ b/test/integration/time.bats
@@ -0,0 +1,34 @@
+#!/usr/bin/env bats
+
+load helper
+
+@test "'time.ZoneName'" {
+ gomplate -i '{{ time.ZoneName }}'
+ [ "$status" -eq 0 ]
+ [[ "${output}" == `date +"%Z"` ]]
+}
+
+@test "'(time.Now).Format'" {
+ gomplate -i '{{ (time.Now).Format "2006-01-02 15 -0700" }}'
+ [ "$status" -eq 0 ]
+ [[ "${output}" == `date +"%Y-%m-%d %H %z"` ]]
+}
+
+@test "'(time.Parse).Format'" {
+ in=`date -u --date='@1234567890'`
+ gomplate -i "{{ (time.Parse \"Mon Jan 02 15:04:05 MST 2006\" \"${in}\").Format \"2006-01-02 15 -0700\" }}"
+ [ "$status" -eq 0 ]
+ [[ "${output}" == "2009-02-13 23 +0000" ]]
+}
+
+@test "'(time.Unix).UTC.Format' int" {
+ gomplate -i '{{ (time.Unix 1234567890).UTC.Format "2006-01-02 15 -0700" }}'
+ [ "$status" -eq 0 ]
+ [[ "${output}" == "2009-02-13 23 +0000" ]]
+}
+
+@test "'(time.Unix).UTC.Format' string" {
+ gomplate -i '{{ (time.Unix "1234567890").UTC.Format "2006-01-02 15 -0700" }}'
+ [ "$status" -eq 0 ]
+ [[ "${output}" == "2009-02-13 23 +0000" ]]
+}
diff --git a/time/time.go b/time/time.go
new file mode 100644
index 00000000..e1241d3f
--- /dev/null
+++ b/time/time.go
@@ -0,0 +1,11 @@
+package time
+
+import (
+ "time"
+)
+
+// ZoneName - a convenience function for determining the current timezone's name
+func ZoneName() string {
+ n, _ := time.Now().Zone()
+ return n
+}