mirror of https://github.com/iSoron/uhabits.git
parent
b58f4d6177
commit
53dc1eb14a
@ -0,0 +1,285 @@
|
||||
/*
|
||||
* Copyright (C) 2016-2021 Álinson Santos Xavier <git@axavier.org>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<Int> {
|
||||
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
|
||||
}
|
Loading…
Reference in new issue