Skip to content

Commit

Permalink
Merge pull request #3 from xhit/2-proposal-add-string-function-that-p…
Browse files Browse the repository at this point in the history
…rints-out-days-and-weeks

Convert duration to string supporting days and weeks
  • Loading branch information
xhit committed Dec 7, 2022
2 parents 741dffc + 37c349f commit 017325b
Show file tree
Hide file tree
Showing 3 changed files with 210 additions and 1 deletion.
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)
}
}
}

0 comments on commit 017325b

Please sign in to comment.