Skip to content

Commit 154b23d

Browse files
authored
Fix display since time round (#14226)
* Fix display since time round * Fix since time * Fix tests
1 parent a7cfb9f commit 154b23d

File tree

2 files changed

+152
-37
lines changed

2 files changed

+152
-37
lines changed

modules/timeutil/since.go

Lines changed: 95 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package timeutil
77
import (
88
"fmt"
99
"html/template"
10+
"math"
1011
"strings"
1112
"time"
1213

@@ -25,7 +26,11 @@ const (
2526
Year = 12 * Month
2627
)
2728

28-
func computeTimeDiff(diff int64, lang string) (int64, string) {
29+
func round(s float64) int64 {
30+
return int64(math.Round(s))
31+
}
32+
33+
func computeTimeDiffFloor(diff int64, lang string) (int64, string) {
2934
diffStr := ""
3035
switch {
3136
case diff <= 0:
@@ -83,6 +88,94 @@ func computeTimeDiff(diff int64, lang string) (int64, string) {
8388
return diff, diffStr
8489
}
8590

91+
func computeTimeDiff(diff int64, lang string) (int64, string) {
92+
diffStr := ""
93+
switch {
94+
case diff <= 0:
95+
diff = 0
96+
diffStr = i18n.Tr(lang, "tool.now")
97+
case diff < 2:
98+
diff = 0
99+
diffStr = i18n.Tr(lang, "tool.1s")
100+
case diff < 1*Minute:
101+
diffStr = i18n.Tr(lang, "tool.seconds", diff)
102+
diff = 0
103+
104+
case diff < Minute+Minute/2:
105+
diff -= 1 * Minute
106+
diffStr = i18n.Tr(lang, "tool.1m")
107+
case diff < 1*Hour:
108+
minutes := round(float64(diff) / Minute)
109+
if minutes > 1 {
110+
diffStr = i18n.Tr(lang, "tool.minutes", minutes)
111+
} else {
112+
diffStr = i18n.Tr(lang, "tool.1m")
113+
}
114+
diff -= diff / Minute * Minute
115+
116+
case diff < Hour+Hour/2:
117+
diff -= 1 * Hour
118+
diffStr = i18n.Tr(lang, "tool.1h")
119+
case diff < 1*Day:
120+
hours := round(float64(diff) / Hour)
121+
if hours > 1 {
122+
diffStr = i18n.Tr(lang, "tool.hours", hours)
123+
} else {
124+
diffStr = i18n.Tr(lang, "tool.1h")
125+
}
126+
diff -= diff / Hour * Hour
127+
128+
case diff < Day+Day/2:
129+
diff -= 1 * Day
130+
diffStr = i18n.Tr(lang, "tool.1d")
131+
case diff < 1*Week:
132+
days := round(float64(diff) / Day)
133+
if days > 1 {
134+
diffStr = i18n.Tr(lang, "tool.days", days)
135+
} else {
136+
diffStr = i18n.Tr(lang, "tool.1d")
137+
}
138+
diff -= diff / Day * Day
139+
140+
case diff < Week+Week/2:
141+
diff -= 1 * Week
142+
diffStr = i18n.Tr(lang, "tool.1w")
143+
case diff < 1*Month:
144+
weeks := round(float64(diff) / Week)
145+
if weeks > 1 {
146+
diffStr = i18n.Tr(lang, "tool.weeks", weeks)
147+
} else {
148+
diffStr = i18n.Tr(lang, "tool.1w")
149+
}
150+
diff -= diff / Week * Week
151+
152+
case diff < 1*Month+Month/2:
153+
diff -= 1 * Month
154+
diffStr = i18n.Tr(lang, "tool.1mon")
155+
case diff < 1*Year:
156+
months := round(float64(diff) / Month)
157+
if months > 1 {
158+
diffStr = i18n.Tr(lang, "tool.months", months)
159+
} else {
160+
diffStr = i18n.Tr(lang, "tool.1mon")
161+
}
162+
diff -= diff / Month * Month
163+
164+
case diff < Year+Year/2:
165+
diff -= 1 * Year
166+
diffStr = i18n.Tr(lang, "tool.1y")
167+
default:
168+
years := round(float64(diff) / Year)
169+
if years > 1 {
170+
diffStr = i18n.Tr(lang, "tool.years", years)
171+
} else {
172+
diffStr = i18n.Tr(lang, "tool.1y")
173+
}
174+
diff -= (diff / Year) * Year
175+
}
176+
return diff, diffStr
177+
}
178+
86179
// MinutesToFriendly returns a user friendly string with number of minutes
87180
// converted to hours and minutes.
88181
func MinutesToFriendly(minutes int, lang string) string {
@@ -111,7 +204,7 @@ func timeSincePro(then, now time.Time, lang string) string {
111204
break
112205
}
113206

114-
diff, diffStr = computeTimeDiff(diff, lang)
207+
diff, diffStr = computeTimeDiffFloor(diff, lang)
115208
timeStr += ", " + diffStr
116209
}
117210
return strings.TrimPrefix(timeStr, ", ")

modules/timeutil/since_test.go

Lines changed: 57 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package timeutil
66

77
import (
8+
"fmt"
89
"os"
910
"testing"
1011
"time"
@@ -45,27 +46,39 @@ func TestTimeSince(t *testing.T) {
4546

4647
// test that each diff in `diffs` yields the expected string
4748
test := func(expected string, diffs ...time.Duration) {
48-
for _, diff := range diffs {
49-
actual := timeSince(BaseDate, BaseDate.Add(diff), "en")
50-
assert.Equal(t, i18n.Tr("en", "tool.ago", expected), actual)
51-
actual = timeSince(BaseDate.Add(diff), BaseDate, "en")
52-
assert.Equal(t, i18n.Tr("en", "tool.from_now", expected), actual)
53-
}
49+
t.Run(expected, func(t *testing.T) {
50+
for _, diff := range diffs {
51+
actual := timeSince(BaseDate, BaseDate.Add(diff), "en")
52+
assert.Equal(t, i18n.Tr("en", "tool.ago", expected), actual)
53+
actual = timeSince(BaseDate.Add(diff), BaseDate, "en")
54+
assert.Equal(t, i18n.Tr("en", "tool.from_now", expected), actual)
55+
}
56+
})
5457
}
5558
test("1 second", time.Second, time.Second+50*time.Millisecond)
5659
test("2 seconds", 2*time.Second, 2*time.Second+50*time.Millisecond)
57-
test("1 minute", time.Minute, time.Minute+30*time.Second)
58-
test("2 minutes", 2*time.Minute, 2*time.Minute+30*time.Second)
59-
test("1 hour", time.Hour, time.Hour+30*time.Minute)
60-
test("2 hours", 2*time.Hour, 2*time.Hour+30*time.Minute)
61-
test("1 day", DayDur, DayDur+12*time.Hour)
62-
test("2 days", 2*DayDur, 2*DayDur+12*time.Hour)
60+
test("1 minute", time.Minute, time.Minute+29*time.Second)
61+
test("2 minutes", 2*time.Minute, time.Minute+30*time.Second)
62+
test("2 minutes", 2*time.Minute, 2*time.Minute+29*time.Second)
63+
test("1 hour", time.Hour, time.Hour+29*time.Minute)
64+
test("2 hours", 2*time.Hour, time.Hour+30*time.Minute)
65+
test("2 hours", 2*time.Hour, 2*time.Hour+29*time.Minute)
66+
test("3 hours", 3*time.Hour, 2*time.Hour+30*time.Minute)
67+
test("1 day", DayDur, DayDur+11*time.Hour)
68+
test("2 days", 2*DayDur, DayDur+12*time.Hour)
69+
test("2 days", 2*DayDur, 2*DayDur+11*time.Hour)
70+
test("3 days", 3*DayDur, 2*DayDur+12*time.Hour)
6371
test("1 week", WeekDur, WeekDur+3*DayDur)
72+
test("2 weeks", 2*WeekDur, WeekDur+4*DayDur)
6473
test("2 weeks", 2*WeekDur, 2*WeekDur+3*DayDur)
65-
test("1 month", MonthDur, MonthDur+15*DayDur)
66-
test("2 months", 2*MonthDur, 2*MonthDur+15*DayDur)
67-
test("1 year", YearDur, YearDur+6*MonthDur)
68-
test("2 years", 2*YearDur, 2*YearDur+6*MonthDur)
74+
test("3 weeks", 3*WeekDur, 2*WeekDur+4*DayDur)
75+
test("1 month", MonthDur, MonthDur+14*DayDur)
76+
test("2 months", 2*MonthDur, MonthDur+15*DayDur)
77+
test("2 months", 2*MonthDur, 2*MonthDur+14*DayDur)
78+
test("1 year", YearDur, YearDur+5*MonthDur)
79+
test("2 years", 2*YearDur, YearDur+6*MonthDur)
80+
test("2 years", 2*YearDur, 2*YearDur+5*MonthDur)
81+
test("3 years", 3*YearDur, 2*YearDur+6*MonthDur)
6982
}
7083

7184
func TestTimeSincePro(t *testing.T) {
@@ -112,38 +125,47 @@ func TestHtmlTimeSince(t *testing.T) {
112125
}
113126
test("1 second", time.Second)
114127
test("3 minutes", 3*time.Minute+5*time.Second)
115-
test("1 day", DayDur+18*time.Hour)
116-
test("1 week", WeekDur+6*DayDur)
117-
test("3 months", 3*MonthDur+3*WeekDur)
128+
test("1 day", DayDur+11*time.Hour)
129+
test("1 week", WeekDur+3*DayDur)
130+
test("3 months", 3*MonthDur+2*WeekDur)
118131
test("2 years", 2*YearDur)
119-
test("3 years", 3*YearDur+11*MonthDur+4*WeekDur)
132+
test("3 years", 2*YearDur+11*MonthDur+4*WeekDur)
120133
}
121134

122135
func TestComputeTimeDiff(t *testing.T) {
123136
// test that for each offset in offsets,
124137
// computeTimeDiff(base + offset) == (offset, str)
125138
test := func(base int64, str string, offsets ...int64) {
126139
for _, offset := range offsets {
127-
diff, diffStr := computeTimeDiff(base+offset, "en")
128-
assert.Equal(t, offset, diff)
129-
assert.Equal(t, str, diffStr)
140+
t.Run(fmt.Sprintf("%s:%d", str, offset), func(t *testing.T) {
141+
diff, diffStr := computeTimeDiff(base+offset, "en")
142+
assert.Equal(t, offset, diff)
143+
assert.Equal(t, str, diffStr)
144+
})
130145
}
131146
}
132147
test(0, "now", 0)
133148
test(1, "1 second", 0)
134149
test(2, "2 seconds", 0)
135-
test(Minute, "1 minute", 0, 1, 30, Minute-1)
136-
test(2*Minute, "2 minutes", 0, Minute-1)
137-
test(Hour, "1 hour", 0, 1, Hour-1)
138-
test(5*Hour, "5 hours", 0, Hour-1)
139-
test(Day, "1 day", 0, 1, Day-1)
140-
test(5*Day, "5 days", 0, Day-1)
141-
test(Week, "1 week", 0, 1, Week-1)
142-
test(3*Week, "3 weeks", 0, 4*Day+25000)
143-
test(Month, "1 month", 0, 1, Month-1)
144-
test(10*Month, "10 months", 0, Month-1)
145-
test(Year, "1 year", 0, Year-1)
146-
test(3*Year, "3 years", 0, Year-1)
150+
test(Minute, "1 minute", 0, 1, 29)
151+
test(Minute, "2 minutes", 30, Minute-1)
152+
test(2*Minute, "2 minutes", 0, 29)
153+
test(2*Minute, "3 minutes", 30, Minute-1)
154+
test(Hour, "1 hour", 0, 1, 29*Minute)
155+
test(Hour, "2 hours", 30*Minute, Hour-1)
156+
test(5*Hour, "5 hours", 0, 29*Minute)
157+
test(Day, "1 day", 0, 1, 11*Hour)
158+
test(Day, "2 days", 12*Hour, Day-1)
159+
test(5*Day, "5 days", 0, 11*Hour)
160+
test(Week, "1 week", 0, 1, 3*Day)
161+
test(Week, "2 weeks", 4*Day, Week-1)
162+
test(3*Week, "3 weeks", 0, 3*Day)
163+
test(Month, "1 month", 0, 1)
164+
test(Month, "2 months", 16*Day, Month-1)
165+
test(10*Month, "10 months", 0, 13*Day)
166+
test(Year, "1 year", 0, 179*Day)
167+
test(Year, "2 years", 180*Day, Year-1)
168+
test(3*Year, "3 years", 0, 179*Day)
147169
}
148170

149171
func TestMinutesToFriendly(t *testing.T) {

0 commit comments

Comments
 (0)