diff --git a/EXAMPLES.md b/EXAMPLES.md index 8845249..f3b697a 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -15,6 +15,7 @@ This document provides practical examples of how to use the library's features. 11. [URLs](#11-urls) 12. [Math](#12-math) 13. [Fake](#13-fake) +14. [Time](#14-time) ## 1. Boolean @@ -1731,4 +1732,543 @@ func main() { ``` 81 Broadway, Rivertown, CT 12345, USA ``` +--- + +## 14. Time + +## 1. `StartOfDay` + +### Get the start of the day for the given time +```go +package main + +import ( + "fmt" + "time" + + utils "github.com/kashifkhan0771/utils/time" +) + +func main() { + t := time.Now() + fmt.Println(utils.StartOfDay(t)) +} +``` +#### Output: +``` +2024-12-29 00:00:00 +0500 PKT +``` + +--- + +## 2. `EndOfDay` + +### Get the end of the day for the given time +```go +package main + +import ( + "fmt" + "time" + + utils "github.com/kashifkhan0771/utils/time" +) + +func main() { + t := time.Now() + fmt.Println(utils.EndOfDay(t)) +} +``` +#### Output: +``` +2024-12-29 23:59:59.999999999 +0500 PKT +``` + +--- + +## 3. `AddBusinessDays` + +### Add business days to a date (skipping weekends) +```go +package main + +import ( + "fmt" + "time" + + utils "github.com/kashifkhan0771/utils/time" +) + +func main() { + t := time.Date(2024, 12, 27, 0, 0, 0, 0, time.Local) // Friday + // Add 3 business days + result := utils.AddBusinessDays(t, 3) + fmt.Println(result) +} +``` +#### Output: +``` +2025-01-01 00:00:00 +0500 PKT +``` + +--- + +## 4. `IsWeekend` + +### Check if a given date is a weekend +```go +package main + +import ( + "fmt" + "time" + + utils "github.com/kashifkhan0771/utils/time" +) + +func main() { + saturday := time.Date(2024, 12, 28, 0, 0, 0, 0, time.Local) + monday := time.Date(2024, 12, 30, 0, 0, 0, 0, time.Local) + + fmt.Printf("Is Saturday a weekend? %v\n", utils.IsWeekend(saturday)) + fmt.Printf("Is Monday a weekend? %v\n", utils.IsWeekend(monday)) +} +``` +#### Output: +``` +Is Saturday a weekend? true +Is Monday a weekend? false +``` + +--- + +## 5. `TimeDifferenceHumanReadable` + +### Get human-readable time difference +```go +package main + +import ( + "fmt" + "time" + + utils "github.com/kashifkhan0771/utils/time" +) + +func main() { + now := time.Now() + future := now.Add(72 * time.Hour) + past := now.Add(-48 * time.Hour) + + fmt.Println(utils.TimeDifferenceHumanReadable(now, future)) + fmt.Println(utils.TimeDifferenceHumanReadable(now, past)) +} +``` +#### Output: +``` +in 3 day(s) +2 day(s) ago +``` + +--- + +## 6. `DurationUntilNext` + +### Calculate duration until next specified weekday +```go +package main + +import ( + "fmt" + "time" + + utils "github.com/kashifkhan0771/utils/time" +) + +func main() { + now := time.Now() + nextMonday := utils.DurationUntilNext(time.Monday, now) + fmt.Printf("Duration until next Monday: %v\n", nextMonday) +} +``` +#### Output: +``` +Duration until next Monday: 24h0m0s +``` + +--- + +## 7. `ConvertToTimeZone` + +### Convert time to different timezone +```go +package main + +import ( + "fmt" + "time" + + utils "github.com/kashifkhan0771/utils/time" +) + +func main() { + t := time.Now() + nyTime, err := utils.ConvertToTimeZone(t, "America/New_York") + if err != nil { + fmt.Println("Error:", err) + return + } + fmt.Println(nyTime) +} +``` +#### Output: +``` +2024-12-29 14:00:00 -0500 EST +``` + +--- + +## 8. `HumanReadableDuration` + +### Format duration in human-readable format +```go +package main + +import ( + "fmt" + "time" + + utils "github.com/kashifkhan0771/utils/time" +) + +func main() { + d := 3*time.Hour + 25*time.Minute + 45*time.Second + fmt.Println(utils.HumanReadableDuration(d)) +} +``` +#### Output: +``` +3h 25m 45s +``` + +--- + +## 9. `CalculateAge` + +### Calculate age from birthdate +```go +package main + +import ( + "fmt" + "time" + + utils "github.com/kashifkhan0771/utils/time" +) + +func main() { + birthDate := time.Date(1990, 5, 15, 0, 0, 0, 0, time.Local) + age := utils.CalculateAge(birthDate) + fmt.Printf("Age: %d years\n", age) +} +``` +#### Output: +``` +Age: 34 years +``` + +--- + +## 10. `IsLeapYear` + +### Check if a year is a leap year +```go +package main + +import ( + "fmt" + + utils "github.com/kashifkhan0771/utils/time" +) + +func main() { + fmt.Printf("Is 2024 a leap year? %v\n", utils.IsLeapYear(2024)) + fmt.Printf("Is 2023 a leap year? %v\n", utils.IsLeapYear(2023)) +} +``` +#### Output: +``` +Is 2024 a leap year? true +Is 2023 a leap year? false +``` + +--- + +## 11. `NextOccurrence` + +### Find next occurrence of a specific time +```go +package main + +import ( + "fmt" + "time" + + utils "github.com/kashifkhan0771/utils/time" +) + +func main() { + now := time.Now() + nextNoon := utils.NextOccurrence(12, 0, 0, now) + fmt.Println("Next noon:", nextNoon) +} +``` +#### Output: +``` +Next noon: 2024-12-30 12:00:00 +0500 PKT +``` + +--- + +## 12. `WeekNumber` + +### Get ISO year and week number +```go +package main + +import ( + "fmt" + "time" + + utils "github.com/kashifkhan0771/utils/time" +) + +func main() { + t := time.Now() + year, week := utils.WeekNumber(t) + fmt.Printf("Year: %d, Week: %d\n", year, week) +} +``` +#### Output: +``` +Year: 2024, Week: 52 +``` + +--- + +## 13. `DaysBetween` + +### Calculate days between two dates +```go +package main + +import ( + "fmt" + "time" + + utils "github.com/kashifkhan0771/utils/time" +) + +func main() { + start := time.Date(2024, 1, 1, 0, 0, 0, 0, time.Local) + end := time.Date(2024, 12, 31, 0, 0, 0, 0, time.Local) + days := utils.DaysBetween(start, end) + fmt.Printf("Days between: %d\n", days) +} +``` +#### Output: +``` +Days between: 365 +``` + +--- + +## 14. `IsTimeBetween` + +### Check if time is between two other times +```go +package main + +import ( + "fmt" + "time" + + utils "github.com/kashifkhan0771/utils/time" +) + +func main() { + now := time.Now() + start := now.Add(-1 * time.Hour) + end := now.Add(1 * time.Hour) + fmt.Printf("Is current time between? %v\n", utils.IsTimeBetween(now, start, end)) +} +``` +#### Output: +``` +Is current time between? true +``` + +--- + +## 15. `UnixMilliToTime` + +### Convert Unix milliseconds to time +```go +package main + +import ( + "fmt" + + utils "github.com/kashifkhan0771/utils/time" +) + +func main() { + ms := int64(1703836800000) // 2024-12-29 00:00:00 + t := utils.UnixMilliToTime(ms) + fmt.Println(t) +} +``` +#### Output: +``` +2024-12-29 00:00:00 +0000 UTC +``` + +--- + +## 16. `SplitDuration` + +### Split duration into components +```go +package main + +import ( + "fmt" + "time" + + utils "github.com/kashifkhan0771/utils/time" +) + +func main() { + d := 50*time.Hour + 30*time.Minute + 15*time.Second + days, hours, minutes, seconds := utils.SplitDuration(d) + fmt.Printf("Days: %d, Hours: %d, Minutes: %d, Seconds: %d\n", + days, hours, minutes, seconds) +} +``` +#### Output: +``` +Days: 2, Hours: 2, Minutes: 30, Seconds: 15 +``` + +--- + +## 17. `GetMonthName` + +### Get month name from number +```go +package main + +import ( + "fmt" + + utils "github.com/kashifkhan0771/utils/time" +) + +func main() { + monthName, err := utils.GetMonthName(12) + if err != nil { + fmt.Println("Error:", err) + return + } + fmt.Printf("Month 12 is: %s\n", monthName) +} +``` +#### Output: +``` +Month 12 is: December +``` + +--- + +## 18. `GetDayName` + +### Get day name from number +```go +package main + +import ( + "fmt" + + utils "github.com/kashifkhan0771/utils/time" +) + +func main() { + dayName, err := utils.GetDayName(1) + if err != nil { + fmt.Println("Error:", err) + return + } + fmt.Printf("Day 1 is: %s\n", dayName) +} +``` +#### Output: +``` +Day 1 is: Monday +``` + +--- + +## 19. `FormatForDisplay` + +### Format time for display +```go +package main + +import ( + "fmt" + "time" + + utils "github.com/kashifkhan0771/utils/time" +) + +func main() { + t := time.Now() + formatted := utils.FormatForDisplay(t) + fmt.Println(formatted) +} +``` +#### Output: +``` +Sunday, 29 Dec 2024 +``` + +--- + +## 20. `IsToday` + +### Check if date is today +```go +package main + +import ( + "fmt" + "time" + + utils "github.com/kashifkhan0771/utils/time" +) + +func main() { + now := time.Now() + tomorrow := now.AddDate(0, 0, 1) + + fmt.Printf("Is now today? %v\n", utils.IsToday(now)) + fmt.Printf("Is tomorrow today? %v\n", utils.IsToday(tomorrow)) +} +``` +#### Output: +``` +Is now today? true +Is tomorrow today? false +``` + --- \ No newline at end of file diff --git a/readME.md b/readME.md index 275e6bd..350508e 100644 --- a/readME.md +++ b/readME.md @@ -137,6 +137,48 @@ require github.com/kashifkhan0771/utils v0.3.0 - **LCM**: Finds the least common multiple (LCM) of two integers. +### 13. Time Utilities + +- **StartOfDay**: Returns a time.Time set to the beginning (00:00:00) of the given day. + +- **EndOfDay**: Returns a time.Time set to the last moment (23:59:59.999999999) of the given day. + +- **AddBusinessDays**: Adds specified number of business days (excluding weekends) to a given date. + +- **IsWeekend**: Determines if a given date falls on a weekend (Saturday or Sunday). + +- **TimeDifferenceHumanReadable**: Converts time difference between two dates into a human-friendly string (e.g., "in 2 days" or "3 hours ago"). + +- **DurationUntilNext**: Calculates duration until the next occurrence of a specific weekday. + +- **ConvertToTimeZone**: Converts a time.Time to a different timezone based on location name. + +- **HumanReadableDuration**: Formats a duration into a human-readable string (e.g., "2h 30m 45s"). + +- **CalculateAge**: Computes age in years given a birth date. + +- **IsLeapYear**: Checks if a given year is a leap year. + +- **NextOccurrence**: Finds the next occurrence of a specific time on the same or next day. + +- **WeekNumber**: Returns the ISO year and week number for a given date. + +- **DaysBetween**: Calculates the number of days between two dates. + +- **IsTimeBetween**: Checks if a time falls between two other times. + +- **UnixMilliToTime**: Converts Unix milliseconds timestamp to time.Time. + +- **SplitDuration**: Breaks down a duration into days, hours, minutes, and seconds. + +- **GetMonthName**: Returns the English name of a month given its number (1-12). + +- **GetDayName**: Returns the English name of a day given its number (0-6). + +- **FormatForDisplay**: Formats a date in a readable format (e.g., "Monday, 2 Jan 2006"). + +- **IsToday**: Checks if a given date is today. + --- ### 13. Fake diff --git a/time/time.go b/time/time.go new file mode 100644 index 0000000..3b803a1 --- /dev/null +++ b/time/time.go @@ -0,0 +1,191 @@ +package time + +import ( + "fmt" + "time" +) + +// StartOfDay returns the start of the day for the given time +func StartOfDay(t time.Time) time.Time { + return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location()) +} + +// EndOfDay returns the end of the day for the given time +func EndOfDay(t time.Time) time.Time { + return time.Date(t.Year(), t.Month(), t.Day(), 23, 59, 59, 999999999, t.Location()) +} + +// AddBusinessDays adds the given number of business days to the given time +func AddBusinessDays(t time.Time, days int) time.Time { + for days > 0 { + t = t.AddDate(0, 0, 1) + if !IsWeekend(t) { + days-- + } + } + + return t +} + +// IsWeekend checks if the given time is a weekend +func IsWeekend(t time.Time) bool { + return t.Weekday() == time.Saturday || t.Weekday() == time.Sunday +} + +// TimeDifferenceHumanReadable returns a human-readable string representing the time difference between two times +func TimeDifferenceHumanReadable(from, to time.Time) string { + diff := to.Sub(from) + if diff < 0 { + diff = -diff + if diff.Hours() > 24 { + return fmt.Sprintf("%d day(s) ago", int(diff.Hours()/24)) + } + return fmt.Sprintf("in %d hour(s)", int(diff.Hours())) + } + if diff.Hours() > 24 { + return fmt.Sprintf("in %d day(s)", int(diff.Hours()/24)) + } + return fmt.Sprintf("in %d hour(s)", int(diff.Hours())) +} + +// DurationUntilNext calculates the duration from the given time `t` to the next occurrence +// of the specified weekday `day`. If the target day is the same as the current day, +// it assumes the next occurrence of that day is in 7 days. +func DurationUntilNext(day time.Weekday, t time.Time) time.Duration { + daysAhead := (int(day) - int(t.Weekday()) + 7) % 7 + if daysAhead == 0 { + daysAhead = 7 + } + + next := t.AddDate(0, 0, daysAhead) + return next.Sub(t) +} + +// ConvertToTimeZone converts the given time to the specified time zone +func ConvertToTimeZone(t time.Time, location string) (time.Time, error) { + loc, err := time.LoadLocation(location) + if err != nil { + return time.Time{}, err + } + + return t.In(loc), nil +} + +// HumanReadableDuration returns a human-readable string representing the given duration +func HumanReadableDuration(d time.Duration) string { + hours := d / time.Hour + minutes := (d % time.Hour) / time.Minute + seconds := (d % time.Minute) / time.Second + + return fmt.Sprintf("%dh %dm %ds", hours, minutes, seconds) +} + +// CalculateAge calculates the age of a person given their birth date +func CalculateAge(birthDate time.Time) int { + today := time.Now() + age := today.Year() - birthDate.Year() + if today.YearDay() < birthDate.YearDay() { + age-- + } + + return age +} + +// IsLeapYear checks if the given year is a leap year +func IsLeapYear(year int) bool { + return (year%4 == 0 && year%100 != 0) || year%400 == 0 +} + +// NextOccurrence returns the next occurrence of the specified time on the same day as the given time +func NextOccurrence(hour, minute, second int, t time.Time) time.Time { + next := time.Date(t.Year(), t.Month(), t.Day(), hour, minute, second, 0, t.Location()) + if !next.After(t) { + next = next.Add(24 * time.Hour) + } + return next +} + +// WeekNumber returns the year and week number for the given time +func WeekNumber(t time.Time) (int, int) { + year, week := t.ISOWeek() + return year, week +} + +// DaysBetween returns the number of days between two times +func DaysBetween(start, end time.Time) int { + start = time.Date(start.Year(), start.Month(), start.Day(), 0, 0, 0, 0, start.Location()) + end = time.Date(end.Year(), end.Month(), end.Day(), 0, 0, 0, 0, end.Location()) + return int(end.Sub(start).Hours() / 24) +} + +// IsTimeBetween checks if the given time is between the start and end times +func IsTimeBetween(t, start, end time.Time) bool { + return (t.After(start) || t.Equal(start)) && t.Before(end) +} + +// UnixMilliToTime converts milliseconds since epoch to time.Time +func UnixMilliToTime(ms int64) time.Time { + return time.Unix(0, ms*int64(time.Millisecond)) +} + +// SplitDuration splits the given duration into days, hours, minutes, and seconds +func SplitDuration(d time.Duration) (days, hours, minutes, seconds int) { + days = int(d.Hours()) / 24 + hours = int(d.Hours()) % 24 + minutes = int(d.Minutes()) % 60 + seconds = int(d.Seconds()) % 60 + return +} + +// GetMonthName returns the name of the month for the given month number +func GetMonthName(monthNumber int) (string, error) { + months := []string{ + "", // Index 0 (placeholder, as months start from 1) + "January", // 1 + "February", // 2 + "March", // 3 + "April", // 4 + "May", // 5 + "June", // 6 + "July", // 7 + "August", // 8 + "September", // 9 + "October", // 10 + "November", // 11 + "December", // 12 + } + + if monthNumber < 1 || monthNumber > 12 { + return "", fmt.Errorf("invalid month number: %d", monthNumber) + } + + return months[monthNumber], nil +} + +// GetDayName returns the name of the day for the given day number +func GetDayName(dayNumber int) (string, error) { + days := []string{ + "Sunday", // 0 + "Monday", // 1 + "Tuesday", // 2 + "Wednesday", // 3 + "Thursday", // 4 + "Friday", // 5 + "Saturday", // 6 + } + + if dayNumber < 0 || dayNumber > 6 { + return "", fmt.Errorf("invalid day number: %d", dayNumber) + } + + return days[dayNumber], nil +} + +func FormatForDisplay(t time.Time) string { + return t.Format("Monday, 2 Jan 2006") +} + +func IsToday(t time.Time) bool { + now := time.Now() + return t.Year() == now.Year() && t.YearDay() == now.YearDay() +} diff --git a/time/time_test.go b/time/time_test.go new file mode 100644 index 0000000..6b049a0 --- /dev/null +++ b/time/time_test.go @@ -0,0 +1,1182 @@ +package time + +import ( + "reflect" + "testing" + "time" +) + +func TestStartOfDay(t *testing.T) { + type args struct { + input time.Time + } + tests := []struct { + name string + args args + want time.Time + }{ + { + name: "success - Start of the day", + args: args{time.Date(2024, 12, 25, 15, 30, 45, 123456789, time.UTC)}, + want: time.Date(2024, 12, 25, 0, 0, 0, 0, time.UTC), + }, + { + name: "success - Start of the day", + args: args{time.Date(2023, 1, 1, 23, 59, 59, 999999999, time.UTC)}, + want: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC), + }, + { + name: "success - Start of the day", + args: args{time.Date(2022, 6, 15, 12, 0, 0, 0, time.FixedZone("TestZone", 3600))}, + want: time.Date(2022, 6, 15, 0, 0, 0, 0, time.FixedZone("TestZone", 3600)), + }, + } + + for _, tc := range tests { + result := StartOfDay(tc.args.input) + if !result.Equal(tc.want) { + t.Errorf("StartOfDay(%v) = %v; want %v", tc.args.input, result, tc.want) + } + } +} + +func TestEndOfDay(t *testing.T) { + type args struct { + input time.Time + } + tests := []struct { + name string + args args + want time.Time + }{ + { + name: "success - End of the day", + args: args{time.Date(2024, 12, 25, 15, 30, 45, 123456789, time.UTC)}, + want: time.Date(2024, 12, 25, 23, 59, 59, 999999999, time.UTC), + }, + { + name: "success - End of the day", + args: args{time.Date(2023, 1, 1, 23, 59, 59, 999999999, time.UTC)}, + want: time.Date(2023, 1, 1, 23, 59, 59, 999999999, time.UTC), + }, + { + name: "success - End of the day", + args: args{time.Date(2022, 6, 15, 12, 0, 0, 0, time.FixedZone("TestZone", 3600))}, + want: time.Date(2022, 6, 15, 23, 59, 59, 999999999, time.FixedZone("TestZone", 3600)), + }, + } + + for _, tc := range tests { + result := EndOfDay(tc.args.input) + if !result.Equal(tc.want) { + t.Errorf("EndOfDay(%v) = %v; want %v", tc.args.input, result, tc.want) + } + } +} + +func TestAddBusinessDays(t *testing.T) { + type args struct { + t time.Time + days int + } + tests := []struct { + name string + args args + want time.Time + }{ + { + name: "success - add 1 business day", + args: args{ + t: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC), + days: 1, + }, + want: time.Date(2023, 1, 2, 0, 0, 0, 0, time.UTC), + }, + { + name: "success - add 2 business days", + args: args{ + t: time.Date(2023, 1, 14, 0, 0, 0, 0, time.UTC), + days: 2, + }, + want: time.Date(2023, 1, 17, 0, 0, 0, 0, time.UTC), + }, + { + name: "success - add 0 business days", + args: args{ + t: time.Date(2023, 1, 14, 0, 0, 0, 0, time.UTC), + days: 0, + }, + want: time.Date(2023, 1, 14, 0, 0, 0, 0, time.UTC), + }, + { + name: "success - add 20 business days", + args: args{ + t: time.Date(2023, 2, 1, 0, 0, 0, 0, time.UTC), + days: 20, + }, + want: time.Date(2023, 3, 1, 0, 0, 0, 0, time.UTC), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := AddBusinessDays(tt.args.t, tt.args.days); !reflect.DeepEqual(got, tt.want) { + t.Errorf("AddBusinessDays() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestIsWeekend(t *testing.T) { + type args struct { + t time.Time + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "success - is weekend (Sunday)", + args: args{t: time.Date(2024, 12, 1, 0, 0, 0, 0, time.UTC)}, + want: true, + }, + { + name: "success - is not weekend (Monday)", + args: args{t: time.Date(2024, 12, 2, 0, 0, 0, 0, time.UTC)}, + want: false, + }, + { + name: "success - is weekend (Saturday)", + args: args{t: time.Date(2024, 12, 7, 0, 0, 0, 0, time.FixedZone("TestZone", 3600))}, + want: true, + }, + { + name: "success - is not weekend (Empty time)", + args: args{t: time.Time{}}, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := IsWeekend(tt.args.t); got != tt.want { + t.Errorf("IsWeekend() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestTimeDifferenceHumanReadable(t *testing.T) { + type args struct { + from time.Time + to time.Time + } + tests := []struct { + name string + args args + want string + }{ + { + name: "success - exact same time", + args: args{ + from: time.Date(2024, time.December, 25, 12, 0, 0, 0, time.UTC), + to: time.Date(2024, time.December, 25, 12, 0, 0, 0, time.UTC), + }, + want: "in 0 hour(s)", + }, + { + name: "success - future within 24 hours", + args: args{ + from: time.Date(2024, time.December, 25, 12, 0, 0, 0, time.UTC), + to: time.Date(2024, time.December, 25, 15, 0, 0, 0, time.UTC), + }, + want: "in 3 hour(s)", + }, + { + name: "success - future beyond 24 hours", + args: args{ + from: time.Date(2024, time.December, 25, 12, 0, 0, 0, time.UTC), + to: time.Date(2024, time.December, 27, 12, 0, 0, 0, time.UTC), + }, + want: "in 2 day(s)", + }, + { + name: "success - past within 24 hours", + args: args{ + from: time.Date(2024, time.December, 25, 15, 0, 0, 0, time.UTC), + to: time.Date(2024, time.December, 25, 12, 0, 0, 0, time.UTC), + }, + want: "in 3 hour(s)", + }, + { + name: "success - past beyond 24 hours", + args: args{ + from: time.Date(2024, time.December, 27, 12, 0, 0, 0, time.UTC), + to: time.Date(2024, time.December, 25, 12, 0, 0, 0, time.UTC), + }, + want: "2 day(s) ago", + }, + { + name: "success - negative time difference within 24 hours", + args: args{ + from: time.Date(2024, time.December, 25, 15, 0, 0, 0, time.UTC), + to: time.Date(2024, time.December, 25, 12, 0, 0, 0, time.UTC), + }, + want: "in 3 hour(s)", + }, + { + name: "success - negative time difference beyond 24 hours", + args: args{ + from: time.Date(2024, time.December, 27, 15, 0, 0, 0, time.UTC), + to: time.Date(2024, time.December, 25, 15, 0, 0, 0, time.UTC), + }, + want: "2 day(s) ago", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := TimeDifferenceHumanReadable(tt.args.from, tt.args.to); got != tt.want { + t.Errorf("TimeDifferenceHumanReadable() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestDurationUntilNext(t *testing.T) { + type args struct { + day time.Weekday + t time.Time + } + tests := []struct { + name string + args args + want time.Duration + }{ + { + name: "Next Monday from Friday", + args: args{ + day: time.Monday, + t: time.Date(2023, 12, 22, 12, 0, 0, 0, time.UTC), + }, + want: 3 * 24 * time.Hour, + }, + { + name: "Next Sunday from Saturday", + args: args{ + day: time.Sunday, + t: time.Date(2023, 12, 23, 12, 0, 0, 0, time.UTC), + }, + want: 24 * time.Hour, + }, + { + name: "Next Wednesday from Wednesday", + args: args{ + day: time.Wednesday, + t: time.Date(2023, 12, 20, 12, 0, 0, 0, time.UTC), + }, + want: 7 * 24 * time.Hour, + }, + { + name: "Next Friday from Thursday", + args: args{ + day: time.Friday, + t: time.Date(2023, 12, 21, 12, 0, 0, 0, time.UTC), + }, + want: 24 * time.Hour, + }, + { + name: "Next Monday from Monday", + args: args{ + day: time.Monday, + t: time.Date(2023, 12, 18, 12, 0, 0, 0, time.UTC), + }, + want: 7 * 24 * time.Hour, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := DurationUntilNext(tt.args.day, tt.args.t); got != tt.want { + t.Errorf("DurationUntilNext() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestConvertToTimeZone(t *testing.T) { + type args struct { + t time.Time + location string + } + tests := []struct { + name string + args args + want time.Time + wantErr bool + }{ + { + name: "Convert UTC to PST", + args: args{ + t: time.Date(2023, 12, 25, 15, 0, 0, 0, time.UTC), // 3 PM UTC + location: "America/Los_Angeles", // PST + }, + want: time.Date(2023, 12, 25, 7, 0, 0, 0, time.FixedZone("PST", -8*60*60)), + wantErr: false, + }, + { + name: "Convert UTC to GMT+5", + args: args{ + t: time.Date(2023, 12, 25, 15, 0, 0, 0, time.UTC), // 3 PM UTC + location: "Asia/Karachi", // GMT+5 + }, + want: time.Date(2023, 12, 25, 20, 0, 0, 0, time.FixedZone("PKT", 5*60*60)), + wantErr: false, + }, + { + name: "Convert UTC to invalid timezone", + args: args{ + t: time.Date(2023, 12, 25, 15, 0, 0, 0, time.UTC), + location: "Invalid/Timezone", + }, + want: time.Time{}, + wantErr: true, + }, + { + name: "Convert UTC to local timezone", + args: args{ + t: time.Date(2023, 12, 25, 15, 0, 0, 0, time.UTC), + location: "Local", + }, + wantErr: false, + want: time.Date(2023, 12, 25, 15, 0, 0, 0, time.UTC).In(time.Now().Location()), + }, + { + name: "Convert PST to IST", + args: args{ + t: time.Date(2023, 12, 25, 7, 0, 0, 0, time.FixedZone("PST", -8*60*60)), + location: "Asia/Kolkata", // IST + }, + want: time.Date(2023, 12, 25, 20, 30, 0, 0, time.FixedZone("IST", 5*60*60+30*60)), + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ConvertToTimeZone(tt.args.t, tt.args.location) + if (err != nil) != tt.wantErr { + t.Errorf("ConvertToTimeZone() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !tt.wantErr && !got.Equal(tt.want) { + t.Errorf("ConvertToTimeZone() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestHumanReadableDuration(t *testing.T) { + type args struct { + d time.Duration + } + tests := []struct { + name string + args args + want string + }{ + { + name: "Zero duration", + args: args{ + d: 0, + }, + want: "0h 0m 0s", + }, + { + name: "Only seconds", + args: args{ + d: 45 * time.Second, + }, + want: "0h 0m 45s", + }, + { + name: "Minutes and seconds", + args: args{ + d: 90 * time.Second, + }, + want: "0h 1m 30s", + }, + { + name: "Hours, minutes, and seconds", + args: args{ + d: 2*time.Hour + 15*time.Minute + 42*time.Second, + }, + want: "2h 15m 42s", + }, + { + name: "More than a day", + args: args{ + d: 26*time.Hour + 5*time.Minute + 10*time.Second, + }, + want: "26h 5m 10s", + }, + { + name: "Negative duration", + args: args{ + d: -2*time.Hour - 10*time.Minute - 30*time.Second, + }, + want: "-2h -10m -30s", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := HumanReadableDuration(tt.args.d); got != tt.want { + t.Errorf("HumanReadableDuration() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCalculateAge(t *testing.T) { + type args struct { + birthDate time.Time + } + tests := []struct { + name string + args args + want int + }{ + { + name: "Birthday today, age remains the same", + args: args{ + birthDate: time.Date(time.Now().Year()-30, time.Now().Month(), time.Now().Day(), 0, 0, 0, 0, time.UTC), + }, + want: 30, + }, + { + name: "Birthday not yet reached this year", + args: args{ + birthDate: time.Date(time.Now().Year()-25, time.Now().Month()+1, time.Now().Day(), 0, 0, 0, 0, time.UTC), + }, + want: 24, + }, + { + name: "Birthday already passed this year", + args: args{ + birthDate: time.Date(time.Now().Year()-40, time.Now().Month()-1, time.Now().Day(), 0, 0, 0, 0, time.UTC), + }, + want: 40, + }, + { + name: "Birthdate is exactly one year ago", + args: args{ + birthDate: time.Date(time.Now().Year()-1, time.Now().Month(), time.Now().Day(), 0, 0, 0, 0, time.UTC), + }, + want: 1, + }, + { + name: "Leap year adjustment, after birthday", + args: args{ + birthDate: time.Date(2000, 2, 29, 0, 0, 0, 0, time.UTC), + }, + want: time.Now().Year() - 2000, + }, + { + name: "Future date, invalid age", + args: args{ + birthDate: time.Date(time.Now().Year()+5, time.Now().Month(), time.Now().Day(), 0, 0, 0, 0, time.UTC), + }, + want: -5, + }, + { + name: "Birthdate today, zero age", + args: args{ + birthDate: time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day(), 0, 0, 0, 0, time.UTC), + }, + want: 0, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := CalculateAge(tt.args.birthDate); got != tt.want { + t.Errorf("CalculateAge() = %v, want %v", got, tt.want) + } + }) + } +} +func TestIsLeapYear(t *testing.T) { + type args struct { + year int + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "Leap year divisible by 4 but not 100", + args: args{year: 2024}, + want: true, + }, + { + name: "Leap year divisible by 400", + args: args{year: 2000}, + want: true, + }, + { + name: "Not a leap year divisible by 100 but not 400", + args: args{year: 1900}, + want: false, + }, + { + name: "Not a leap year divisible by 4 but not 100", + args: args{year: 2023}, + want: false, + }, + { + name: "Year 0 (edge case)", + args: args{year: 0}, + want: true, + }, + { + name: "Very far future year", + args: args{year: 10000}, + want: true, + }, + { + name: "Very far future year divisible by 100 but not 400", + args: args{year: 2100}, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := IsLeapYear(tt.args.year); got != tt.want { + t.Errorf("IsLeapYear() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestNextOccurrence(t *testing.T) { + loc, _ := time.LoadLocation("UTC") + currentTime := time.Date(2024, time.December, 29, 10, 30, 0, 0, loc) + + type args struct { + hour int + minute int + second int + t time.Time + } + tests := []struct { + name string + args args + want time.Time + }{ + { + name: "Next occurrence after current time", + args: args{ + hour: 11, + minute: 30, + second: 0, + t: currentTime, + }, + want: time.Date(2024, time.December, 29, 11, 30, 0, 0, loc), + }, + { + name: "Next occurrence next day (time is in the past)", + args: args{ + hour: 9, + minute: 30, + second: 0, + t: currentTime, + }, + want: time.Date(2024, time.December, 30, 9, 30, 0, 0, loc), + }, + { + name: "Exact next second after current time", + args: args{ + hour: 10, + minute: 30, + second: 1, + t: currentTime, + }, + want: time.Date(2024, time.December, 29, 10, 30, 1, 0, loc), + }, + { + name: "Midnight next occurrence", + args: args{ + hour: 0, + minute: 0, + second: 0, + t: currentTime, + }, + want: time.Date(2024, time.December, 30, 0, 0, 0, 0, loc), + }, + { + name: "Same day, time is already passed", + args: args{ + hour: 9, + minute: 0, + second: 0, + t: currentTime, + }, + want: time.Date(2024, time.December, 30, 9, 0, 0, 0, loc), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := NextOccurrence(tt.args.hour, tt.args.minute, tt.args.second, tt.args.t) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("NextOccurrence() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestWeekNumber(t *testing.T) { + type args struct { + t time.Time + } + tests := []struct { + name string + args args + wantYear int + wantWeek int + }{ + { + name: "Week 1 of the year 2024", + args: args{ + t: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), + }, + wantYear: 2024, + wantWeek: 1, + }, + { + name: "Last week of the year 2024", + args: args{ + t: time.Date(2024, 12, 29, 0, 0, 0, 0, time.UTC), + }, + wantYear: 2024, + wantWeek: 52, + }, + { + name: "Middle of the year 2024", + args: args{ + t: time.Date(2024, 6, 15, 0, 0, 0, 0, time.UTC), + }, + wantYear: 2024, + wantWeek: 24, + }, + { + name: "New Year's Eve 2025", + args: args{ + t: time.Date(2024, 12, 31, 23, 59, 59, 0, time.UTC), + }, + wantYear: 2025, + wantWeek: 1, + }, + { + name: "Start of the week May 2024", + args: args{ + t: time.Date(2024, 5, 5, 0, 0, 0, 0, time.UTC), + }, + wantYear: 2024, + wantWeek: 18, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + year, week := WeekNumber(tt.args.t) + if year != tt.wantYear { + t.Errorf("WeekNumber() gotYear = %v, wantYear %v", year, tt.wantYear) + } + if week != tt.wantWeek { + t.Errorf("WeekNumber() gotWeek = %v, wantWeek %v", week, tt.wantWeek) + } + }) + } +} + +func TestDaysBetween(t *testing.T) { + type args struct { + start time.Time + end time.Time + } + tests := []struct { + name string + args args + want int + }{ + { + name: "Same day", + args: args{ + start: time.Date(2024, 12, 29, 0, 0, 0, 0, time.UTC), + end: time.Date(2024, 12, 29, 23, 59, 59, 0, time.UTC), + }, + want: 0, // Same day, 0 days between + }, + { + name: "One day difference", + args: args{ + start: time.Date(2024, 12, 29, 0, 0, 0, 0, time.UTC), + end: time.Date(2024, 12, 30, 0, 0, 0, 0, time.UTC), + }, + want: 1, // One day difference + }, + { + name: "Multiple days", + args: args{ + start: time.Date(2024, 12, 29, 0, 0, 0, 0, time.UTC), + end: time.Date(2024, 12, 31, 0, 0, 0, 0, time.UTC), + }, + want: 2, // Two days difference + }, + { + name: "Leap year day", + args: args{ + start: time.Date(2020, 2, 28, 0, 0, 0, 0, time.UTC), // Feb 28, 2020 (Leap Year) + end: time.Date(2020, 3, 1, 0, 0, 0, 0, time.UTC), // Mar 1, 2020 + }, + want: 2, // 2 days difference (Leap year: Feb 29th included) + }, + { + name: "Negative days (start after end)", + args: args{ + start: time.Date(2024, 12, 30, 0, 0, 0, 0, time.UTC), + end: time.Date(2024, 12, 29, 0, 0, 0, 0, time.UTC), + }, + want: -1, // Negative 1 day difference + }, + { + name: "Crossing months", + args: args{ + start: time.Date(2024, 1, 31, 0, 0, 0, 0, time.UTC), + end: time.Date(2024, 2, 2, 0, 0, 0, 0, time.UTC), + }, + want: 2, // 2 days difference between Jan 31 and Feb 2 + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := DaysBetween(tt.args.start, tt.args.end); got != tt.want { + t.Errorf("DaysBetween() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestIsTimeBetween(t *testing.T) { + type args struct { + t time.Time + start time.Time + end time.Time + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "Time between start and end", + args: args{ + t: time.Date(2024, 12, 29, 10, 30, 0, 0, time.UTC), + start: time.Date(2024, 12, 29, 10, 0, 0, 0, time.UTC), + end: time.Date(2024, 12, 29, 11, 0, 0, 0, time.UTC), + }, + want: true, // t is between start and end + }, + { + name: "Time exactly equal to start", + args: args{ + t: time.Date(2024, 12, 29, 10, 0, 0, 0, time.UTC), + start: time.Date(2024, 12, 29, 10, 0, 0, 0, time.UTC), + end: time.Date(2024, 12, 29, 11, 0, 0, 0, time.UTC), + }, + want: true, // t is equal to start, should be considered inside the range + }, + { + name: "Time exactly equal to end", + args: args{ + t: time.Date(2024, 12, 29, 11, 0, 0, 0, time.UTC), + start: time.Date(2024, 12, 29, 10, 0, 0, 0, time.UTC), + end: time.Date(2024, 12, 29, 11, 0, 0, 0, time.UTC), + }, + want: false, // t is equal to end, should not be considered inside the range + }, + { + name: "Time before start", + args: args{ + t: time.Date(2024, 12, 29, 9, 0, 0, 0, time.UTC), + start: time.Date(2024, 12, 29, 10, 0, 0, 0, time.UTC), + end: time.Date(2024, 12, 29, 11, 0, 0, 0, time.UTC), + }, + want: false, // t is before start + }, + { + name: "Time after end", + args: args{ + t: time.Date(2024, 12, 29, 12, 0, 0, 0, time.UTC), + start: time.Date(2024, 12, 29, 10, 0, 0, 0, time.UTC), + end: time.Date(2024, 12, 29, 11, 0, 0, 0, time.UTC), + }, + want: false, // t is after end + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := IsTimeBetween(tt.args.t, tt.args.start, tt.args.end); got != tt.want { + t.Errorf("IsTimeBetween() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestUnixMilliToTime(t *testing.T) { + type args struct { + ms int64 + } + tests := []struct { + name string + args args + want time.Time + }{ + { + name: "Epoch time (0 milliseconds)", + args: args{ + ms: 0, + }, + want: time.Unix(0, 0).UTC(), + }, + { + name: "Specific time in the future", + args: args{ + ms: time.Date(2025, 7, 7, 12, 0, 0, 0, time.UTC).UnixMilli(), + }, + want: time.Date(2025, 7, 7, 12, 0, 0, 0, time.UTC), + }, + { + name: "Specific time in the past", + args: args{ + ms: time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC).UnixMilli(), + }, + want: time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC), + }, + { + name: "Time with a fraction of second", + args: args{ + ms: time.Date(2021, 1, 1, 12, 0, 1, 0, time.UTC).UnixMilli(), + }, + want: time.Date(2021, 1, 1, 12, 0, 1, 0, time.UTC), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := UnixMilliToTime(tt.args.ms).UTC(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("UnixMilliToTime() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSplitDuration(t *testing.T) { + type args struct { + d time.Duration + } + tests := []struct { + name string + args args + wantDays int + wantHours int + wantMinutes int + wantSeconds int + }{ + { + name: "Zero duration", + args: args{ + d: 0, + }, + wantDays: 0, + wantHours: 0, + wantMinutes: 0, + wantSeconds: 0, + }, + { + name: "1 day 2 hours 30 minutes 45 seconds", + args: args{ + d: time.Duration(1*24*time.Hour + 2*time.Hour + 30*time.Minute + 45*time.Second), + }, + wantDays: 1, + wantHours: 2, + wantMinutes: 30, + wantSeconds: 45, + }, + { + name: "3 days 5 hours", + args: args{ + d: time.Duration(3*24*time.Hour + 5*time.Hour), + }, + wantDays: 3, + wantHours: 5, + wantMinutes: 0, + wantSeconds: 0, + }, + { + name: "No days, 1 hour 30 minutes", + args: args{ + d: time.Duration(1*time.Hour + 30*time.Minute), + }, + wantDays: 0, + wantHours: 1, + wantMinutes: 30, + wantSeconds: 0, + }, + { + name: "Negative duration", + args: args{ + d: time.Duration(-26 * time.Hour), + }, + wantDays: -1, + wantHours: -2, + wantMinutes: 0, + wantSeconds: 0, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotDays, gotHours, gotMinutes, gotSeconds := SplitDuration(tt.args.d) + if gotDays != tt.wantDays { + t.Errorf("SplitDuration() gotDays = %v, want %v", gotDays, tt.wantDays) + } + if gotHours != tt.wantHours { + t.Errorf("SplitDuration() gotHours = %v, want %v", gotHours, tt.wantHours) + } + if gotMinutes != tt.wantMinutes { + t.Errorf("SplitDuration() gotMinutes = %v, want %v", gotMinutes, tt.wantMinutes) + } + if gotSeconds != tt.wantSeconds { + t.Errorf("SplitDuration() gotSeconds = %v, want %v", gotSeconds, tt.wantSeconds) + } + }) + } +} + +func TestGetMonthName(t *testing.T) { + type args struct { + monthNumber int + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + { + name: "Valid month number 1", + args: args{ + monthNumber: 1, + }, + want: "January", + wantErr: false, + }, + { + name: "Valid month number 12", + args: args{ + monthNumber: 12, + }, + want: "December", + wantErr: false, + }, + { + name: "Invalid month number 0", + args: args{ + monthNumber: 0, + }, + want: "", + wantErr: true, + }, + { + name: "Invalid month number 13", + args: args{ + monthNumber: 13, + }, + want: "", + wantErr: true, + }, + { + name: "Valid month number 6", + args: args{ + monthNumber: 6, + }, + want: "June", + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := GetMonthName(tt.args.monthNumber) + if (err != nil) != tt.wantErr { + t.Errorf("GetMonthName() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("GetMonthName() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestGetDayName(t *testing.T) { + type args struct { + dayNumber int + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + { + name: "Valid day number 0", + args: args{ + dayNumber: 0, + }, + want: "Sunday", + wantErr: false, + }, + { + name: "Valid day number 3", + args: args{ + dayNumber: 3, + }, + want: "Wednesday", + wantErr: false, + }, + { + name: "Valid day number 6", + args: args{ + dayNumber: 6, + }, + want: "Saturday", + wantErr: false, + }, + { + name: "Invalid day number -1", + args: args{ + dayNumber: -1, + }, + want: "", + wantErr: true, + }, + { + name: "Invalid day number 7", + args: args{ + dayNumber: 7, + }, + want: "", + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := GetDayName(tt.args.dayNumber) + if (err != nil) != tt.wantErr { + t.Errorf("GetDayName() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("GetDayName() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestFormatForDisplay(t *testing.T) { + type args struct { + t time.Time + } + tests := []struct { + name string + args args + want string + }{ + { + name: "Valid date", + args: args{ + t: time.Date(2024, time.December, 30, 10, 30, 0, 0, time.UTC), + }, + want: "Monday, 30 Dec 2024", + }, + { + name: "Edge case: January 1st, 2023", + args: args{ + t: time.Date(2023, time.January, 1, 0, 0, 0, 0, time.UTC), + }, + want: "Sunday, 1 Jan 2023", + }, + { + name: "Edge case: December 31st, 2024", + args: args{ + t: time.Date(2024, time.December, 31, 0, 0, 0, 0, time.UTC), + }, + want: "Tuesday, 31 Dec 2024", + }, + { + name: "Edge case: Empty time", + args: args{ + t: time.Time{}, + }, + want: "Monday, 1 Jan 0001", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := FormatForDisplay(tt.args.t); got != tt.want { + t.Errorf("FormatForDisplay() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestIsToday(t *testing.T) { + type args struct { + t time.Time + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "Is today", + args: args{ + t: time.Now(), + }, + want: true, + }, + { + name: "Yesterday", + args: args{ + t: time.Now().AddDate(0, 0, -1), + }, + want: false, + }, + { + name: "Tomorrow", + args: args{ + t: time.Now().AddDate(0, 0, 1), + }, + want: false, + }, + { + name: "Same date, different time", + args: args{ + t: time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day(), 23, 59, 59, 0, time.UTC), + }, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := IsToday(tt.args.t); got != tt.want { + t.Errorf("IsToday() = %v, want %v", got, tt.want) + } + }) + } +}