Move query builders to kmp

pull/2910/head
Alex Baker 5 months ago
parent bf4167651b
commit 79ebc9a2c7

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

@ -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.provider.CalendarContract
import android.text.format.Time
import com.todoroo.andlib.utility.DateUtilities
import org.tasks.data.entity.Task
import dagger.hilt.android.qualifiers.ApplicationContext
import org.tasks.R
import org.tasks.Strings.isNullOrEmpty
import org.tasks.calendars.CalendarEventProvider
import org.tasks.data.dao.TaskDao
import org.tasks.data.entity.Task
import org.tasks.preferences.PermissionChecker
import org.tasks.preferences.Preferences
import org.tasks.time.DateTimeUtils2.currentTimeMillis
import org.tasks.time.ONE_HOUR
import timber.log.Timber
import java.util.TimeZone
import javax.inject.Inject
@ -174,6 +174,6 @@ class GCalHelper @Inject constructor(
companion object {
/** 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
import com.todoroo.andlib.utility.DateUtilities
import com.todoroo.astrid.alarms.AlarmService
import com.todoroo.astrid.dao.TaskDao
import com.todoroo.astrid.gcal.GCalHelper
@ -21,6 +20,9 @@ import org.tasks.data.setRecurrence
import org.tasks.date.DateTimeUtils.newDateTime
import org.tasks.repeats.RecurrenceUtils.newRecur
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 java.text.ParseException
import java.util.*
@ -160,7 +162,7 @@ class RepeatTaskHelper @Inject constructor(
recur: Recur, original: DateTime, hasDueTime: Boolean): Long {
val byDay = recur.dayList
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)
Collections.sort(byDay, weekdayCompare)
val next = findNextWeekday(byDay, date)
@ -266,8 +268,8 @@ class RepeatTaskHelper @Inject constructor(
@Deprecated("probably don't need this?")
private fun handleSubdayRepeat(startDate: DateTime, recur: Recur): Long {
val millis: Long = when (recur.frequency) {
Recur.Frequency.HOURLY -> DateUtilities.ONE_HOUR
Recur.Frequency.MINUTELY -> DateUtilities.ONE_MINUTE
Recur.Frequency.HOURLY -> ONE_HOUR
Recur.Frequency.MINUTELY -> ONE_MINUTE
else -> throw RuntimeException(
"Error handing subday repeat: " + recur.frequency) // $NON-NLS-1$
}

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

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

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

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

@ -345,7 +345,7 @@ class iCalendar @Inject constructor(
)
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? {
try {

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

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

@ -170,7 +170,7 @@ class DateTimePicker : BaseDateTimePicker() {
binding.shortcuts.currentDateSelection.text = if (customDate == MULTIPLE_DAYS) {
requireContext().getString(R.string.date_picker_multiple)
} 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) {
requireContext().getString(R.string.date_picker_multiple)
} 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 com.todoroo.andlib.utility.DateUtilities
import com.todoroo.astrid.dao.TaskDao
import org.tasks.data.entity.Task
import dagger.hilt.android.AndroidEntryPoint
import org.tasks.R
import org.tasks.data.entity.Task
import org.tasks.databinding.DialogStartDatePickerBinding
import org.tasks.date.DateTimeUtils.newDateTime
import org.tasks.dialogs.MyTimePickerDialog.Companion.newTimePicker
import org.tasks.notifications.NotificationManager
import org.tasks.time.DateTime
import java.time.format.FormatStyle
import java.util.*
import java.util.Locale
import javax.inject.Inject
@AndroidEntryPoint
@ -112,7 +112,7 @@ class StartDatePicker : BaseDateTimePicker() {
binding.shortcuts.dateGroup.check(R.id.current_date_selection)
binding.shortcuts.currentDateSelection.visibility = View.VISIBLE
binding.shortcuts.currentDateSelection.text =
DateUtilities.getRelativeDay(context, selectedDay, locale, FormatStyle.MEDIUM)
DateUtilities.getRelativeDay(requireContext(), selectedDay, locale, FormatStyle.MEDIUM)
}
}
if (Task.hasDueTime(selectedTime.toLong())) {
@ -125,7 +125,7 @@ class StartDatePicker : BaseDateTimePicker() {
customTime = selectedTime
binding.shortcuts.timeGroup.check(R.id.current_time_selection)
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) {

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

@ -145,17 +145,11 @@ class DateTime {
return subtract(Calendar.SECOND, seconds)
}
fun minusDays(days: Int): DateTime {
return subtract(Calendar.DATE, days)
}
fun minusDays(days: Int): DateTime = DateTime(millis.minusDays(days))
fun minusMinutes(minutes: Int): DateTime {
return subtract(Calendar.MINUTE, minutes)
}
fun minusMinutes(minutes: Int): DateTime = DateTime(millis.minusMinutes(minutes))
fun minusMillis(millis: Long): DateTime {
return DateTime(this.millis - millis, timeZone)
}
fun minusMillis(millis: Long): DateTime = DateTime(this.millis.minusMillis(millis))
val isAfterNow: Boolean
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
object QueryUtils {
private val HIDDEN = Pattern.compile("tasks\\.hideUntil<=?\\(strftime\\('%s','now'\\)\\*1000\\)")
private val UNCOMPLETED = Pattern.compile("tasks\\.completed<?=0")
private val ORDER = Pattern.compile("order by .*? (asc|desc)", Pattern.CASE_INSENSITIVE)
private val HIDDEN = "tasks\\.hideUntil<=?\\(strftime\\('%s','now'\\)\\*1000\\)".toPattern()
private val UNCOMPLETED = "tasks\\.completed<?=0".toPattern()
private val ORDER = "order by .*? (asc|desc)".toPattern(Pattern.CASE_INSENSITIVE)
@JvmStatic
fun showHidden(query: String): String = HIDDEN.matcher(query).replaceAll("1")
@JvmStatic
fun showCompleted(query: String): String = UNCOMPLETED.matcher(query).replaceAll("1")
@JvmStatic
fun showHiddenAndCompleted(query: String): String = showCompleted(showHidden(query))
fun removeOrder(query: String): String = ORDER.matcher(query).replaceAll("")

@ -1,19 +1,20 @@
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.Order.Companion.desc
import org.tasks.data.sql.QueryTemplate
import org.tasks.data.entity.Task
import kotlinx.parcelize.Parcelize
import org.tasks.themes.CustomIcons
import org.tasks.time.DateTime
import org.tasks.time.DateTimeUtils2.currentTimeMillis
import org.tasks.time.minusDays
import org.tasks.time.startOfMinute
@Parcelize
@CommonParcelize
data class RecentlyModifiedFilter(
override val title: String,
) : Filter {
override val icon: Int
get() = CustomIcons.HISTORY
get() = 6 // CustomIcons.HISTORY
override val sql: String
get() = QueryTemplate()
@ -21,7 +22,7 @@ data class RecentlyModifiedFilter(
and(
Task.DELETION_DATE.lte(0),
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
import kotlinx.datetime.DateTimeUnit
import kotlinx.datetime.Instant
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.LocalTime
import kotlinx.datetime.TimeZone
import kotlinx.datetime.atTime
import kotlinx.datetime.minus
import kotlinx.datetime.toInstant
import kotlinx.datetime.toLocalDateTime
@ -92,6 +95,32 @@ fun Long.withMillisOfDay(millisOfDay: Int): Long =
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
get() = if (this > 0) toLocalDateTime().time.toMillisecondOfDay() else 0

Loading…
Cancel
Save