diff --git a/uhabits-core/src/commonMain/kotlin/org/isoron/platform/i18n/LocaleExtensions.kt b/uhabits-core/src/commonMain/kotlin/org/isoron/platform/i18n/LocaleExtensions.kt index 718187a3a..64301b175 100644 --- a/uhabits-core/src/commonMain/kotlin/org/isoron/platform/i18n/LocaleExtensions.kt +++ b/uhabits-core/src/commonMain/kotlin/org/isoron/platform/i18n/LocaleExtensions.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2021 Álinson Santos Xavier + * Copyright (C) 2016-2021 Álinson Santos Xavier * * This file is part of Loop Habit Tracker. * 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 327509fac..ba6569a77 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 @@ -231,6 +231,15 @@ data class LocalDate(val daysSince2000: Int) { ).totalSeconds * 1000 return localTimestamp - offsetDifference } + + fun removeTimezone(timestamp: Long): Long { + val tz = getTimeZone() + return timestamp + ( + tz.offsetAt( + Instant.fromEpochMilliseconds(timestamp) + ).totalSeconds * 1000 + ) + } } } diff --git a/uhabits-core/src/commonMain/kotlin/org/isoron/platform/time/Dates.kt.orig b/uhabits-core/src/commonMain/kotlin/org/isoron/platform/time/Dates.kt.orig new file mode 100644 index 000000000..aece20df4 --- /dev/null +++ b/uhabits-core/src/commonMain/kotlin/org/isoron/platform/time/Dates.kt.orig @@ -0,0 +1,285 @@ +/* + * Copyright (C) 2016-2021 Álinson Santos Xavier + * + * This file is part of Loop Habit Tracker. + * + * Loop Habit Tracker is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * Loop Habit Tracker is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +package org.isoron.platform.time + +import io.fluidsonic.locale.Locale +import kotlinx.datetime.Clock +import kotlinx.datetime.Instant +import kotlinx.datetime.TimeZone +import kotlinx.datetime.offsetAt +import org.isoron.platform.i18n.getDefault +import kotlin.jvm.JvmStatic +import kotlin.math.abs +import kotlin.math.ceil + +enum class DayOfWeek(val daysSinceSunday: Int) { + SUNDAY(0), + MONDAY(1), + TUESDAY(2), + WEDNESDAY(3), + THURSDAY(4), + FRIDAY(5), + SATURDAY(6), +} + +data class LocalDate(val daysSince2000: Int) { + + var yearCache = -1 + var monthCache = -1 + var dayCache = -1 + + constructor(year: Int, month: Int, day: Int) : + this(daysSince2000(year, month, day)) + + val dayOfWeek: DayOfWeek + get() { + return when (daysSince2000 % 7) { + 0 -> DayOfWeek.SATURDAY + 1 -> DayOfWeek.SUNDAY + 2 -> DayOfWeek.MONDAY + 3 -> DayOfWeek.TUESDAY + 4 -> DayOfWeek.WEDNESDAY + 5 -> DayOfWeek.THURSDAY + else -> DayOfWeek.FRIDAY + } + } + + val year: Int + get() { + if (yearCache < 0) updateYearMonthDayCache() + return yearCache + } + + val month: Int + get() { + if (monthCache < 0) updateYearMonthDayCache() + return monthCache + } + + val day: Int + get() { + if (dayCache < 0) updateYearMonthDayCache() + return dayCache + } + + val monthLength: Int + get() = when (month) { + 4, 6, 9, 11 -> 30 + 2 -> if (isLeapYear(year)) 29 else 28 + else -> 31 + } + + private fun updateYearMonthDayCache() { + var currYear = 2000 + var currDay = 0 + if (daysSince2000 < 0) { + currYear -= 400 + currDay -= 146097 + } + while (true) { + val currYearLength = if (isLeapYear(currYear)) 366 else 365 + if (daysSince2000 < currDay + currYearLength) { + yearCache = currYear + break + } else { + currYear++ + currDay += currYearLength + } + } + var currMonth = 1 + val monthOffset = if (isLeapYear(currYear)) leapOffset else nonLeapOffset + while (true) { + if (daysSince2000 < currDay + monthOffset[currMonth]) { + monthCache = currMonth + break + } else { + currMonth++ + } + } + currDay += monthOffset[currMonth - 1] + dayCache = daysSince2000 - currDay + 1 + } + + fun isOlderThan(other: LocalDate): Boolean { + return daysSince2000 < other.daysSince2000 + } + + fun isNewerThan(other: LocalDate): Boolean { + return daysSince2000 > other.daysSince2000 + } + + fun plus(days: Int): LocalDate { + return LocalDate(daysSince2000 + days) + } + + fun minus(days: Int): LocalDate { + return LocalDate(daysSince2000 - days) + } + + fun distanceTo(other: LocalDate): Int { + return abs(daysSince2000 - other.daysSince2000) + } + + override fun toString(): String { + return "LocalDate($year-$month-$day)" + } + + companion object { + /** + * Number of milliseconds in one second. + */ + const val SECOND_LENGTH: Long = 1000 + + /** + * Number of milliseconds in one minute. + */ + const val MINUTE_LENGTH: Long = 60 * SECOND_LENGTH + + /** + * Number of milliseconds in one hour. + */ + const val HOUR_LENGTH: Long = 60 * MINUTE_LENGTH + + /** + * Number of milliseconds in one day. + */ + 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 + + val tz = getTimeZone() + val now = testTimeInMillis ?: Clock.System.now().toEpochMilliseconds() + return now + (tz.offsetAt(Instant.fromEpochMilliseconds(now)).totalSeconds * 1000) + } + + fun getTimeZone(): TimeZone { + return fixedTimeZone ?: TimeZone.currentSystemDefault() + } + + fun getLocale(): Locale { + return fixedLocale ?: Locale.getDefault() + } + + /** + * Returns a vector of exactly seven integers, where the first integer is + * the provided firstWeekday number, and each subsequent number is the + * previous number plus 1, wrapping back to 1 after 7. For example, + * providing 3 as firstWeekday returns {3,4,5,6,7,1,2} + * + * This function is supposed to be used to construct a sequence of weekday + * number following java.util.Calendar conventions. + */ + fun getWeekdaySequence(firstWeekday: Int): Array { + return arrayOf( + (firstWeekday - 1) % 7 + 1, + (firstWeekday) % 7 + 1, + (firstWeekday + 1) % 7 + 1, + (firstWeekday + 2) % 7 + 1, + (firstWeekday + 3) % 7 + 1, + (firstWeekday + 4) % 7 + 1, + (firstWeekday + 5) % 7 + 1, + ) + } + + 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()) + + fun applyTimezone(localTimestamp: Long): Long { + val tz = getTimeZone() + val offset = tz.offsetAt( + Instant.fromEpochMilliseconds(localTimestamp) + ).totalSeconds * 1000 + val difference = localTimestamp - offset + val offsetDifference = tz.offsetAt( + Instant.fromEpochMilliseconds(difference) + ).totalSeconds * 1000 + return localTimestamp - offsetDifference + } + + fun removeTimezone(timestamp: Long): Long { + val tz = getTimeZone() +<<<<<<< Updated upstream + return timestamp + ( + tz.offsetAt( + Instant.fromEpochMilliseconds(timestamp) + ).totalSeconds * 1000 + ) +======= + return timestamp + (tz.offsetAt( + Instant.fromEpochMilliseconds(timestamp) + ).totalSeconds * 1000) +>>>>>>> Stashed changes + } + } +} + +interface LocalDateFormatter { + fun shortWeekdayName(weekday: DayOfWeek): String + fun shortWeekdayName(date: LocalDate): String + fun shortMonthName(date: LocalDate): String +} + +private fun isLeapYear(year: Int): Boolean { + return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0 +} + +val leapOffset = arrayOf( + 0, 31, 60, 91, 121, 152, 182, + 213, 244, 274, 305, 335, 366 +) +val nonLeapOffset = arrayOf( + 0, 31, 59, 90, 120, 151, 181, + 212, 243, 273, 304, 334, 365 +) + +private fun daysSince2000(year: Int, month: Int, day: Int): Int { + + var result = 365 * (year - 2000) + result += ceil((year - 2000) / 4.0).toInt() + result -= ceil((year - 2000) / 100.0).toInt() + result += ceil((year - 2000) / 400.0).toInt() + result += if (isLeapYear(year)) { + leapOffset[month - 1] + } else { + nonLeapOffset[month - 1] + } + result += (day - 1) + return result +} 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 c0d849cf0..b8d5605ad 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 @@ -30,6 +30,7 @@ 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 org.isoron.platform.time.LocalDate.Companion.removeTimezone import kotlin.test.Test import kotlin.test.assertContentEquals import kotlin.test.assertEquals @@ -255,6 +256,145 @@ class DatesTest : BaseUnitTest() { ) } + @Test + fun test_removeTimezone() { + LocalDate.fixedTimeZone = kotlinx.datetime.TimeZone.of("Australia/Sydney") + assertEquals( + removeTimezone(unixTime(2017, Month.JULY, 30, 8, 0)), + unixTime(2017, Month.JULY, 30, 18, 0) + ) + assertEquals( + removeTimezone(unixTime(2017, Month.SEPTEMBER, 29, 14, 0)), + unixTime(2017, Month.SEPTEMBER, 30, 0, 0) + ) + assertEquals( + removeTimezone(unixTime(2017, Month.SEPTEMBER, 30, 0, 0)), + unixTime(2017, Month.SEPTEMBER, 30, 10, 0) + ) + assertEquals( + removeTimezone(unixTime(2017, Month.SEPTEMBER, 30, 1, 0)), + unixTime(2017, Month.SEPTEMBER, 30, 11, 0) + ) + assertEquals( + removeTimezone(unixTime(2017, Month.SEPTEMBER, 30, 2, 0)), + unixTime(2017, Month.SEPTEMBER, 30, 12, 0) + ) + assertEquals( + removeTimezone(unixTime(2017, Month.SEPTEMBER, 30, 3, 0)), + unixTime(2017, Month.SEPTEMBER, 30, 13, 0) + ) + assertEquals( + removeTimezone(unixTime(2017, Month.SEPTEMBER, 30, 12, 0)), + unixTime(2017, Month.SEPTEMBER, 30, 22, 0) + ) + assertEquals( + removeTimezone(unixTime(2017, Month.SEPTEMBER, 30, 13, 0)), + unixTime(2017, Month.SEPTEMBER, 30, 23, 0) + ) + assertEquals( + removeTimezone(unixTime(2017, Month.SEPTEMBER, 30, 14, 0)), + unixTime(2017, Month.OCTOBER, 1, 0, 0) + ) + assertEquals( + removeTimezone(unixTime(2017, Month.SEPTEMBER, 30, 15, 0)), + unixTime(2017, Month.OCTOBER, 1, 1, 0) + ) + assertEquals( + removeTimezone(unixTime(2017, Month.SEPTEMBER, 30, 15, 59)), + unixTime(2017, Month.OCTOBER, 1, 1, 59) + ) + // DST begins + assertEquals( + removeTimezone(unixTime(2017, Month.SEPTEMBER, 30, 16, 0)), + unixTime(2017, Month.OCTOBER, 1, 3, 0) + ) + assertEquals( + removeTimezone(unixTime(2017, Month.SEPTEMBER, 30, 17, 0)), + unixTime(2017, Month.OCTOBER, 1, 4, 0) + ) + assertEquals( + removeTimezone(unixTime(2017, Month.SEPTEMBER, 30, 18, 0)), + unixTime(2017, Month.OCTOBER, 1, 5, 0) + ) + assertEquals( + removeTimezone(unixTime(2017, Month.OCTOBER, 1, 0, 0)), + unixTime(2017, Month.OCTOBER, 1, 11, 0) + ) + assertEquals( + removeTimezone(unixTime(2017, Month.OCTOBER, 1, 1, 0)), + unixTime(2017, Month.OCTOBER, 1, 12, 0) + ) + assertEquals( + removeTimezone(unixTime(2017, Month.OCTOBER, 1, 2, 0)), + unixTime(2017, Month.OCTOBER, 1, 13, 0) + ) + assertEquals( + removeTimezone(unixTime(2017, Month.OCTOBER, 1, 3, 0)), + unixTime(2017, Month.OCTOBER, 1, 14, 0) + ) + assertEquals( + removeTimezone(unixTime(2017, Month.OCTOBER, 1, 4, 0)), + unixTime(2017, Month.OCTOBER, 1, 15, 0) + ) + assertEquals( + removeTimezone(unixTime(2017, Month.OCTOBER, 1, 8, 0)), + unixTime(2017, Month.OCTOBER, 1, 19, 0) + ) + assertEquals( + removeTimezone(unixTime(2017, Month.OCTOBER, 2, 8, 0)), + unixTime(2017, Month.OCTOBER, 2, 19, 0) + ) + assertEquals( + removeTimezone(unixTime(2017, Month.NOVEMBER, 30, 8, 0)), + unixTime(2017, Month.NOVEMBER, 30, 19, 0) + ) + assertEquals( + removeTimezone(unixTime(2018, Month.MARCH, 30, 13, 0)), + unixTime(2018, Month.MARCH, 31, 0, 0) + ) + assertEquals( + removeTimezone(unixTime(2018, Month.MARCH, 31, 1, 0)), + unixTime(2018, Month.MARCH, 31, 12, 0) + ) + assertEquals( + removeTimezone(unixTime(2018, Month.MARCH, 31, 7, 0)), + unixTime(2018, Month.MARCH, 31, 18, 0) + ) + assertEquals( + removeTimezone(unixTime(2018, Month.MARCH, 31, 13, 0)), + unixTime(2018, Month.APRIL, 1, 0, 0) + ) + assertEquals( + removeTimezone(unixTime(2018, Month.MARCH, 31, 14, 0)), + unixTime(2018, Month.APRIL, 1, 1, 0) + ) + assertEquals( + removeTimezone(unixTime(2018, Month.MARCH, 31, 14, 59)), + unixTime(2018, Month.APRIL, 1, 1, 59) + ) + // DST ends + assertEquals( + removeTimezone(unixTime(2018, Month.MARCH, 31, 16, 0)), + unixTime(2018, Month.APRIL, 1, 2, 0) + ) + assertEquals( + removeTimezone(unixTime(2018, Month.MARCH, 31, 17, 0)), + unixTime(2018, Month.APRIL, 1, 3, 0) + ) + assertEquals( + removeTimezone(unixTime(2018, Month.MARCH, 31, 18, 0)), + unixTime(2018, Month.APRIL, 1, 4, 0) + ) + assertEquals( + removeTimezone(unixTime(2018, Month.APRIL, 1, 0, 0)), + unixTime(2018, Month.APRIL, 1, 10, 0) + ) + assertEquals( + removeTimezone(unixTime(2018, Month.APRIL, 1, 8, 0)), + unixTime(2018, Month.APRIL, 1, 18, 0) + ) + } + private fun unixTime(year: Int, month: Month, day: Int): Long { return unixTime(year, month, day, 0, 0) } 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 2e20f5033..58c89339d 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 @@ -21,6 +21,7 @@ package org.isoron.uhabits.core.reminders import org.isoron.platform.time.LocalDate.Companion.applyTimezone import org.isoron.platform.time.LocalDate.Companion.getLocalTime import org.isoron.platform.time.LocalDate.Companion.getStartOfDayWithOffset +import org.isoron.platform.time.LocalDate.Companion.removeTimezone import org.isoron.uhabits.core.AppScope import org.isoron.uhabits.core.commands.ChangeHabitColorCommand import org.isoron.uhabits.core.commands.Command @@ -30,7 +31,6 @@ import org.isoron.uhabits.core.models.Habit 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.removeTimezone import java.util.Locale import java.util.Objects import javax.inject.Inject 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 45af855c3..82e65a5aa 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 @@ -18,8 +18,6 @@ */ package org.isoron.uhabits.core.utils -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.applyTimezone @@ -148,12 +146,6 @@ abstract class DateUtils { fun getStartOfTodayCalendarWithOffset(): GregorianCalendar = getCalendar(getStartOfTodayWithOffset()) - @JvmStatic - fun removeTimezone(timestamp: Long): Long { - val tz = getTimeZone() - return timestamp + (tz.offsetAt(Instant.fromEpochMilliseconds(timestamp)).totalSeconds * 1000) - } - private fun getLocale(): Locale { return Locale.forLanguageTag(LocalDate.getLocale().toLanguageTag().toString()) } diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/reminders/ReminderSchedulerTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/reminders/ReminderSchedulerTest.kt index 1fefe5ef2..7373ba7f2 100644 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/reminders/ReminderSchedulerTest.kt +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/reminders/ReminderSchedulerTest.kt @@ -24,13 +24,13 @@ import com.nhaarman.mockitokotlin2.verify import com.nhaarman.mockitokotlin2.whenever import org.isoron.platform.time.LocalDate import org.isoron.platform.time.LocalDate.Companion.applyTimezone +import org.isoron.platform.time.LocalDate.Companion.removeTimezone import org.isoron.uhabits.core.BaseUnitTest import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.models.Reminder import org.isoron.uhabits.core.models.WeekdayList import org.isoron.uhabits.core.preferences.WidgetPreferences import org.isoron.uhabits.core.utils.DateUtils.Companion.getStartOfTodayCalendar -import org.isoron.uhabits.core.utils.DateUtils.Companion.removeTimezone import org.junit.Before import org.junit.Test import org.junit.runner.RunWith 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 88066ce51..8e79b607b 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 @@ -19,7 +19,6 @@ package org.isoron.uhabits.core.utils import io.fluidsonic.locale.Locale -import junit.framework.Assert.assertEquals import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.core.IsEqual.equalTo import org.isoron.platform.core.BaseUnitTest.Companion.FIXED_LOCAL_TIME @@ -33,7 +32,6 @@ import org.isoron.uhabits.core.models.Timestamp import org.isoron.uhabits.core.utils.DateUtils.Companion.formatHeaderDate 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.truncate import org.junit.Before import org.junit.Test @@ -317,143 +315,4 @@ class DateUtilsTest : BaseUnitTest() { equalTo(Timestamp(FIXED_LOCAL_TIME - DAY_LENGTH)) ) } - - @Test - fun test_removeTimezone() { - LocalDate.fixedTimeZone = kotlinx.datetime.TimeZone.of("Australia/Sydney") - assertEquals( - removeTimezone(unixTime(2017, Calendar.JULY, 30, 8, 0)), - unixTime(2017, Calendar.JULY, 30, 18, 0) - ) - assertEquals( - removeTimezone(unixTime(2017, Calendar.SEPTEMBER, 29, 14, 0)), - unixTime(2017, Calendar.SEPTEMBER, 30, 0, 0) - ) - assertEquals( - removeTimezone(unixTime(2017, Calendar.SEPTEMBER, 30, 0, 0)), - unixTime(2017, Calendar.SEPTEMBER, 30, 10, 0) - ) - assertEquals( - removeTimezone(unixTime(2017, Calendar.SEPTEMBER, 30, 1, 0)), - unixTime(2017, Calendar.SEPTEMBER, 30, 11, 0) - ) - assertEquals( - removeTimezone(unixTime(2017, Calendar.SEPTEMBER, 30, 2, 0)), - unixTime(2017, Calendar.SEPTEMBER, 30, 12, 0) - ) - assertEquals( - removeTimezone(unixTime(2017, Calendar.SEPTEMBER, 30, 3, 0)), - unixTime(2017, Calendar.SEPTEMBER, 30, 13, 0) - ) - assertEquals( - removeTimezone(unixTime(2017, Calendar.SEPTEMBER, 30, 12, 0)), - unixTime(2017, Calendar.SEPTEMBER, 30, 22, 0) - ) - assertEquals( - removeTimezone(unixTime(2017, Calendar.SEPTEMBER, 30, 13, 0)), - unixTime(2017, Calendar.SEPTEMBER, 30, 23, 0) - ) - assertEquals( - removeTimezone(unixTime(2017, Calendar.SEPTEMBER, 30, 14, 0)), - unixTime(2017, Calendar.OCTOBER, 1, 0, 0) - ) - assertEquals( - removeTimezone(unixTime(2017, Calendar.SEPTEMBER, 30, 15, 0)), - unixTime(2017, Calendar.OCTOBER, 1, 1, 0) - ) - assertEquals( - removeTimezone(unixTime(2017, Calendar.SEPTEMBER, 30, 15, 59)), - unixTime(2017, Calendar.OCTOBER, 1, 1, 59) - ) - // DST begins - assertEquals( - removeTimezone(unixTime(2017, Calendar.SEPTEMBER, 30, 16, 0)), - unixTime(2017, Calendar.OCTOBER, 1, 3, 0) - ) - assertEquals( - removeTimezone(unixTime(2017, Calendar.SEPTEMBER, 30, 17, 0)), - unixTime(2017, Calendar.OCTOBER, 1, 4, 0) - ) - assertEquals( - removeTimezone(unixTime(2017, Calendar.SEPTEMBER, 30, 18, 0)), - unixTime(2017, Calendar.OCTOBER, 1, 5, 0) - ) - assertEquals( - removeTimezone(unixTime(2017, Calendar.OCTOBER, 1, 0, 0)), - unixTime(2017, Calendar.OCTOBER, 1, 11, 0) - ) - assertEquals( - removeTimezone(unixTime(2017, Calendar.OCTOBER, 1, 1, 0)), - unixTime(2017, Calendar.OCTOBER, 1, 12, 0) - ) - assertEquals( - removeTimezone(unixTime(2017, Calendar.OCTOBER, 1, 2, 0)), - unixTime(2017, Calendar.OCTOBER, 1, 13, 0) - ) - assertEquals( - removeTimezone(unixTime(2017, Calendar.OCTOBER, 1, 3, 0)), - unixTime(2017, Calendar.OCTOBER, 1, 14, 0) - ) - assertEquals( - removeTimezone(unixTime(2017, Calendar.OCTOBER, 1, 4, 0)), - unixTime(2017, Calendar.OCTOBER, 1, 15, 0) - ) - assertEquals( - removeTimezone(unixTime(2017, Calendar.OCTOBER, 1, 8, 0)), - unixTime(2017, Calendar.OCTOBER, 1, 19, 0) - ) - assertEquals( - removeTimezone(unixTime(2017, Calendar.OCTOBER, 2, 8, 0)), - unixTime(2017, Calendar.OCTOBER, 2, 19, 0) - ) - assertEquals( - removeTimezone(unixTime(2017, Calendar.NOVEMBER, 30, 8, 0)), - unixTime(2017, Calendar.NOVEMBER, 30, 19, 0) - ) - assertEquals( - removeTimezone(unixTime(2018, Calendar.MARCH, 30, 13, 0)), - unixTime(2018, Calendar.MARCH, 31, 0, 0) - ) - assertEquals( - removeTimezone(unixTime(2018, Calendar.MARCH, 31, 1, 0)), - unixTime(2018, Calendar.MARCH, 31, 12, 0) - ) - assertEquals( - removeTimezone(unixTime(2018, Calendar.MARCH, 31, 7, 0)), - unixTime(2018, Calendar.MARCH, 31, 18, 0) - ) - assertEquals( - removeTimezone(unixTime(2018, Calendar.MARCH, 31, 13, 0)), - unixTime(2018, Calendar.APRIL, 1, 0, 0) - ) - assertEquals( - removeTimezone(unixTime(2018, Calendar.MARCH, 31, 14, 0)), - unixTime(2018, Calendar.APRIL, 1, 1, 0) - ) - assertEquals( - removeTimezone(unixTime(2018, Calendar.MARCH, 31, 14, 59)), - unixTime(2018, Calendar.APRIL, 1, 1, 59) - ) - // DST ends - assertEquals( - removeTimezone(unixTime(2018, Calendar.MARCH, 31, 16, 0)), - unixTime(2018, Calendar.APRIL, 1, 2, 0) - ) - assertEquals( - removeTimezone(unixTime(2018, Calendar.MARCH, 31, 17, 0)), - unixTime(2018, Calendar.APRIL, 1, 3, 0) - ) - assertEquals( - removeTimezone(unixTime(2018, Calendar.MARCH, 31, 18, 0)), - unixTime(2018, Calendar.APRIL, 1, 4, 0) - ) - assertEquals( - removeTimezone(unixTime(2018, Calendar.APRIL, 1, 0, 0)), - unixTime(2018, Calendar.APRIL, 1, 10, 0) - ) - assertEquals( - removeTimezone(unixTime(2018, Calendar.APRIL, 1, 8, 0)), - unixTime(2018, Calendar.APRIL, 1, 18, 0) - ) - } }