GitHub

@@ -0,0 +1,93 @@

1+

package timestamp

2+3+

import (

4+

"fmt"

5+

"strconv"

6+

"strings"

7+

"time"

8+9+

"github.com/pkg/errors"

10+

)

11+12+

type Formatter struct {

13+

format func(time.Time) string

14+

location *time.Location

15+

}

16+17+

func New(formatString string, locationString string) (*Formatter, error) {

18+

location, err := time.LoadLocation(locationString) // no shadow

19+

if err != nil {

20+

return nil, errors.Wrapf(err, "load location from string %q", locationString)

21+

}

22+

makeFormatFunc := func(formatString string) (func(time.Time) string, error) {

23+

// NB: we use zfs.EntityNamecheck in higher-level code to filter out all invalid characters.

24+

// This check here is specifically so that we know for sure that the `+`=>`_` replacement

25+

// that we do in the returned func replaces exactly the timezone offset `+` and not some other `+`.

26+

if strings.Contains(formatString, "+") {

27+

return nil, fmt.Errorf("character '+' is not allowed in ZFS snapshot names and has special handling")

28+

}

29+

return func(t time.Time) string {

30+

res := t.Format(formatString)

31+

// if the formatString contains a time zone specifier

32+

// and the location would result in a positive offset to UTC

33+

// then the result of t.Format would contain a '+' sign.

34+

if isLocationPositiveOffsetToUTC(location) {

35+

// the only source of `+` can be the positive time zone offset because we disallowed `+` as a character in the format string

36+

res = strings.Replace(res, "+", "_", 1)

37+

}

38+

if strings.Contains(res, "+") {

39+

panic(fmt.Sprintf("format produced a string containing illegal character '+' that wasn't the expected case of positive time zone offset: format=%q location=%q unix=%q result=%q", formatString, location, t.Unix(), res))

40+

}

41+

return res

42+

}, nil

43+

}

44+

var formatFunc func(time.Time) string

45+

mustUseUtcError := func() error {

46+

return fmt.Errorf("format string requires UTC location")

47+

}

48+

switch strings.ToLower(formatString) {

49+

case "dense":

50+

if location != time.UTC {

51+

err = mustUseUtcError()

52+

} else {

53+

formatFunc, err = makeFormatFunc("20060102_150405_000")

54+

}

55+

case "human":

56+

if location != time.UTC {

57+

err = mustUseUtcError()

58+

} else {

59+

formatFunc, err = makeFormatFunc("2006-01-02_15:04:05")

60+

}

61+

case "iso-8601":

62+

formatFunc, err = makeFormatFunc("2006-01-02T15:04:05.000Z0700")

63+

case "unix-seconds":

64+

if location != time.UTC {

65+

// Technically not required because unix time is by definition in UTC

66+

// but let's make that clear to confused users...

67+

err = mustUseUtcError()

68+

} else {

69+

formatFunc = func(t time.Time) string {

70+

return strconv.FormatInt(t.Unix(), 10)

71+

}

72+

}

73+

default:

74+

formatFunc, err = makeFormatFunc(formatString)

75+

}

76+

if err != nil {

77+

return nil, errors.Wrapf(err, "invalid format string %q or location %q", formatString, locationString)

78+

}

79+

return &Formatter{

80+

format: formatFunc,

81+

location: location,

82+

}, nil

83+

}

84+85+

func isLocationPositiveOffsetToUTC(location *time.Location) bool {

86+

_, offsetSeconds := time.Now().In(location).Zone()

87+

return offsetSeconds > 0

88+

}

89+90+

func (f *Formatter) Format(t time.Time) string {

91+

t = t.In(f.location)

92+

return f.format(t)

93+

}

Read the original on github.com ↗