diff --git a/src/androidTest/java/com/todoroo/andlib/utility/DateUtilitiesTest.java b/src/androidTest/java/com/todoroo/andlib/utility/DateUtilitiesTest.java index 7481ffcd0..192d1773f 100644 --- a/src/androidTest/java/com/todoroo/andlib/utility/DateUtilitiesTest.java +++ b/src/androidTest/java/com/todoroo/andlib/utility/DateUtilitiesTest.java @@ -173,7 +173,7 @@ public class DateUtilitiesTest extends AndroidTestCase { public void testShouldGetStartOfDay() { DateTime now = new DateTime(2014, 1, 3, 10, 41, 41, 520); assertEquals( - now.withMillisOfDay(0).getMillis(), + now.startOfDay().getMillis(), getStartOfDay(now.getMillis())); } diff --git a/src/androidTest/java/com/todoroo/astrid/data/TaskTest.java b/src/androidTest/java/com/todoroo/astrid/data/TaskTest.java index c20969ac6..0dcae1c15 100644 --- a/src/androidTest/java/com/todoroo/astrid/data/TaskTest.java +++ b/src/androidTest/java/com/todoroo/astrid/data/TaskTest.java @@ -114,7 +114,7 @@ public class TaskTest extends AndroidTestCase { } public void testNoDueTime() { - assertFalse(hasDueTime(newDateTime().withMillisOfDay(0).getMillis())); + assertFalse(hasDueTime(newDateTime().startOfDay().getMillis())); assertFalse(hasDueTime(newDateTime().withMillisOfDay(60000).getMillis())); } @@ -198,7 +198,7 @@ public class TaskTest extends AndroidTestCase { } public void testTaskNotOverdueBeforeNoonOnDueDate() { - final DateTime dueDate = new DateTime().withMillisOfDay(0); + final DateTime dueDate = new DateTime().startOfDay(); freezeAt(dueDate.plusHours(12).minusMillis(1)).thawAfter(new Snippet() {{ Task task = new Task(); task.setValue(DUE_DATE, dueDate.getMillis()); @@ -208,7 +208,7 @@ public class TaskTest extends AndroidTestCase { } public void testTaskOverdueAtNoonOnDueDate() { - final DateTime dueDate = new DateTime().withMillisOfDay(0); + final DateTime dueDate = new DateTime().startOfDay(); freezeAt(dueDate.plusHours(12)).thawAfter(new Snippet() {{ Task task = new Task(); task.setValue(DUE_DATE, dueDate.getMillis()); @@ -218,7 +218,7 @@ public class TaskTest extends AndroidTestCase { } public void testTaskWithNoDueTimeIsOverdue() { - final DateTime dueDate = new DateTime().withMillisOfDay(0); + final DateTime dueDate = new DateTime().startOfDay(); freezeAt(dueDate.plusDays(1)).thawAfter(new Snippet() {{ Task task = new Task(); task.setValue(DUE_DATE, dueDate.getMillis()); diff --git a/src/androidTest/java/org/tasks/time/DateTimeTest.java b/src/androidTest/java/org/tasks/time/DateTimeTest.java index c7da3bcf7..7ed3fca7e 100644 --- a/src/androidTest/java/org/tasks/time/DateTimeTest.java +++ b/src/androidTest/java/org/tasks/time/DateTimeTest.java @@ -6,6 +6,7 @@ import org.tasks.Freeze; import org.tasks.Snippet; import java.util.TimeZone; +import java.util.concurrent.TimeUnit; public class DateTimeTest extends AndroidTestCase { public void testGetMillisOfDay() { @@ -18,6 +19,55 @@ public class DateTimeTest extends AndroidTestCase { new DateTime(2015, 10, 6, 0, 0, 0, 0).withMillisOfDay(7248412)); } + public void testWithMillisOfDayDuringDST() { + TimeZone def = TimeZone.getDefault(); + try { + TimeZone.setDefault(TimeZone.getTimeZone("America/Chicago")); + assertEquals(2, new DateTime(2015, 10, 31, 2, 0, 0).withMillisOfDay((int) TimeUnit.HOURS.toMillis(2)).getHourOfDay()); + } finally { + TimeZone.setDefault(def); + } + } + + public void testWithMillisOfDayAfterDST() { + TimeZone def = TimeZone.getDefault(); + try { + TimeZone.setDefault(TimeZone.getTimeZone("America/Chicago")); + assertEquals(2, new DateTime(2015, 11, 2, 2, 0, 0).withMillisOfDay((int) TimeUnit.HOURS.toMillis(2)).getHourOfDay()); + } finally { + TimeZone.setDefault(def); + } + } + + public void testWithMillisOfDayStartDST() { + TimeZone def = TimeZone.getDefault(); + try { + TimeZone.setDefault(TimeZone.getTimeZone("America/Chicago")); + assertEquals(1, new DateTime(2015, 3, 8, 0, 0, 0).withMillisOfDay((int) TimeUnit.HOURS.toMillis(1)).getHourOfDay()); + assertEquals(3, new DateTime(2015, 3, 8, 0, 0, 0).withMillisOfDay((int) TimeUnit.HOURS.toMillis(2)).getHourOfDay()); + assertEquals(3, new DateTime(2015, 3, 8, 0, 0, 0).withMillisOfDay((int) TimeUnit.HOURS.toMillis(3)).getHourOfDay()); + assertEquals(4, new DateTime(2015, 3, 8, 0, 0, 0).withMillisOfDay((int) TimeUnit.HOURS.toMillis(4)).getHourOfDay()); + + assertEquals( + new DateTime(2015, 3, 8, 0, 0, 0).withMillisOfDay((int) TimeUnit.HOURS.toMillis(2)).getMillis(), + new DateTime(2015, 3, 8, 0, 0, 0).withMillisOfDay((int) TimeUnit.HOURS.toMillis(3)).getMillis()); + } finally { + TimeZone.setDefault(def); + } + } + + public void testWithMillisOfDayEndDST() { + TimeZone def = TimeZone.getDefault(); + try { + TimeZone.setDefault(TimeZone.getTimeZone("America/Chicago")); + assertEquals(1, new DateTime(2015, 11, 1, 0, 0, 0).withMillisOfDay((int) TimeUnit.HOURS.toMillis(1)).getHourOfDay()); + assertEquals(2, new DateTime(2015, 11, 1, 0, 0, 0).withMillisOfDay((int) TimeUnit.HOURS.toMillis(2)).getHourOfDay()); + assertEquals(3, new DateTime(2015, 11, 1, 0, 0, 0).withMillisOfDay((int) TimeUnit.HOURS.toMillis(3)).getHourOfDay()); + } finally { + TimeZone.setDefault(def); + } + } + public void testPlusMonths() { assertEquals( new DateTime(2015, 11, 6, 2, 0, 48, 412), diff --git a/src/main/java/com/todoroo/andlib/utility/DateUtilities.java b/src/main/java/com/todoroo/andlib/utility/DateUtilities.java index 0818bc5a0..afee62ec8 100644 --- a/src/main/java/com/todoroo/andlib/utility/DateUtilities.java +++ b/src/main/java/com/todoroo/andlib/utility/DateUtilities.java @@ -220,6 +220,6 @@ public class DateUtilities { } public static long getStartOfDay(long time) { - return newDateTime(time).withMillisOfDay(0).getMillis(); + return newDateTime(time).startOfDay().getMillis(); } } diff --git a/src/main/java/com/todoroo/astrid/repeats/RepeatTaskCompleteListener.java b/src/main/java/com/todoroo/astrid/repeats/RepeatTaskCompleteListener.java index 348269535..82600ce47 100644 --- a/src/main/java/com/todoroo/astrid/repeats/RepeatTaskCompleteListener.java +++ b/src/main/java/com/todoroo/astrid/repeats/RepeatTaskCompleteListener.java @@ -97,7 +97,7 @@ public class RepeatTaskCompleteListener extends InjectingBroadcastReceiver { } static boolean repeatFinished(long newDueDate, long repeatUntil) { - return repeatUntil > 0 && newDateTime(newDueDate).withMillisOfDay(0).isAfter(newDateTime(repeatUntil).withMillisOfDay(0)); + return repeatUntil > 0 && newDateTime(newDueDate).startOfDay().isAfter(newDateTime(repeatUntil).startOfDay()); } public static void rescheduleTask(Context context, GCalHelper gcalHelper, TaskService taskService, Task task, long newDueDate) { diff --git a/src/main/java/com/todoroo/astrid/ui/ReminderControlSet.java b/src/main/java/com/todoroo/astrid/ui/ReminderControlSet.java index 6b4d13ba9..e390ffcb1 100644 --- a/src/main/java/com/todoroo/astrid/ui/ReminderControlSet.java +++ b/src/main/java/com/todoroo/astrid/ui/ReminderControlSet.java @@ -102,7 +102,7 @@ public class ReminderControlSet extends TaskEditControlSetBase implements Adapte private void addNewAlarm() { taskEditFragment.startActivityForResult(new Intent(taskEditFragment.getActivity(), DateAndTimePickerActivity.class) {{ - putExtra(DateAndTimePickerActivity.EXTRA_TIMESTAMP, newDateTime().withMillisOfDay(0).getMillis()); + putExtra(DateAndTimePickerActivity.EXTRA_TIMESTAMP, newDateTime().startOfDay().getMillis()); }}, REQUEST_NEW_ALARM); } diff --git a/src/main/java/org/tasks/activities/TimePickerActivity.java b/src/main/java/org/tasks/activities/TimePickerActivity.java index 9d2373bf1..80b0b5db6 100644 --- a/src/main/java/org/tasks/activities/TimePickerActivity.java +++ b/src/main/java/org/tasks/activities/TimePickerActivity.java @@ -46,7 +46,7 @@ public class TimePickerActivity extends FragmentActivity implements TimePickerDi public void onTimeSet(RadialPickerLayout radialPickerLayout, final int hours, final int minutes) { setResult(RESULT_OK, new Intent() {{ putExtra(EXTRA_TIMESTAMP, initial - .withMillisOfDay(0) + .startOfDay() .withHourOfDay(hours) .withMinuteOfHour(minutes) .getMillis()); diff --git a/src/main/java/org/tasks/scheduling/MidnightIntentService.java b/src/main/java/org/tasks/scheduling/MidnightIntentService.java index 49633261d..a280f2f83 100644 --- a/src/main/java/org/tasks/scheduling/MidnightIntentService.java +++ b/src/main/java/org/tasks/scheduling/MidnightIntentService.java @@ -50,7 +50,7 @@ public abstract class MidnightIntentService extends InjectingIntentService { } private static long nextMidnight(long timestamp) { - return newDateTime(timestamp).withMillisOfDay(0).plusDays(1).getMillis(); + return newDateTime(timestamp).startOfDay().plusDays(1).getMillis(); } abstract void run(); diff --git a/src/main/java/org/tasks/time/DateTime.java b/src/main/java/org/tasks/time/DateTime.java index d3e9463ef..20127cacd 100644 --- a/src/main/java/org/tasks/time/DateTime.java +++ b/src/main/java/org/tasks/time/DateTime.java @@ -10,6 +10,9 @@ public class DateTime { private static final int MAX_MILLIS_PER_DAY = (int) TimeUnit.DAYS.toMillis(1); private static final TimeZone UTC = TimeZone.getTimeZone("GMT"); + private static final int MILLIS_PER_HOUR = (int) TimeUnit.HOURS.toMillis(1); + private static final int MILLIS_PER_MINUTE = (int) TimeUnit.MINUTES.toMillis(1); + private static final int MILLIS_PER_SECOND = (int) TimeUnit.SECONDS.toMillis(1); private final TimeZone timeZone; private final long timestamp; @@ -47,19 +50,35 @@ public class DateTime { this(calendar.getTimeInMillis(), calendar.getTimeZone()); } + private DateTime setTime(int hours, int minutes, int seconds, int milliseconds) { + Calendar calendar = getCalendar(); + calendar.set(Calendar.HOUR_OF_DAY, hours); + calendar.set(Calendar.MINUTE, minutes); + calendar.set(Calendar.SECOND, seconds); + calendar.set(Calendar.MILLISECOND, milliseconds); + return new DateTime(calendar); + } + public DateTime startOfDay() { - return withHourOfDay(0) - .withMinuteOfHour(0) - .withSecondOfMinute(0) - .withMillisOfSecond(0); + Calendar calendar = getCalendar(); + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + return new DateTime(calendar); } public DateTime withMillisOfDay(int millisOfDay) { - if (millisOfDay >= MAX_MILLIS_PER_DAY) { + if (millisOfDay >= MAX_MILLIS_PER_DAY || millisOfDay < 0) { throw new RuntimeException("Illegal millis of day: " + millisOfDay); } - - return new DateTime(startOfDay().getMillis() + millisOfDay, timeZone); + int hours = millisOfDay / MILLIS_PER_HOUR; + millisOfDay %= MILLIS_PER_HOUR; + int minutes = millisOfDay / MILLIS_PER_MINUTE; + millisOfDay %= MILLIS_PER_MINUTE; + int seconds = millisOfDay / MILLIS_PER_SECOND; + millisOfDay %= MILLIS_PER_SECOND; + return startOfDay().setTime(hours, minutes, seconds, millisOfDay); } public long getMillis() { @@ -67,7 +86,13 @@ public class DateTime { } public int getMillisOfDay() { - return (int) (timestamp - withMillisOfDay(0).getMillis()); + Calendar calendar = getCalendar(); + long millisOfDay = + calendar.get(Calendar.MILLISECOND) + + TimeUnit.SECONDS.toMillis(calendar.get(Calendar.SECOND)) + + TimeUnit.MINUTES.toMillis(calendar.get(Calendar.MINUTE)) + + TimeUnit.HOURS.toMillis(calendar.get(Calendar.HOUR_OF_DAY)); + return (int) millisOfDay; } public int getYear() { diff --git a/src/main/java/org/tasks/ui/DeadlineControlSet.java b/src/main/java/org/tasks/ui/DeadlineControlSet.java index ab6b9968c..5044ae188 100644 --- a/src/main/java/org/tasks/ui/DeadlineControlSet.java +++ b/src/main/java/org/tasks/ui/DeadlineControlSet.java @@ -114,7 +114,7 @@ public class DeadlineControlSet extends TaskEditControlSetBase { } private void updateDueDateOptions() { - DateTime today = newDateTime().withMillisOfDay(0); + DateTime today = newDateTime().startOfDay(); String nextWeekString = activity.getString(R.string.next, today.plusWeeks(1).toString("EEEE")); if (date == 0) { dueDateOptions.set(0, activity.getString(R.string.TEA_no_date)); @@ -187,7 +187,7 @@ public class DeadlineControlSet extends TaskEditControlSetBase { dueDateSpinner.setBackgroundDrawable(getThemedUnderline()); } else { dueDateSpinner.setAlpha(1.0f); - if (date < newDateTime().withMillisOfDay(0).getMillis()) { + if (date < newDateTime().startOfDay().getMillis()) { dueDateSpinner.setBackgroundDrawable(getRedUnderline()); tv.setTextColor(activity.getResources().getColor(R.color.overdue)); } else { @@ -199,7 +199,7 @@ public class DeadlineControlSet extends TaskEditControlSetBase { if (date == 0) { dueDateSpinner.setBackgroundDrawable(getThemedUnderline()); tv.setTextColor(unsetColor); - } else if (date < newDateTime().withMillisOfDay(0).getMillis()) { + } else if (date < newDateTime().startOfDay().getMillis()) { dueDateSpinner.setBackgroundDrawable(getRedUnderline()); tv.setTextColor(activity.getResources().getColor(R.color.overdue)); } else { @@ -298,7 +298,7 @@ public class DeadlineControlSet extends TaskEditControlSetBase { dueDateSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView parent, View view, int position, long id) { - DateTime today = newDateTime().withMillisOfDay(0); + DateTime today = newDateTime().startOfDay(); switch (position) { case 0: return; @@ -421,7 +421,7 @@ public class DeadlineControlSet extends TaskEditControlSetBase { if (dateTime.isBeforeNow()) { dateTime = dateTime.plusDays(1); } - date = dateTime.withMillisOfDay(0).getMillis(); + date = dateTime.startOfDay().getMillis(); } refreshDisplayView(); @@ -432,7 +432,7 @@ public class DeadlineControlSet extends TaskEditControlSetBase { Long dueDate = model.getDueDate(); if (dueDate > 0) { DateTime dateTime = newDateTime(dueDate); - date = dateTime.withMillisOfDay(0).getMillis(); + date = dateTime.startOfDay().getMillis(); if (Task.hasDueTime(dateTime.getMillis())) { setTime(dateTime.getMillisOfDay()); } else { diff --git a/src/main/java/org/tasks/ui/TimePreference.java b/src/main/java/org/tasks/ui/TimePreference.java index 192846310..bd0f8325a 100644 --- a/src/main/java/org/tasks/ui/TimePreference.java +++ b/src/main/java/org/tasks/ui/TimePreference.java @@ -33,7 +33,7 @@ public class TimePreference extends Preference { @Override public void onSetInitialValue(boolean restoreValue, Object defaultValue) { if (restoreValue) { - int noon = new DateTime().withMillisOfDay(0).withHourOfDay(12).getMillisOfDay(); + int noon = new DateTime().startOfDay().withHourOfDay(12).getMillisOfDay(); millisOfDay = getPersistedInt(noon); } else { millisOfDay = Integer.parseInt((String) defaultValue);