Parse standard units of time.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

duration.go 8.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. // Package duration contains routines to parse standard units of time.
  2. package duration
  3. import (
  4. "errors"
  5. "time"
  6. )
  7. // Duration is a standard unit of time.
  8. type Duration time.Duration
  9. // String returns a string representing the duration in the form "3d1h3m".
  10. // Leading zero units are omitted. As a special case, durations less than one
  11. // second format use a smaller unit (milli-, micro-, or nanoseconds) to ensure
  12. // that the leading digit is non-zero. Duration more than a day or more than a
  13. // week lose granularity and are truncated to resp. days-hours-minutes and
  14. // weeks-days-hours. The zero duration formats as 0s.
  15. func (d Duration) String() string {
  16. // Largest time is 2540400h10m10.000000000s
  17. var buf [32]byte
  18. w := len(buf)
  19. u := uint64(d)
  20. neg := d < 0
  21. if neg {
  22. u = -u
  23. }
  24. if u < uint64(Second) {
  25. // Special case: if duration is smaller than a second,
  26. // use smaller units, like 1.2ms
  27. var prec int
  28. w--
  29. buf[w] = 's'
  30. w--
  31. switch {
  32. case u == 0:
  33. return "0s"
  34. case u < uint64(Microsecond):
  35. // print nanoseconds
  36. prec = 0
  37. buf[w] = 'n'
  38. case u < uint64(Millisecond):
  39. // print microseconds
  40. prec = 3
  41. // U+00B5 'µ' micro sign == 0xC2 0xB5
  42. w-- // Need room for two bytes.
  43. copy(buf[w:], "µ")
  44. default:
  45. // print milliseconds
  46. prec = 6
  47. buf[w] = 'm'
  48. }
  49. w, u = fmtFrac(buf[:w], u, prec)
  50. w = fmtInt(buf[:w], u)
  51. } else if u > uint64(Week) {
  52. // Special case: if duration is larger than a week,
  53. // use bigger units like 4w3d2h
  54. w--
  55. buf[w] = 'h'
  56. u /= uint64(Hour)
  57. // u is now integer hours
  58. w = fmtInt(buf[:w], u%24)
  59. u /= 24
  60. // u is now integer days
  61. if u > 0 {
  62. w--
  63. buf[w] = 'd'
  64. w = fmtInt(buf[:w], u%7)
  65. u /= 7
  66. // u is now integer weeks
  67. // Stop at hours because days can be different lengths.
  68. if u > 0 {
  69. w--
  70. buf[w] = 'w'
  71. w = fmtInt(buf[:w], u)
  72. }
  73. }
  74. } else if u > uint64(Day) {
  75. // Special case: if duration is larger than a day,
  76. // use bigger units like 3d2h6m
  77. w--
  78. buf[w] = 'm'
  79. u /= uint64(Minute)
  80. // u is now integer minutes
  81. w = fmtInt(buf[:w], u%60)
  82. u /= 60
  83. // u is now integer hours
  84. if u > 0 {
  85. w--
  86. buf[w] = 'h'
  87. w = fmtInt(buf[:w], u%24)
  88. u /= 24
  89. // u is now integer weeks
  90. if u > 0 {
  91. w--
  92. buf[w] = 'd'
  93. w = fmtInt(buf[:w], u)
  94. }
  95. }
  96. } else {
  97. w--
  98. buf[w] = 's'
  99. w, u = fmtFrac(buf[:w], u, 9)
  100. // u is now integer seconds
  101. w = fmtInt(buf[:w], u%60)
  102. u /= 60
  103. // u is now integer minutes
  104. if u > 0 {
  105. w--
  106. buf[w] = 'm'
  107. w = fmtInt(buf[:w], u%60)
  108. u /= 60
  109. // u is now integer hours
  110. // Stop at hours because days can be different lengths.
  111. if u > 0 {
  112. w--
  113. buf[w] = 'h'
  114. w = fmtInt(buf[:w], u)
  115. }
  116. }
  117. }
  118. if neg {
  119. w--
  120. buf[w] = '-'
  121. }
  122. return string(buf[w:])
  123. }
  124. // fmtFrac formats the fraction of v/10**prec (e.g., ".12345") into the
  125. // tail of buf, omitting trailing zeros. it omits the decimal
  126. // point too when the fraction is 0. It returns the index where the
  127. // output bytes begin and the value v/10**prec.
  128. func fmtFrac(buf []byte, v uint64, prec int) (nw int, nv uint64) {
  129. // Omit trailing zeros up to and including decimal point.
  130. w := len(buf)
  131. print := false
  132. for i := 0; i < prec; i++ {
  133. digit := v % 10
  134. print = print || digit != 0
  135. if print {
  136. w--
  137. buf[w] = byte(digit) + '0'
  138. }
  139. v /= 10
  140. }
  141. if print {
  142. w--
  143. buf[w] = '.'
  144. }
  145. return w, v
  146. }
  147. // fmtInt formats v into the tail of buf.
  148. // It returns the index where the output begins.
  149. func fmtInt(buf []byte, v uint64) int {
  150. w := len(buf)
  151. if v == 0 {
  152. w--
  153. buf[w] = '0'
  154. } else {
  155. for v > 0 {
  156. w--
  157. buf[w] = byte(v%10) + '0'
  158. v /= 10
  159. }
  160. }
  161. return w
  162. }
  163. // Nanoseconds returns the duration as an integer nanosecond count.
  164. func (d Duration) Nanoseconds() int64 { return int64(d) }
  165. // Seconds returns the duration as a floating point number of seconds.
  166. func (d Duration) Seconds() float64 {
  167. sec := d / Second
  168. nsec := d % Second
  169. return float64(sec) + float64(nsec)*1e-9
  170. }
  171. // Hours returns the duration as a floating point number of hours.
  172. func (d Duration) Hours() float64 {
  173. hour := d / Hour
  174. nsec := d % Hour
  175. return float64(hour) + float64(nsec)*(1e-9/60/60)
  176. }
  177. // Days returns the duration as a floating point number of days.
  178. func (d Duration) Days() float64 {
  179. hour := d / Hour
  180. nsec := d % Hour
  181. return float64(hour) + float64(nsec)*(1e-9/60/60/24)
  182. }
  183. // Weeks returns the duration as a floating point number of days.
  184. func (d Duration) Weeks() float64 {
  185. hour := d / Hour
  186. nsec := d % Hour
  187. return float64(hour) + float64(nsec)*(1e-9/60/60/24/7)
  188. }
  189. // Minutes returns the duration as a floating point number of minutes.
  190. func (d Duration) Minutes() float64 {
  191. min := d / Minute
  192. nsec := d % Minute
  193. return float64(min) + float64(nsec)*(1e-9/60)
  194. }
  195. // Standard unit of time.
  196. var (
  197. Nanosecond = Duration(time.Nanosecond)
  198. Microsecond = Duration(time.Microsecond)
  199. Millisecond = Duration(time.Millisecond)
  200. Second = Duration(time.Second)
  201. Minute = Duration(time.Minute)
  202. Hour = Duration(time.Hour)
  203. Day = Hour * 24
  204. Week = Day * 7
  205. Fortnight = Week * 2
  206. Month = Day * 30 // Approximation
  207. Year = Day * 365 // Approximation
  208. Decade = Year * 10 // Approximation
  209. Century = Year * 100 // Approximation
  210. Millennium = Year * 1000 // Approximation
  211. )
  212. var errLeadingInt = errors.New("duration: bad [0-9]*") // never printed
  213. // leadingInt consumes the leading [0-9]* from s.
  214. func leadingInt(s string) (x int64, rem string, err error) {
  215. i := 0
  216. for ; i < len(s); i++ {
  217. c := s[i]
  218. if c < '0' || c > '9' {
  219. break
  220. }
  221. if x > (1<<63-1)/10 {
  222. // overflow
  223. return 0, "", errLeadingInt
  224. }
  225. x = x*10 + int64(c) - '0'
  226. if x < 0 {
  227. // overflow
  228. return 0, "", errLeadingInt
  229. }
  230. }
  231. return x, s[i:], nil
  232. }
  233. var unitMap = map[string]int64{
  234. "ns": int64(Nanosecond),
  235. "us": int64(Microsecond),
  236. "µs": int64(Microsecond), // U+00B5 = micro symbol
  237. "μs": int64(Microsecond), // U+03BC = Greek letter mu
  238. "ms": int64(Millisecond),
  239. "s": int64(Second),
  240. "m": int64(Minute),
  241. "h": int64(Hour),
  242. "d": int64(Day),
  243. "w": int64(Week),
  244. "y": int64(Year), // Approximation
  245. }
  246. // ParseDuration parses a duration string.
  247. // A duration string is a possibly signed sequence of
  248. // decimal numbers, each with optional fraction and a unit suffix,
  249. // such as "300ms", "-1.5h" or "2h45m".
  250. // Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h", "d", "w", "y".
  251. func ParseDuration(s string) (Duration, error) {
  252. // [-+]?([0-9]*(\.[0-9]*)?[a-z]+)+
  253. orig := s
  254. var d int64
  255. neg := false
  256. // Consume [-+]?
  257. if s != "" {
  258. c := s[0]
  259. if c == '-' || c == '+' {
  260. neg = c == '-'
  261. s = s[1:]
  262. }
  263. }
  264. // Special case: if all that is left is "0", this is zero.
  265. if s == "0" {
  266. return 0, nil
  267. }
  268. if s == "" {
  269. return 0, errors.New("time: invalid duration " + orig)
  270. }
  271. for s != "" {
  272. var (
  273. v, f int64 // integers before, after decimal point
  274. scale float64 = 1 // value = v + f/scale
  275. )
  276. var err error
  277. // The next character must be [0-9.]
  278. if !(s[0] == '.' || '0' <= s[0] && s[0] <= '9') {
  279. return 0, errors.New("time: invalid duration " + orig)
  280. }
  281. // Consume [0-9]*
  282. pl := len(s)
  283. v, s, err = leadingInt(s)
  284. if err != nil {
  285. return 0, errors.New("time: invalid duration " + orig)
  286. }
  287. pre := pl != len(s) // whether we consumed anything before a period
  288. // Consume (\.[0-9]*)?
  289. post := false
  290. if s != "" && s[0] == '.' {
  291. s = s[1:]
  292. pl := len(s)
  293. f, s, err = leadingInt(s)
  294. if err != nil {
  295. return 0, errors.New("time: invalid duration " + orig)
  296. }
  297. for n := pl - len(s); n > 0; n-- {
  298. scale *= 10
  299. }
  300. post = pl != len(s)
  301. }
  302. if !pre && !post {
  303. // no digits (e.g. ".s" or "-.s")
  304. return 0, errors.New("time: invalid duration " + orig)
  305. }
  306. // Consume unit.
  307. i := 0
  308. for ; i < len(s); i++ {
  309. c := s[i]
  310. if c == '.' || '0' <= c && c <= '9' {
  311. break
  312. }
  313. }
  314. if i == 0 {
  315. return 0, errors.New("time: missing unit in duration " + orig)
  316. }
  317. u := s[:i]
  318. s = s[i:]
  319. unit, ok := unitMap[u]
  320. if !ok {
  321. return 0, errors.New("time: unknown unit " + u + " in duration " + orig)
  322. }
  323. if v > (1<<63-1)/unit {
  324. // overflow
  325. return 0, errors.New("time: invalid duration " + orig)
  326. }
  327. v *= unit
  328. if f > 0 {
  329. // float64 is needed to be nanosecond accurate for fractions of hours.
  330. // v >= 0 && (f*unit/scale) <= 3.6e+12 (ns/h, h is the largest unit)
  331. v += int64(float64(f) * (float64(unit) / scale))
  332. if v < 0 {
  333. // overflow
  334. return 0, errors.New("time: invalid duration " + orig)
  335. }
  336. }
  337. d += v
  338. if d < 0 {
  339. // overflow
  340. return 0, errors.New("time: invalid duration " + orig)
  341. }
  342. }
  343. if neg {
  344. d = -d
  345. }
  346. return Duration(d), nil
  347. }