Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
go_version: ['1.16', '1.17', '1.18', '1.19', '1.20', '1.21', '1.22', '1.23']
go_version: ['1.16', '1.17', '1.18', '1.19', '1.20', '1.21', '1.22', '1.23', '1.24']

steps:
- name: Check out code into the Go module directory
Expand Down
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"cSpell.words": [
"datetime",
"justnow",
"nums",
"timeago"
]
}
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Release Notes v3

## v3.3.0 (2025-06-16)
- Now you can parse different times of timestamps, such as `string`, `int64`, `uint`, `uint64`, `int32`, `uint32`

## v3.2.2 (2025-06-04)
- Change so that all JSON files are included in the final binary by using `go:embed` functionality
- Remove support for go version 1.13, 1.14, 1.15 because they do not have `go:embed`
Expand Down
57 changes: 0 additions & 57 deletions internal/utils/duration.go

This file was deleted.

58 changes: 58 additions & 0 deletions internal/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,68 @@ import (
"time"
)

const (
second time.Duration = time.Second
minute time.Duration = time.Minute
hour time.Duration = time.Hour
day time.Duration = hour * 24
week time.Duration = day * 7
month time.Duration = day * 30
year time.Duration = day * 365
)

func UnixFromPastDate(subDuration time.Duration) int {
return int(SubTime(subDuration).UnixNano() / 1000000000)
}

func UnixFromFutureDate(duration time.Duration) int {
return int(AddTime(duration).UnixNano() / 1000000000)
}

func Errorf(msg string, a ...interface{}) error {
return fmt.Errorf("[Timeago]: "+msg, a...)
}

func SubTime(duration time.Duration) time.Time {
return time.Now().Add(-duration)
}

func AddTime(duration time.Duration) time.Time {
return time.Now().Add(duration)
}

func SubSeconds(duration time.Duration) time.Time {
return SubTime(second * duration)
}

func SubMinutes(duration time.Duration) time.Time {
return SubTime(minute * duration)
}

func AddMinutes(duration time.Duration) time.Time {
return AddTime(minute * duration)
}

func SubHours(duration time.Duration) time.Time {
return SubTime(hour * duration)
}

func AddHours(duration time.Duration) time.Time {
return AddTime(hour * duration)
}

func SubDays(duration time.Duration) time.Time {
return SubTime(day * duration)
}

func SubWeeks(duration time.Duration) time.Time {
return SubTime(week * duration)
}

func SubMonths(duration time.Duration) time.Time {
return SubTime(month * duration)
}

func SubYears(duration time.Duration) time.Time {
return SubTime(year * duration)
}
35 changes: 29 additions & 6 deletions timeago.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ type timeNumbers struct {
Years int
}

