durafmt.go 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. // Package durafmt formats time.Duration into a human readable format.
  2. package durafmt
  3. import (
  4. "errors"
  5. "strconv"
  6. "strings"
  7. "time"
  8. )
  9. var (
  10. units = []string{"years", "weeks", "days", "hours", "minutes", "seconds", "milliseconds"}
  11. )
  12. // Durafmt holds the parsed duration and the original input duration.
  13. type Durafmt struct {
  14. duration time.Duration
  15. input string // Used as reference.
  16. short bool
  17. }
  18. // Parse creates a new *Durafmt struct, returns error if input is invalid.
  19. func Parse(dinput time.Duration) *Durafmt {
  20. input := dinput.String()
  21. return &Durafmt{dinput, input, false}
  22. }
  23. // ParseShort creates a new *Durafmt struct, short form, returns error if input is invalid.
  24. func ParseShort(dinput time.Duration) *Durafmt {
  25. input := dinput.String()
  26. return &Durafmt{dinput, input, true}
  27. }
  28. // ParseString creates a new *Durafmt struct from a string.
  29. // returns an error if input is invalid.
  30. func ParseString(input string) (*Durafmt, error) {
  31. if input == "0" || input == "-0" {
  32. return nil, errors.New("durafmt: missing unit in duration " + input)
  33. }
  34. duration, err := time.ParseDuration(input)
  35. if err != nil {
  36. return nil, err
  37. }
  38. return &Durafmt{duration, input, false}, nil
  39. }
  40. // ParseStringShort creates a new *Durafmt struct from a string, short form
  41. // returns an error if input is invalid.
  42. func ParseStringShort(input string) (*Durafmt, error) {
  43. if input == "0" || input == "-0" {
  44. return nil, errors.New("durafmt: missing unit in duration " + input)
  45. }
  46. duration, err := time.ParseDuration(input)
  47. if err != nil {
  48. return nil, err
  49. }
  50. return &Durafmt{duration, input, true}, nil
  51. }
  52. // String parses d *Durafmt into a human readable duration.
  53. func (d *Durafmt) String() string {
  54. var duration string
  55. // Check for minus durations.
  56. if string(d.input[0]) == "-" {
  57. duration += "-"
  58. d.duration = -d.duration
  59. }
  60. // Convert duration.
  61. seconds := int64(d.duration.Seconds()) % 60
  62. minutes := int64(d.duration.Minutes()) % 60
  63. hours := int64(d.duration.Hours()) % 24
  64. days := int64(d.duration/(24*time.Hour)) % 365 % 7
  65. // Edge case between 364 and 365 days.
  66. // We need to calculate weeks from what is left from years
  67. leftYearDays := int64(d.duration/(24*time.Hour)) % 365
  68. weeks := leftYearDays / 7
  69. if leftYearDays >= 364 && leftYearDays < 365 {
  70. weeks = 52
  71. }
  72. years := int64(d.duration/(24*time.Hour)) / 365
  73. milliseconds := int64(d.duration/time.Millisecond) -
  74. (seconds * 1000) - (minutes * 60000) - (hours * 3600000) -
  75. (days * 86400000) - (weeks * 604800000) - (years * 31536000000)
  76. // Create a map of the converted duration time.
  77. durationMap := map[string]int64{
  78. "milliseconds": milliseconds,
  79. "seconds": seconds,
  80. "minutes": minutes,
  81. "hours": hours,
  82. "days": days,
  83. "weeks": weeks,
  84. "years": years,
  85. }
  86. // Construct duration string.
  87. for _, u := range units {
  88. v := durationMap[u]
  89. strval := strconv.FormatInt(v, 10)
  90. switch {
  91. // add to the duration string if v > 1.
  92. case v > 1:
  93. duration += strval + " " + u + " "
  94. // remove the plural 's', if v is 1.
  95. case v == 1:
  96. duration += strval + " " + strings.TrimRight(u, "s") + " "
  97. // omit any value with 0s or 0.
  98. case d.duration.String() == "0" || d.duration.String() == "0s":
  99. // note: milliseconds and minutes have the same suffix (m)
  100. // so we have to check if the units match with the suffix.
  101. // check for a suffix that is NOT the milliseconds suffix.
  102. if strings.HasSuffix(d.input, string(u[0])) && !strings.Contains(d.input, "ms") {
  103. // if it happens that the units are milliseconds, skip.
  104. if u == "milliseconds" {
  105. continue
  106. }
  107. duration += strval + " " + u
  108. }
  109. // process milliseconds here.
  110. if u == "milliseconds" {
  111. if strings.Contains(d.input, "ms") {
  112. duration += strval + " " + u
  113. break
  114. }
  115. }
  116. break
  117. // omit any value with 0.
  118. case v == 0:
  119. continue
  120. }
  121. }
  122. // trim any remaining spaces.
  123. duration = strings.TrimSpace(duration)
  124. // if more than 2 spaces present return the first 2 strings
  125. // if short version is requested
  126. if d.short {
  127. duration = strings.Join(strings.Split(duration, " ")[:2], " ")
  128. }
  129. return duration
  130. }