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/DateFormats.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/utils/DateFormats.kt
similarity index 53%
rename from android/uhabits-core/src/main/java/org/isoron/uhabits/core/utils/DateFormats.java
rename to android/uhabits-core/src/main/java/org/isoron/uhabits/core/utils/DateFormats.kt
index 42b164e20..9414f50a3 100644
--- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/utils/DateFormats.java
+++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/utils/DateFormats.kt
@@ -17,31 +17,29 @@
* with this program. If not, see .
*/
-package org.isoron.uhabits.core.utils;
+package org.isoron.uhabits.core.utils
-import androidx.annotation.*;
+import java.text.SimpleDateFormat
+import java.util.Locale
+import java.util.TimeZone
-import java.text.*;
-import java.util.*;
+class DateFormats {
-public class DateFormats
-{
- @NonNull
- public static SimpleDateFormat fromSkeleton(@NonNull String skeleton,
- @NonNull Locale locale)
- {
- SimpleDateFormat df = new SimpleDateFormat(skeleton, locale);
- df.setTimeZone(TimeZone.getTimeZone("UTC"));
- return df;
- }
+ companion object {
- public static SimpleDateFormat getBackupDateFormat()
- {
- return fromSkeleton("yyyy-MM-dd HHmmss", Locale.US);
- }
+ @JvmStatic fun fromSkeleton(
+ skeleton: String,
+ locale: Locale
+ ): SimpleDateFormat {
+ val df = SimpleDateFormat(skeleton, locale)
+ df.timeZone = TimeZone.getTimeZone("UTC")
+ return df
+ }
+
+ @JvmStatic fun getBackupDateFormat(): SimpleDateFormat =
+ fromSkeleton("yyyy-MM-dd HHmmss", Locale.US)
- public static SimpleDateFormat getCSVDateFormat()
- {
- return fromSkeleton("yyyy-MM-dd", Locale.US);
+ @JvmStatic fun getCSVDateFormat(): SimpleDateFormat =
+ fromSkeleton("yyyy-MM-dd", Locale.US)
}
}
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
+ }
+}
diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/utils/MidnightTimer.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/utils/MidnightTimer.java
deleted file mode 100644
index 0beee5cd8..000000000
--- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/utils/MidnightTimer.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2017 Á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.*;
-
-import java.util.*;
-import java.util.concurrent.*;
-
-import javax.inject.*;
-
-/**
- * A class that emits events when a new day starts.
- */
-@AppScope
-public class MidnightTimer
-{
- private final List listeners;
-
- private ScheduledExecutorService executor;
-
- @Inject
- public MidnightTimer()
- {
- this.listeners = new LinkedList<>();
- }
-
- public synchronized void addListener(MidnightListener listener)
- {
- this.listeners.add(listener);
- }
-
- public synchronized void onPause()
- {
- executor.shutdownNow();
- }
-
- public synchronized void onResume()
- {
- executor = Executors.newSingleThreadScheduledExecutor();
- executor.scheduleAtFixedRate(() -> notifyListeners(),
- DateUtils.millisecondsUntilTomorrowWithOffset() + 1000,
- DateUtils.DAY_LENGTH, TimeUnit.MILLISECONDS);
- }
-
- public synchronized void removeListener(MidnightListener listener)
- {
- this.listeners.remove(listener);
- }
-
- private synchronized void notifyListeners()
- {
- for (MidnightListener l : listeners) l.atMidnight();
- }
-
- public interface MidnightListener
- {
- void atMidnight();
- }
-}
diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/utils/MidnightTimer.kt b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/utils/MidnightTimer.kt
new file mode 100644
index 000000000..ec322e55f
--- /dev/null
+++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/utils/MidnightTimer.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2017 Á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.AppScope
+import java.util.LinkedList
+import java.util.concurrent.Executors
+import java.util.concurrent.ScheduledExecutorService
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+
+/**
+ * A class that emits events when a new day starts.
+ */
+@AppScope
+open class MidnightTimer @Inject constructor() {
+ private val listeners: MutableList = LinkedList()
+ private lateinit var executor: ScheduledExecutorService
+
+ @Synchronized fun addListener(listener: MidnightListener) {
+ this.listeners.add(listener)
+ }
+
+ @Synchronized fun onPause() = executor.shutdownNow()
+
+ @Synchronized fun onResume() {
+ executor = Executors.newSingleThreadScheduledExecutor()
+ executor.scheduleAtFixedRate(
+ { notifyListeners() },
+ DateUtils.millisecondsUntilTomorrowWithOffset() + 1000,
+ DateUtils.DAY_LENGTH,
+ TimeUnit.MILLISECONDS
+ )
+ }
+
+ @Synchronized fun removeListener(listener: MidnightListener) = this.listeners.remove(listener)
+
+ @Synchronized private fun notifyListeners() {
+ for (l in listeners) {
+ l.atMidnight()
+ }
+ }
+
+ interface MidnightListener {
+ fun atMidnight()
+ }
+}
diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/utils/StringUtils.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/utils/StringUtils.java
deleted file mode 100644
index a31b261ee..000000000
--- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/utils/StringUtils.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2017 Á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.apache.commons.lang3.builder.*;
-
-import java.math.*;
-import java.util.*;
-
-public class StringUtils
-{
- private static StandardToStringStyle toStringStyle = null;
-
- public static String getRandomId()
- {
- return new BigInteger(260, new Random()).toString(32).substring(0, 32);
- }
-
- public static ToStringStyle defaultToStringStyle()
- {
- if (toStringStyle == null)
- {
- toStringStyle = new StandardToStringStyle();
- toStringStyle.setFieldSeparator(", ");
- toStringStyle.setUseClassName(false);
- toStringStyle.setUseIdentityHashCode(false);
- toStringStyle.setContentStart("{");
- toStringStyle.setContentEnd("}");
- toStringStyle.setFieldNameValueSeparator(": ");
- toStringStyle.setArrayStart("[");
- toStringStyle.setArrayEnd("]");
- }
-
- return toStringStyle;
- }
-
- public static String joinLongs(long values[])
- {
- return org.apache.commons.lang3.StringUtils.join(values, ',');
- }
-
- public static long[] splitLongs(String str)
- {
- String parts[] = org.apache.commons.lang3.StringUtils.split(str, ',');
-
- long numbers[] = new long[parts.length];
- for (int i = 0; i < parts.length; i++) numbers[i] = Long.valueOf(parts[i]);
- return numbers;
- }
-}
diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/utils/StringUtils.kt b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/utils/StringUtils.kt
new file mode 100644
index 000000000..5ad68cad9
--- /dev/null
+++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/utils/StringUtils.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2017 Á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.apache.commons.lang3.builder.StandardToStringStyle
+import org.apache.commons.lang3.builder.ToStringStyle
+import java.math.BigInteger
+import java.util.Random
+
+class StringUtils {
+
+ companion object {
+ private lateinit var toStringStyle: StandardToStringStyle
+
+ @JvmStatic
+ fun getRandomId(): String {
+ return BigInteger(260, Random()).toString(32).subSequence(0, 32).toString()
+ }
+
+ @JvmStatic
+ fun defaultToStringStyle(): ToStringStyle {
+ toStringStyle = StandardToStringStyle()
+ toStringStyle.apply {
+ fieldSeparator = ", "
+ isUseClassName = false
+ isUseIdentityHashCode = false
+ contentStart = "{"
+ contentEnd = "}"
+ fieldNameValueSeparator = ": "
+ arrayStart = "["
+ arrayEnd = "]"
+ }
+
+ return toStringStyle
+ }
+
+ @JvmStatic
+ fun joinLongs(values: LongArray): String {
+ return org.apache.commons.lang3.StringUtils.join(values, ',')
+ }
+
+ @JvmStatic
+ fun splitLongs(str: String): LongArray {
+ val parts: Array = org.apache.commons.lang3.StringUtils.split(str, ',')
+ val numbers = LongArray(parts.size) {
+ i ->
+ parts[i].toLong()
+ }
+ return numbers
+ }
+ }
+}