forked from pawelszydlo/humanize
-
Notifications
You must be signed in to change notification settings - Fork 0
/
time.go
142 lines (123 loc) · 4.31 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
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 int) string {
if seconds < 1 {
panic("Cannot humanize durations < 1 sec.")
}
secondsLeft := seconds
humanized := []string{}
for i := -1; secondsLeft > 0 && (precise < 0 || i < precise); i++ {
// Find the ranges closest but bigger then diff.
n := sort.Search(len(humanizer.provider.Times.Ranges), func(i int) bool {
return humanizer.provider.Times.Ranges[i].UpperLimit > secondsLeft
})
// Within the ranges find the one matching our time best.
timeRanges := humanizer.provider.Times.Ranges[n]
k := sort.Search(len(timeRanges.Ranges), func(i int) bool {
return timeRanges.Ranges[i].UpperLimit > secondsLeft
})
timeRange := timeRanges.Ranges[k]
actualTime := secondsLeft / timeRanges.DivideBy // Integer division!
// If range has a placeholder for a number, insert it.
if strings.Contains(timeRange.Format, "%d") {
humanized = append(humanized, fmt.Sprintf(timeRange.Format, actualTime))
} else {
humanized = append(humanized, timeRange.Format)
}
// Subtract the time span covered by this part.
secondsLeft -= actualTime * timeRanges.DivideBy
if precise == 0 { // We don't care about the reminder.
secondsLeft = 0
}
}
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 int) 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=0 -> "3 months"
// precise=2 -> "2 months, 1 week and 3 days"
//
// TODO: in precise mode some ranges should be skipped, like weeks in the example above.
func (humanizer *Humanizer) TimeDiff(startDate, endDate time.Time, precise int) string {
diff := endDate.Unix() - startDate.Unix()
if diff == 0 {
return humanizer.provider.Times.Now
}
// 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 fmt.Sprintf(humanizer.provider.Times.Future, humanized)
}
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.
number, err := strconv.ParseFloat(matched[1]+"."+matched[2], 64)
if err != nil {
return time.Duration(0), err
}
// 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
}