mirror of
https://git.freebsd.org/ports.git
synced 2025-05-18 10:03:10 -04:00
388 lines
8.5 KiB
Go
388 lines
8.5 KiB
Go
// Package duration contains routines to parse standard units of time.
|
|
package duration
|
|
|
|
import (
|
|
"errors"
|
|
"time"
|
|
)
|
|
|
|
// Duration is a standard unit of time.
|
|
type Duration time.Duration
|
|
|
|
// String returns a string representing the duration in the form "3d1h3m".
|
|
// Leading zero units are omitted. As a special case, durations less than one
|
|
// second format use a smaller unit (milli-, micro-, or nanoseconds) to ensure
|
|
// that the leading digit is non-zero. Duration more than a day or more than a
|
|
// week lose granularity and are truncated to resp. days-hours-minutes and
|
|
// weeks-days-hours. The zero duration formats as 0s.
|
|
func (d Duration) String() string {
|
|
// Largest time is 2540400h10m10.000000000s
|
|
var buf [32]byte
|
|
w := len(buf)
|
|
|
|
u := uint64(d)
|
|
neg := d < 0
|
|
if neg {
|
|
u = -u
|
|
}
|
|
|
|
if u < uint64(Second) {
|
|
// Special case: if duration is smaller than a second,
|
|
// use smaller units, like 1.2ms
|
|
var prec int
|
|
w--
|
|
buf[w] = 's'
|
|
w--
|
|
switch {
|
|
case u == 0:
|
|
return "0s"
|
|
case u < uint64(Microsecond):
|
|
// print nanoseconds
|
|
prec = 0
|
|
buf[w] = 'n'
|
|
case u < uint64(Millisecond):
|
|
// print microseconds
|
|
prec = 3
|
|
// U+00B5 'µ' micro sign == 0xC2 0xB5
|
|
w-- // Need room for two bytes.
|
|
copy(buf[w:], "µ")
|
|
default:
|
|
// print milliseconds
|
|
prec = 6
|
|
buf[w] = 'm'
|
|
}
|
|
w, u = fmtFrac(buf[:w], u, prec)
|
|
w = fmtInt(buf[:w], u)
|
|
|
|
} else if u > uint64(Week) {
|
|
// Special case: if duration is larger than a week,
|
|
// use bigger units like 4w3d2h
|
|
w--
|
|
buf[w] = 'h'
|
|
|
|
u /= uint64(Hour)
|
|
|
|
// u is now integer hours
|
|
w = fmtInt(buf[:w], u%24)
|
|
u /= 24
|
|
|
|
// u is now integer days
|
|
if u > 0 {
|
|
w--
|
|
buf[w] = 'd'
|
|
w = fmtInt(buf[:w], u%7)
|
|
u /= 7
|
|
|
|
// u is now integer weeks
|
|
// Stop at hours because days can be different lengths.
|
|
if u > 0 {
|
|
w--
|
|
buf[w] = 'w'
|
|
w = fmtInt(buf[:w], u)
|
|
}
|
|
}
|
|
|
|
} else if u > uint64(Day) {
|
|
// Special case: if duration is larger than a day,
|
|
// use bigger units like 3d2h6m
|
|
w--
|
|
buf[w] = 'm'
|
|
|
|
u /= uint64(Minute)
|
|
|
|
// u is now integer minutes
|
|
w = fmtInt(buf[:w], u%60)
|
|
u /= 60
|
|
|
|
// u is now integer hours
|
|
if u > 0 {
|
|
w--
|
|
buf[w] = 'h'
|
|
w = fmtInt(buf[:w], u%24)
|
|
u /= 24
|
|
|
|
// u is now integer weeks
|
|
if u > 0 {
|
|
w--
|
|
buf[w] = 'd'
|
|
w = fmtInt(buf[:w], u)
|
|
}
|
|
}
|
|
|
|
} else {
|
|
w--
|
|
buf[w] = 's'
|
|
|
|
w, u = fmtFrac(buf[:w], u, 9)
|
|
|
|
// u is now integer seconds
|
|
w = fmtInt(buf[:w], u%60)
|
|
u /= 60
|
|
|
|
// u is now integer minutes
|
|
if u > 0 {
|
|
w--
|
|
buf[w] = 'm'
|
|
w = fmtInt(buf[:w], u%60)
|
|
u /= 60
|
|
|
|
// u is now integer hours
|
|
// Stop at hours because days can be different lengths.
|
|
if u > 0 {
|
|
w--
|
|
buf[w] = 'h'
|
|
w = fmtInt(buf[:w], u)
|
|
}
|
|
}
|
|
}
|
|
|
|
if neg {
|
|
w--
|
|
buf[w] = '-'
|
|
}
|
|
|
|
return string(buf[w:])
|
|
}
|
|
|
|
// fmtFrac formats the fraction of v/10**prec (e.g., ".12345") into the
|
|
// tail of buf, omitting trailing zeros. it omits the decimal
|
|
// point too when the fraction is 0. It returns the index where the
|
|
// output bytes begin and the value v/10**prec.
|
|
func fmtFrac(buf []byte, v uint64, prec int) (nw int, nv uint64) {
|
|
// Omit trailing zeros up to and including decimal point.
|
|
w := len(buf)
|
|
print := false
|
|
for i := 0; i < prec; i++ {
|
|
digit := v % 10
|
|
print = print || digit != 0
|
|
if print {
|
|
w--
|
|
buf[w] = byte(digit) + '0'
|
|
}
|
|
v /= 10
|
|
}
|
|
if print {
|
|
w--
|
|
buf[w] = '.'
|
|
}
|
|
return w, v
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// Nanoseconds returns the duration as an integer nanosecond count.
|
|
func (d Duration) Nanoseconds() int64 { return int64(d) }
|
|
|
|
// Seconds returns the duration as a floating point number of seconds.
|
|
func (d Duration) Seconds() float64 {
|
|
sec := d / Second
|
|
nsec := d % Second
|
|
return float64(sec) + float64(nsec)*1e-9
|
|
}
|
|
|
|
// Hours returns the duration as a floating point number of hours.
|
|
func (d Duration) Hours() float64 {
|
|
hour := d / Hour
|
|
nsec := d % Hour
|
|
return float64(hour) + float64(nsec)*(1e-9/60/60)
|
|
}
|
|
|
|
// Days returns the duration as a floating point number of days.
|
|
func (d Duration) Days() float64 {
|
|
hour := d / Hour
|
|
nsec := d % Hour
|
|
return float64(hour) + float64(nsec)*(1e-9/60/60/24)
|
|
}
|
|
|
|
// Weeks returns the duration as a floating point number of days.
|
|
func (d Duration) Weeks() float64 {
|
|
hour := d / Hour
|
|
nsec := d % Hour
|
|
return float64(hour) + float64(nsec)*(1e-9/60/60/24/7)
|
|
}
|
|
|
|
// Minutes returns the duration as a floating point number of minutes.
|
|
func (d Duration) Minutes() float64 {
|
|
min := d / Minute
|
|
nsec := d % Minute
|
|
return float64(min) + float64(nsec)*(1e-9/60)
|
|
}
|
|
|
|
// Standard unit of time.
|
|
var (
|
|
Nanosecond = Duration(time.Nanosecond)
|
|
Microsecond = Duration(time.Microsecond)
|
|
Millisecond = Duration(time.Millisecond)
|
|
Second = Duration(time.Second)
|
|
Minute = Duration(time.Minute)
|
|
Hour = Duration(time.Hour)
|
|
Day = Hour * 24
|
|
Week = Day * 7
|
|
Fortnight = Week * 2
|
|
Month = Day * 30 // Approximation
|
|
Year = Day * 365 // Approximation
|
|
Decade = Year * 10 // Approximation
|
|
Century = Year * 100 // Approximation
|
|
Millennium = Year * 1000 // Approximation
|
|
)
|
|
|
|
var errLeadingInt = errors.New("duration: bad [0-9]*") // never printed
|
|
|
|
// leadingInt consumes the leading [0-9]* from s.
|
|
func leadingInt(s string) (x int64, rem string, err error) {
|
|
i := 0
|
|
for ; i < len(s); i++ {
|
|
c := s[i]
|
|
if c < '0' || c > '9' {
|
|
break
|
|
}
|
|
if x > (1<<63-1)/10 {
|
|
// overflow
|
|
return 0, "", errLeadingInt
|
|
}
|
|
x = x*10 + int64(c) - '0'
|
|
if x < 0 {
|
|
// overflow
|
|
return 0, "", errLeadingInt
|
|
}
|
|
}
|
|
return x, s[i:], nil
|
|
}
|
|
|
|
var unitMap = map[string]int64{
|
|
"ns": int64(Nanosecond),
|
|
"us": int64(Microsecond),
|
|
"µs": int64(Microsecond), // U+00B5 = micro symbol
|
|
"μs": int64(Microsecond), // U+03BC = Greek letter mu
|
|
"ms": int64(Millisecond),
|
|
"s": int64(Second),
|
|
"m": int64(Minute),
|
|
"h": int64(Hour),
|
|
"d": int64(Day),
|
|
"w": int64(Week),
|
|
"y": int64(Year), // Approximation
|
|
}
|
|
|
|
// ParseDuration parses a duration string.
|
|
// A duration string is a possibly signed sequence of
|
|
// decimal numbers, each with optional fraction and a unit suffix,
|
|
// such as "300ms", "-1.5h" or "2h45m".
|
|
// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h", "d", "w", "y".
|
|
func ParseDuration(s string) (Duration, error) {
|
|
// [-+]?([0-9]*(\.[0-9]*)?[a-z]+)+
|
|
orig := s
|
|
var d int64
|
|
neg := false
|
|
|
|
// Consume [-+]?
|
|
if s != "" {
|
|
c := s[0]
|
|
if c == '-' || c == '+' {
|
|
neg = c == '-'
|
|
s = s[1:]
|
|
}
|
|
}
|
|
// Special case: if all that is left is "0", this is zero.
|
|
if s == "0" {
|
|
return 0, nil
|
|
}
|
|
if s == "" {
|
|
return 0, errors.New("time: invalid duration " + orig)
|
|
}
|
|
for s != "" {
|
|
var (
|
|
v, f int64 // integers before, after decimal point
|
|
scale float64 = 1 // value = v + f/scale
|
|
)
|
|
|
|
var err error
|
|
|
|
// The next character must be [0-9.]
|
|
if !(s[0] == '.' || '0' <= s[0] && s[0] <= '9') {
|
|
return 0, errors.New("time: invalid duration " + orig)
|
|
}
|
|
// Consume [0-9]*
|
|
pl := len(s)
|
|
v, s, err = leadingInt(s)
|
|
if err != nil {
|
|
return 0, errors.New("time: invalid duration " + orig)
|
|
}
|
|
pre := pl != len(s) // whether we consumed anything before a period
|
|
|
|
// Consume (\.[0-9]*)?
|
|
post := false
|
|
if s != "" && s[0] == '.' {
|
|
s = s[1:]
|
|
pl := len(s)
|
|
f, s, err = leadingInt(s)
|
|
if err != nil {
|
|
return 0, errors.New("time: invalid duration " + orig)
|
|
}
|
|
for n := pl - len(s); n > 0; n-- {
|
|
scale *= 10
|
|
}
|
|
post = pl != len(s)
|
|
}
|
|
if !pre && !post {
|
|
// no digits (e.g. ".s" or "-.s")
|
|
return 0, errors.New("time: invalid duration " + orig)
|
|
}
|
|
|
|
// Consume unit.
|
|
i := 0
|
|
for ; i < len(s); i++ {
|
|
c := s[i]
|
|
if c == '.' || '0' <= c && c <= '9' {
|
|
break
|
|
}
|
|
}
|
|
if i == 0 {
|
|
return 0, errors.New("time: missing unit in duration " + orig)
|
|
}
|
|
u := s[:i]
|
|
s = s[i:]
|
|
unit, ok := unitMap[u]
|
|
if !ok {
|
|
return 0, errors.New("time: unknown unit " + u + " in duration " + orig)
|
|
}
|
|
if v > (1<<63-1)/unit {
|
|
// overflow
|
|
return 0, errors.New("time: invalid duration " + orig)
|
|
}
|
|
v *= unit
|
|
if f > 0 {
|
|
// float64 is needed to be nanosecond accurate for fractions of hours.
|
|
// v >= 0 && (f*unit/scale) <= 3.6e+12 (ns/h, h is the largest unit)
|
|
v += int64(float64(f) * (float64(unit) / scale))
|
|
if v < 0 {
|
|
// overflow
|
|
return 0, errors.New("time: invalid duration " + orig)
|
|
}
|
|
}
|
|
d += v
|
|
if d < 0 {
|
|
// overflow
|
|
return 0, errors.New("time: invalid duration " + orig)
|
|
}
|
|
}
|
|
|
|
if neg {
|
|
d = -d
|
|
}
|
|
return Duration(d), nil
|
|
}
|