From 6e845b5482b62bd5885c78d1f30b85358a2f0242 Mon Sep 17 00:00:00 2001 From: MarKco Date: Mon, 4 Jan 2021 19:03:01 +0100 Subject: [PATCH] Convert DateUtils to Kotlin Keep converting utils classes. In order to make DateUtils work properly with java/kotlin interoperability I changed the type of the localeWeekdayList array in FrequencyChart from int[] to Integer[] and explicitly referenced DateUtils in HeaderView --- .../common/views/FrequencyChart.java | 2 +- .../habits/list/views/HeaderView.kt | 7 +- .../isoron/uhabits/core/utils/DateUtils.java | 333 ------------------ .../isoron/uhabits/core/utils/DateUtils.kt | 331 +++++++++++++++++ 4 files changed, 335 insertions(+), 338 deletions(-) delete mode 100644 android/uhabits-core/src/main/java/org/isoron/uhabits/core/utils/DateUtils.java create mode 100644 android/uhabits-core/src/main/java/org/isoron/uhabits/core/utils/DateUtils.kt diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/views/FrequencyChart.java b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/views/FrequencyChart.java index bfa91cd06..324119c73 100644 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/views/FrequencyChart.java +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/views/FrequencyChart.java @@ -205,7 +205,7 @@ public class FrequencyChart extends ScrollableChart float rowHeight = rect.height() / 8.0f; prevRect.set(rect); - int[] localeWeekdayList = DateUtils.getWeekdaySequence(firstWeekday); + Integer[] localeWeekdayList = DateUtils.getWeekdaySequence(firstWeekday); for (int j = 0; j < localeWeekdayList.length; j++) { rect.set(0, 0, baseSize, baseSize); diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HeaderView.kt b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HeaderView.kt index b4c8c131b..b349a8f2c 100644 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HeaderView.kt +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HeaderView.kt @@ -32,8 +32,7 @@ import android.view.View.MeasureSpec.EXACTLY import org.isoron.uhabits.R import org.isoron.uhabits.activities.common.views.ScrollableChart import org.isoron.uhabits.core.preferences.Preferences -import org.isoron.uhabits.core.utils.DateUtils.formatHeaderDate -import org.isoron.uhabits.core.utils.DateUtils.getStartOfTodayCalendarWithOffset +import org.isoron.uhabits.core.utils.DateUtils import org.isoron.uhabits.core.utils.MidnightTimer import org.isoron.uhabits.utils.dim import org.isoron.uhabits.utils.dp @@ -115,7 +114,7 @@ class HeaderView( } fun draw(canvas: Canvas) { - val day = getStartOfTodayCalendarWithOffset() + val day = DateUtils.getStartOfTodayCalendarWithOffset() val width = dim(R.dimen.checkmarkWidth) val height = dim(R.dimen.checkmarkHeight) val isReversed = prefs.isCheckmarkSequenceReversed @@ -139,7 +138,7 @@ class HeaderView( val y1 = rect.centerY() - 0.25 * em val y2 = rect.centerY() + 1.25 * em - val lines = formatHeaderDate(day).toUpperCase().split("\n") + val lines = DateUtils.formatHeaderDate(day).toUpperCase().split("\n") canvas.drawText(lines[0], rect.centerX(), y1.toFloat(), paint) canvas.drawText(lines[1], rect.centerX(), y2.toFloat(), paint) day.add(GregorianCalendar.DAY_OF_MONTH, -1) diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/utils/DateUtils.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/utils/DateUtils.java deleted file mode 100644 index aa3e89a54..000000000 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/utils/DateUtils.java +++ /dev/null @@ -1,333 +0,0 @@ -/* - * Copyright (C) 2016 Á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.uhabits.core.utils; - -import androidx.annotation.*; - -import org.isoron.uhabits.core.models.*; -import org.jetbrains.annotations.*; - -import java.util.*; - -import static java.util.Calendar.*; - -public abstract class DateUtils -{ - - private static Long fixedLocalTime = null; - - private static TimeZone fixedTimeZone = null; - - private static Locale fixedLocale = null; - - private static int startDayHourOffset = 0; - - private static int startDayMinuteOffset = 0; - - /** - * Number of milliseconds in one minute. - */ - - public static final long MINUTE_LENGTH = 60 * 1000; - - /** - * Number of milliseconds in one hour. - */ - public static final long HOUR_LENGTH = 60 * MINUTE_LENGTH; - - /** - * Number of milliseconds in one day. - */ - public static final long DAY_LENGTH = 24 * HOUR_LENGTH; - - public static long applyTimezone(long localTimestamp) - { - TimeZone tz = getTimezone(); - return localTimestamp - tz.getOffset(localTimestamp - tz.getOffset(localTimestamp)); - } - - public static String formatHeaderDate(GregorianCalendar day) - { - Locale locale = getLocale(); - String dayOfMonth = Integer.toString(day.get(DAY_OF_MONTH)); - String dayOfWeek = day.getDisplayName(DAY_OF_WEEK, SHORT, locale); - return dayOfWeek + "\n" + dayOfMonth; - } - - private static GregorianCalendar getCalendar(long timestamp) - { - GregorianCalendar day = - new GregorianCalendar(TimeZone.getTimeZone("GMT"), getLocale()); - day.setTimeInMillis(timestamp); - return day; - } - - public static long getLocalTime() - { - if (fixedLocalTime != null) return fixedLocalTime; - - TimeZone tz = getTimezone(); - long now = new Date().getTime(); - return now + tz.getOffset(now); - } - - /** - * Returns an array of strings with the names for each day of the week, - * in either SHORT or LONG format. The first entry corresponds to the - * first day of the week, according to the provided argument. - * - * @param format Either GregorianCalendar.SHORT or LONG - * @param firstWeekday An integer representing the first day of the week, - * following java.util.Calendar conventions. That is, - * Saturday corresponds to 7, and Sunday corresponds - * to 1. - */ - @NotNull - private static String[] getWeekdayNames(int format, int firstWeekday) - { - String[] days = new String[7]; - Calendar calendar = new GregorianCalendar(); - calendar.set(DAY_OF_WEEK, firstWeekday); - for (int i = 0; i < days.length; i++) { - days[i] = calendar.getDisplayName(DAY_OF_WEEK, format, - getLocale()); - calendar.add(DAY_OF_MONTH, 1); - } - - return days; - } - - /** - * 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. - */ - public static int[] getWeekdaySequence(int firstWeekday) - { - return new int[] - { - (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, - }; - } - - /** - * @return An integer representing the first day of the week, according to - * the current locale. Sunday corresponds to 1, Monday to 2, and so on, - * until Saturday, which is represented by 7. This is consistent - * with java.util.Calendar constants. - */ - public static int getFirstWeekdayNumberAccordingToLocale() - { - return new GregorianCalendar().getFirstDayOfWeek(); - } - - /** - * @return A vector of strings with the long names for the week days, - * according to the current locale. The first entry corresponds to Saturday, - * the second entry corresponds to Monday, and so on. - * - * @param firstWeekday Either Calendar.SATURDAY, Calendar.MONDAY, or other - * weekdays defined in this class. - */ - public static String[] getLongWeekdayNames(int firstWeekday) - { - return getWeekdayNames(GregorianCalendar.LONG, firstWeekday); - } - - /** - * Returns a vector of strings with the short names for the week days, - * according to the current locale. The first entry corresponds to Saturday, - * the second entry corresponds to Monday, and so on. - * - * @param firstWeekday Either Calendar.SATURDAY, Calendar.MONDAY, or other - * weekdays defined in this class. - */ - public static String[] getShortWeekdayNames(int firstWeekday) - { - return getWeekdayNames(GregorianCalendar.SHORT, firstWeekday); - } - - @NonNull - public static Timestamp getToday() - { - return new Timestamp(getStartOfToday()); - } - - @NonNull - public static Timestamp getTodayWithOffset() - { - return new Timestamp(getStartOfTodayWithOffset()); - } - - public static long getStartOfDay(long timestamp) - { - return (timestamp / DAY_LENGTH) * DAY_LENGTH; - } - - public static long getStartOfDayWithOffset(long timestamp) - { - long offset = startDayHourOffset * HOUR_LENGTH + startDayMinuteOffset * MINUTE_LENGTH; - return getStartOfDay(timestamp - offset); - } - - public static long getStartOfToday() - { - return getStartOfDay(getLocalTime()); - } - - public static long getStartOfTomorrowWithOffset() - { - return getUpcomingTimeInMillis(startDayHourOffset, startDayMinuteOffset); - } - - public static long getStartOfTodayWithOffset() - { - return getStartOfDayWithOffset(getLocalTime()); - } - - public static long millisecondsUntilTomorrowWithOffset() - { - return getStartOfTomorrowWithOffset() - getLocalTime(); - } - - public static GregorianCalendar getStartOfTodayCalendar() - { - return getCalendar(getStartOfToday()); - } - - public static GregorianCalendar getStartOfTodayCalendarWithOffset() - { - return getCalendar(getStartOfTodayWithOffset()); - } - - private static TimeZone getTimezone() - { - if(fixedTimeZone != null) return fixedTimeZone; - return TimeZone.getDefault(); - } - - public static void setFixedTimeZone(TimeZone tz) - { - fixedTimeZone = tz; - } - - public static long removeTimezone(long timestamp) - { - TimeZone tz = getTimezone(); - return timestamp + tz.getOffset(timestamp); - } - - public static void setFixedLocalTime(Long timestamp) - { - fixedLocalTime = timestamp; - } - - public static void setFixedLocale(Locale locale) - { - fixedLocale = locale; - } - - public static void setStartDayOffset(int hourOffset, int minuteOffset) - { - startDayHourOffset = hourOffset; - startDayMinuteOffset = minuteOffset; - } - - private static Locale getLocale() - { - if(fixedLocale != null) return fixedLocale; - return Locale.getDefault(); - } - - public static Timestamp truncate(TruncateField field, - Timestamp timestamp, - int firstWeekday) - { - return new Timestamp(truncate(field, timestamp.getUnixTime(), firstWeekday)); - } - - public static Long truncate(TruncateField field, - long timestamp, - int firstWeekday) - { - GregorianCalendar cal = DateUtils.getCalendar(timestamp); - - - switch (field) - { - case DAY: - return cal.getTimeInMillis(); - - case MONTH: - cal.set(DAY_OF_MONTH, 1); - return cal.getTimeInMillis(); - - case WEEK_NUMBER: - int weekday = cal.get(DAY_OF_WEEK); - int delta = weekday - firstWeekday; - if (delta < 0) delta += 7; - cal.add(Calendar.DAY_OF_YEAR, -delta); - return cal.getTimeInMillis(); - - case QUARTER: - int quarter = cal.get(Calendar.MONTH) / 3; - cal.set(DAY_OF_MONTH, 1); - cal.set(Calendar.MONTH, quarter * 3); - return cal.getTimeInMillis(); - - case YEAR: - cal.set(Calendar.MONTH, Calendar.JANUARY); - cal.set(DAY_OF_MONTH, 1); - return cal.getTimeInMillis(); - - default: - throw new IllegalArgumentException(); - } - } - - public static long getUpcomingTimeInMillis(int hour, int minute) - { - Calendar calendar = DateUtils.getStartOfTodayCalendar(); - calendar.set(Calendar.HOUR_OF_DAY, hour); - calendar.set(Calendar.MINUTE, minute); - calendar.set(Calendar.SECOND, 0); - long time = calendar.getTimeInMillis(); - - if (DateUtils.getLocalTime() > time) - time += DateUtils.DAY_LENGTH; - - return applyTimezone(time); - } - - public enum TruncateField - { - DAY, MONTH, WEEK_NUMBER, YEAR, QUARTER - } -} diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/utils/DateUtils.kt b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/utils/DateUtils.kt new file mode 100644 index 000000000..0e3658389 --- /dev/null +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/utils/DateUtils.kt @@ -0,0 +1,331 @@ +/* + * Copyright (C) 2016 Á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.uhabits.core.utils + +import org.isoron.uhabits.core.models.Timestamp +import java.util.Calendar +import java.util.Calendar.DAY_OF_MONTH +import java.util.Calendar.DAY_OF_WEEK +import java.util.Calendar.SHORT +import java.util.Date +import java.util.GregorianCalendar +import java.util.Locale +import java.util.TimeZone + +abstract class DateUtils { + companion object { + private var fixedLocalTime: Long? = null + private var fixedTimeZone: TimeZone? = null + private var fixedLocale: Locale? = null + private var startDayHourOffset: Int = 0 + private var startDayMinuteOffset: Int = 0 + + /** + * Number of milliseconds in one minute. + */ + const val MINUTE_LENGTH: Long = 60 * 1000 + + /** + * 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 + + @JvmStatic + fun applyTimezone(localTimestamp: Long): Long { + val tz: TimeZone = getTimeZone() + return localTimestamp - tz.getOffset(localTimestamp - tz.getOffset(localTimestamp)) + } + + @JvmStatic + fun formatHeaderDate(day: GregorianCalendar): String { + val locale = getLocale() + val dayOfMonth: String = day.get(DAY_OF_MONTH).toString() + val dayOfWeek = day.getDisplayName(DAY_OF_WEEK, SHORT, locale) + return dayOfWeek + "\n" + dayOfMonth + } + + private fun getCalendar(timestamp: Long): GregorianCalendar { + val day = GregorianCalendar(TimeZone.getTimeZone("GMT"), getLocale()) + day.timeInMillis = timestamp + return day + } + + @JvmStatic + fun getLocalTime(): Long { + if (fixedLocalTime != null) return fixedLocalTime as Long + + val tz = getTimeZone() + val now = Date().getTime() + return now + tz.getOffset(now) + } + + /** + * Returns an array of strings with the names for each day of the week, + * in either SHORT or LONG format. The first entry corresponds to the + * first day of the week, according to the provided argument. + * + * @param format Either GregorianCalendar.SHORT or LONG + * @param firstWeekDay An integer representing the first day of the week, + * following java.util.Calendar conventions. That is, + * Saturday corresponds to 7, and Sunday corresponds + * to 1. + */ + private fun getWeekdayNames( + format: Int, + firstWeekDay: Int + ): Array { + val calendar = GregorianCalendar() + calendar.set(DAY_OF_WEEK, firstWeekDay) + + val daysNullable = ArrayList() + for (i in 1..7) { + daysNullable.add( + calendar.getDisplayName( + DAY_OF_WEEK, + format, + getLocale() + ) + ) + + calendar.add(DAY_OF_MONTH, 1) + } + + return daysNullable.toTypedArray() + } + + /** + * 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. + */ + @JvmStatic + 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, + ) + } + + /** + * @return An integer representing the first day of the week, according to + * the current locale. Sunday corresponds to 1, Monday to 2, and so on, + * until Saturday, which is represented by 7. This is consistent + * with java.util.Calendar constants. + */ + @JvmStatic + fun getFirstWeekdayNumberAccordingToLocale(): Int { + return GregorianCalendar().firstDayOfWeek + } + + /** + * @return A vector of strings with the long names for the week days, + * according to the current locale. The first entry corresponds to Saturday, + * the second entry corresponds to Monday, and so on. + * + * @param firstWeekday Either Calendar.SATURDAY, Calendar.MONDAY, or other + * weekdays defined in this class. + */ + @JvmStatic + fun getLongWeekdayNames(firstWeekday: Int): Array { + return getWeekdayNames(GregorianCalendar.LONG, firstWeekday) + } + + /** + * Returns a vector of strings with the short names for the week days, + * according to the current locale. The first entry corresponds to Saturday, + * the second entry corresponds to Monday, and so on. + * + * @param firstWeekday Either Calendar.SATURDAY, Calendar.MONDAY, or other + * weekdays defined in this class. + */ + @JvmStatic + fun getShortWeekdayNames(firstWeekday: Int): Array { + return getWeekdayNames(GregorianCalendar.SHORT, firstWeekday) + } + + @JvmStatic + fun getToday(): Timestamp = Timestamp(getStartOfToday()) + + @JvmStatic + fun getTodayWithOffset(): Timestamp = Timestamp(getStartOfTodayWithOffset()) + + @JvmStatic + fun getStartOfDay(timestamp: Long): Long = (timestamp / DAY_LENGTH) * DAY_LENGTH + + @JvmStatic + fun getStartOfDayWithOffset(timestamp: Long): Long { + val offset = startDayHourOffset * HOUR_LENGTH + startDayMinuteOffset * MINUTE_LENGTH + return getStartOfDay(timestamp - offset) + } + + @JvmStatic + fun getStartOfToday(): Long = getStartOfDay(getLocalTime()) + + @JvmStatic + fun getStartOfTomorrowWithOffset(): Long = getUpcomingTimeInMillis( + startDayHourOffset, + startDayMinuteOffset + ) + + @JvmStatic + fun getStartOfTodayWithOffset(): Long = getStartOfDayWithOffset(getLocalTime()) + + @JvmStatic + fun millisecondsUntilTomorrowWithOffset(): Long = getStartOfTomorrowWithOffset() - getLocalTime() + + @JvmStatic + fun getStartOfTodayCalendar(): GregorianCalendar = getCalendar(getStartOfToday()) + + @JvmStatic + fun getStartOfTodayCalendarWithOffset(): GregorianCalendar = getCalendar(getStartOfTodayWithOffset()) + + @JvmStatic + fun getTimeZone(): TimeZone { + return fixedTimeZone ?: TimeZone.getDefault() + } + + @JvmStatic + fun getTimezone(): TimeZone { + return fixedTimeZone ?: TimeZone.getDefault() + } + + @JvmStatic + fun removeTimezone(timestamp: Long): Long { + val tz = getTimeZone() + return timestamp + tz.getOffset(timestamp) + } + + @JvmStatic + fun setStartDayOffset(hourOffset: Int, minuteOffset: Int) { + startDayHourOffset = hourOffset + startDayMinuteOffset = minuteOffset + } + + @JvmStatic + fun getLocale(): Locale { + return fixedLocale ?: Locale.getDefault() + } + + @JvmStatic + fun truncate( + field: TruncateField, + timestamp: Timestamp, + firstWeekday: Int + ): Timestamp { + return Timestamp( + truncate( + field, + timestamp.unixTime, + firstWeekday + ) + ) + } + + @JvmStatic + fun truncate( + field: TruncateField, + timestamp: Long, + firstWeekday: Int + ): Long { + val cal = getCalendar(timestamp) + + return when (field) { + + TruncateField.DAY -> { cal.timeInMillis } + + TruncateField.MONTH -> { + cal.set(DAY_OF_MONTH, 1) + cal.timeInMillis + } + + TruncateField.WEEK_NUMBER -> { + val weekDay = cal.get(DAY_OF_WEEK) + var delta = weekDay - firstWeekday + if (delta < 0) { delta += 7 } + cal.add(Calendar.DAY_OF_YEAR, -delta) + cal.timeInMillis + } + + TruncateField.QUARTER -> { + val quarter = cal.get(Calendar.MONTH) / 3 + cal.set(DAY_OF_MONTH, 1) + cal.set(Calendar.MONTH, quarter * 3) + cal.timeInMillis + } + + TruncateField.YEAR -> { + cal.set(Calendar.MONTH, Calendar.JANUARY) + cal.set(DAY_OF_MONTH, 1) + cal.timeInMillis + } + } + } + + @JvmStatic + fun getUpcomingTimeInMillis( + hour: Int, + minute: Int + ): Long { + val calendar = getStartOfTodayCalendar() + calendar.set(Calendar.HOUR_OF_DAY, hour) + calendar.set(Calendar.MINUTE, minute) + calendar.set(Calendar.SECOND, 0) + var time = calendar.timeInMillis + + if (getLocalTime() > time) { + time += DAY_LENGTH + } + + return applyTimezone(time) + } + + @JvmStatic + fun setFixedLocalTime(newFixedLocalTime: Long?) { + this.fixedLocalTime = newFixedLocalTime + } + + @JvmStatic + fun setFixedTimeZone(newTimeZone: TimeZone?) { + this.fixedTimeZone = newTimeZone + } + + @JvmStatic + fun setFixedLocale(newLocale: Locale?) { + this.fixedLocale = newLocale + } + } + + enum class TruncateField { + DAY, MONTH, WEEK_NUMBER, YEAR, QUARTER + } +}