diff --git a/uhabits-core/src/commonMain/kotlin/org/isoron/platform/time/Dates.kt b/uhabits-core/src/commonMain/kotlin/org/isoron/platform/time/Dates.kt index 9837f6da6..c5f80bd92 100644 --- a/uhabits-core/src/commonMain/kotlin/org/isoron/platform/time/Dates.kt +++ b/uhabits-core/src/commonMain/kotlin/org/isoron/platform/time/Dates.kt @@ -141,10 +141,6 @@ data class LocalDate(val daysSince2000: Int) { } companion object { - var fixedLocalTime: Long? = null - var fixedTimeZone: TimeZone? = null - var fixedLocale: Locale? = null - /** * Number of milliseconds in one second. */ @@ -165,6 +161,17 @@ data class LocalDate(val daysSince2000: Int) { */ const val DAY_LENGTH: Long = 24 * HOUR_LENGTH + var fixedLocalTime: Long? = null + var fixedTimeZone: TimeZone? = null + var fixedLocale: Locale? = null + var startDayHourOffset: Int = 0 + var startDayMinuteOffset: Int = 0 + + fun setStartDayOffset(hourOffset: Int, minuteOffset: Int) { + startDayHourOffset = hourOffset + startDayMinuteOffset = minuteOffset + } + fun getLocalTime(testTimeInMillis: Long? = null): Long { if (fixedLocalTime != null) return fixedLocalTime as Long @@ -205,6 +212,13 @@ data class LocalDate(val daysSince2000: Int) { fun getStartOfDay(timestamp: Long): Long = (timestamp / DAY_LENGTH) * DAY_LENGTH fun getStartOfToday(): Long = getStartOfDay(getLocalTime()) + + fun getStartOfDayWithOffset(timestamp: Long): Long { + val offset = startDayHourOffset * HOUR_LENGTH + startDayMinuteOffset * MINUTE_LENGTH + return getStartOfDay(timestamp - offset) + } + + fun getStartOfTodayWithOffset(): Long = getStartOfDayWithOffset(getLocalTime()) } } diff --git a/uhabits-core/src/commonTest/kotlin/org/isoron/platform/time/DatesTest.kt b/uhabits-core/src/commonTest/kotlin/org/isoron/platform/time/DatesTest.kt index deb29a750..b8c73a05a 100644 --- a/uhabits-core/src/commonTest/kotlin/org/isoron/platform/time/DatesTest.kt +++ b/uhabits-core/src/commonTest/kotlin/org/isoron/platform/time/DatesTest.kt @@ -23,7 +23,9 @@ import kotlinx.datetime.LocalDateTime import kotlinx.datetime.TimeZone import kotlinx.datetime.toInstant import org.isoron.platform.time.LocalDate.Companion.getStartOfDay +import org.isoron.platform.time.LocalDate.Companion.getStartOfDayWithOffset import org.isoron.platform.time.LocalDate.Companion.getStartOfToday +import org.isoron.platform.time.LocalDate.Companion.getStartOfTodayWithOffset import org.isoron.platform.time.LocalDate.Companion.getWeekdaySequence import kotlin.test.Test import kotlin.test.assertContentEquals @@ -42,9 +44,7 @@ class DatesTest { fun testGetLocalTime() { LocalDate.fixedLocalTime = null LocalDate.fixedTimeZone = TimeZone.of("Australia/Sydney") - val utcTestTimeInMillis = LocalDateTime( - 2015, 1, 11, 0, 0, 0, 0 - ).toInstant(TimeZone.UTC).toEpochMilliseconds() + val utcTestTimeInMillis = unixTime(2015, 1, 11) val localTimeInMillis = LocalDate.getLocalTime(utcTestTimeInMillis) val expectedUnixTimeOffsetForSydney = 11 * 60 * 60 * 1000 val expectedUnixTimeForSydney = utcTestTimeInMillis + expectedUnixTimeOffsetForSydney @@ -59,25 +59,74 @@ class DatesTest { @Test fun testGetStartOfDay() { - val expectedStartOfDayUtc = LocalDateTime( - 2017, 1, 1, 0, 0, 0, 0 - ).toInstant(TimeZone.UTC).toEpochMilliseconds() - val laterInTheDayUtc = LocalDateTime( - 2017, 1, 1, 20, 0, 0, 0 - ).toInstant(TimeZone.UTC).toEpochMilliseconds() + val expectedStartOfDayUtc = unixTime(2017, 1, 1) + val laterInTheDayUtc = unixTime(2017, 1, 1, 20, 0) val startOfDay = getStartOfDay(laterInTheDayUtc) assertEquals(expectedStartOfDayUtc, startOfDay) } + @Test fun testGetStartOfToday() { - val expectedStartOfDayUtc = LocalDateTime( - 2017, 1, 1, 0, 0, 0, 0 - ).toInstant(TimeZone.UTC).toEpochMilliseconds() - val laterInTheDayUtc = LocalDateTime( - 2017, 1, 1, 20, 0, 0, 0 - ).toInstant(TimeZone.UTC).toEpochMilliseconds() + val expectedStartOfDayUtc = unixTime(2017, 1, 1) + val laterInTheDayUtc = unixTime(2017, 1, 1, 20, 0) LocalDate.fixedLocalTime = laterInTheDayUtc val startOfToday = getStartOfToday() assertEquals(expectedStartOfDayUtc, startOfToday) } + + @Test + @Throws(Exception::class) + fun testGetStartOfDayWithOffset() { + val timestamp = unixTime(2020, 9, 3) + assertEquals( + timestamp, + getStartOfDayWithOffset(timestamp + LocalDate.HOUR_LENGTH), + ) + LocalDate.setStartDayOffset(3, 30) + assertEquals( + (timestamp - LocalDate.DAY_LENGTH), + getStartOfDayWithOffset(timestamp + 3 * LocalDate.HOUR_LENGTH + 29 * LocalDate.MINUTE_LENGTH), + ) + } + + @Test + fun testGetStartOfTodayWithOffset_priorToOffset() { + val hourOffset = 3 + LocalDate.setStartDayOffset(hourOffset, 0) + LocalDate.fixedTimeZone = kotlinx.datetime.TimeZone.UTC + val startOfYesterday = unixTime(2017, 1, 1, 0, 0) + val priorToOffset = unixTime(2017, 1, 2, hourOffset - 1, 0) + LocalDate.fixedLocalTime = priorToOffset + val startOfTodayWithOffset = getStartOfTodayWithOffset() + assertEquals(startOfYesterday, startOfTodayWithOffset) + } + + @Test + fun testGetStartOfTodayWithOffset_afterOffset() { + val hourOffset = 3 + LocalDate.setStartDayOffset(hourOffset, 0) + LocalDate.fixedTimeZone = kotlinx.datetime.TimeZone.UTC + val startOfToday = unixTime(2017, 1, 1, 0, 0) + val afterOffset = unixTime(2017, 1, 1, hourOffset + 1, 0) + LocalDate.fixedLocalTime = afterOffset + val startOfTodayWithOffset = getStartOfTodayWithOffset() + assertEquals(startOfToday, startOfTodayWithOffset) + } + + private fun unixTime(year: Int, month: Int, day: Int): Long { + return unixTime(year, month, day, 0, 0) + } + + private fun unixTime( + year: Int, + month: Int, + day: Int, + hour: Int, + minute: Int, + milliseconds: Long = 0 + ): Long { + return LocalDateTime( + year, month, day, hour, minute, (milliseconds / 1000).toInt(), 0 + ).toInstant(TimeZone.UTC).toEpochMilliseconds() + } } diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/reminders/ReminderScheduler.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/reminders/ReminderScheduler.kt index 986f2d195..68c19ce1c 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/reminders/ReminderScheduler.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/reminders/ReminderScheduler.kt @@ -19,6 +19,7 @@ package org.isoron.uhabits.core.reminders import org.isoron.platform.time.LocalDate.Companion.getLocalTime +import org.isoron.platform.time.LocalDate.Companion.getStartOfDayWithOffset import org.isoron.uhabits.core.AppScope import org.isoron.uhabits.core.commands.ChangeHabitColorCommand import org.isoron.uhabits.core.commands.Command @@ -29,7 +30,6 @@ import org.isoron.uhabits.core.models.HabitList import org.isoron.uhabits.core.models.HabitMatcher import org.isoron.uhabits.core.preferences.WidgetPreferences import org.isoron.uhabits.core.utils.DateUtils.Companion.applyTimezone -import org.isoron.uhabits.core.utils.DateUtils.Companion.getStartOfDayWithOffset import org.isoron.uhabits.core.utils.DateUtils.Companion.removeTimezone import java.util.Locale import java.util.Objects diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/utils/DateUtils.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/utils/DateUtils.kt index c2f0b0e46..653e0f196 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/utils/DateUtils.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/utils/DateUtils.kt @@ -22,12 +22,12 @@ import kotlinx.datetime.Instant import kotlinx.datetime.offsetAt import org.isoron.platform.time.LocalDate import org.isoron.platform.time.LocalDate.Companion.DAY_LENGTH -import org.isoron.platform.time.LocalDate.Companion.HOUR_LENGTH -import org.isoron.platform.time.LocalDate.Companion.MINUTE_LENGTH import org.isoron.platform.time.LocalDate.Companion.getLocalTime -import org.isoron.platform.time.LocalDate.Companion.getStartOfDay import org.isoron.platform.time.LocalDate.Companion.getStartOfToday +import org.isoron.platform.time.LocalDate.Companion.getStartOfTodayWithOffset import org.isoron.platform.time.LocalDate.Companion.getTimeZone +import org.isoron.platform.time.LocalDate.Companion.startDayHourOffset +import org.isoron.platform.time.LocalDate.Companion.startDayMinuteOffset import org.isoron.uhabits.core.models.Timestamp import java.util.Calendar import java.util.Calendar.DAY_OF_MONTH @@ -36,12 +36,9 @@ import java.util.Calendar.SHORT import java.util.GregorianCalendar import java.util.Locale import java.util.TimeZone -import kotlin.collections.ArrayList abstract class DateUtils { companion object { - private var startDayHourOffset: Int = 0 - private var startDayMinuteOffset: Int = 0 @JvmStatic fun applyTimezone(localTimestamp: Long): Long { @@ -147,21 +144,12 @@ abstract class DateUtils { @JvmStatic fun getTodayWithOffset(): Timestamp = Timestamp(getStartOfTodayWithOffset()) - @JvmStatic - fun getStartOfDayWithOffset(timestamp: Long): Long { - val offset = startDayHourOffset * HOUR_LENGTH + startDayMinuteOffset * MINUTE_LENGTH - return getStartOfDay(timestamp - offset) - } - @JvmStatic fun getStartOfTomorrowWithOffset(): Long = getUpcomingTimeInMillis( startDayHourOffset, startDayMinuteOffset ) - @JvmStatic - fun getStartOfTodayWithOffset(): Long = getStartOfDayWithOffset(getLocalTime()) - @JvmStatic fun millisecondsUntilTomorrowWithOffset(): Long = getStartOfTomorrowWithOffset() - getLocalTime() @@ -179,12 +167,6 @@ abstract class DateUtils { return timestamp + (tz.offsetAt(Instant.fromEpochMilliseconds(timestamp)).totalSeconds * 1000) } - @JvmStatic - fun setStartDayOffset(hourOffset: Int, minuteOffset: Int) { - startDayHourOffset = hourOffset - startDayMinuteOffset = minuteOffset - } - private fun getLocale(): Locale { return Locale.forLanguageTag(LocalDate.getLocale().toLanguageTag().toString()) } diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/BaseUnitTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/BaseUnitTest.kt index 9720248d7..e102ab7fc 100644 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/BaseUnitTest.kt +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/BaseUnitTest.kt @@ -22,6 +22,7 @@ import com.nhaarman.mockitokotlin2.spy import com.nhaarman.mockitokotlin2.validateMockitoUsage import org.apache.commons.io.IOUtils import org.isoron.platform.time.LocalDate +import org.isoron.platform.time.LocalDate.Companion.setStartDayOffset import org.isoron.uhabits.core.commands.CommandRunner import org.isoron.uhabits.core.database.Database import org.isoron.uhabits.core.database.DatabaseOpener @@ -34,7 +35,6 @@ import org.isoron.uhabits.core.models.memory.MemoryModelFactory import org.isoron.uhabits.core.tasks.SingleThreadTaskRunner import org.isoron.uhabits.core.test.HabitFixtures import org.isoron.uhabits.core.utils.DateUtils.Companion.getStartOfTodayCalendar -import org.isoron.uhabits.core.utils.DateUtils.Companion.setStartDayOffset import org.junit.After import org.junit.Before import org.junit.Test diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/utils/DateUtilsTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/utils/DateUtilsTest.kt index ed8c1fe78..557053d92 100644 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/utils/DateUtilsTest.kt +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/utils/DateUtilsTest.kt @@ -26,15 +26,14 @@ import org.isoron.platform.time.LocalDate import org.isoron.platform.time.LocalDate.Companion.DAY_LENGTH import org.isoron.platform.time.LocalDate.Companion.HOUR_LENGTH import org.isoron.platform.time.LocalDate.Companion.MINUTE_LENGTH +import org.isoron.platform.time.LocalDate.Companion.setStartDayOffset import org.isoron.uhabits.core.BaseUnitTest import org.isoron.uhabits.core.models.Timestamp import org.isoron.uhabits.core.utils.DateUtils.Companion.applyTimezone import org.isoron.uhabits.core.utils.DateUtils.Companion.formatHeaderDate -import org.isoron.uhabits.core.utils.DateUtils.Companion.getStartOfDayWithOffset import org.isoron.uhabits.core.utils.DateUtils.Companion.getTodayWithOffset import org.isoron.uhabits.core.utils.DateUtils.Companion.millisecondsUntilTomorrowWithOffset import org.isoron.uhabits.core.utils.DateUtils.Companion.removeTimezone -import org.isoron.uhabits.core.utils.DateUtils.Companion.setStartDayOffset import org.isoron.uhabits.core.utils.DateUtils.Companion.truncate import org.junit.Before import org.junit.Test @@ -144,30 +143,6 @@ class DateUtilsTest : BaseUnitTest() { assertThat(startOfTomorrowWithOffset, equalTo(startOfTomorrow)) } - @Test - fun testGetStartOfTodayWithOffset_priorToOffset() { - val hourOffset = 3 - setStartDayOffset(hourOffset, 0) - LocalDate.fixedTimeZone = kotlinx.datetime.TimeZone.UTC - val startOfYesterday = unixTime(2017, Calendar.JANUARY, 1, 0, 0) - val priorToOffset = unixTime(2017, Calendar.JANUARY, 2, hourOffset - 1, 0) - LocalDate.fixedLocalTime = priorToOffset - val startOfTodayWithOffset = DateUtils.getStartOfTodayWithOffset() - assertThat(startOfYesterday, equalTo(startOfTodayWithOffset)) - } - - @Test - fun testGetStartOfTodayWithOffset_afterOffset() { - val hourOffset = 3 - setStartDayOffset(hourOffset, 0) - LocalDate.fixedTimeZone = kotlinx.datetime.TimeZone.UTC - val startOfToday = unixTime(2017, Calendar.JANUARY, 1, 0, 0) - val afterOffset = unixTime(2017, Calendar.JANUARY, 1, hourOffset + 1, 0) - LocalDate.fixedLocalTime = afterOffset - val startOfTodayWithOffset = DateUtils.getStartOfTodayWithOffset() - assertThat(startOfToday, equalTo(startOfTodayWithOffset)) - } - @Test fun testTruncate_dayOfWeek() { val field = DateUtils.TruncateField.WEEK_NUMBER @@ -343,21 +318,6 @@ class DateUtilsTest : BaseUnitTest() { ) } - @Test - @Throws(Exception::class) - fun testGetStartOfDayWithOffset() { - val timestamp = unixTime(2020, Calendar.SEPTEMBER, 3) - assertThat( - getStartOfDayWithOffset(timestamp + HOUR_LENGTH), - equalTo(timestamp) - ) - setStartDayOffset(3, 30) - assertThat( - getStartOfDayWithOffset(timestamp + 3 * HOUR_LENGTH + 29 * MINUTE_LENGTH), - equalTo(timestamp - DAY_LENGTH) - ) - } - @Test fun test_applyTimezone() { LocalDate.fixedTimeZone = kotlinx.datetime.TimeZone.of("Australia/Sydney")