From 37c349f302cdf5e3e570be087aa0e9a39866bf3f Mon Sep 17 00:00:00 2001 From: Santiago De la Cruz Date: Tue, 6 Dec 2022 20:30:19 -0400 Subject: [PATCH] convert duration to string supporting days and weeks --- README.md | 2 + str2duration.go | 147 +++++++++++++++++++++++++++++++++++++++++++ str2duration_test.go | 62 +++++++++++++++++- 3 files changed, 210 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c7b2a21..0326373 100644 --- a/README.md +++ b/README.md @@ -84,3 +84,5 @@ func main() { } } ``` + +Also, you can convert to string the duration using `String(t time.Duration)` function. This support weeks and days and not return the ugly decimals from golang standard `t.String()` function. Units with 0 values aren't returned. For example: `1d1ms` means 1 day 1 millisecond. \ No newline at end of file diff --git a/str2duration.go b/str2duration.go index 948ce0f..51631db 100644 --- a/str2duration.go +++ b/str2duration.go @@ -182,3 +182,150 @@ func leadingFraction(s string) (x int64, scale float64, rem string) { } return x, scale, s[i:] } + +// String returns a string representing the duration in the form "1w4d2h3m5s". +// Units with 0 values aren't returned, for example: 1d1ms is 1 day 1 milliseconds +func String(d time.Duration) string { + if d == 0 { + return "0s" + } + + // Largest time is 15250w1d23h47m16s854ms775us807ns + var buf [32]byte + w := len(buf) + var sign string + + u := uint64(d) + neg := d < 0 + if neg { + u = -u + sign = "-" + } + + // u is nanoseconds (ns) + if u > 0 { + w-- + + if u%1000 > 0 { + buf[w] = 's' + w-- + buf[w] = 'n' + w = fmtInt(buf[:w], u%1000) + } else { + w++ + } + + u /= 1000 + + // u is now integer microseconds (us) + if u > 0 { + w-- + if u%1000 > 0 { + buf[w] = 's' + w-- + buf[w] = 'u' + w = fmtInt(buf[:w], u%1000) + } else { + w++ + } + u /= 1000 + + // u is now integer milliseconds (ms) + if u > 0 { + w-- + if u%1000 > 0 { + buf[w] = 's' + w-- + buf[w] = 'm' + w = fmtInt(buf[:w], u%1000) + } else { + w++ + } + u /= 1000 + + // u is now integer seconds (s) + if u > 0 { + w-- + if u%60 > 0 { + buf[w] = 's' + w = fmtInt(buf[:w], u%60) + } else { + w++ + } + u /= 60 + + // u is now integer minutes (m) + if u > 0 { + w-- + + if u%60 > 0 { + buf[w] = 'm' + w = fmtInt(buf[:w], u%60) + } else { + w++ + } + + u /= 60 + + // u is now integer hours (h) + if u > 0 { + w-- + + if u%24 > 0 { + buf[w] = 'h' + w = fmtInt(buf[:w], u%24) + } else { + w++ + } + + u /= 24 + + // u is now integer days (d) + if u > 0 { + w-- + + if u%7 > 0 { + buf[w] = 'd' + w = fmtInt(buf[:w], u%7) + } else { + w++ + } + + u /= 7 + + // u is now integer weeks (w) + if u > 0 { + w-- + buf[w] = 'w' + w = fmtInt(buf[:w], u) + } + + } + + } + } + } + } + } + + } + + return sign + string(buf[w:]) +} + +// fmtInt formats v into the tail of buf. +// It returns the index where the output begins. +func fmtInt(buf []byte, v uint64) int { + w := len(buf) + if v == 0 { + w-- + buf[w] = '0' + } else { + for v > 0 { + w-- + buf[w] = byte(v%10) + '0' + v /= 10 + } + } + return w +} diff --git a/str2duration_test.go b/str2duration_test.go index f16c575..989e794 100644 --- a/str2duration_test.go +++ b/str2duration_test.go @@ -49,7 +49,7 @@ func TestParseString(t *testing.T) { } } -//TestParseDuration test if string returned by a duration is equal to string returned with the package +// TestParseDuration test if string returned by a duration is equal to string returned with the package func TestParseDuration(t *testing.T) { for i, duration := range []time.Duration{ @@ -89,3 +89,63 @@ func TestParseDuration(t *testing.T) { } } } + +func TestString(t *testing.T) { + + for i, tt := range []struct { + dur time.Duration + expected string + }{ + //This times are returned with time.Duration string + {time.Duration(0), "0s"}, + {time.Duration(time.Hour), "1h"}, + {time.Duration(time.Minute), "1m"}, + {time.Duration(time.Second), "1s"}, + {time.Duration(time.Millisecond), "1ms"}, + {time.Duration(time.Microsecond), "1us"}, + {time.Duration(time.Nanosecond), "1ns"}, + {time.Duration(4*time.Second + time.Nanosecond), "4s1ns"}, + {time.Duration(time.Hour + 4*time.Second + time.Nanosecond), "1h4s1ns"}, + {time.Duration(61*time.Minute + 10*time.Millisecond), "1h1m10ms"}, + {time.Duration(61*time.Minute + 123456789*time.Nanosecond), "1h1m123ms456us789ns"}, + {time.Duration(time.Millisecond + 20*time.Nanosecond), "1ms20ns"}, + {time.Duration(time.Second + 20*time.Nanosecond), "1s20ns"}, + {time.Duration(693 * time.Nanosecond), "693ns"}, + {time.Duration(10*time.Second + time.Microsecond + 693*time.Nanosecond), "10s1us693ns"}, + {time.Duration(time.Millisecond + 1*time.Nanosecond), "1ms1ns"}, + {time.Duration(time.Second + 20*time.Nanosecond), "1s20ns"}, + {time.Duration(60*time.Hour + 8*time.Millisecond), "2d12h8ms"}, + {time.Duration(96*time.Hour + 63*time.Second), "4d1m3s"}, + {time.Duration(48*time.Hour + 3*time.Second + 96*time.Nanosecond), "2d3s96ns"}, + {time.Duration(168*time.Hour + 48*time.Hour + 3*time.Second + 96*time.Nanosecond), "1w2d3s96ns"}, + {time.Duration(168*time.Hour + 48*time.Hour + 3*time.Second + 3*time.Microsecond + 96*time.Nanosecond), "1w2d3s3us96ns"}, + + // this is the maximum supported by golang std: 2540400h10m10.000000000s + {time.Duration(2540400*time.Hour + 10*time.Minute + 10*time.Second), "15121w3d10m10s"}, + + // this is max int64, the max duration supported to convert + {time.Duration(9_223_372_036_854_775_807 * time.Nanosecond), "15250w1d23h47m16s854ms775us807ns"}, + + // this is max int64 in negative, the max negative duration supported to convert + {time.Duration(-9_223_372_036_854_775_807 * time.Nanosecond), "-15250w1d23h47m16s854ms775us807ns"}, + + // negatives + {time.Duration(-time.Hour), "-1h"}, + {time.Duration(-time.Minute), "-1m"}, + {time.Duration(-time.Second), "-1s"}, + {time.Duration(-time.Millisecond), "-1ms"}, + {time.Duration(-time.Microsecond), "-1us"}, + {time.Duration(-time.Nanosecond), "-1ns"}, + {time.Duration(-4*time.Second - time.Nanosecond), "-4s1ns"}, + } { + stringDuration := String(tt.dur) + if tt.expected != stringDuration { + t.Errorf("index %d -> in: %s returned: %s\tnot equal to %s", i, tt.dur, stringDuration, tt.expected) + } + + durationParsed, _ := ParseDuration(stringDuration) + if durationParsed != tt.dur { + t.Errorf("error converting string to duration: index %d -> in: %s returned: %s", i, tt.dur, durationParsed) + } + } +}