// Parse coverts privided datetime into `x time ago` format.
// The first argument can have 3 types:
// 1. int (Unix timestamp)
// 2. time.Time (Type from Go time package)
// 3. string (Datetime string in format 'YYYY-MM-DD HH:MM:SS')
// Parse coverts provided datetime into `x time ago` format.
// The first argument can different types of inputs:
// 1. Unix timestamp: int, int32, int64, unit, uint32, uint64, string
// 2. Type from Go time package: time.Time
// 3. Datetime string in format 'YYYY-MM-DD HH:MM:SS': string
func Parse(date interface{}, opts ...opt) (string, error) {
options = []opt{}
langSet = nil
Expand All @@ -51,8 +51,22 @@ func Parse(date interface{}, opts ...opt) (string, error) {
switch userDate := date.(type) {
case int:
t = unixToTime(userDate)
case int32:
t = unixToTime(int(userDate))
case int64:
t = unixToTime(int(userDate))
case uint:
t = unixToTime(int(userDate))
case uint32:
t = unixToTime(int(userDate))
case uint64:
t = unixToTime(int(userDate))
case string:
t, err = strToTime(userDate)
if isUnsignedInteger(userDate) {
t, err = strTimestampToTime(userDate)
} else {
t, err = strToTime(userDate)
}
default:
t = date.(time.Time)
}
Expand Down Expand Up @@ -104,6 +118,15 @@ func defaultConfig() *Config {
return NewConfig("en", "UTC", []LangSet{}, 60, 60)
}

func strTimestampToTime(userDate string) (time.Time, error) {
sec, err := strconv.Atoi(userDate)
if err != nil {
return time.Time{}, err
}

return unixToTime(sec), nil
}

func strToTime(userDate string) (time.Time, error) {
if !conf.isLocationProvided() {
parsedTime, _ := time.Parse(time.DateTime, userDate)
Expand Down
146 changes: 92 additions & 54 deletions timeago_test.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
package timeago

import (
"strconv"
"testing"
"time"

"github.com/SerhiiCho/timeago/v3/internal/utils"
)

func TestParseFunctionCanExceptTimestamp(t *testing.T) {
func TestParse(t *testing.T) {
cases := []struct {
date int
date interface{}
res string
}{
{utils.UnixFromPastDate(time.Minute), "1 minute ago"},
// Integer timestamp input parsing
{utils.UnixFromPastDate(time.Minute * 5), "5 minutes ago"},
{utils.UnixFromPastDate(time.Hour), "1 hour ago"},
{utils.UnixFromPastDate(time.Hour * 3), "3 hours ago"},
Expand All @@ -24,31 +25,79 @@ func TestParseFunctionCanExceptTimestamp(t *testing.T) {
{utils.UnixFromPastDate(time.Hour * 24 * 5), "5 days ago"},
{utils.UnixFromPastDate(time.Hour * 24 * 6), "6 days ago"},
{utils.UnixFromPastDate(time.Hour * 24 * 7), "1 week ago"},
}

Reconfigure(Config{Language: LangEn})

for _, tc := range cases {
t.Run(tc.res, func(t *testing.T) {
res, err := Parse(tc.date)

if err != nil {
t.Errorf("Error must be nil, but got %q instead", err)
}

if res != tc.res {
t.Errorf("Result must be %q, but got %q instead", tc.res, res)
}
})
}
}

func TestParseFunctionCanExceptTimePackage(t *testing.T) {
cases := []struct {
date time.Time
res string
}{
{utils.SubMinutes(1), "1 minute ago"},
// Integer 64 timestamp input parsing
{int64(utils.UnixFromPastDate(time.Minute * 5)), "5 minutes ago"},
{int64(utils.UnixFromPastDate(time.Hour)), "1 hour ago"},
{int64(utils.UnixFromPastDate(time.Hour * 3)), "3 hours ago"},
{int64(utils.UnixFromPastDate(time.Hour * 5)), "5 hours ago"},
{int64(utils.UnixFromPastDate(time.Hour * 24)), "1 day ago"},
{int64(utils.UnixFromPastDate(time.Hour * 24 * 2)), "2 days ago"},
{int64(utils.UnixFromPastDate(time.Hour * 24 * 3)), "3 days ago"},
{int64(utils.UnixFromPastDate(time.Hour * 24 * 4)), "4 days ago"},
{int64(utils.UnixFromPastDate(time.Hour * 24 * 5)), "5 days ago"},
{int64(utils.UnixFromPastDate(time.Hour * 24 * 6)), "6 days ago"},
{int64(utils.UnixFromPastDate(time.Hour * 24 * 7)), "1 week ago"},
// Unsigned Integer timestamp input parsing
{uint(utils.UnixFromPastDate(time.Minute * 5)), "5 minutes ago"},
{uint(utils.UnixFromPastDate(time.Hour)), "1 hour ago"},
{uint(utils.UnixFromPastDate(time.Hour * 3)), "3 hours ago"},
{uint(utils.UnixFromPastDate(time.Hour * 5)), "5 hours ago"},
{uint(utils.UnixFromPastDate(time.Hour * 24)), "1 day ago"},
{uint(utils.UnixFromPastDate(time.Hour * 24 * 2)), "2 days ago"},
{uint(utils.UnixFromPastDate(time.Hour * 24 * 3)), "3 days ago"},
{uint(utils.UnixFromPastDate(time.Hour * 24 * 4)), "4 days ago"},
{uint(utils.UnixFromPastDate(time.Hour * 24 * 5)), "5 days ago"},
{uint(utils.UnixFromPastDate(time.Hour * 24 * 6)), "6 days ago"},
{uint(utils.UnixFromPastDate(time.Hour * 24 * 7)), "1 week ago"},
// Unsigned Integer 32 timestamp input parsing
{uint32(utils.UnixFromPastDate(time.Minute * 5)), "5 minutes ago"},
{uint32(utils.UnixFromPastDate(time.Hour)), "1 hour ago"},
{uint32(utils.UnixFromPastDate(time.Hour * 3)), "3 hours ago"},
{uint32(utils.UnixFromPastDate(time.Hour * 5)), "5 hours ago"},
{uint32(utils.UnixFromPastDate(time.Hour * 24)), "1 day ago"},
{uint32(utils.UnixFromPastDate(time.Hour * 24 * 2)), "2 days ago"},
{uint32(utils.UnixFromPastDate(time.Hour * 24 * 3)), "3 days ago"},
{uint32(utils.UnixFromPastDate(time.Hour * 24 * 4)), "4 days ago"},
{uint32(utils.UnixFromPastDate(time.Hour * 24 * 5)), "5 days ago"},
{uint32(utils.UnixFromPastDate(time.Hour * 24 * 6)), "6 days ago"},
{uint32(utils.UnixFromPastDate(time.Hour * 24 * 7)), "1 week ago"},
// Unsigned Integer 64 timestamp input parsing
{uint64(utils.UnixFromPastDate(time.Minute * 5)), "5 minutes ago"},
{uint64(utils.UnixFromPastDate(time.Hour)), "1 hour ago"},
{uint64(utils.UnixFromPastDate(time.Hour * 3)), "3 hours ago"},
{uint64(utils.UnixFromPastDate(time.Hour * 5)), "5 hours ago"},
{uint64(utils.UnixFromPastDate(time.Hour * 24)), "1 day ago"},
{uint64(utils.UnixFromPastDate(time.Hour * 24 * 2)), "2 days ago"},
{uint64(utils.UnixFromPastDate(time.Hour * 24 * 3)), "3 days ago"},
{uint64(utils.UnixFromPastDate(time.Hour * 24 * 4)), "4 days ago"},
{uint64(utils.UnixFromPastDate(time.Hour * 24 * 5)), "5 days ago"},
{uint64(utils.UnixFromPastDate(time.Hour * 24 * 6)), "6 days ago"},
{uint64(utils.UnixFromPastDate(time.Hour * 24 * 7)), "1 week ago"},
// Integer 32 timestamp input parsing
{int32(utils.UnixFromPastDate(time.Minute * 5)), "5 minutes ago"},
{int32(utils.UnixFromPastDate(time.Hour)), "1 hour ago"},
{int32(utils.UnixFromPastDate(time.Hour * 3)), "3 hours ago"},
{int32(utils.UnixFromPastDate(time.Hour * 5)), "5 hours ago"},
{int32(utils.UnixFromPastDate(time.Hour * 24)), "1 day ago"},
{int32(utils.UnixFromPastDate(time.Hour * 24 * 2)), "2 days ago"},
{int32(utils.UnixFromPastDate(time.Hour * 24 * 3)), "3 days ago"},
{int32(utils.UnixFromPastDate(time.Hour * 24 * 4)), "4 days ago"},
{int32(utils.UnixFromPastDate(time.Hour * 24 * 5)), "5 days ago"},
{int32(utils.UnixFromPastDate(time.Hour * 24 * 6)), "6 days ago"},
{int32(utils.UnixFromPastDate(time.Hour * 24 * 7)), "1 week ago"},
// Negative integer timestamp input parsing
{utils.UnixFromFutureDate(time.Minute * 5), "5 minutes"},
{utils.UnixFromFutureDate(time.Hour), "1 hour"},
{utils.UnixFromFutureDate(time.Hour * 3), "3 hours"},
{utils.UnixFromFutureDate(time.Hour * 5), "5 hours"},
{utils.UnixFromFutureDate(time.Hour * 24), "1 day"},
{utils.UnixFromFutureDate(time.Hour * 24 * 2), "2 days"},
{utils.UnixFromFutureDate(time.Hour * 24 * 3), "3 days"},
{utils.UnixFromFutureDate(time.Hour * 24 * 4), "4 days"},
{utils.UnixFromFutureDate(time.Hour * 24 * 5), "5 days"},
{utils.UnixFromFutureDate(time.Hour * 24 * 6), "6 days"},
{utils.UnixFromFutureDate(time.Hour * 24 * 7), "1 week"},
// time.Time input parsing
{utils.SubMinutes(2), "2 minutes ago"},
{utils.SubMinutes(3), "3 minutes ago"},
{utils.SubMinutes(4), "4 minutes ago"},
Expand All @@ -59,42 +108,31 @@ func TestParseFunctionCanExceptTimePackage(t *testing.T) {
{utils.SubHours(9), "9 hours ago"},
{utils.SubHours(10), "10 hours ago"},
{utils.SubHours(11), "11 hours ago"},
}

Reconfigure(Config{Language: LangEn})

for _, tc := range cases {
t.Run("Test for date "+tc.date.String(), func(t *testing.T) {
res, err := Parse(tc.date)

if err != nil {
t.Errorf("Error must be nil, but got %q instead", err)
}

if res != tc.res {
t.Errorf("Result must be %q, but got %q instead", tc.res, res)
}
})
}
}

func TestParseFuncWillCalculateIntervalToFutureDate(t *testing.T) {
testCases := []struct {
date time.Time
res string
}{
// time.Time future date parsing
{utils.AddMinutes(2), "2 minutes"},
{utils.AddMinutes(5), "5 minutes"},
{utils.AddMinutes(10), "10 minutes"},
{utils.AddHours(1), "1 hour"},
{utils.AddHours(24), "1 day"},
{utils.AddHours(48), "2 days"},
// Timestamp string input parsing
{strconv.Itoa(utils.UnixFromPastDate(time.Minute * 5)), "5 minutes ago"},
{strconv.Itoa(utils.UnixFromPastDate(time.Hour)), "1 hour ago"},
{strconv.Itoa(utils.UnixFromPastDate(time.Hour * 3)), "3 hours ago"},
{strconv.Itoa(utils.UnixFromPastDate(time.Hour * 5)), "5 hours ago"},
{strconv.Itoa(utils.UnixFromPastDate(time.Hour * 24)), "1 day ago"},
{strconv.Itoa(utils.UnixFromPastDate(time.Hour * 24 * 2)), "2 days ago"},
{strconv.Itoa(utils.UnixFromPastDate(time.Hour * 24 * 3)), "3 days ago"},
{strconv.Itoa(utils.UnixFromPastDate(time.Hour * 24 * 4)), "4 days ago"},
{strconv.Itoa(utils.UnixFromPastDate(time.Hour * 24 * 5)), "5 days ago"},
{strconv.Itoa(utils.UnixFromPastDate(time.Hour * 24 * 6)), "6 days ago"},
{strconv.Itoa(utils.UnixFromPastDate(time.Hour * 24 * 7)), "1 week ago"},
}

Reconfigure(Config{Language: LangEn})

for _, tc := range testCases {
t.Run("Test for date: "+tc.date.String(), func(t *testing.T) {
for _, tc := range cases {
t.Run(tc.res, func(t *testing.T) {
res, err := Parse(tc.date)

if err != nil {
Expand Down
Loading