-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathtime.go
166 lines (144 loc) · 5.17 KB
/
time.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
package humanize
// Time values humanization functions.
import (
"fmt"
"regexp"
"sort"
"strconv"
"strings"
"time"
)
// Time constants.
const (
Second = 1
Minute = 60
Hour = 60 * Minute
Day = 24 * Hour
Week = 7 * Day
Month = 30 * Day
Year = 12 * Month
LongTime = 35 * Year
)
// buildTimeInputRe will build a regular expression to match all possible time inputs.
func (humanizer *Humanizer) buildTimeInputRe() {
// Get all possible time units.
units := make([]string, 0, len(humanizer.provider.times.units))
for unit := range humanizer.provider.times.units {
units = append(units, unit)
}
// Regexp will match: number, optional coma or dot, optional second number, unit name
humanizer.timeInputRe = regexp.MustCompile("([0-9]+)[.,]?([0-9]*?) (" + strings.Join(units, "|") + ")")
}
// humanizeDuration will return a humanized form of time duration.
func (humanizer *Humanizer) humanizeDuration(seconds int64, precise bool) string {
if seconds < 1 {
return humanizer.provider.times.now
}
secondsLeft := seconds
humanized := []string{}
for secondsLeft > 0 {
// Within all the ranges, find the one matching our time best (closest, but bigger).
rangeIndex := sort.Search(len(humanizer.provider.times.ranges), func(i int) bool {
// If we are in precise mode, and next range would be a fit but should be skipped, use this one.
if precise && i < len(humanizer.provider.times.ranges)-1 &&
humanizer.provider.times.ranges[i+1].upperLimit > secondsLeft &&
humanizer.provider.times.ranges[i+1].skipWhenPrecise {
return true
}
return humanizer.provider.times.ranges[i].upperLimit > secondsLeft
})
// Select this unit range and convert actualTime to it.
unitRanges := humanizer.provider.times.ranges[rangeIndex]
actualTime := secondsLeft / unitRanges.divideBy // Integer division!
// Subtract the time span covered by this part.
secondsLeft -= actualTime * unitRanges.divideBy
// TODO: smarter imprecise mode.
if !precise { // We don't care about the reminder.
secondsLeft = 0
}
if actualTime == 1 { // Special case for singular unit.
humanized = append(humanized, unitRanges.singular)
continue
}
// Within the unit range, find the unit best fitting our actualTime (closest, but bigger).
searchTime := actualTime
if unitRanges.onlyLastDigitAfter != 0 && actualTime > unitRanges.onlyLastDigitAfter {
searchTime = actualTime % 10
}
unitIndex := sort.Search(len(unitRanges.ranges), func(i int) bool {
return unitRanges.ranges[i].upperLimit > searchTime
})
timeRange := unitRanges.ranges[unitIndex]
humanized = append(humanized, fmt.Sprintf(timeRange.format, actualTime))
}
if len(humanized) == 1 {
return humanized[0]
}
return fmt.Sprintf(
"%s %s %s",
strings.Join(humanized[:len(humanized)-1], ", "),
humanizer.provider.times.remainderSep,
humanized[len(humanized)-1],
)
}
// TimeDiffNow is a convenience method returning humanized time from now till date.
func (humanizer *Humanizer) TimeDiffNow(date time.Time, precise bool) string {
return humanizer.TimeDiff(time.Now(), date, precise)
}
// TimeDiff will return the humanized time difference between the given dates.
// Precise setting determines whether a rough approximation or exact description should be returned, e.g.:
// precise=false -> "3 months"
// precise=true -> "2 months and 10 days"
//
func (humanizer *Humanizer) TimeDiff(startDate, endDate time.Time, precise bool) string {
diff := endDate.Unix() - startDate.Unix()
// Don't bother with Math.Abs
absDiff := diff
if absDiff < 0 {
absDiff = -absDiff
}
humanized := humanizer.humanizeDuration(absDiff, precise)
// Past or future?
if diff == 0 {
return humanized
} else if diff > 0 {
return fmt.Sprintf(humanizer.provider.times.future, humanized)
} else {
return fmt.Sprintf(humanizer.provider.times.past, humanized)
}
}
// ParseDuration will return time duration as parsed from input string.
func (humanizer *Humanizer) ParseDuration(input string) (time.Duration, error) {
allMatched := humanizer.timeInputRe.FindAllStringSubmatch(input, -1)
if len(allMatched) == 0 {
return time.Duration(0), fmt.Errorf("Cannot parse '%s'.", input)
}
totalDuration := time.Duration(0)
for _, matched := range allMatched {
// 0 - full match, 1 - number, 2 - decimal, 3 - unit
if matched[2] == "" { // Decimal component is empty.
matched[2] = "0"
}
// Parse first two groups into a float. Can only fail if the regexp is wrong and allows non numbers.
number, _ := strconv.ParseFloat(matched[1]+"."+matched[2], 64)
// Get the value of the unit in seconds.
seconds, _ := humanizer.provider.times.units[matched[3]]
// Parser will simply sum up all the found durations.
totalDuration += time.Duration(int64(number * float64(seconds) * float64(time.Second)))
}
return totalDuration, nil
}
// SecondsToTimeString converts the time in seconds into a human readable timestamp, eg.:
// 76 -> 01:16
// 3620 -> 1:00:20
func (humanizer *Humanizer) SecondsToTimeString(duration int64) string {
h := duration / 3600
duration -= h * 3600
m := duration / 60
duration -= m * 60
s := duration
if h > 0 {
return fmt.Sprintf("%d:%02d:%02d", h, m, s)
}
return fmt.Sprintf("%02d:%02d", m, s)
}