From 3a17edfaf070ea48dca0b6bccafc06e8680e8118 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20Schw=C3=B6rer?= Date: Mon, 26 Aug 2024 14:35:49 +0200 Subject: [PATCH] v0.0.509 --- goextVersion.go | 4 +-- timeext/diff.go | 31 +++++++++++++++++++++++ timeext/diff_test.go | 60 ++++++++++++++++++++++++++++++++++++++++++++ timeext/time.go | 7 ++++++ timeext/time_test.go | 36 ++++++++++++++++++++++++++ 5 files changed, 136 insertions(+), 2 deletions(-) diff --git a/goextVersion.go b/goextVersion.go index 52f75c9..a14004d 100644 --- a/goextVersion.go +++ b/goextVersion.go @@ -1,5 +1,5 @@ package goext -const GoextVersion = "0.0.508" +const GoextVersion = "0.0.509" -const GoextVersionTimestamp = "2024-08-25T17:36:20+0200" +const GoextVersionTimestamp = "2024-08-26T14:35:49+0200" diff --git a/timeext/diff.go b/timeext/diff.go index 118dadc..9000550 100644 --- a/timeext/diff.go +++ b/timeext/diff.go @@ -2,6 +2,9 @@ package timeext import "time" +// YearDifference calculates the difference between two timestamps in years. +// = t1 - t2 +// returns a float value func YearDifference(t1 time.Time, t2 time.Time, tz *time.Location) float64 { yDelta := float64(t1.Year() - t2.Year()) @@ -11,3 +14,31 @@ func YearDifference(t1 time.Time, t2 time.Time, tz *time.Location) float64 { return yDelta + (processT1 - processT2) } + +// MonthDifference calculates the difference between two timestamps in months. +// = t1 - t2 +// returns a float value +func MonthDifference(t1 time.Time, t2 time.Time) float64 { + + yDelta := float64(t1.Year() - t2.Year()) + mDelta := float64(t1.Month() - t2.Month()) + + dDelta := float64(0) + + t1MonthDays := DaysInMonth(t1) + t2MonthDays := DaysInMonth(t2) + + if t2.Year() > t1.Year() || (t2.Year() == t1.Year() && t2.Month() > t1.Month()) { + dDelta -= 1 + dDelta += float64(t1MonthDays-t1.Day()) / float64(t1MonthDays) + dDelta += float64(t2.Day()) / float64(t2MonthDays) + } else if t2.Year() < t1.Year() || (t2.Year() == t1.Year() && t2.Month() < t1.Month()) { + dDelta -= 1 + dDelta += float64(t1.Day()) / float64(t1MonthDays) + dDelta += float64(t2MonthDays-t2.Day()) / float64(t2MonthDays) + } else { + dDelta += float64(t1.Day()-t2.Day()) / float64(t1MonthDays) + } + + return yDelta*12 + mDelta + dDelta +} diff --git a/timeext/diff_test.go b/timeext/diff_test.go index 79cbf4d..c22588e 100644 --- a/timeext/diff_test.go +++ b/timeext/diff_test.go @@ -81,3 +81,63 @@ func epsilonEquals(a, b float64) bool { epsilon := 0.01 return math.Abs(a-b) < epsilon } + +func TestMonthDifferenceSameDate(t *testing.T) { + t1 := time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC) + t2 := time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC) + expected := 0.0 + result := MonthDifference(t2, t1) + if !epsilonEquals(result, expected) { + t.Errorf("Expected %v, got %v", expected, result) + } +} + +func TestMonthDifferenceSameMonth(t *testing.T) { + t1 := time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC) + t2 := time.Date(2022, 1, 31, 0, 0, 0, 0, time.UTC) + expected := 0.967741935483871 // Approximation of 30/31 days + result := MonthDifference(t2, t1) + if !epsilonEquals(result, expected) { + t.Errorf("Expected %v, got %v", expected, result) + } +} + +func TestMonthDifferenceDifferentMonthsSameYear(t *testing.T) { + t1 := time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC) + t2 := time.Date(2022, 3, 1, 0, 0, 0, 0, time.UTC) + expected := 2.0 + result := MonthDifference(t2, t1) + if !epsilonEquals(result, expected) { + t.Errorf("Expected %v, got %v", expected, result) + } +} + +func TestMonthDifferenceDifferentYears(t *testing.T) { + t1 := time.Date(2021, 12, 1, 0, 0, 0, 0, time.UTC) + t2 := time.Date(2022, 2, 1, 0, 0, 0, 0, time.UTC) + expected := 2.0 + result := MonthDifference(t2, t1) + if !epsilonEquals(result, expected) { + t.Errorf("Expected %v, got %v", expected, result) + } +} + +func TestMonthDifferenceT1BeforeT2(t *testing.T) { + t1 := time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC) + t2 := time.Date(2022, 6, 1, 0, 0, 0, 0, time.UTC) + expected := 5.0 + result := MonthDifference(t2, t1) + if !epsilonEquals(result, expected) { + t.Errorf("Expected %v, got %v", expected, result) + } +} + +func TestMonthDifferenceT1AfterT2(t *testing.T) { + t1 := time.Date(2022, 6, 1, 0, 0, 0, 0, time.UTC) + t2 := time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC) + expected := -5.0 + result := MonthDifference(t2, t1) + if !epsilonEquals(result, expected) { + t.Errorf("Expected %v, got %v", expected, result) + } +} diff --git a/timeext/time.go b/timeext/time.go index 1b0b168..fb4837c 100644 --- a/timeext/time.go +++ b/timeext/time.go @@ -184,3 +184,10 @@ func AddYears(t time.Time, yearCount float64, tz *time.Location) time.Time { return t.Add(time.Duration(float64(t1.Sub(t0)) * floatCount)) } + +func DaysInMonth(t time.Time) int { + // https://stackoverflow.com/a/73882035/1761622 + + y, m, _ := t.Date() + return time.Date(y, m+1, 0, 0, 0, 0, 0, time.UTC).Day() +} diff --git a/timeext/time_test.go b/timeext/time_test.go index 2aed255..24c980c 100644 --- a/timeext/time_test.go +++ b/timeext/time_test.go @@ -191,3 +191,39 @@ func TestCombineDateAndTime_CombineDifferentParts(t *testing.T) { t.Errorf("Expected %v, got %v", expected, result) } } + +func TestDaysInMonth_31Days(t *testing.T) { + date := time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC) // January + expected := 31 + result := DaysInMonth(date) + if result != expected { + t.Errorf("Expected %d but got %d", expected, result) + } +} + +func TestDaysInMonth_30Days(t *testing.T) { + date := time.Date(2022, 4, 1, 0, 0, 0, 0, time.UTC) // April + expected := 30 + result := DaysInMonth(date) + if result != expected { + t.Errorf("Expected %d but got %d", expected, result) + } +} + +func TestDaysInMonth_FebruaryLeapYear(t *testing.T) { + date := time.Date(2020, 2, 1, 0, 0, 0, 0, time.UTC) // February in a leap year + expected := 29 + result := DaysInMonth(date) + if result != expected { + t.Errorf("Expected %d but got %d", expected, result) + } +} + +func TestDaysInMonth_FebruaryNonLeapYear(t *testing.T) { + date := time.Date(2021, 2, 1, 0, 0, 0, 0, time.UTC) // February in a non-leap year + expected := 28 + result := DaysInMonth(date) + if result != expected { + t.Errorf("Expected %d but got %d", expected, result) + } +}