diff --git a/pkg/utils/date.go b/pkg/utils/date.go index 0ced26172..2b9dcaf0f 100644 --- a/pkg/utils/date.go +++ b/pkg/utils/date.go @@ -7,17 +7,54 @@ import ( func UnixToTimeAgo(timestamp int64) string { now := time.Now().Unix() - delta := float64(now - timestamp) - // we go seconds, minutes, hours, days, weeks, months, years - conversions := []float64{60, 60, 24, 7, 4.34524, 12} - labels := []string{"s", "m", "h", "d", "w", "m", "y"} - for i, conversion := range conversions { - if delta < conversion { - return fmt.Sprintf("%d%s", int(delta), labels[i]) + return formatSecondsAgo(now - timestamp) +} + +const ( + SECONDS_IN_SECOND = 1 + SECONDS_IN_MINUTE = 60 + SECONDS_IN_HOUR = 3600 + SECONDS_IN_DAY = 86400 + SECONDS_IN_WEEK = 604800 + SECONDS_IN_YEAR = 31536000 + SECONDS_IN_MONTH = SECONDS_IN_YEAR / 12 +) + +type period struct { + label string + secondsInPeriod int64 +} + +var periods = []period{ + {"s", SECONDS_IN_SECOND}, + {"m", SECONDS_IN_MINUTE}, + {"h", SECONDS_IN_HOUR}, + {"d", SECONDS_IN_DAY}, + {"w", SECONDS_IN_WEEK}, + // we're using 'm' for both minutes and months which is ambiguous but + // disambiguating with another character feels like overkill. + {"m", SECONDS_IN_MONTH}, + {"y", SECONDS_IN_YEAR}, +} + +func formatSecondsAgo(secondsAgo int64) string { + for i, period := range periods { + if i == 0 { + continue + } + + if secondsAgo < period.secondsInPeriod { + return fmt.Sprintf("%d%s", + secondsAgo/periods[i-1].secondsInPeriod, + periods[i-1].label, + ) } - delta /= conversion } - return fmt.Sprintf("%dy", int(delta)) + + return fmt.Sprintf("%d%s", + secondsAgo/periods[len(periods)-1].secondsInPeriod, + periods[len(periods)-1].label, + ) } // formats the date in a smart way, if the date is today, it will show the time, otherwise it will show the date diff --git a/pkg/utils/date_test.go b/pkg/utils/date_test.go new file mode 100644 index 000000000..17deeed94 --- /dev/null +++ b/pkg/utils/date_test.go @@ -0,0 +1,97 @@ +package utils + +import ( + "testing" +) + +func TestFormatSecondsAgo(t *testing.T) { + tests := []struct { + name string + args int64 + want string + }{ + { + name: "zero", + args: 0, + want: "0s", + }, + { + name: "one second", + args: 1, + want: "1s", + }, + { + name: "almost a minute", + args: 59, + want: "59s", + }, + { + name: "one minute", + args: 60, + want: "1m", + }, + { + name: "one minute and one second", + args: 61, + want: "1m", + }, + { + name: "almost one hour", + args: 3599, + want: "59m", + }, + { + name: "one hour", + args: 3600, + want: "1h", + }, + { + name: "almost one day", + args: 86399, + want: "23h", + }, + { + name: "one day", + args: 86400, + want: "1d", + }, + { + name: "almost a week", + args: 604799, + want: "6d", + }, + { + name: "one week", + args: 604800, + want: "1w", + }, + { + name: "six months", + args: SECONDS_IN_YEAR / 2, + want: "6m", + }, + { + name: "almost one year", + args: 31535999, + want: "11m", + }, + { + name: "one year", + args: SECONDS_IN_YEAR, + want: "1y", + }, + { + name: "50 years", + args: SECONDS_IN_YEAR * 50, + want: "50y", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := formatSecondsAgo(tt.args); got != tt.want { + t.Errorf("formatSecondsAgo(%d) = %v, want %v", tt.args, got, tt.want) + } + }) + } +}