Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convert duration to string supporting days and weeks #3

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Expand Up @@ -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.
147 changes: 147 additions & 0 deletions str2duration.go
Expand Up @@ -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
}
62 changes: 61 additions & 1 deletion str2duration_test.go
Expand Up @@ -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{
Expand Down Expand Up @@ -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)
}
}
}