Passed
Push — v3.1.0 ( 6c2907...78424f )
by Serhii
01:13
created

timeago.computeTimeSince   B

Complexity

Conditions 8

Size

Total Lines 34
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 18
dl 0
loc 34
rs 7.3333
c 0
b 0
f 0
nop 1
1
package timeago
2
3
import (
4
	"math"
5
	"strconv"
6
	"strings"
7
	"time"
8
)
9
10
var (
11
	// cachedJsonRes saves parsed JSON translations to prevent
12
	// parsing the same JSON file multiple times.
13
	cachedJsonRes = map[string]*LangSet{}
14
15
	// options is a list of options that modify the final output.
16
	// Some options are noSuffix, upcoming, online, and justNow.
17
	options = []Option{}
18
19
	// conf is configuration provided by the user.
20
	conf = defaultConfig()
21
22
	// langSet is a pointer to the current language set that
23
	// is currently being used.
24
	langSet *LangSet
25
)
26
27
type timeNumbers struct {
28
	Seconds int
29
	Minutes int
30
	Hours   int
31
	Days    int
32
	Weeks   int
33
	Months  int
34
	Years   int
35
}
36
37
// Parse coverts privided datetime into `x time ago` format.
38
// The first argument can have 3 types:
39
// 1. int (Unix timestamp)
40
// 2. time.Time (Type from Go time package)
41
// 3. string (Datetime string in format 'YYYY-MM-DD HH:MM:SS')
42
func Parse(datetime interface{}, opts ...Option) (string, error) {
43
	options = []Option{}
44
	langSet = nil
45
46
	var input time.Time
47
	var err error
48
49
	switch date := datetime.(type) {
50
	case int:
51
		input = unixToTime(date)
52
	case string:
53
		input, err = strToTime(date)
54
	default:
55
		input = datetime.(time.Time)
56
	}
57
58
	if err != nil {
59
		return "", err
60
	}
61
62
	enableOptions(opts)
63
64
	return computeTimeSince(input)
65
}
66
67
// Configure applies the given configuration to the timeago without
68
// overriding the previous configuration. It will only override the
69
// provided configuration. If you want to override the previous
70
// configurations, use Reconfigure function instead.
71
func Configure(c Config) {
72
	if c.Language != "" {
73
		conf.Language = c.Language
74
	}
75
76
	if c.Location != "" {
77
		conf.Location = c.Location
78
	}
79
80
	if len(c.Translations) > 0 {
81
		conf.Translations = c.Translations
82
	}
83
}
84
85
// Reconfigure reconfigures the timeago with the provided configuration.
86
// It will override the previous configuration with the new one.
87
func Reconfigure(c Config) {
88
	conf = defaultConfig()
89
	cachedJsonRes = map[string]*LangSet{}
90
	Configure(c)
91
}
92
93
func defaultConfig() *Config {
94
	return NewConfig("en", "", []LangSet{})
95
}
96
97
func strToTime(datetime string) (time.Time, error) {
98
	if !conf.isLocationProvided() {
99
		parsedTime, _ := time.Parse("2006-01-02 15:04:05", datetime)
100
		return parsedTime, nil
101
	}
102
103
	loc, err := location()
104
	if err != nil {
105
		return time.Time{}, err
106
	}
107
108
	parsedTime, err := time.ParseInLocation("2006-01-02 15:04:05", datetime, loc)
109
	if err != nil {
110
		return time.Time{}, errorf("%v", err)
111
	}
112
113
	return parsedTime, nil
114
}
115
116
// location loads location from the time package
117
func location() (*time.Location, error) {
118
	if !conf.isLocationProvided() {
119
		return time.Now().Location(), nil
120
	}
121
122
	loc, err := time.LoadLocation(conf.Location)
123
	if err != nil {
124
		return nil, errorf("%v", err)
125
	}
126
127
	return loc, nil
128
}
129
130
func computeTimeSince(t time.Time) (string, error) {
131
	now := time.Now()
132
	var err error
133
134
	// Adjust times based on location if provided
135
	if t, now, err = adjustTimesForLocation(t, now); err != nil {
136
		return "", err
137
	}
138
139
	timeInSec := computeTimeDifference(t, now)
140
141
	if langSet, err = newLangSet(); err != nil {
142
		return "", err
143
	}
144
145
	if optionIsEnabled("online") && timeInSec < 60 {
146
		return langSet.Online, nil
147
	}
148
149
	if optionIsEnabled("justNow") && timeInSec < 60 {
150
		return langSet.JustNow, nil
151
	}
152
153
	var timeUnit string
154
155
	langForms, timeNum := findLangForms(timeInSec)
156
157
	if timeUnit, err = computeTimeUnit(langForms, timeNum); err != nil {
158
		return "", err
159
	}
160
161
	suffix := computeSuffix()
162
163
	return mergeFinalOutput(timeNum, timeUnit, suffix)
164
}
165
166
// adjustTimesForLocation adjusts the given times based on the provided location.
167
func adjustTimesForLocation(t, now time.Time) (time.Time, time.Time, error) {
168
	if !conf.isLocationProvided() {
169
		return t, now, nil
170
	}
171
172
	loc, err := location()
173
	if err != nil {
174
		return t, now, err
175
	}
176
177
	return t.In(loc), now.In(loc), nil
178
}
179
180
// computeTimeDifference returns the absolute time difference in seconds.
181
func computeTimeDifference(t, now time.Time) int {
182
	timeInSec := int(now.Sub(t).Seconds())
183
	if timeInSec < 0 {
184
		enableOption(Upcoming)
185
		return -timeInSec
186
	}
187
188
	return timeInSec
189
}
190
191
func mergeFinalOutput(timeNum int, timeUnit, suffix string) (string, error) {
192
	replacer := strings.NewReplacer(
193
		"{timeUnit}", timeUnit,
194
		"{num}", strconv.Itoa(timeNum),
195
		"{ago}", suffix,
196
	)
197
198
	out := replacer.Replace(langSet.Format)
199
200
	return strings.TrimSpace(out), nil
201
}
202
203
func findLangForms(timeInSec int) (LangForms, int) {
204
	nums := calculateTimeNumbers(float64(timeInSec))
205
206
	switch {
207
	case timeInSec < 60:
208
		return langSet.Second, nums.Seconds
209
	case nums.Minutes < 60:
210
		return langSet.Minute, nums.Minutes
211
	case nums.Hours < 24:
212
		return langSet.Hour, nums.Hours
213
	case nums.Days < 7:
214
		return langSet.Day, nums.Days
215
	case nums.Weeks < 4:
216
		return langSet.Week, nums.Weeks
217
	case nums.Months < 12:
218
		if nums.Months == 0 {
219
			nums.Months = 1
220
		}
221
222
		return langSet.Month, nums.Months
223
	}
224
225
	return langSet.Year, nums.Years
226
}
227
228
func computeSuffix() string {
229
	if optionIsEnabled("noSuffix") || optionIsEnabled("upcoming") {
230
		return ""
231
	}
232
233
	return langSet.Ago
234
}
235
236
func calculateTimeNumbers(seconds float64) timeNumbers {
237
	minutes := math.Round(seconds / 60)
238
	hours := math.Round(seconds / 3600)
239
	days := math.Round(seconds / 86400)
240
	weeks := math.Round(seconds / 604800)
241
	months := math.Round(seconds / 2629440)
242
	years := math.Round(seconds / 31553280)
243
244
	return timeNumbers{
245
		Seconds: int(seconds),
246
		Minutes: int(minutes),
247
		Hours:   int(hours),
248
		Days:    int(days),
249
		Weeks:   int(weeks),
250
		Months:  int(months),
251
		Years:   int(years),
252
	}
253
}
254
255
func computeTimeUnit(langForm LangForms, num int) (string, error) {
256
	form, err := timeUnitForm(num)
257
	if err != nil {
258
		return "", err
259
	}
260
261
	if unit, ok := langForm[form]; ok {
262
		return unit, nil
263
	}
264
265
	return langForm["other"], nil
266
}
267
268
func timeUnitForm(num int) (string, error) {
269
	rule, err := identifyGrammarRules(num, conf.Language)
270
	if err != nil {
271
		return "", err
272
	}
273
274
	switch {
275
	case rule.Zero:
276
		return "zero", nil
277
	case rule.One:
278
		return "one", nil
279
	case rule.Few:
280
		return "few", nil
281
	case rule.Two:
282
		return "two", nil
283
	case rule.Many:
284
		return "many", nil
285
	}
286
287
	return "other", nil
288
}
289