From 97ddda62369c8c9c5bef587129816520d1078b37 Mon Sep 17 00:00:00 2001 From: David Muir Sharnoff Date: Thu, 22 Sep 2022 21:50:11 -0700 Subject: [PATCH] make RFC3339Nano match "time" implementation --- README.md | 3 ++ layout.go | 6 +-- layout_test.go | 14 ++++--- strftime.go | 103 ++++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 116 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 2712cd1..8e1bb9b 100644 --- a/README.md +++ b/README.md @@ -151,8 +151,10 @@ BenchmarkRFC3339Fasttime-4 6393230 189.1 ns/op 0 B/op | `%g` | Like %G, but without century, that is, with a 2-digit year (00-99). | | `%h` | Equivalent to %b. | | `%H` | The hour as a decimal number using a 24-hour clock (range 00 to 23). | +| `%i` | '.' + Microsecond as a decimal number, trailing zeros dropped. | | `%I` | The hour as a decimal number using a 12-hour clock (range 01 to 12). | | `%j` | The day of the year as a decimal number (range 001 to 366). | +| `%J` | '.' + Nanosecond as a decimal number, trailing zeros dropped. | | `%k` | The hour (24-hour clock) as a decimal number (range 0 to 23); single digits are preceded by a blank. (See also %H.) | | `%l` | The hour (12-hour clock) as a decimal number (range 1 to 12); single digits are preceded by a blank. (See also %I.) | | `%m` | The month as a decimal number (range 01 to 12). | @@ -161,6 +163,7 @@ BenchmarkRFC3339Fasttime-4 6393230 189.1 ns/op 0 B/op | `%N` | Nanosecond as a decimal number, zero-padded on the left. | | `%p` | Either "AM" or "PM" according to the given time value, or the corresponding strings for the current locale. Noon is treated as "PM" and midnight as "AM". | | `%P` | Like %p but in lowercase: "am" or "pm" or a corresponding string for the current locale. | +| `%q` | '.' + Millisecond as a decimal number, trailing zeros dropped. | | `%Q` | Millisecond as a decimal number, zero-padded on the left. | | `%r` | The time in a.m. or p.m. notation. In the POSIX locale this is equivalent to %I:%M:%S %p. | | `%R` | The time in 24-hour notation (%H:%M). For a version including the seconds, see %T below. | diff --git a/layout.go b/layout.go index ff12e67..b865e35 100644 --- a/layout.go +++ b/layout.go @@ -11,9 +11,9 @@ const ( RFC1123 = "%a, %d %b %Y %H:%M:%S %Z" // Mon, 02 Jan 2006 15:04:05 MST RFC1123Z = "%a, %d %b %Y %H:%M:%S %z" // Mon, 02 Jan 2006 15:04:05 -0700 RFC3339 = "%Y-%m-%dT%H:%M:%S%:z" // 2006-01-02T15:04:05Z07:00 - RFC3339Milli = "%Y-%m-%dT%H:%M:%S.%Q%:z" // 2006-01-02T15:04:05.000Z07:00 - RFC3339Micro = "%Y-%m-%dT%H:%M:%S.%f%:z" // 2006-01-02T15:04:05.000000Z07:00 - RFC3339Nano = "%Y-%m-%dT%H:%M:%S.%N%:z" // 2006-01-02T15:04:05.000000000Z07:00 + RFC3339Milli = "%Y-%m-%dT%H:%M:%S%q%:z" // 2006-01-02T15:04:05.999Z07:00 + RFC3339Micro = "%Y-%m-%dT%H:%M:%S%i%:z" // 2006-01-02T15:04:05.999999Z07:00 + RFC3339Nano = "%Y-%m-%dT%H:%M:%S%J%:z" // 2006-01-02T15:04:05.999999999Z07:00 Kitchen = "%-I:%M%p" // 3:04PM // Handy time stamps. Stamp = "%b %e %H:%M:%S" // Jan _2 15:04:05 diff --git a/layout_test.go b/layout_test.go index 2f942cb..ae075e7 100644 --- a/layout_test.go +++ b/layout_test.go @@ -20,10 +20,9 @@ func TestLayout(t *testing.T) { {RFC1123, time.RFC1123}, {RFC1123Z, time.RFC1123Z}, {RFC3339, time.RFC3339}, - {RFC3339Milli, "2006-01-02T15:04:05.000Z07:00"}, - {RFC3339Micro, "2006-01-02T15:04:05.000000Z07:00"}, - {RFC3339Nano, "2006-01-02T15:04:05.000000000Z07:00"}, - // {RFC3339Nano, time.RFC3339Nano}, + {RFC3339Milli, "2006-01-02T15:04:05.999Z07:00"}, + {RFC3339Micro, "2006-01-02T15:04:05.999999Z07:00"}, + {RFC3339Nano, time.RFC3339Nano}, {Kitchen, time.Kitchen}, {Stamp, time.Stamp}, {StampMilli, time.StampMilli}, @@ -34,8 +33,11 @@ func TestLayout(t *testing.T) { now := time.Now() for _, c := range cases { - if got, want := Strftime(c.Layout, now), now.Format(c.StdLayout); got != want { - t.Errorf("Strftime(%#v, %#v) want=%v got=%v", c.Layout, atime, want, got) + for _, rounding := range []time.Duration{time.Nanosecond, time.Nanosecond * 10, time.Nanosecond * 100, time.Microsecond, time.Microsecond * 10, time.Microsecond * 100, time.Millisecond, time.Millisecond * 10, time.Millisecond * 100, time.Second, time.Minute} { + now := now.Round(rounding) + if got, want := Strftime(c.Layout, now), now.Format(c.StdLayout); got != want { + t.Errorf("Strftime(%#v, %#v) want=%v got=%v", c.Layout, atime, want, got) + } } } } diff --git a/strftime.go b/strftime.go index 08f05ed..21a8cbf 100644 --- a/strftime.go +++ b/strftime.go @@ -57,8 +57,8 @@ func AppendStrftime(dst []byte, format string, t time.Time) []byte { case 'E': panic("not implemented") case 'f': - var tmp [6]byte a := t.Nanosecond() / 1000 + var tmp [6]byte b := a % 100 * 2 tmp[5] = tab[b+1] tmp[4] = tab[b] @@ -83,6 +83,8 @@ func AppendStrftime(dst []byte, format string, t time.Time) []byte { dst = append(dst, tab[a], tab[a+1]) case 'H': dst = append(dst, tab[hour*2], tab[hour*2+1]) + case 'i': + dst = significantOnly(dst, t.Nanosecond()/int(time.Microsecond), 6) case 'I': a := hour % 12 if a == 0 { @@ -94,6 +96,8 @@ func AppendStrftime(dst []byte, format string, t time.Time) []byte { a := t.YearDay() / 100 b := t.YearDay() % 100 * 2 dst = append(dst, byte(a)+'0', tab[b], tab[b+1]) + case 'J': + dst = significantOnly(dst, t.Nanosecond(), 9) case 'k': if hour >= 10 { dst = append(dst, tab[hour*2], tab[hour*2+1]) @@ -150,6 +154,8 @@ func AppendStrftime(dst []byte, format string, t time.Time) []byte { } else { dst = append(dst, "pm"...) } + case 'q': + dst = significantOnly(dst, t.Nanosecond()/int(time.Millisecond), 3) case 'Q': a := t.Nanosecond() / 1000000 b := a % 100 * 2 @@ -417,6 +423,101 @@ func AppendStrftime(dst []byte, format string, t time.Time) []byte { return dst } +func significantOnly(dst []byte, a, digits int) []byte { + if a == 0 { + return dst + } + for digits > 3 && a%1000 == 0 { + a /= 1000 + digits -= 3 + } + // at most two more digits to remove + if a%10 == 0 { + a /= 10 + digits-- + if a%10 == 0 { + a /= 10 + digits-- + } + } + switch digits { + case 9: + var tmp [10]byte + b := a % 100 * 2 + tmp[8], tmp[9] = tab[b], tab[b+1] + a /= 100 + b = a % 100 * 2 + tmp[6], tmp[7] = tab[b], tab[b+1] + a /= 100 + b = a % 100 * 2 + tmp[4], tmp[5] = tab[b], tab[b+1] + a /= 100 + b = a % 100 * 2 + tmp[0], tmp[1], tmp[2], tmp[3] = '.', byte(a/100)+'0', tab[b], tab[b+1] + return append(dst, tmp[:]...) + case 8: + var tmp [9]byte + b := a % 100 * 2 + tmp[7], tmp[8] = tab[b], tab[b+1] + a /= 100 + b = a % 100 * 2 + tmp[5], tmp[6] = tab[b], tab[b+1] + a /= 100 + b = a % 100 * 2 + tmp[3], tmp[4] = tab[b], tab[b+1] + a /= 100 + b = a % 100 * 2 + tmp[0], tmp[1], tmp[2] = '.', tab[b], tab[b+1] + return append(dst, tmp[:]...) + case 7: + var tmp [8]byte + b := a % 100 * 2 + tmp[6], tmp[7] = tab[b], tab[b+1] + a /= 100 + b = a % 100 * 2 + tmp[4], tmp[5] = tab[b], tab[b+1] + a /= 100 + b = a % 100 * 2 + tmp[0], tmp[1], tmp[2], tmp[3] = '.', byte(a/100)+'0', tab[b], tab[b+1] + return append(dst, tmp[:]...) + case 6: + var tmp [7]byte + b := a % 100 * 2 + tmp[5], tmp[6] = tab[b], tab[b+1] + a /= 100 + b = a % 100 * 2 + tmp[3], tmp[4] = tab[b], tab[b+1] + a /= 100 + b = a % 100 * 2 + tmp[0], tmp[1], tmp[2] = '.', tab[b], tab[b+1] + return append(dst, tmp[:]...) + case 5: + var tmp [6]byte + b := a % 100 * 2 + tmp[4], tmp[5] = tab[b], tab[b+1] + a /= 100 + b = a % 100 * 2 + tmp[0], tmp[1], tmp[2], tmp[3] = '.', byte(a/100)+'0', tab[b], tab[b+1] + return append(dst, tmp[:]...) + case 4: + var tmp [5]byte + b := a % 100 * 2 + tmp[3], tmp[4] = tab[b], tab[b+1] + a /= 100 + b = a % 100 * 2 + tmp[0], tmp[1], tmp[2] = '.', tab[b], tab[b+1] + return append(dst, tmp[:]...) + case 3: + b := a % 100 * 2 + return append(dst, '.', byte(a/100)+'0', tab[b], tab[b+1]) + case 2: + b := a * 2 + return append(dst, '.', tab[b], tab[b+1]) + default: + return append(dst, '.', byte(a)+'0') + } +} + func appendUpper(dst []byte, s string) []byte { for _, c := range []byte(s) { if 'a' <= c && c <= 'z' {