Passed
Push — v3.1.0 ( 23510f...abc924 )
by Serhii
01:21
created

timeago.Configure   B

Complexity

Conditions 6

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 11
dl 0
loc 19
rs 8.6666
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 = []opt{}
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 ...opt) (string, error) {
43
	options = []opt{}
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.OnlineThreshold > 0 {
73
		conf.OnlineThreshold = c.OnlineThreshold
74
	}
75
76
	if c.JustNowThreshold > 0 {
77
		conf.JustNowThreshold = c.JustNowThreshold
78
	}
79
80
	if c.Language != "" {
81
		conf.Language = c.Language
82
	}
83
84
	if c.Location != "" {
85
		conf.Location = c.Location
86
	}
87
88
	if len(c.Translations) > 0 {
89
		conf.Translations = c.Translations
90
	}
91
}
92
93
// Reconfigure reconfigures the timeago with the provided configuration.
94
// It will override the previous configuration with the new one.
95
func Reconfigure(c Config) {
96
	conf = defaultConfig()
97
	cachedJsonRes = map[string]*LangSet{}
98
	Configure(c)
99
}
100
101
func defaultConfig() *Config {
102
	return NewConfig("en", "UTC", []LangSet{}, 60, 60)
103
}
104
105
func strToTime(datetime string) (time.Time, error) {
106
	if !conf.isLocationProvided() {
107
		parsedTime, _ := time.Parse("2006-01-02 15:04:05", datetime)
108
		return parsedTime, nil
109
	}
110
111
	loc, err := location()
112
	if err != nil {
113
		return time.Time{}, err
114
	}
115
116
	parsedTime, err := time.ParseInLocation("2006-01-02 15:04:05", datetime, loc)
117
	if err != nil {
118
		return time.Time{}, errorf("%v", err)
119
	}
120
121
	return parsedTime, nil
122
}
123
124
// location loads location from the time package
125
func location() (*time.Location, error) {
126
	if !conf.isLocationProvided() {
127
		return time.Now().Location(), nil
128
	}
129
130
	loc, err := time.LoadLocation(conf.Location)
131
	if err != nil {
132
		return nil, errorf("%v", err)
133
	}
134
135
	return loc, nil
136
}
137
138
func computeTimeSince(t time.Time) (string, error) {
139
	now := time.Now()
140
	var err error
141
142
	// Adjust times based on location if provided
143
	if t, now, err = adjustTimesForLocation(t, now); err != nil {
144
		return "", err
145
	}
146
147
	timeInSec := computeTimeDifference(t, now)
148
149
	if langSet, err = newLangSet(); err != nil {
150
		return "", err
151
	}
152
153
	if optionIsEnabled(OptOnline) && timeInSec < 60 {
154
		return langSet.Online, nil
155
	}
156
157
	if optionIsEnabled(OptJustNow) && timeInSec < 60 {
158
		return langSet.JustNow, nil
159
	}
160
161
	var timeUnit string
162
163
	langForms, timeNum := findLangForms(timeInSec)
164
165
	if timeUnit, err = computeTimeUnit(langForms, timeNum); err != nil {
166
		return "", err
167
	}
168
169
	suffix := computeSuffix()
170
171
	return mergeFinalOutput(timeNum, timeUnit, suffix)
172
}
173
174
// adjustTimesForLocation adjusts the given times based on the provided location.
175
func adjustTimesForLocation(t, now time.Time) (time.Time, time.Time, error) {
176
	if !conf.isLocationProvided() {
177
		return t, now, nil
178
	}
179
180
	loc, err := location()
181
	if err != nil {
182
		return t, now, err
183
	}
184
185
	return t.In(loc), now.In(loc), nil
186
}
187
188
// computeTimeDifference returns the absolute time difference in seconds.
189
func computeTimeDifference(t, now time.Time) int {
190
	timeInSec := int(now.Sub(t).Seconds())
191
	if timeInSec < 0 {
192
		enableOption(OptUpcoming)
193
		return -timeInSec
194
	}
195
196
	return timeInSec
197
}
198
199
func mergeFinalOutput(timeNum int, timeUnit, suffix string) (string, error) {
200
	replacer := strings.NewReplacer(
201
		"{timeUnit}", timeUnit,
202
		"{num}", strconv.Itoa(timeNum),
203
		"{ago}", suffix,
204
	)
205
206
	out := replacer.Replace(langSet.Format)
207
208
	return strings.TrimSpace(out), nil
209
}
210
211
func findLangForms(timeInSec int) (LangForms, int) {
212
	nums := calculateTimeNumbers(float64(timeInSec))
213
214
	switch {
215
	case timeInSec < 60:
216
		return langSet.Second, nums.Seconds
217
	case nums.Minutes < 60:
218
		return langSet.Minute, nums.Minutes
219
	case nums.Hours < 24:
220
		return langSet.Hour, nums.Hours
221
	case nums.Days < 7:
222
		return langSet.Day, nums.Days
223
	case nums.Weeks < 4:
224
		return langSet.Week, nums.Weeks
225
	case nums.Months < 12:
226
		if nums.Months == 0 {
227
			nums.Months = 1
228
		}
229
230
		return langSet.Month, nums.Months
231
	}
232
233
	return langSet.Year, nums.Years
234
}
235
236
func computeSuffix() string {
237
	if optionIsEnabled(OptNoSuffix) || optionIsEnabled(OptUpcoming) {
238
		return ""
239
	}
240
241
	return langSet.Ago
242
}
243
244
func calculateTimeNumbers(seconds float64) timeNumbers {
245
	minutes := math.Round(seconds / 60)
246
	hours := math.Round(seconds / 3600)
247
	days := math.Round(seconds / 86400)
248
	weeks := math.Round(seconds / 604800)
249
	months := math.Round(seconds / 2629440)
250
	years := math.Round(seconds / 31553280)
251
252
	return timeNumbers{
253
		Seconds: int(seconds),
254
		Minutes: int(minutes),
255
		Hours:   int(hours),
256
		Days:    int(days),
257
		Weeks:   int(weeks),
258
		Months:  int(months),
259
		Years:   int(years),
260
	}
261
}
262
263
func computeTimeUnit(langForm LangForms, num int) (string, error) {
264
	form, err := timeUnitForm(num)
265
	if err != nil {
266
		return "", err
267
	}
268
269
	if unit, ok := langForm[form]; ok {
270
		return unit, nil
271
	}
272
273
	return langForm["other"], nil
274
}
275
276
func timeUnitForm(num int) (string, error) {
277
	rule, err := identifyGrammarRules(num, conf.Language)
278
	if err != nil {
279
		return "", err
280
	}
281
282
	switch {
283
	case rule.Zero:
284
		return "zero", nil
285
	case rule.One:
286
		return "one", nil
287
	case rule.Few:
288
		return "few", nil
289
	case rule.Two:
290
		return "two", nil
291
	case rule.Many:
292
		return "many", nil
293
	}
294
295
	return "other", nil
296
}
297