Passed
Pull Request — main (#40)
by Serhii
01:18
created

timeago.Parse   A

Complexity

Conditions 5

Size

Total Lines 23
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 16
dl 0
loc 23
rs 9.1333
c 0
b 0
f 0
nop 2
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
// Parse coverts privided datetime into `x time ago` format.
28
// The first argument can have 3 types:
29
// 1. int (Unix timestamp)
30
// 2. time.Time (Type from Go time package)
31
// 3. string (Datetime string in format 'YYYY-MM-DD HH:MM:SS')
32
func Parse(datetime interface{}, opts ...Option) (string, error) {
33
	options = []Option{}
34
	langSet = nil
35
36
	var input time.Time
37
	var err error
38
39
	switch date := datetime.(type) {
40
	case int:
41
		input = unixToTime(date)
42
	case string:
43
		input, err = strToTime(date)
44
	default:
45
		input = datetime.(time.Time)
46
	}
47
48
	if err != nil {
49
		return "", err
50
	}
51
52
	enableOptions(opts)
53
54
	return computeTimeSince(input)
55
}
56
57
// Configure applies the given configuration to the timeago without
58
// overriding the previous configuration. It will only override the
59
// provided configuration. If you want to override the previous
60
// configurations, use Reconfigure function instead.
61
func Configure(c Config) {
62
	if c.Language != "" {
63
		conf.Language = c.Language
64
	}
65
66
	if c.Location != "" {
67
		conf.Location = c.Location
68
	}
69
70
	if len(c.Translations) > 0 {
71
		conf.Translations = c.Translations
72
	}
73
}
74
75
// Reconfigure reconfigures the timeago with the provided configuration.
76
// It will override the previous configuration with the new one.
77
func Reconfigure(c Config) {
78
	conf = defaultConfig()
79
	cachedJsonRes = map[string]*LangSet{}
80
81
	Configure(c)
82
}
83
84
func defaultConfig() *Config {
85
	return NewConfig("en", "", []LangSet{})
86
}
87
88
func strToTime(datetime string) (time.Time, error) {
89
	if !conf.isLocationProvided() {
90
		parsedTime, _ := time.Parse("2006-01-02 15:04:05", datetime)
91
		return parsedTime, nil
92
	}
93
94
	loc, err := location()
95
96
	if err != nil {
97
		return time.Time{}, err
98
	}
99
100
	parsedTime, err := time.ParseInLocation("2006-01-02 15:04:05", datetime, loc)
101
102
	if err != nil {
103
		return time.Time{}, errorf("%v", err)
104
	}
105
106
	return parsedTime, nil
107
}
108
109
// location loads location from the time package
110
func location() (*time.Location, error) {
111
	if !conf.isLocationProvided() {
112
		return time.Now().Location(), nil
113
	}
114
115
	loc, err := time.LoadLocation(conf.Location)
116
117
	if err != nil {
118
		return nil, errorf("%v", err)
119
	}
120
121
	return loc, nil
122
}
123
124
func computeTimeSince(t time.Time) (string, error) {
125
	now := time.Now()
126
127
	if conf.isLocationProvided() {
128
		loc, err := location()
129
130
		if err != nil {
131
			return "", err
132
		}
133
134
		t = t.In(loc)
135
		now = now.In(loc)
136
	}
137
138
	seconds := int(now.Sub(t).Seconds())
139
140
	if seconds < 0 {
141
		enableOption(Upcoming)
142
		seconds = -seconds
143
	}
144
145
	set, err := newLangSet()
146
147
	if err != nil {
148
		return "", err
149
	}
150
151
	langSet = set
152
153
	return computeTimeUnit(seconds)
154
}
155
156
func computeTimeUnit(seconds int) (string, error) {
157
	minutes, hours, days, weeks, months, years := timeCalculations(float64(seconds))
158
159
	switch {
160
	case optionIsEnabled("online") && seconds < 60:
161
		return langSet.Online, nil
162
	case optionIsEnabled("justNow") && seconds < 60:
163
		return langSet.JustNow, nil
164
	case seconds < 60:
165
		return timeUnits(langSet.Second, seconds)
166
	case minutes < 60:
167
		return timeUnits(langSet.Minute, minutes)
168
	case hours < 24:
169
		return timeUnits(langSet.Hour, hours)
170
	case days < 7:
171
		return timeUnits(langSet.Day, days)
172
	case weeks < 4:
173
		return timeUnits(langSet.Week, weeks)
174
	case months < 12:
175
		if months == 0 {
176
			months = 1
177
		}
178
179
		return timeUnits(langSet.Month, months)
180
	}
181
182
	return timeUnits(langSet.Year, years)
183
}
184
185
// timeUnits decides rather the word must be singular or plural,
186
// and depending on the result it adds the correct word after
187
// the time number.
188
func timeUnits(langForm LangForms, num int) (string, error) {
189
	timeUnit, err := finalTimeUnit(langForm, num)
190
191
	if err != nil {
192
		return "", err
193
	}
194
195
	res := langSet.Format
196
	res = strings.Replace(res, "{timeUnit}", timeUnit, -1)
197
	res = strings.Replace(res, "{num}", strconv.Itoa(num), -1)
198
199
	if optionIsEnabled("noSuffix") || optionIsEnabled("upcoming") {
200
		res = strings.Replace(res, "{ago}", "", -1)
201
		return strings.Trim(res, " "), nil
202
	}
203
204
	return strings.Replace(res, "{ago}", langSet.Ago, -1), nil
205
}
206
207
func finalTimeUnit(langForm LangForms, num int) (string, error) {
208
	form, err := timeUnitForm(num)
209
210
	if err != nil {
211
		return "", err
212
	}
213
214
	if unit, ok := langForm[form]; ok {
215
		return unit, nil
216
	}
217
218
	return langForm["other"], nil
219
}
220
221
func timeUnitForm(num int) (string, error) {
222
	rule, err := identifyGrammarRules(num, conf.Language)
223
224
	if err != nil {
225
		return "", err
226
	}
227
228
	switch {
229
	case rule.Zero:
230
		return "zero", nil
231
	case rule.One:
232
		return "one", nil
233
	case rule.Few:
234
		return "few", nil
235
	case rule.Two:
236
		return "two", nil
237
	case rule.Many:
238
		return "many", nil
239
	}
240
241
	return "other", nil
242
}
243
244
func timeCalculations(seconds float64) (int, int, int, int, int, int) {
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 int(minutes), int(hours), int(days), int(weeks), int(months), int(years)
253
}
254