From fc38b8e676edf08c5216f2394c7a419f2a5fe265 Mon Sep 17 00:00:00 2001 From: Alex Baker Date: Mon, 22 Dec 2025 15:12:25 -0600 Subject: [PATCH] Preserve timezone when modifying DateTime --- app/src/main/java/org/tasks/time/DateTime.kt | 49 ++++++++++++++---- .../test/java/org/tasks/time/DateTimeTest.kt | 51 +++++++++++++++---- 2 files changed, 81 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/org/tasks/time/DateTime.kt b/app/src/main/java/org/tasks/time/DateTime.kt index 399d36668..68e923b2b 100644 --- a/app/src/main/java/org/tasks/time/DateTime.kt +++ b/app/src/main/java/org/tasks/time/DateTime.kt @@ -52,23 +52,52 @@ class DateTime { timeZone = timeZone ) - fun startOfMinute(): DateTime = DateTime(millis.startOfMinute()) + fun startOfMinute(): DateTime = DateTime(millis.startOfMinute(), timeZone) - fun startOfSecond(): DateTime = DateTime(millis.startOfSecond()) + fun startOfSecond(): DateTime = DateTime(millis.startOfSecond(), timeZone) - fun endOfMinute(): DateTime = DateTime(millis.endOfMinute()) + fun endOfMinute(): DateTime = DateTime(millis.endOfMinute(), timeZone) - fun noon(): DateTime = DateTime(millis.noon()) + fun noon(): DateTime = DateTime( + year = year, + month = monthOfYear, + day = dayOfMonth, + hour = 12, + timeZone = timeZone + ) - fun endOfDay(): DateTime = DateTime(millis.endOfDay()) + fun endOfDay(): DateTime = DateTime( + year = year, + month = monthOfYear, + day = dayOfMonth, + hour = 23, + minute = 59, + second = 59, + timeZone = timeZone + ) - fun withMillisOfDay(millisOfDay: Int): DateTime = DateTime(millis.withMillisOfDay(millisOfDay)) + fun withMillisOfDay(millisOfDay: Int): DateTime { + val hours = millisOfDay / 3600000 + val minutes = (millisOfDay % 3600000) / 60000 + val seconds = (millisOfDay % 60000) / 1000 + val millis = millisOfDay % 1000 + return DateTime( + year = year, + month = monthOfYear, + day = dayOfMonth, + hour = hours, + minute = minutes, + second = seconds, + millisecond = millis, + timeZone = timeZone + ) + } val offset: Long get() = timeZone.getOffset(millis).toLong() val millisOfDay: Int - get() = millis.millisOfDay + get() = hourOfDay * 3600000 + minuteOfHour * 60000 + secondOfMinute * 1000 + calendar[Calendar.MILLISECOND] val year: Int get() = calendar[Calendar.YEAR] @@ -151,11 +180,11 @@ class DateTime { return add(Calendar.SECOND, -seconds) } - fun minusDays(days: Int): DateTime = DateTime(millis.minusDays(days)) + fun minusDays(days: Int): DateTime = add(Calendar.DATE, -days) - fun minusMinutes(minutes: Int): DateTime = DateTime(millis.minusMinutes(minutes)) + fun minusMinutes(minutes: Int): DateTime = add(Calendar.MINUTE, -minutes) - fun minusMillis(millis: Long): DateTime = DateTime(this.millis.minusMillis(millis)) + fun minusMillis(millis: Long): DateTime = DateTime(this.millis - millis, timeZone) val isAfterNow: Boolean get() = isAfter(currentTimeMillis()) diff --git a/app/src/test/java/org/tasks/time/DateTimeTest.kt b/app/src/test/java/org/tasks/time/DateTimeTest.kt index d3e921b35..f1d61e1f1 100644 --- a/app/src/test/java/org/tasks/time/DateTimeTest.kt +++ b/app/src/test/java/org/tasks/time/DateTimeTest.kt @@ -219,7 +219,9 @@ class DateTimeTest { @Test fun testMinusMinutes() { assertEquals( - DateTime(2015, 11, 4, 23, 59, 0), DateTime(2015, 11, 5, 0, 1, 0).minusMinutes(2)) + DateTime(2015, 11, 4, 23, 59, 0, timeZone = DateTime.UTC), + DateTime(2015, 11, 5, 0, 1, 0, timeZone = DateTime.UTC).minusMinutes(2) + ) } @Test @@ -292,16 +294,21 @@ class DateTimeTest { @Test fun testMinusMillis() { assertEquals( - DateTime(2015, 11, 6, 16, 18, 20, 452), - DateTime(2015, 11, 6, 16, 18, 21, 374).minusMillis(922)) + DateTime(2015, 11, 6, 16, 18, 20, 452, DateTime.UTC), + DateTime(2015, 11, 6, 16, 18, 21, 374, DateTime.UTC).minusMillis(922) + ) } @Test fun testMinusDays() { assertEquals( - DateTime(2015, 11, 6, 16, 19, 16), DateTime(2015, 12, 4, 16, 19, 16).minusDays(28)) + DateTime(2015, 11, 6, 16, 19, 16, timeZone = DateTime.UTC), + DateTime(2015, 12, 4, 16, 19, 16, timeZone = DateTime.UTC).minusDays(28) + ) assertEquals( - DateTime(2015, 11, 6, 16, 19, 16), DateTime(2015, 11, 7, 16, 19, 16).minusDays(1)) + DateTime(2015, 11, 6, 16, 19, 16, timeZone = DateTime.UTC), + DateTime(2015, 11, 7, 16, 19, 16, timeZone = DateTime.UTC).minusDays(1) + ) } @Test @@ -347,15 +354,17 @@ class DateTimeTest { @Test fun testStartOfMinute() { assertEquals( - DateTime(2017, 9, 3, 0, 51, 0, 0), - DateTime(2017, 9, 3, 0, 51, 13, 427).startOfMinute()) + DateTime(2017, 9, 3, 0, 51, 0, 0, DateTime.UTC), + DateTime(2017, 9, 3, 0, 51, 13, 427, DateTime.UTC).startOfMinute() + ) } @Test fun testEndOfMinute() { assertEquals( - DateTime(2017, 9, 22, 14, 47, 59, 999), - DateTime(2017, 9, 22, 14, 47, 14, 453).endOfMinute()) + DateTime(2017, 9, 22, 14, 47, 59, 999, DateTime.UTC), + DateTime(2017, 9, 22, 14, 47, 14, 453, DateTime.UTC).endOfMinute() + ) } @Test @@ -444,4 +453,28 @@ class DateTimeTest { assertEquals(DateTime(2024, 12, 20, timeZone = DateTime.UTC), utcMidnight) } } + + @Test + fun noon() { + assertEquals( + DateTime(2024, 12, 20, 12, timeZone = DateTime.UTC), + DateTime(2024, 12, 20, 8, 30, 45, 123, DateTime.UTC).noon() + ) + } + + @Test + fun endOfDay() { + assertEquals( + DateTime(2024, 12, 20, 23, 59, 59, timeZone = DateTime.UTC), + DateTime(2024, 12, 20, 8, 30, 45, 123, DateTime.UTC).endOfDay() + ) + } + + @Test + fun millisOfDayRespectsTimezone() { + val instant = DateTime(2024, 12, 20, 12, timeZone = DateTime.UTC) + val berlinDateTime = DateTime(instant.millis, TimeZone.getTimeZone("Europe/Berlin")) // UTC+1 + assertEquals(12 * 3600000, instant.millisOfDay) + assertEquals(13 * 3600000, berlinDateTime.millisOfDay) + } }