Move query builders to kmp

pull/2910/head
Alex Baker 1 year ago
parent bf4167651b
commit 79ebc9a2c7

@ -17,7 +17,7 @@ import org.tasks.Freeze.Companion.freezeAt
import org.tasks.date.DateTimeUtils import org.tasks.date.DateTimeUtils
import org.tasks.time.DateTime import org.tasks.time.DateTime
import java.time.format.FormatStyle import java.time.format.FormatStyle
import java.util.* import java.util.Locale
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class DateUtilitiesTest { class DateUtilitiesTest {
@ -29,21 +29,21 @@ class DateUtilitiesTest {
@Test @Test
fun testGet24HourTime() { fun testGet24HourTime() {
DateUtilities.is24HourOverride = true DateUtilities.is24HourOverride = true
assertEquals("09:05", DateUtilities.getTimeString(null, DateTime(2014, 1, 4, 9, 5, 36))) assertEquals("09:05", DateUtilities.getTimeString(ApplicationProvider.getApplicationContext(), DateTime(2014, 1, 4, 9, 5, 36)))
assertEquals("13:00", DateUtilities.getTimeString(null, DateTime(2014, 1, 4, 13, 0, 1))) assertEquals("13:00", DateUtilities.getTimeString(ApplicationProvider.getApplicationContext(), DateTime(2014, 1, 4, 13, 0, 1)))
} }
@Test @Test
fun testGetTime() { fun testGetTime() {
DateUtilities.is24HourOverride = false DateUtilities.is24HourOverride = false
assertEquals("9:05 AM", DateUtilities.getTimeString(null, DateTime(2014, 1, 4, 9, 5, 36))) assertEquals("9:05 AM", DateUtilities.getTimeString(ApplicationProvider.getApplicationContext(), DateTime(2014, 1, 4, 9, 5, 36)))
assertEquals("1:05 PM", DateUtilities.getTimeString(null, DateTime(2014, 1, 4, 13, 5, 36))) assertEquals("1:05 PM", DateUtilities.getTimeString(ApplicationProvider.getApplicationContext(), DateTime(2014, 1, 4, 13, 5, 36)))
} }
@Test @Test
fun testGetTimeWithNoMinutes() { fun testGetTimeWithNoMinutes() {
DateUtilities.is24HourOverride = false DateUtilities.is24HourOverride = false
assertEquals("1 PM", DateUtilities.getTimeString(null, DateTime(2014, 1, 4, 13, 0, 59))) // derp? assertEquals("1 PM", DateUtilities.getTimeString(ApplicationProvider.getApplicationContext(), DateTime(2014, 1, 4, 13, 0, 59))) // derp?
} }
@Test @Test

@ -1,197 +0,0 @@
/*
* Copyright (c) 2012 Todoroo Inc
*
* See the file "LICENSE" for the full license governing this code.
*/
package com.todoroo.andlib.utility;
import static org.tasks.date.DateTimeUtils.newDateTime;
import android.content.Context;
import android.text.format.DateFormat;
import androidx.annotation.Nullable;
import org.tasks.data.entity.Task;
import org.tasks.BuildConfig;
import org.tasks.R;
import org.tasks.time.DateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.time.format.TextStyle;
import java.util.Locale;
public class DateUtilities {
/** Represents a single hour */
public static final long ONE_HOUR = 3600000L;
/** Represents a single day */
public static final long ONE_DAY = 24 * ONE_HOUR;
/** Represents a single week */
public static final long ONE_WEEK = 7 * ONE_DAY;
/** Represents a single minute */
public static final long ONE_MINUTE = 60000L;
static Boolean is24HourOverride = null;
/* ======================================================================
* =========================================================== formatters
* ====================================================================== */
private static boolean is24HourFormat(Context context) {
return BuildConfig.DEBUG && is24HourOverride != null
? is24HourOverride
: DateFormat.is24HourFormat(context);
}
public static String getTimeString(Context context, DateTime date) {
String value;
if (is24HourFormat(context)) {
value = "HH:mm";
} else if (date.getMinuteOfHour() == 0) {
value = "h a";
} else {
value = "h:mm a";
}
return date.toString(value);
}
public static String getLongDateString(DateTime date, java.util.Locale locale) {
return getFullDate(date, locale, FormatStyle.LONG);
}
/**
* @param date date to format
* @return date, with month, day, and year
*/
public static String getDateString(Context context, DateTime date) {
return getRelativeDay(
context, date.getMillis(), Locale.getDefault(), FormatStyle.MEDIUM);
}
static String getWeekday(DateTime date, java.util.Locale locale) {
return date.toLocalDate().getDayOfWeek().getDisplayName(TextStyle.FULL, locale);
}
/** @return weekday */
public static String getWeekdayShort(DateTime date, java.util.Locale locale) {
return date.toLocalDate().getDayOfWeek().getDisplayName(TextStyle.SHORT, locale);
}
public static String getLongDateStringWithTime(long timestamp, java.util.Locale locale) {
return getFullDateTime(newDateTime(timestamp), locale, FormatStyle.LONG);
}
public static String getRelativeDateTime(
Context context, long date, java.util.Locale locale, FormatStyle style) {
return getRelativeDateTime(context, date, locale, style, false, false);
}
public static String getRelativeDateTime(
Context context, long date, java.util.Locale locale, FormatStyle style, boolean lowercase) {
return getRelativeDateTime(context, date, locale, style, false, lowercase);
}
public static String getRelativeDateTime(
Context context, long date, java.util.Locale locale, FormatStyle style, boolean alwaysDisplayFullDate, boolean lowercase) {
if(alwaysDisplayFullDate || !isWithinSixDays(date)) {
return Task.hasDueTime(date)
? getFullDateTime(newDateTime(date), locale, style)
: getFullDate(newDateTime(date), locale, style);
}
String day = getRelativeDay(context, date, locale, isAbbreviated(style), lowercase);
if (Task.hasDueTime(date)) {
String time = getTimeString(context, newDateTime(date));
return newDateTime().startOfDay().equals(newDateTime(date).startOfDay()) ? time : String.format("%s %s", day, time);
} else {
return day;
}
}
private static boolean isAbbreviated(FormatStyle style) {
return style == FormatStyle.SHORT || style == FormatStyle.MEDIUM;
}
public static String getRelativeDay(
Context context,
long date,
java.util.Locale locale,
FormatStyle style) {
return getRelativeDay(context, date, locale, style, false,false);
}
public static String getRelativeDay(
Context context,
long date,
java.util.Locale locale,
FormatStyle style,
boolean alwaysDisplayFullDate,
boolean lowercase) {
if(alwaysDisplayFullDate) {
return getFullDate(newDateTime(date), locale, style);
}
return isWithinSixDays(date)
? getRelativeDay(context, date, locale, isAbbreviated(style), lowercase)
: getFullDate(newDateTime(date), locale, style);
}
private static String getFullDate(DateTime date, java.util.Locale locale, FormatStyle style) {
return stripYear(
DateTimeFormatter.ofLocalizedDate(style)
.withLocale(locale)
.format(date.toLocalDate()),
newDateTime().getYear());
}
private static String getFullDateTime(DateTime date, java.util.Locale locale, FormatStyle style) {
return stripYear(
DateTimeFormatter.ofLocalizedDateTime(style, FormatStyle.SHORT)
.withLocale(locale)
.format(date.toLocalDateTime()),
newDateTime().getYear());
}
private static String stripYear(String date, int year) {
return date.replaceAll("(?: de |, |/| )?" + year + "(?:年|년 | г\\.)?", "");
}
private static @Nullable String getRelativeDay(Context context, long date, java.util.Locale locale, boolean abbreviated, boolean lowercase) {
DateTime startOfToday = newDateTime().startOfDay();
DateTime startOfDate = newDateTime(date).startOfDay();
if (startOfToday.equals(startOfDate)) {
return context.getString(lowercase ? R.string.today_lowercase : R.string.today);
}
if (startOfToday.plusDays(1).equals(startOfDate)) {
return context.getString(
abbreviated
? lowercase ? R.string.tomorrow_abbrev_lowercase : R.string.tmrw
: lowercase ? R.string.tomorrow_lowercase : R.string.tomorrow);
}
if (startOfDate.plusDays(1).equals(startOfToday)) {
return context.getString(
abbreviated
? lowercase ? R.string.yesterday_abbrev_lowercase : R.string.yest
: lowercase ? R.string.yesterday_lowercase : R.string.yesterday);
}
DateTime dateTime = newDateTime(date);
return abbreviated
? DateUtilities.getWeekdayShort(dateTime, locale)
: DateUtilities.getWeekday(dateTime, locale);
}
private static boolean isWithinSixDays(long date){
DateTime startOfToday = newDateTime().startOfDay();
DateTime startOfDate = newDateTime(date).startOfDay();
return Math.abs(startOfToday.getMillis() - startOfDate.getMillis()) <= DateUtilities.ONE_DAY * 6;
}
}

@ -0,0 +1,205 @@
/*
* Copyright (c) 2012 Todoroo Inc
*
* See the file "LICENSE" for the full license governing this code.
*/
package com.todoroo.andlib.utility
import android.content.Context
import android.text.format.DateFormat
import org.tasks.BuildConfig
import org.tasks.R
import org.tasks.data.entity.Task.Companion.hasDueTime
import org.tasks.date.DateTimeUtils.newDateTime
import org.tasks.time.DateTime
import org.tasks.time.ONE_DAY
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
import java.time.format.TextStyle
import java.util.Locale
import kotlin.math.abs
object DateUtilities {
var is24HourOverride: Boolean? = null
/* ======================================================================
* =========================================================== formatters
* ====================================================================== */
private fun is24HourFormat(context: Context): Boolean {
return if (BuildConfig.DEBUG && is24HourOverride != null
) is24HourOverride!!
else DateFormat.is24HourFormat(context)
}
@JvmStatic
fun getTimeString(context: Context, date: DateTime): String {
val value = if (is24HourFormat(context)) {
"HH:mm"
} else if (date.minuteOfHour == 0) {
"h a"
} else {
"h:mm a"
}
return date.toString(value)
}
fun getLongDateString(date: DateTime, locale: Locale): String {
return getFullDate(date, locale, FormatStyle.LONG)
}
/**
* @param date date to format
* @return date, with month, day, and year
*/
fun getDateString(context: Context, date: DateTime): String {
return getRelativeDay(
context, date.millis, Locale.getDefault(), FormatStyle.MEDIUM
)
}
fun getWeekday(date: DateTime, locale: Locale?): String {
return date.toLocalDate()!!
.dayOfWeek.getDisplayName(TextStyle.FULL, locale)
}
/** @return weekday
*/
fun getWeekdayShort(date: DateTime, locale: Locale?): String {
return date.toLocalDate()!!
.dayOfWeek.getDisplayName(TextStyle.SHORT, locale)
}
fun getLongDateStringWithTime(timestamp: Long, locale: Locale): String {
return getFullDateTime(newDateTime(timestamp), locale, FormatStyle.LONG)
}
fun getRelativeDateTime(
context: Context, date: Long, locale: Locale, style: FormatStyle
): String {
return getRelativeDateTime(context, date, locale, style, false, false)
}
fun getRelativeDateTime(
context: Context, date: Long, locale: Locale, style: FormatStyle, lowercase: Boolean
): String {
return getRelativeDateTime(context, date, locale, style, false, lowercase)
}
fun getRelativeDateTime(
context: Context,
date: Long,
locale: Locale,
style: FormatStyle,
alwaysDisplayFullDate: Boolean,
lowercase: Boolean
): String {
if (alwaysDisplayFullDate || !isWithinSixDays(date)) {
return if (hasDueTime(date)
) getFullDateTime(newDateTime(date), locale, style)
else getFullDate(newDateTime(date), locale, style)
}
val day = getRelativeDay(context, date, locale, isAbbreviated(style), lowercase)
if (hasDueTime(date)) {
val time = getTimeString(context, newDateTime(date))
return if (newDateTime().startOfDay()
.equals(newDateTime(date).startOfDay())
) time else String.format("%s %s", day, time)
} else {
return day
}
}
private fun isAbbreviated(style: FormatStyle): Boolean {
return style == FormatStyle.SHORT || style == FormatStyle.MEDIUM
}
fun getRelativeDay(
context: Context,
date: Long,
locale: Locale,
style: FormatStyle
): String {
return getRelativeDay(context, date, locale, style, false, false)
}
fun getRelativeDay(
context: Context,
date: Long,
locale: Locale,
style: FormatStyle,
alwaysDisplayFullDate: Boolean,
lowercase: Boolean
): String {
if (alwaysDisplayFullDate) {
return getFullDate(newDateTime(date), locale, style)
}
return if (isWithinSixDays(date)
) getRelativeDay(context, date, locale, isAbbreviated(style), lowercase)
else getFullDate(newDateTime(date), locale, style)
}
private fun getFullDate(date: DateTime, locale: Locale, style: FormatStyle): String {
return stripYear(
DateTimeFormatter.ofLocalizedDate(style)
.withLocale(locale)
.format(date.toLocalDate()),
newDateTime().year
)
}
private fun getFullDateTime(date: DateTime, locale: Locale, style: FormatStyle): String {
return stripYear(
DateTimeFormatter.ofLocalizedDateTime(style, FormatStyle.SHORT)
.withLocale(locale)
.format(date.toLocalDateTime()),
newDateTime().year
)
}
private fun stripYear(date: String, year: Int): String {
return date.replace("(?: de |, |/| )?$year(?:年|년 | г\\.)?".toRegex(), "")
}
private fun getRelativeDay(
context: Context,
date: Long,
locale: Locale,
abbreviated: Boolean,
lowercase: Boolean
): String {
val startOfToday = newDateTime().startOfDay()
val startOfDate = newDateTime(date).startOfDay()
if (startOfToday.equals(startOfDate)) {
return context.getString(if (lowercase) R.string.today_lowercase else R.string.today)
}
if (startOfToday.plusDays(1).equals(startOfDate)) {
return context.getString(
if (abbreviated
) if (lowercase) R.string.tomorrow_abbrev_lowercase else R.string.tmrw
else if (lowercase) R.string.tomorrow_lowercase else R.string.tomorrow
)
}
if (startOfDate.plusDays(1).equals(startOfToday)) {
return context.getString(
if (abbreviated
) if (lowercase) R.string.yesterday_abbrev_lowercase else R.string.yest
else if (lowercase) R.string.yesterday_lowercase else R.string.yesterday
)
}
val dateTime = newDateTime(date)
return if (abbreviated
) getWeekdayShort(dateTime, locale)
else getWeekday(dateTime, locale)
}
private fun isWithinSixDays(date: Long): Boolean {
val startOfToday = newDateTime().startOfDay()
val startOfDate = newDateTime(date).startOfDay()
return abs((startOfToday.millis - startOfDate.millis).toDouble()) <= ONE_DAY * 6
}
}

@ -1,124 +0,0 @@
/*
* Copyright (c) 2012 Todoroo Inc
*
* See the file "LICENSE" for the full license governing this code.
*/
package com.todoroo.astrid.api;
import static org.tasks.date.DateTimeUtils.newDateTime;
import static org.tasks.time.DateTimeUtils2.currentTimeMillis;
import com.todoroo.andlib.utility.DateUtilities;
import org.tasks.time.DateTime;
/**
* PermaSql allows for creating SQL statements that can be saved and used later without dates
* getting stale. It also allows these values to be used in
*
* @author Tim Su <tim@todoroo.com>
*/
public final class PermaSql {
// --- placeholder strings
/** value to be replaced by end of day as long */
public static final String VALUE_EOD = "EOD()"; // $NON-NLS-1$
/** value to be replaced by noon today as long */
public static final String VALUE_NOON = "NOON()"; // $NON-NLS-1$
/** value to be replaced by end of day yesterday as long */
public static final String VALUE_EOD_YESTERDAY = "EODY()"; // $NON-NLS-1$
/** value to be replaced by end of day tomorrow as long */
public static final String VALUE_EOD_TOMORROW = "EODT()"; // $NON-NLS-1$
/** value to be replaced by end of day day after tomorrow as long */
public static final String VALUE_EOD_DAY_AFTER = "EODTT()"; // $NON-NLS-1$
/** value to be replaced by end of day next week as long */
public static final String VALUE_EOD_NEXT_WEEK = "EODW()"; // $NON-NLS-1$
/** value to be replaced by approximate end of day next month as long */
public static final String VALUE_EOD_NEXT_MONTH = "EODM()"; // $NON-NLS-1$
/** value to be replaced with the current time as long */
public static final String VALUE_NOW = "NOW()"; // $NON-NLS-1$
/** value to be replaced by noon yesterday as long */
private static final String VALUE_NOON_YESTERDAY = "NOONY()"; // $NON-NLS-1$
/** value to be replaced by noon tomorrow as long */
private static final String VALUE_NOON_TOMORROW = "NOONT()"; // $NON-NLS-1$
/** value to be replaced by noon day after tomorrow as long */
private static final String VALUE_NOON_DAY_AFTER = "NOONTT()"; // $NON-NLS-1$
/** value to be replaced by noon next week as long */
private static final String VALUE_NOON_NEXT_WEEK = "NOONW()"; // $NON-NLS-1$
/** value to be replaced by approximate noon next month as long */
private static final String VALUE_NOON_NEXT_MONTH = "NOONM()"; // $NON-NLS-1$
/** Replace placeholder strings with actual */
public static String replacePlaceholdersForQuery(String value) {
if (value.contains(VALUE_NOW)) {
value = value.replace(VALUE_NOW, Long.toString(currentTimeMillis()));
}
if (value.contains(VALUE_EOD)
|| value.contains(VALUE_EOD_DAY_AFTER)
|| value.contains(VALUE_EOD_NEXT_WEEK)
|| value.contains(VALUE_EOD_TOMORROW)
|| value.contains(VALUE_EOD_YESTERDAY)
|| value.contains(VALUE_EOD_NEXT_MONTH)) {
value = replaceEodValues(value);
}
if (value.contains(VALUE_NOON)
|| value.contains(VALUE_NOON_DAY_AFTER)
|| value.contains(VALUE_NOON_NEXT_WEEK)
|| value.contains(VALUE_NOON_TOMORROW)
|| value.contains(VALUE_NOON_YESTERDAY)
|| value.contains(VALUE_NOON_NEXT_MONTH)) {
value = replaceNoonValues(value);
}
return value;
}
public static String replacePlaceholdersForNewTask(String value) {
if (value.contains(VALUE_NOW)) {
value = value.replace(VALUE_NOW, Long.toString(currentTimeMillis()));
}
if (value.contains(VALUE_EOD)
|| value.contains(VALUE_EOD_DAY_AFTER)
|| value.contains(VALUE_EOD_NEXT_WEEK)
|| value.contains(VALUE_EOD_TOMORROW)
|| value.contains(VALUE_EOD_YESTERDAY)
|| value.contains(VALUE_EOD_NEXT_MONTH)) {
value = replaceEodValues(value, newDateTime().noon());
}
if (value.contains(VALUE_NOON)
|| value.contains(VALUE_NOON_DAY_AFTER)
|| value.contains(VALUE_NOON_NEXT_WEEK)
|| value.contains(VALUE_NOON_TOMORROW)
|| value.contains(VALUE_NOON_YESTERDAY)
|| value.contains(VALUE_NOON_NEXT_MONTH)) {
value = replaceNoonValues(value);
}
return value;
}
private static String replaceEodValues(String value) {
return replaceEodValues(value, newDateTime().endOfDay());
}
private static String replaceEodValues(String value, DateTime dateTime) {
long time = dateTime.getMillis();
value = value.replace(VALUE_EOD_YESTERDAY, Long.toString(time - DateUtilities.ONE_DAY));
value = value.replace(VALUE_EOD, Long.toString(time));
value = value.replace(VALUE_EOD_TOMORROW, Long.toString(time + DateUtilities.ONE_DAY));
value = value.replace(VALUE_EOD_DAY_AFTER, Long.toString(time + 2 * DateUtilities.ONE_DAY));
value = value.replace(VALUE_EOD_NEXT_WEEK, Long.toString(time + 7 * DateUtilities.ONE_DAY));
value = value.replace(VALUE_EOD_NEXT_MONTH, Long.toString(time + 30 * DateUtilities.ONE_DAY));
return value;
}
private static String replaceNoonValues(String value) {
long time = newDateTime().noon().getMillis();
value = value.replace(VALUE_NOON_YESTERDAY, Long.toString(time - DateUtilities.ONE_DAY));
value = value.replace(VALUE_NOON, Long.toString(time));
value = value.replace(VALUE_NOON_TOMORROW, Long.toString(time + DateUtilities.ONE_DAY));
value = value.replace(VALUE_NOON_DAY_AFTER, Long.toString(time + 2 * DateUtilities.ONE_DAY));
value = value.replace(VALUE_NOON_NEXT_WEEK, Long.toString(time + 7 * DateUtilities.ONE_DAY));
value = value.replace(VALUE_NOON_NEXT_MONTH, Long.toString(time + 30 * DateUtilities.ONE_DAY));
return value;
}
}

@ -1,238 +0,0 @@
/*
* Copyright (c) 2012 Todoroo Inc
*
* See the file "LICENSE" for the full license governing this code.
*/
package com.todoroo.astrid.core;
import static org.tasks.data.dao.CaldavDaoKt.APPLE_EPOCH;
import static org.tasks.db.QueryUtils.showCompleted;
import static org.tasks.db.QueryUtils.showHidden;
import android.annotation.SuppressLint;
import androidx.annotation.Nullable;
import org.tasks.data.sql.Functions;
import org.tasks.data.sql.Order;
import org.tasks.data.sql.OrderType;
import org.tasks.data.entity.Task;
import org.tasks.data.entity.CaldavCalendar;
import org.tasks.preferences.QueryPreferences;
import java.util.Locale;
/**
* Helpers for sorting a list of tasks
*
* @author Tim Su <tim@todoroo.com>
*/
public class SortHelper {
public static final int GROUP_NONE = -1;
public static final int SORT_AUTO = 0;
public static final int SORT_ALPHA = 1;
public static final int SORT_DUE = 2;
public static final int SORT_IMPORTANCE = 3;
public static final int SORT_MODIFIED = 4;
public static final int SORT_CREATED = 5;
public static final int SORT_GTASKS = 6;
public static final int SORT_CALDAV = 7;
public static final int SORT_START = 8;
public static final int SORT_LIST = 9;
public static final int SORT_COMPLETED = 10;
public static final int SORT_MANUAL = 11;
@SuppressLint("DefaultLocale")
public static final String CALDAV_ORDER_COLUMN =
String.format(Locale.US, "IFNULL(tasks.`order`, (tasks.created - %d) / 1000)", APPLE_EPOCH);
private static final String ADJUSTED_DUE_DATE =
"(CASE WHEN (dueDate / 1000) % 60 > 0 THEN dueDate ELSE (dueDate + 43140000) END)";
private static final String ADJUSTED_START_DATE =
"(CASE WHEN (hideUntil / 1000) % 60 > 0 THEN hideUntil ELSE (hideUntil + 86399000) END)";
private static final Long NO_DATE = 3538339200000L;
private static final String GROUP_DUE_DATE = "((CASE WHEN (tasks.dueDate=0) THEN " + NO_DATE + " ELSE "
+ "tasks.dueDate END)+tasks.importance * 1000)";
private static final String SORT_DUE_DATE = "((CASE WHEN (tasks.dueDate=0) THEN " + NO_DATE + " ELSE "
+ ADJUSTED_DUE_DATE.replace("dueDate", "tasks.dueDate")
+ " END)+tasks.importance * 1000)";
private static final String GROUP_START_DATE = "((CASE WHEN (tasks.hideUntil=0) THEN " + NO_DATE + " ELSE "
+ "tasks.hideUntil END)+tasks.importance * 1000)";
private static final String SORT_START_DATE = "((CASE WHEN (tasks.hideUntil=0) THEN " + NO_DATE + " ELSE "
+ ADJUSTED_START_DATE.replace("hideUntil", "tasks.hideUntil")
+ " END)+tasks.importance * 1000)";
private static final Order ORDER_TITLE = Order.asc(Functions.upper(Task.TITLE));
private static final Order ORDER_LIST =
Order.asc(Functions.upper(CaldavCalendar.ORDER))
.addSecondaryExpression(Order.asc(CaldavCalendar.NAME));
/** Takes a SQL query, and if there isn't already an order, creates an order. */
public static String adjustQueryForFlagsAndSort(
QueryPreferences preferences, String originalSql, int sort) {
// sort
if (originalSql == null) {
originalSql = "";
}
if (!originalSql.toUpperCase().contains("ORDER BY")) {
Order order = orderForSortType(sort);
if (order.getOrderType() == OrderType.ASC != preferences.getSortAscending()) {
order = order.reverse();
}
originalSql += " ORDER BY " + order;
}
return adjustQueryForFlags(preferences, originalSql);
}
public static String adjustQueryForFlags(QueryPreferences preferences, String originalSql) {
String adjustedSql = originalSql;
// flags
if (preferences.getShowCompleted()) {
adjustedSql = showCompleted(adjustedSql);
}
if (preferences.getShowHidden()) {
adjustedSql = showHidden(adjustedSql);
}
return adjustedSql;
}
private static Order orderForSortType(int sortType) {
Order order;
switch (sortType) {
case SORT_ALPHA:
order = ORDER_TITLE;
break;
case SORT_DUE:
order =
Order.asc(
"(CASE WHEN (dueDate=0) THEN (strftime('%s','now')*1000)*2 ELSE "
+ ADJUSTED_DUE_DATE
+ " END)+importance");
break;
case SORT_START:
order =
Order.asc(
"(CASE WHEN (hideUntil=0) THEN (strftime('%s','now')*1000)*2 ELSE "
+ ADJUSTED_START_DATE
+ " END)+importance");
break;
case SORT_IMPORTANCE:
order = Order.asc("importance");
break;
case SORT_MODIFIED:
order = Order.desc(Task.MODIFICATION_DATE);
break;
case SORT_CREATED:
order = Order.desc(Task.CREATION_DATE);
break;
case SORT_LIST:
order = ORDER_LIST;
break;
default:
order =
Order.asc(
"(CASE WHEN (dueDate=0) "
+ // if no due date
"THEN (strftime('%s','now')*1000)*2 "
+ // then now * 2
"ELSE ("
+ ADJUSTED_DUE_DATE
+ ") END) "
+ // else due time
// add slightly less than 2 days * importance to give due date priority over importance in case of tie
"+ 172799999 * importance");
}
if (sortType != SORT_ALPHA) {
order.addSecondaryExpression(ORDER_TITLE);
}
return order;
}
public static @Nullable String getSortGroup(int sortType) {
switch (sortType) {
case SORT_DUE:
return "tasks.dueDate";
case SORT_START:
return "tasks.hideUntil";
case SORT_IMPORTANCE:
return "tasks.importance";
case SORT_MODIFIED:
return "tasks.modified";
case SORT_CREATED:
return "tasks.created";
case SORT_LIST:
return "cdl_id";
default:
return null;
}
}
private static String sortGroup(String column) {
return "datetime(" + column + " / 1000, 'unixepoch', 'localtime', 'start of day')";
}
public static String orderSelectForSortTypeRecursive(int sortType, boolean grouping) {
return switch (sortType) {
case GROUP_NONE -> "1";
case SORT_ALPHA -> "UPPER(tasks.title)";
case SORT_DUE -> grouping ? sortGroup(GROUP_DUE_DATE) : SORT_DUE_DATE;
case SORT_START -> grouping ? sortGroup(GROUP_START_DATE) : SORT_START_DATE;
case SORT_IMPORTANCE -> "tasks.importance";
case SORT_MODIFIED -> grouping ? sortGroup("tasks.modified") : "tasks.modified";
case SORT_CREATED -> grouping ? sortGroup("tasks.created") : "tasks.created";
case SORT_GTASKS -> "tasks.`order`";
case SORT_CALDAV -> CALDAV_ORDER_COLUMN;
case SORT_LIST -> "CASE WHEN cdl_order = -1 THEN cdl_name ELSE cdl_order END";
case SORT_COMPLETED -> "tasks.completed";
default -> "(CASE WHEN (tasks.dueDate=0) "
+ // if no due date
"THEN (strftime('%s','now')*1000)*2 "
+ // then now * 2
"ELSE ("
+ ADJUSTED_DUE_DATE.replace("dueDate", "tasks.dueDate")
+ ") END) "
+ // else due time
// add slightly less than 2 days * importance to give due date priority over importance in case of tie
"+ 172799999 * tasks.importance";
};
}
public static Order orderForGroupTypeRecursive(int groupMode, boolean ascending) {
return ascending
? Order.asc("primary_group")
: Order.desc("primary_group");
}
public static Order orderForSortTypeRecursive(
int sortMode,
boolean primaryAscending,
int secondaryMode,
boolean secondaryAscending
) {
Order order = primaryAscending || sortMode == SORT_GTASKS || sortMode == SORT_CALDAV
? Order.asc("primary_sort")
: Order.desc("primary_sort");
order.addSecondaryExpression(
secondaryAscending || secondaryMode == SORT_GTASKS || secondaryMode == SORT_CALDAV
? Order.asc("secondary_sort")
: Order.desc("secondary_sort")
);
if (sortMode != SORT_ALPHA) {
order.addSecondaryExpression(Order.asc("sort_title"));
}
return order;
}
}

@ -11,16 +11,16 @@ import android.content.Context
import android.net.Uri import android.net.Uri
import android.provider.CalendarContract import android.provider.CalendarContract
import android.text.format.Time import android.text.format.Time
import com.todoroo.andlib.utility.DateUtilities
import org.tasks.data.entity.Task
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import org.tasks.R import org.tasks.R
import org.tasks.Strings.isNullOrEmpty import org.tasks.Strings.isNullOrEmpty
import org.tasks.calendars.CalendarEventProvider import org.tasks.calendars.CalendarEventProvider
import org.tasks.data.dao.TaskDao import org.tasks.data.dao.TaskDao
import org.tasks.data.entity.Task
import org.tasks.preferences.PermissionChecker import org.tasks.preferences.PermissionChecker
import org.tasks.preferences.Preferences import org.tasks.preferences.Preferences
import org.tasks.time.DateTimeUtils2.currentTimeMillis import org.tasks.time.DateTimeUtils2.currentTimeMillis
import org.tasks.time.ONE_HOUR
import timber.log.Timber import timber.log.Timber
import java.util.TimeZone import java.util.TimeZone
import javax.inject.Inject import javax.inject.Inject
@ -174,6 +174,6 @@ class GCalHelper @Inject constructor(
companion object { companion object {
/** If task has no estimated time, how early to set a task in calendar (seconds) */ /** If task has no estimated time, how early to set a task in calendar (seconds) */
private const val DEFAULT_CAL_TIME = DateUtilities.ONE_HOUR private const val DEFAULT_CAL_TIME = ONE_HOUR
} }
} }

@ -5,7 +5,6 @@
*/ */
package com.todoroo.astrid.repeats package com.todoroo.astrid.repeats
import com.todoroo.andlib.utility.DateUtilities
import com.todoroo.astrid.alarms.AlarmService import com.todoroo.astrid.alarms.AlarmService
import com.todoroo.astrid.dao.TaskDao import com.todoroo.astrid.dao.TaskDao
import com.todoroo.astrid.gcal.GCalHelper import com.todoroo.astrid.gcal.GCalHelper
@ -21,6 +20,9 @@ import org.tasks.data.setRecurrence
import org.tasks.date.DateTimeUtils.newDateTime import org.tasks.date.DateTimeUtils.newDateTime
import org.tasks.repeats.RecurrenceUtils.newRecur import org.tasks.repeats.RecurrenceUtils.newRecur
import org.tasks.time.DateTime import org.tasks.time.DateTime
import org.tasks.time.ONE_HOUR
import org.tasks.time.ONE_MINUTE
import org.tasks.time.ONE_WEEK
import timber.log.Timber import timber.log.Timber
import java.text.ParseException import java.text.ParseException
import java.util.* import java.util.*
@ -160,7 +162,7 @@ class RepeatTaskHelper @Inject constructor(
recur: Recur, original: DateTime, hasDueTime: Boolean): Long { recur: Recur, original: DateTime, hasDueTime: Boolean): Long {
val byDay = recur.dayList val byDay = recur.dayList
var newDate = original.millis var newDate = original.millis
newDate += DateUtilities.ONE_WEEK * (recur.interval.coerceAtLeast(1) - 1) newDate += ONE_WEEK * (recur.interval.coerceAtLeast(1) - 1)
var date = DateTime(newDate) var date = DateTime(newDate)
Collections.sort(byDay, weekdayCompare) Collections.sort(byDay, weekdayCompare)
val next = findNextWeekday(byDay, date) val next = findNextWeekday(byDay, date)
@ -266,8 +268,8 @@ class RepeatTaskHelper @Inject constructor(
@Deprecated("probably don't need this?") @Deprecated("probably don't need this?")
private fun handleSubdayRepeat(startDate: DateTime, recur: Recur): Long { private fun handleSubdayRepeat(startDate: DateTime, recur: Recur): Long {
val millis: Long = when (recur.frequency) { val millis: Long = when (recur.frequency) {
Recur.Frequency.HOURLY -> DateUtilities.ONE_HOUR Recur.Frequency.HOURLY -> ONE_HOUR
Recur.Frequency.MINUTELY -> DateUtilities.ONE_MINUTE Recur.Frequency.MINUTELY -> ONE_MINUTE
else -> throw RuntimeException( else -> throw RuntimeException(
"Error handing subday repeat: " + recur.frequency) // $NON-NLS-1$ "Error handing subday repeat: " + recur.frequency) // $NON-NLS-1$
} }

@ -1,6 +1,5 @@
package com.todoroo.astrid.service package com.todoroo.astrid.service
import com.todoroo.andlib.utility.DateUtilities
import com.todoroo.astrid.api.PermaSql import com.todoroo.astrid.api.PermaSql
import com.todoroo.astrid.dao.TaskDao import com.todoroo.astrid.dao.TaskDao
import com.todoroo.astrid.gcal.GCalHelper import com.todoroo.astrid.gcal.GCalHelper
@ -39,6 +38,7 @@ import org.tasks.filters.mapFromSerializedString
import org.tasks.preferences.DefaultFilterProvider import org.tasks.preferences.DefaultFilterProvider
import org.tasks.preferences.Preferences import org.tasks.preferences.Preferences
import org.tasks.time.DateTimeUtils2.currentTimeMillis import org.tasks.time.DateTimeUtils2.currentTimeMillis
import org.tasks.time.ONE_HOUR
import org.tasks.time.startOfDay import org.tasks.time.startOfDay
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -210,7 +210,7 @@ class TaskCreator @Inject constructor(
companion object { companion object {
fun Task.setDefaultReminders(preferences: Preferences) { fun Task.setDefaultReminders(preferences: Preferences) {
randomReminder = DateUtilities.ONE_HOUR * preferences.getIntegerFromString( randomReminder = ONE_HOUR * preferences.getIntegerFromString(
R.string.p_rmd_default_random_hours, R.string.p_rmd_default_random_hours,
0 0
) )

@ -112,7 +112,7 @@ class TimerControlSet : TaskEditControlFragment() {
viewModel.addComment(String.format( viewModel.addComment(String.format(
"%s %s\n%s %s", // $NON-NLS-1$ "%s %s\n%s %s", // $NON-NLS-1$
getString(R.string.TEA_timer_comment_stopped), getString(R.string.TEA_timer_comment_stopped),
DateUtilities.getTimeString(context, DateTimeUtils.newDateTime()), DateUtilities.getTimeString(requireContext(), DateTimeUtils.newDateTime()),
getString(R.string.TEA_timer_comment_spent), getString(R.string.TEA_timer_comment_spent),
elapsedTime), elapsedTime),
null) null)
@ -125,7 +125,7 @@ class TimerControlSet : TaskEditControlFragment() {
viewModel.addComment(String.format( viewModel.addComment(String.format(
"%s %s", "%s %s",
getString(R.string.TEA_timer_comment_started), getString(R.string.TEA_timer_comment_started),
DateUtilities.getTimeString(context, DateTimeUtils.newDateTime())), DateUtilities.getTimeString(requireContext(), DateTimeUtils.newDateTime())),
null) null)
return model return model
} }

@ -58,7 +58,7 @@ class StartDateControlSet : TaskEditControlFragment() {
hasDueDate = viewModel.dueDate.collectAsStateWithLifecycle().value > 0, hasDueDate = viewModel.dueDate.collectAsStateWithLifecycle().value > 0,
printDate = { printDate = {
DateUtilities.getRelativeDateTime( DateUtilities.getRelativeDateTime(
context, requireContext(),
selectedDay + selectedTime, selectedDay + selectedTime,
locale, locale,
FormatStyle.FULL, FormatStyle.FULL,

@ -19,7 +19,6 @@ import com.google.android.material.textfield.TextInputLayout
import org.tasks.data.sql.Field import org.tasks.data.sql.Field
import org.tasks.data.sql.Query import org.tasks.data.sql.Query
import org.tasks.data.sql.UnaryCriterion import org.tasks.data.sql.UnaryCriterion
import com.todoroo.andlib.utility.AndroidUtilities
import com.todoroo.astrid.activity.MainActivity import com.todoroo.astrid.activity.MainActivity
import com.todoroo.astrid.activity.TaskListFragment import com.todoroo.astrid.activity.TaskListFragment
import com.todoroo.astrid.api.BooleanCriterion import com.todoroo.astrid.api.BooleanCriterion
@ -338,7 +337,7 @@ class FilterSettingsActivity : BaseListSettingsActivity() {
if (instance.type == CriterionInstance.TYPE_UNIVERSE || instance.criterion.sql == null) { if (instance.type == CriterionInstance.TYPE_UNIVERSE || instance.criterion.sql == null) {
sql.append(activeAndVisible()).append(' ') sql.append(activeAndVisible()).append(' ')
} else { } else {
var subSql: String? = instance.criterion.sql.replace( var subSql: String = instance.criterion.sql.replace(
"?", "?",
UnaryCriterion.sanitize(instance.valueFromCriterion!!) UnaryCriterion.sanitize(instance.valueFromCriterion!!)
) )

@ -345,7 +345,7 @@ class iCalendar @Inject constructor(
) )
internal fun getLocal(property: DateProperty): Long = internal fun getLocal(property: DateProperty): Long =
org.tasks.time.DateTime.from(property.date)?.toLocal()?.millis ?: 0 org.tasks.time.DateTime.from(property.date).toLocal().millis
fun fromVtodo(vtodo: String): Task? { fun fromVtodo(vtodo: String): Task? {
try { try {

@ -1,11 +1,12 @@
package org.tasks.data package org.tasks.data
import com.todoroo.andlib.utility.DateUtilities
import net.fortuna.ical4j.model.Recur import net.fortuna.ical4j.model.Recur
import org.tasks.data.entity.Task import org.tasks.data.entity.Task
import org.tasks.date.DateTimeUtils import org.tasks.date.DateTimeUtils
import org.tasks.date.DateTimeUtils.toDateTime import org.tasks.date.DateTimeUtils.toDateTime
import org.tasks.time.DateTimeUtils2.currentTimeMillis import org.tasks.time.DateTimeUtils2.currentTimeMillis
import org.tasks.time.ONE_DAY
import org.tasks.time.ONE_WEEK
import org.tasks.time.startOfDay import org.tasks.time.startOfDay
/** Checks whether task is hidden. Requires HIDDEN_UNTIL */ /** Checks whether task is hidden. Requires HIDDEN_UNTIL */
@ -22,8 +23,8 @@ fun Task.createHideUntil(setting: Int, customDate: Long): Long {
val date: Long = when (setting) { val date: Long = when (setting) {
Task.HIDE_UNTIL_NONE -> return 0 Task.HIDE_UNTIL_NONE -> return 0
Task.HIDE_UNTIL_DUE, Task.HIDE_UNTIL_DUE_TIME -> dueDate Task.HIDE_UNTIL_DUE, Task.HIDE_UNTIL_DUE_TIME -> dueDate
Task.HIDE_UNTIL_DAY_BEFORE -> dueDate - DateUtilities.ONE_DAY Task.HIDE_UNTIL_DAY_BEFORE -> dueDate - ONE_DAY
Task.HIDE_UNTIL_WEEK_BEFORE -> dueDate - DateUtilities.ONE_WEEK Task.HIDE_UNTIL_WEEK_BEFORE -> dueDate - ONE_WEEK
Task.HIDE_UNTIL_SPECIFIC_DAY, Task.HIDE_UNTIL_SPECIFIC_DAY_TIME -> customDate Task.HIDE_UNTIL_SPECIFIC_DAY, Task.HIDE_UNTIL_SPECIFIC_DAY_TIME -> customDate
else -> throw IllegalArgumentException("Unknown setting $setting") else -> throw IllegalArgumentException("Unknown setting $setting")
} }
@ -67,10 +68,10 @@ fun createDueDate(setting: Int, customDate: Long): Long {
val date: Long = when (setting) { val date: Long = when (setting) {
Task.URGENCY_NONE -> 0 Task.URGENCY_NONE -> 0
Task.URGENCY_TODAY -> currentTimeMillis() Task.URGENCY_TODAY -> currentTimeMillis()
Task.URGENCY_TOMORROW -> currentTimeMillis() + DateUtilities.ONE_DAY Task.URGENCY_TOMORROW -> currentTimeMillis() + ONE_DAY
Task.URGENCY_DAY_AFTER -> currentTimeMillis() + 2 * DateUtilities.ONE_DAY Task.URGENCY_DAY_AFTER -> currentTimeMillis() + 2 * ONE_DAY
Task.URGENCY_NEXT_WEEK -> currentTimeMillis() + DateUtilities.ONE_WEEK Task.URGENCY_NEXT_WEEK -> currentTimeMillis() + ONE_WEEK
Task.URGENCY_IN_TWO_WEEKS -> currentTimeMillis() + 2 * DateUtilities.ONE_WEEK Task.URGENCY_IN_TWO_WEEKS -> currentTimeMillis() + 2 * ONE_WEEK
Task.URGENCY_SPECIFIC_DAY, Task.URGENCY_SPECIFIC_DAY_TIME -> customDate Task.URGENCY_SPECIFIC_DAY, Task.URGENCY_SPECIFIC_DAY_TIME -> customDate
else -> throw IllegalArgumentException("Unknown setting $setting") else -> throw IllegalArgumentException("Unknown setting $setting")
} }

@ -113,10 +113,10 @@ abstract class BaseDateTimePicker : BottomSheetDialogFragment() {
afternoon = preferences.dateShortcutAfternoon + 1000 afternoon = preferences.dateShortcutAfternoon + 1000
evening = preferences.dateShortcutEvening + 1000 evening = preferences.dateShortcutEvening + 1000
night = preferences.dateShortcutNight + 1000 night = preferences.dateShortcutNight + 1000
morningButton.text = DateUtilities.getTimeString(context, DateTimeUtils.newDateTime().withMillisOfDay(morning)) morningButton.text = DateUtilities.getTimeString(requireContext(), DateTimeUtils.newDateTime().withMillisOfDay(morning))
afternoonButton.text = DateUtilities.getTimeString(context, DateTimeUtils.newDateTime().withMillisOfDay(afternoon)) afternoonButton.text = DateUtilities.getTimeString(requireContext(), DateTimeUtils.newDateTime().withMillisOfDay(afternoon))
eveningButton.text = DateUtilities.getTimeString(context, DateTimeUtils.newDateTime().withMillisOfDay(evening)) eveningButton.text = DateUtilities.getTimeString(requireContext(), DateTimeUtils.newDateTime().withMillisOfDay(evening))
nightButton.text = DateUtilities.getTimeString(context, DateTimeUtils.newDateTime().withMillisOfDay(night)) nightButton.text = DateUtilities.getTimeString(requireContext(), DateTimeUtils.newDateTime().withMillisOfDay(night))
val firstDayOfWeek = preferences.firstDayOfWeek val firstDayOfWeek = preferences.firstDayOfWeek
if (firstDayOfWeek in 1..7) { if (firstDayOfWeek in 1..7) {
calendarView.firstDayOfWeek = firstDayOfWeek calendarView.firstDayOfWeek = firstDayOfWeek

@ -170,7 +170,7 @@ class DateTimePicker : BaseDateTimePicker() {
binding.shortcuts.currentDateSelection.text = if (customDate == MULTIPLE_DAYS) { binding.shortcuts.currentDateSelection.text = if (customDate == MULTIPLE_DAYS) {
requireContext().getString(R.string.date_picker_multiple) requireContext().getString(R.string.date_picker_multiple)
} else { } else {
DateUtilities.getRelativeDay(context, selectedDay, locale, FormatStyle.MEDIUM) DateUtilities.getRelativeDay(requireContext(), selectedDay, locale, FormatStyle.MEDIUM)
} }
} }
} }
@ -187,7 +187,7 @@ class DateTimePicker : BaseDateTimePicker() {
binding.shortcuts.currentTimeSelection.text = if (customTime == MULTIPLE_TIMES) { binding.shortcuts.currentTimeSelection.text = if (customTime == MULTIPLE_TIMES) {
requireContext().getString(R.string.date_picker_multiple) requireContext().getString(R.string.date_picker_multiple)
} else { } else {
DateUtilities.getTimeString(context, today.withMillisOfDay(selectedTime)) DateUtilities.getTimeString(requireContext(), today.withMillisOfDay(selectedTime))
} }
} }
} }

@ -10,16 +10,16 @@ import android.view.ViewGroup
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import com.todoroo.andlib.utility.DateUtilities import com.todoroo.andlib.utility.DateUtilities
import com.todoroo.astrid.dao.TaskDao import com.todoroo.astrid.dao.TaskDao
import org.tasks.data.entity.Task
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.tasks.R import org.tasks.R
import org.tasks.data.entity.Task
import org.tasks.databinding.DialogStartDatePickerBinding import org.tasks.databinding.DialogStartDatePickerBinding
import org.tasks.date.DateTimeUtils.newDateTime import org.tasks.date.DateTimeUtils.newDateTime
import org.tasks.dialogs.MyTimePickerDialog.Companion.newTimePicker import org.tasks.dialogs.MyTimePickerDialog.Companion.newTimePicker
import org.tasks.notifications.NotificationManager import org.tasks.notifications.NotificationManager
import org.tasks.time.DateTime import org.tasks.time.DateTime
import java.time.format.FormatStyle import java.time.format.FormatStyle
import java.util.* import java.util.Locale
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
@ -112,7 +112,7 @@ class StartDatePicker : BaseDateTimePicker() {
binding.shortcuts.dateGroup.check(R.id.current_date_selection) binding.shortcuts.dateGroup.check(R.id.current_date_selection)
binding.shortcuts.currentDateSelection.visibility = View.VISIBLE binding.shortcuts.currentDateSelection.visibility = View.VISIBLE
binding.shortcuts.currentDateSelection.text = binding.shortcuts.currentDateSelection.text =
DateUtilities.getRelativeDay(context, selectedDay, locale, FormatStyle.MEDIUM) DateUtilities.getRelativeDay(requireContext(), selectedDay, locale, FormatStyle.MEDIUM)
} }
} }
if (Task.hasDueTime(selectedTime.toLong())) { if (Task.hasDueTime(selectedTime.toLong())) {
@ -125,7 +125,7 @@ class StartDatePicker : BaseDateTimePicker() {
customTime = selectedTime customTime = selectedTime
binding.shortcuts.timeGroup.check(R.id.current_time_selection) binding.shortcuts.timeGroup.check(R.id.current_time_selection)
binding.shortcuts.currentTimeSelection.visibility = View.VISIBLE binding.shortcuts.currentTimeSelection.visibility = View.VISIBLE
binding.shortcuts.currentTimeSelection.text = DateUtilities.getTimeString(context, today.withMillisOfDay(selectedTime)) binding.shortcuts.currentTimeSelection.text = DateUtilities.getTimeString(requireContext(), today.withMillisOfDay(selectedTime))
} }
} }
if (selectedDay == DUE_TIME) { if (selectedDay == DUE_TIME) {

@ -14,7 +14,6 @@ import androidx.core.net.toUri
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.todoroo.andlib.utility.AndroidUtilities import com.todoroo.andlib.utility.AndroidUtilities
import com.todoroo.andlib.utility.DateUtilities
import com.todoroo.astrid.activity.BeastModePreferences import com.todoroo.astrid.activity.BeastModePreferences
import com.todoroo.astrid.core.SortHelper import com.todoroo.astrid.core.SortHelper
import org.tasks.BuildConfig import org.tasks.BuildConfig
@ -30,6 +29,7 @@ import org.tasks.themes.ColorProvider
import org.tasks.themes.ThemeBase import org.tasks.themes.ThemeBase
import org.tasks.time.DateTime import org.tasks.time.DateTime
import org.tasks.time.DateTimeUtils2.currentTimeMillis import org.tasks.time.DateTimeUtils2.currentTimeMillis
import org.tasks.time.ONE_WEEK
import timber.log.Timber import timber.log.Timber
import java.io.File import java.io.File
import java.net.URI import java.net.URI
@ -497,7 +497,7 @@ class Preferences @JvmOverloads constructor(
get() = BuildConfig.DEBUG && getBoolean(R.string.p_flipper, false) get() = BuildConfig.DEBUG && getBoolean(R.string.p_flipper, false)
var isPositionHackEnabled: Boolean var isPositionHackEnabled: Boolean
get() = getLong(R.string.p_google_tasks_position_hack, 0) > currentTimeMillis() - DateUtilities.ONE_WEEK get() = getLong(R.string.p_google_tasks_position_hack, 0) > currentTimeMillis() - ONE_WEEK
set(value) { setLong(R.string.p_google_tasks_position_hack, if (value) currentTimeMillis() else 0) } set(value) { setLong(R.string.p_google_tasks_position_hack, if (value) currentTimeMillis() else 0) }
override var isManualSort: Boolean override var isManualSort: Boolean

@ -145,17 +145,11 @@ class DateTime {
return subtract(Calendar.SECOND, seconds) return subtract(Calendar.SECOND, seconds)
} }
fun minusDays(days: Int): DateTime { fun minusDays(days: Int): DateTime = DateTime(millis.minusDays(days))
return subtract(Calendar.DATE, days)
}
fun minusMinutes(minutes: Int): DateTime { fun minusMinutes(minutes: Int): DateTime = DateTime(millis.minusMinutes(minutes))
return subtract(Calendar.MINUTE, minutes)
}
fun minusMillis(millis: Long): DateTime { fun minusMillis(millis: Long): DateTime = DateTime(this.millis.minusMillis(millis))
return DateTime(this.millis - millis, timeZone)
}
val isAfterNow: Boolean val isAfterNow: Boolean
get() = isAfter(currentTimeMillis()) get() = isAfter(currentTimeMillis())

@ -0,0 +1,138 @@
/*
* Copyright (c) 2012 Todoroo Inc
*
* See the file "LICENSE" for the full license governing this code.
*/
package com.todoroo.astrid.api
import org.tasks.time.DateTimeUtils2.currentTimeMillis
import org.tasks.time.ONE_DAY
import org.tasks.time.endOfDay
import org.tasks.time.noon
/**
* PermaSql allows for creating SQL statements that can be saved and used later without dates
* getting stale. It also allows these values to be used in
*
* @author Tim Su <tim></tim>@todoroo.com>
*/
object PermaSql {
// --- placeholder strings
/** value to be replaced by end of day as long */
const val VALUE_EOD: String = "EOD()" // $NON-NLS-1$
/** value to be replaced by noon today as long */
const val VALUE_NOON: String = "NOON()" // $NON-NLS-1$
/** value to be replaced by end of day yesterday as long */
const val VALUE_EOD_YESTERDAY: String = "EODY()" // $NON-NLS-1$
/** value to be replaced by end of day tomorrow as long */
const val VALUE_EOD_TOMORROW: String = "EODT()" // $NON-NLS-1$
/** value to be replaced by end of day day after tomorrow as long */
const val VALUE_EOD_DAY_AFTER: String = "EODTT()" // $NON-NLS-1$
/** value to be replaced by end of day next week as long */
const val VALUE_EOD_NEXT_WEEK: String = "EODW()" // $NON-NLS-1$
/** value to be replaced by approximate end of day next month as long */
const val VALUE_EOD_NEXT_MONTH: String = "EODM()" // $NON-NLS-1$
/** value to be replaced with the current time as long */
const val VALUE_NOW: String = "NOW()" // $NON-NLS-1$
/** value to be replaced by noon yesterday as long */
private const val VALUE_NOON_YESTERDAY = "NOONY()" // $NON-NLS-1$
/** value to be replaced by noon tomorrow as long */
private const val VALUE_NOON_TOMORROW = "NOONT()" // $NON-NLS-1$
/** value to be replaced by noon day after tomorrow as long */
private const val VALUE_NOON_DAY_AFTER = "NOONTT()" // $NON-NLS-1$
/** value to be replaced by noon next week as long */
private const val VALUE_NOON_NEXT_WEEK = "NOONW()" // $NON-NLS-1$
/** value to be replaced by approximate noon next month as long */
private const val VALUE_NOON_NEXT_MONTH = "NOONM()" // $NON-NLS-1$
/** Replace placeholder strings with actual */
fun replacePlaceholdersForQuery(value: String): String {
var value = value
if (value.contains(VALUE_NOW)) {
value = value.replace(VALUE_NOW, currentTimeMillis().toString())
}
if (value.contains(VALUE_EOD)
|| value.contains(VALUE_EOD_DAY_AFTER)
|| value.contains(VALUE_EOD_NEXT_WEEK)
|| value.contains(VALUE_EOD_TOMORROW)
|| value.contains(VALUE_EOD_YESTERDAY)
|| value.contains(VALUE_EOD_NEXT_MONTH)
) {
value = replaceEodValues(value)
}
if (value.contains(VALUE_NOON)
|| value.contains(VALUE_NOON_DAY_AFTER)
|| value.contains(VALUE_NOON_NEXT_WEEK)
|| value.contains(VALUE_NOON_TOMORROW)
|| value.contains(VALUE_NOON_YESTERDAY)
|| value.contains(VALUE_NOON_NEXT_MONTH)
) {
value = replaceNoonValues(value)
}
return value
}
fun replacePlaceholdersForNewTask(value: String): String {
var value = value
if (value.contains(VALUE_NOW)) {
value = value.replace(VALUE_NOW, currentTimeMillis().toString())
}
if (value.contains(VALUE_EOD)
|| value.contains(VALUE_EOD_DAY_AFTER)
|| value.contains(VALUE_EOD_NEXT_WEEK)
|| value.contains(VALUE_EOD_TOMORROW)
|| value.contains(VALUE_EOD_YESTERDAY)
|| value.contains(VALUE_EOD_NEXT_MONTH)
) {
value = replaceEodValues(value, currentTimeMillis().noon())
}
if (value.contains(VALUE_NOON)
|| value.contains(VALUE_NOON_DAY_AFTER)
|| value.contains(VALUE_NOON_NEXT_WEEK)
|| value.contains(VALUE_NOON_TOMORROW)
|| value.contains(VALUE_NOON_YESTERDAY)
|| value.contains(VALUE_NOON_NEXT_MONTH)
) {
value = replaceNoonValues(value)
}
return value
}
private fun replaceEodValues(
value: String,
dateTime: Long = currentTimeMillis().endOfDay()
): String {
var value = value
value = value.replace(VALUE_EOD_YESTERDAY, (dateTime - ONE_DAY).toString())
value = value.replace(VALUE_EOD, dateTime.toString())
value = value.replace(VALUE_EOD_TOMORROW, (dateTime + ONE_DAY).toString())
value = value.replace(VALUE_EOD_DAY_AFTER, (dateTime + 2 * ONE_DAY).toString())
value = value.replace(VALUE_EOD_NEXT_WEEK, (dateTime + 7 * ONE_DAY).toString())
value = value.replace(VALUE_EOD_NEXT_MONTH, (dateTime + 30 * ONE_DAY).toString())
return value
}
private fun replaceNoonValues(value: String): String {
var value = value
val time = currentTimeMillis().noon()
value = value.replace(VALUE_NOON_YESTERDAY, (time - ONE_DAY).toString())
value = value.replace(VALUE_NOON, time.toString())
value = value.replace(VALUE_NOON_TOMORROW, (time + ONE_DAY).toString())
value = value.replace(VALUE_NOON_DAY_AFTER, (time + 2 * ONE_DAY).toString())
value = value.replace(VALUE_NOON_NEXT_WEEK, (time + 7 * ONE_DAY).toString())
value = value.replace(VALUE_NOON_NEXT_MONTH, (time + 30 * ONE_DAY).toString())
return value
}
}

@ -0,0 +1,200 @@
package com.todoroo.astrid.core
import org.tasks.data.dao.APPLE_EPOCH
import org.tasks.data.entity.CaldavCalendar
import org.tasks.data.entity.Task
import org.tasks.data.sql.Functions.upper
import org.tasks.data.sql.Order
import org.tasks.data.sql.Order.Companion.asc
import org.tasks.data.sql.Order.Companion.desc
import org.tasks.data.sql.OrderType
import org.tasks.db.QueryUtils.showCompleted
import org.tasks.db.QueryUtils.showHidden
import org.tasks.preferences.QueryPreferences
object SortHelper {
const val GROUP_NONE: Int = -1
const val SORT_AUTO: Int = 0
const val SORT_ALPHA: Int = 1
const val SORT_DUE: Int = 2
const val SORT_IMPORTANCE: Int = 3
const val SORT_MODIFIED: Int = 4
const val SORT_CREATED: Int = 5
const val SORT_GTASKS: Int = 6
const val SORT_CALDAV: Int = 7
const val SORT_START: Int = 8
const val SORT_LIST: Int = 9
const val SORT_COMPLETED: Int = 10
const val SORT_MANUAL: Int = 11
private const val CALDAV_ORDER_COLUMN: String =
"IFNULL(tasks.`order`, (tasks.created - $APPLE_EPOCH) / 1000)"
private const val ADJUSTED_DUE_DATE =
"(CASE WHEN (dueDate / 1000) % 60 > 0 THEN dueDate ELSE (dueDate + 43140000) END)"
private const val ADJUSTED_START_DATE =
"(CASE WHEN (hideUntil / 1000) % 60 > 0 THEN hideUntil ELSE (hideUntil + 86399000) END)"
private const val NO_DATE = 3538339200000L
private const val GROUP_DUE_DATE = ("((CASE WHEN (tasks.dueDate=0) THEN " + NO_DATE + " ELSE "
+ "tasks.dueDate END)+tasks.importance * 1000)")
private val SORT_DUE_DATE = ("((CASE WHEN (tasks.dueDate=0) THEN " + NO_DATE + " ELSE "
+ ADJUSTED_DUE_DATE.replace("dueDate", "tasks.dueDate")
+ " END)+tasks.importance * 1000)")
private const val GROUP_START_DATE =
("((CASE WHEN (tasks.hideUntil=0) THEN " + NO_DATE + " ELSE "
+ "tasks.hideUntil END)+tasks.importance * 1000)")
private val SORT_START_DATE = ("((CASE WHEN (tasks.hideUntil=0) THEN " + NO_DATE + " ELSE "
+ ADJUSTED_START_DATE.replace("hideUntil", "tasks.hideUntil")
+ " END)+tasks.importance * 1000)")
private val ORDER_TITLE = asc(upper(Task.TITLE))
private val ORDER_LIST = asc(upper(CaldavCalendar.ORDER))
.addSecondaryExpression(asc(CaldavCalendar.NAME))
/** Takes a SQL query, and if there isn't already an order, creates an order. */
fun adjustQueryForFlagsAndSort(
preferences: QueryPreferences, originalSql: String?, sort: Int
): String {
// sort
var originalSql = originalSql
if (originalSql == null) {
originalSql = ""
}
if (!originalSql.contains("ORDER BY", ignoreCase = true)) {
var order = orderForSortType(sort)
if (order.orderType == OrderType.ASC != preferences.sortAscending) {
order = order.reverse()
}
originalSql += " ORDER BY $order"
}
return adjustQueryForFlags(preferences, originalSql)
}
fun adjustQueryForFlags(preferences: QueryPreferences, originalSql: String): String {
var adjustedSql = originalSql
// flags
if (preferences.showCompleted) {
adjustedSql = showCompleted(adjustedSql)
}
if (preferences.showHidden) {
adjustedSql = showHidden(adjustedSql)
}
return adjustedSql
}
private fun orderForSortType(sortType: Int): Order {
val order = when (sortType) {
SORT_ALPHA -> ORDER_TITLE
SORT_DUE -> asc(
"(CASE WHEN (dueDate=0) THEN (strftime('%s','now')*1000)*2 ELSE "
+ ADJUSTED_DUE_DATE
+ " END)+importance"
)
SORT_START -> asc(
"(CASE WHEN (hideUntil=0) THEN (strftime('%s','now')*1000)*2 ELSE "
+ ADJUSTED_START_DATE
+ " END)+importance"
)
SORT_IMPORTANCE -> asc("importance")
SORT_MODIFIED -> desc(Task.MODIFICATION_DATE)
SORT_CREATED -> desc(Task.CREATION_DATE)
SORT_LIST -> ORDER_LIST
else -> asc(
"(CASE WHEN (dueDate=0) "
+ // if no due date
"THEN (strftime('%s','now')*1000)*2 "
+ // then now * 2
"ELSE ("
+ ADJUSTED_DUE_DATE
+ ") END) "
+ // else due time
// add slightly less than 2 days * importance to give due date priority over importance in case of tie
"+ 172799999 * importance"
)
}
if (sortType != SORT_ALPHA) {
order.addSecondaryExpression(ORDER_TITLE)
}
return order
}
fun getSortGroup(sortType: Int): String? {
return when (sortType) {
SORT_DUE -> "tasks.dueDate"
SORT_START -> "tasks.hideUntil"
SORT_IMPORTANCE -> "tasks.importance"
SORT_MODIFIED -> "tasks.modified"
SORT_CREATED -> "tasks.created"
SORT_LIST -> "cdl_id"
else -> null
}
}
private fun sortGroup(column: String): String {
return "datetime($column / 1000, 'unixepoch', 'localtime', 'start of day')"
}
fun orderSelectForSortTypeRecursive(sortType: Int, grouping: Boolean): String {
return when (sortType) {
GROUP_NONE -> "1"
SORT_ALPHA -> "UPPER(tasks.title)"
SORT_DUE -> if (grouping) sortGroup(GROUP_DUE_DATE) else SORT_DUE_DATE
SORT_START -> if (grouping) sortGroup(GROUP_START_DATE) else SORT_START_DATE
SORT_IMPORTANCE -> "tasks.importance"
SORT_MODIFIED -> if (grouping) sortGroup("tasks.modified") else "tasks.modified"
SORT_CREATED -> if (grouping) sortGroup("tasks.created") else "tasks.created"
SORT_GTASKS -> "tasks.`order`"
SORT_CALDAV -> CALDAV_ORDER_COLUMN
SORT_LIST -> "CASE WHEN cdl_order = -1 THEN cdl_name ELSE cdl_order END"
SORT_COMPLETED -> "tasks.completed"
else -> ("(CASE WHEN (tasks.dueDate=0) "
+ // if no due date
"THEN (strftime('%s','now')*1000)*2 "
+ // then now * 2
"ELSE ("
+ ADJUSTED_DUE_DATE.replace("dueDate", "tasks.dueDate")
+ ") END) "
+ // else due time
// add slightly less than 2 days * importance to give due date priority over importance in case of tie
"+ 172799999 * tasks.importance")
}
}
fun orderForGroupTypeRecursive(groupMode: Int, ascending: Boolean): Order {
return if (ascending
) asc("primary_group")
else desc("primary_group")
}
fun orderForSortTypeRecursive(
sortMode: Int,
primaryAscending: Boolean,
secondaryMode: Int,
secondaryAscending: Boolean
): Order {
val order = if (primaryAscending || sortMode == SORT_GTASKS || sortMode == SORT_CALDAV
) asc("primary_sort")
else desc("primary_sort")
order.addSecondaryExpression(
if (secondaryAscending || secondaryMode == SORT_GTASKS || secondaryMode == SORT_CALDAV
) asc("secondary_sort")
else desc("secondary_sort")
)
if (sortMode != SORT_ALPHA) {
order.addSecondaryExpression(asc("sort_title"))
}
return order
}
}

@ -3,17 +3,14 @@ package org.tasks.db
import java.util.regex.Pattern import java.util.regex.Pattern
object QueryUtils { object QueryUtils {
private val HIDDEN = Pattern.compile("tasks\\.hideUntil<=?\\(strftime\\('%s','now'\\)\\*1000\\)") private val HIDDEN = "tasks\\.hideUntil<=?\\(strftime\\('%s','now'\\)\\*1000\\)".toPattern()
private val UNCOMPLETED = Pattern.compile("tasks\\.completed<?=0") private val UNCOMPLETED = "tasks\\.completed<?=0".toPattern()
private val ORDER = Pattern.compile("order by .*? (asc|desc)", Pattern.CASE_INSENSITIVE) private val ORDER = "order by .*? (asc|desc)".toPattern(Pattern.CASE_INSENSITIVE)
@JvmStatic
fun showHidden(query: String): String = HIDDEN.matcher(query).replaceAll("1") fun showHidden(query: String): String = HIDDEN.matcher(query).replaceAll("1")
@JvmStatic
fun showCompleted(query: String): String = UNCOMPLETED.matcher(query).replaceAll("1") fun showCompleted(query: String): String = UNCOMPLETED.matcher(query).replaceAll("1")
@JvmStatic
fun showHiddenAndCompleted(query: String): String = showCompleted(showHidden(query)) fun showHiddenAndCompleted(query: String): String = showCompleted(showHidden(query))
fun removeOrder(query: String): String = ORDER.matcher(query).replaceAll("") fun removeOrder(query: String): String = ORDER.matcher(query).replaceAll("")

@ -1,19 +1,20 @@
package org.tasks.filters package org.tasks.filters
import org.tasks.CommonParcelize
import org.tasks.data.entity.Task
import org.tasks.data.sql.Criterion.Companion.and import org.tasks.data.sql.Criterion.Companion.and
import org.tasks.data.sql.Order.Companion.desc import org.tasks.data.sql.Order.Companion.desc
import org.tasks.data.sql.QueryTemplate import org.tasks.data.sql.QueryTemplate
import org.tasks.data.entity.Task import org.tasks.time.DateTimeUtils2.currentTimeMillis
import kotlinx.parcelize.Parcelize import org.tasks.time.minusDays
import org.tasks.themes.CustomIcons import org.tasks.time.startOfMinute
import org.tasks.time.DateTime
@Parcelize @CommonParcelize
data class RecentlyModifiedFilter( data class RecentlyModifiedFilter(
override val title: String, override val title: String,
) : Filter { ) : Filter {
override val icon: Int override val icon: Int
get() = CustomIcons.HISTORY get() = 6 // CustomIcons.HISTORY
override val sql: String override val sql: String
get() = QueryTemplate() get() = QueryTemplate()
@ -21,7 +22,7 @@ data class RecentlyModifiedFilter(
and( and(
Task.DELETION_DATE.lte(0), Task.DELETION_DATE.lte(0),
Task.MODIFICATION_DATE.gt( Task.MODIFICATION_DATE.gt(
DateTime().minusDays(1).startOfMinute().millis currentTimeMillis().minusDays(1).startOfMinute()
) )
) )
) )

@ -0,0 +1,13 @@
package org.tasks.time
/** Represents a single hour */
const val ONE_HOUR: Long = 3600000L
/** Represents a single day */
const val ONE_DAY: Long = 24 * ONE_HOUR
/** Represents a single week */
const val ONE_WEEK: Long = 7 * ONE_DAY
/** Represents a single minute */
const val ONE_MINUTE: Long = 60000L

@ -1,9 +1,12 @@
package org.tasks.time package org.tasks.time
import kotlinx.datetime.DateTimeUnit
import kotlinx.datetime.Instant import kotlinx.datetime.Instant
import kotlinx.datetime.LocalDateTime import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.LocalTime import kotlinx.datetime.LocalTime
import kotlinx.datetime.TimeZone import kotlinx.datetime.TimeZone
import kotlinx.datetime.atTime
import kotlinx.datetime.minus
import kotlinx.datetime.toInstant import kotlinx.datetime.toInstant
import kotlinx.datetime.toLocalDateTime import kotlinx.datetime.toLocalDateTime
@ -92,6 +95,32 @@ fun Long.withMillisOfDay(millisOfDay: Int): Long =
0 0
} }
fun Long.minusDays(days: Int): Long =
if (this > 0) {
with (toLocalDateTime()) {
date
.minus(days, DateTimeUnit.DAY)
.atTime(time)
.toEpochMilliseconds()
}
} else {
0
}
fun Long.minusMinutes(minutes: Int): Long = minus(minutes, DateTimeUnit.MINUTE)
fun Long.minusMillis(millis: Long): Long = minus(millis.toInt(), DateTimeUnit.MILLISECOND)
private fun Long.minus(value: Int, units: DateTimeUnit.TimeBased): Long =
if (this > 0) {
Instant
.fromEpochMilliseconds(this)
.minus(value, units)
.toEpochMilliseconds()
} else {
0
}
val Long.millisOfDay: Int val Long.millisOfDay: Int
get() = if (this > 0) toLocalDateTime().time.toMillisecondOfDay() else 0 get() = if (this > 0) toLocalDateTime().time.toMillisecondOfDay() else 0

Loading…
Cancel
Save