diff --git a/app/src/main/java/com/philliphsu/clock2/Alarm.java b/app/src/main/java/com/philliphsu/clock2/Alarm.java index 0c9d474..24998fa 100644 --- a/app/src/main/java/com/philliphsu/clock2/Alarm.java +++ b/app/src/main/java/com/philliphsu/clock2/Alarm.java @@ -12,6 +12,7 @@ import org.json.JSONObject; import java.util.Calendar; import java.util.GregorianCalendar; +import static com.philliphsu.clock2.DaysOfWeek.NUM_DAYS; import static com.philliphsu.clock2.DaysOfWeek.SATURDAY; import static com.philliphsu.clock2.DaysOfWeek.SUNDAY; @@ -20,7 +21,7 @@ import static com.philliphsu.clock2.DaysOfWeek.SUNDAY; */ @AutoValue public abstract class Alarm implements JsonSerializable { - private static final int MAX_MINUTES_CAN_SNOOZE = 30; // TODO: Delete this along with all snooze stuff. + private static final int MAX_MINUTES_CAN_SNOOZE = 30; // JSON property names private static final String KEY_SNOOZING_UNTIL_MILLIS = "snoozing_until_millis"; @@ -82,7 +83,7 @@ public abstract class Alarm implements JsonSerializable { .id(-1) .hour(0) .minutes(0) - .recurringDays(new boolean[DaysOfWeek.NUM_DAYS]) + .recurringDays(new boolean[NUM_DAYS]) .label("") .ringtone("") .vibrates(false); @@ -146,26 +147,57 @@ public abstract class Alarm implements JsonSerializable { calendar.set(Calendar.MINUTE, minutes()); calendar.set(Calendar.SECOND, 0); calendar.set(Calendar.MILLISECOND, 0); - if (calendar.getTimeInMillis() <= System.currentTimeMillis()) { - // The specified time has passed for today - // TODO: This should be wrapped in an if (!hasRecurrence())? - calendar.add(Calendar.HOUR_OF_DAY, 24); - // TODO: Else, compute ring time for the next closest recurring day + + if (!hasRecurrence()) { + if (calendar.getTimeInMillis() <= System.currentTimeMillis()) { + // The specified time has passed for today + calendar.add(Calendar.HOUR_OF_DAY, 24); + } + } else { + // Compute the ring time just for the next closest recurring day. + // Remember that day constants defined in the Calendar class are not zero-based like ours, so we have to + // compensate with an offset of magnitude one, with the appropriate sign based on the situation. + int weekdayToday = calendar.get(Calendar.DAY_OF_WEEK); + int numDaysFromToday = -1; + + for (int i = weekdayToday; i <= Calendar.SATURDAY; i++) { + if (isRecurring(i - 1 /*match up with our day constant*/)) { + if (i == weekdayToday) { + if (calendar.getTimeInMillis() > System.currentTimeMillis()) { + // The normal ring time has not passed yet + numDaysFromToday = 0; + break; + } + } else { + numDaysFromToday = i - weekdayToday; + break; + } + } + } + + // Not computed yet + if (numDaysFromToday < 0) { + for (int i = Calendar.SUNDAY; i < weekdayToday; i++) { + if (isRecurring(i - 1 /*match up with our day constant*/)) { + numDaysFromToday = Calendar.SATURDAY - weekdayToday + i; + break; + } + } + } + + // Still not computed yet. The only recurring day is weekdayToday, + // and its normal ring time has already passed. + if (numDaysFromToday < 0 && isRecurring(weekdayToday - 1) + && calendar.getTimeInMillis() <= System.currentTimeMillis()) { + numDaysFromToday = 7; + } + + if (numDaysFromToday < 0) + throw new IllegalStateException("How did we get here?"); + + calendar.add(Calendar.HOUR_OF_DAY, 24 * numDaysFromToday); } - /* // Not fully thought out or completed! - // TODO: Compute ring time for the next closest recurring day - for (int i = 0; i < NUM_DAYS; i++) { - // The constants for the days defined in Calendar are - // not zero-based, but we are, so we must add 1. - int day = i + 1; // day for this index - int calendarDay = mCalendar.get(Calendar.DAY_OF_WEEK); - if (mRecurringDays[day]) { - mCalendar.add(Calendar.DAY_OF_WEEK, day); - break; - } - } - */ return calendar.getTimeInMillis(); } @@ -238,14 +270,16 @@ public abstract class Alarm implements JsonSerializable { this.id(++idCount); // TOneverDO: change to post-increment without also adding offset of 1 to idCount in rebuild() Alarm alarm = autoBuild(); checkTime(alarm.hour(), alarm.minutes()); + checkRecurringDaysArrayLength(alarm.recurringDays()); return alarm; } - /** Should only be called when recreating an instance from JSON */ + /** Should only be called when recreating an instance from JSON */ private Alarm rebuild() { Alarm alarm = autoBuild(); idCount = alarm.id(); // prevent future instances from id collision checkTime(alarm.hour(), alarm.minutes()); + checkRecurringDaysArrayLength(alarm.recurringDays()); return alarm; } } @@ -261,4 +295,10 @@ public abstract class Alarm implements JsonSerializable { throw new IllegalStateException("Hour and minutes invalid"); } } + + private static void checkRecurringDaysArrayLength(boolean[] b) { + if (b.length != NUM_DAYS) { + throw new IllegalStateException("Invalid length for recurring days array"); + } + } } diff --git a/app/src/main/java/com/philliphsu/clock2/DaysOfWeek.java b/app/src/main/java/com/philliphsu/clock2/DaysOfWeek.java index dd42900..928e713 100644 --- a/app/src/main/java/com/philliphsu/clock2/DaysOfWeek.java +++ b/app/src/main/java/com/philliphsu/clock2/DaysOfWeek.java @@ -11,7 +11,7 @@ import java.util.Arrays; /** * Created by Phillip Hsu on 5/30/2016. */ -public class DaysOfWeek { +public class DaysOfWeek implements DaysOfWeekHelper { private static final String TAG = "DaysOfWeek"; // DAY_OF_WEEK constants in Calendar class not zero-based public static final int SUNDAY = 0; @@ -56,14 +56,14 @@ public class DaysOfWeek { return sAppContext.getString(LABELS_RES[weekDay]); } - /** @return the week day at {@code position} within the user-defined week */ + @Override public int weekDayAt(int position) { if (position < 0 || position > 6) throw new ArrayIndexOutOfBoundsException("Ordinal day out of range"); return DAYS[position]; } - /** @return the position of the {@code weekDay} within the user-defined week */ + @Override public int positionOf(int weekDay) { if (weekDay < SUNDAY || weekDay > SATURDAY) throw new ArrayIndexOutOfBoundsException("Week day ("+weekDay+") out of range"); diff --git a/app/src/main/java/com/philliphsu/clock2/DaysOfWeekHelper.java b/app/src/main/java/com/philliphsu/clock2/DaysOfWeekHelper.java new file mode 100644 index 0000000..d6a7a12 --- /dev/null +++ b/app/src/main/java/com/philliphsu/clock2/DaysOfWeekHelper.java @@ -0,0 +1,12 @@ +package com.philliphsu.clock2; + +/** + * Created by Phillip Hsu on 6/9/2016. + */ +public interface DaysOfWeekHelper { + /** @return the week day at {@code position} within the user-defined week */ + int weekDayAt(int position); + + /** @return the position of the {@code weekDay} within the user-defined week */ + int positionOf(int weekDay); +} diff --git a/app/src/test/java/com/philliphsu/clock2/AlarmTest.java b/app/src/test/java/com/philliphsu/clock2/AlarmTest.java index e1f365e..eeac554 100644 --- a/app/src/test/java/com/philliphsu/clock2/AlarmTest.java +++ b/app/src/test/java/com/philliphsu/clock2/AlarmTest.java @@ -5,6 +5,7 @@ import org.junit.Test; import java.util.Calendar; import java.util.GregorianCalendar; +import static com.philliphsu.clock2.DaysOfWeek.SATURDAY; import static com.philliphsu.clock2.DaysOfWeek.SUNDAY; import static java.lang.System.out; import static java.util.Calendar.HOUR_OF_DAY; @@ -25,14 +26,14 @@ public class AlarmTest { Alarm alarm = Alarm.builder().build(); // Some true, some false - for (int i = SUNDAY; i <= DaysOfWeek.SATURDAY; i++) { + for (int i = SUNDAY; i <= SATURDAY; i++) { alarm.setRecurring(i, i % 2 == 0); assertTrue(alarm.isRecurring(i) == (i % 2 == 0)); } assertTrue(alarm.hasRecurrence()); // All false - for (int i = DaysOfWeek.SUNDAY; i <= DaysOfWeek.SATURDAY; i++) { + for (int i = SUNDAY; i <= SATURDAY; i++) { alarm.setRecurring(i, false); assertFalse(alarm.isRecurring(i)); } @@ -82,6 +83,116 @@ public class AlarmTest { } } + @Test + public void alarm_RingsAt_RecurringDays_ReturnsCorrectRingTime() { + Calendar cal = new GregorianCalendar(); + int weekDayToday = cal.get(Calendar.DAY_OF_WEEK); + + for (int h = 0; h < 24; h++) { + for (int m = 0; m < 60; m++) { + for (int d = SUNDAY; d <= SATURDAY; d++) { + out.println(String.format("Testing %02d:%02d for day %d", h, m, d)); + int hC = cal.get(HOUR_OF_DAY); // Current hour + int mC = cal.get(MINUTE); // Current minute + Alarm a = Alarm.builder().hour(h).minutes(m).build(); + a.setRecurring(d, true); + long calculatedRingTime; + boolean calculatedToNextDay = true; + if (h <= hC) { + if (m <= mC) { + calculatedRingTime = (23 - hC + h) * 3600000 + (60 - mC + m) * 60000; + } else { + calculatedRingTime = (m - mC) * 60000; + if (h < hC) { + calculatedRingTime += (24 - hC + h) * 3600000; + } else { + // h == hC + calculatedToNextDay = false; + } + } + } else { + if (m <= mC) { + calculatedRingTime = (h - hC - 1) * 3600000 + (60 - mC + m) * 60000; + } else { + calculatedRingTime = (h - hC) * 3600000 + (m - mC) * 60000; + } + calculatedToNextDay = false; + } + + int day = d + 1; // Match up with day constant defined in Calendar class + int amount = calculatedToNextDay ? d : day; // the amount to add on + if (day > weekDayToday) { + calculatedRingTime += 24 * (amount - weekDayToday) * 3600000; + } else if (day < weekDayToday) { + calculatedRingTime += 24 * (Calendar.SATURDAY - weekDayToday + amount) * 3600000; + } else { + long initialTime = cal.getTimeInMillis(); + // Temporarily add on whatever we have so far + cal.setTimeInMillis(initialTime + calculatedRingTime); + cal.set(SECOND, 0); + cal.set(MILLISECOND, 0); + if (calculatedToNextDay) { + // Temporarily subtract off a whole day's worth of millis + cal.add(HOUR_OF_DAY, -24); + } + if (cal.getTimeInMillis() <= System.currentTimeMillis()) { + calculatedRingTime += 24 * (calculatedToNextDay ? 6 : 7) * 3600000; + } + cal.setTimeInMillis(initialTime); + } + + cal.setTimeInMillis(cal.getTimeInMillis() + calculatedRingTime); + cal.set(SECOND, 0); + cal.set(MILLISECOND, 0); + assertEquals(a.ringsAt(), cal.getTimeInMillis()); + // VERY IMPORTANT TO RESET AT THE END!!!! + cal.setTimeInMillis(System.currentTimeMillis()); + } + } + } + } + + @Test + public void alarm_RingsAt_AllRecurringDays_ReturnsCorrectRingTime() { + // The results of this test should be the same as the normal ringsAt test: + // alarm_RingsAt_ReturnsCorrectRingTime(). + GregorianCalendar now = new GregorianCalendar(); + for (int h = 0; h < 24; h++) { + for (int m = 0; m < 60; m++) { + out.println(String.format("Testing %02d:%02d", h, m)); + int hC = now.get(HOUR_OF_DAY); // Current hour + int mC = now.get(MINUTE); // Current minute + Alarm a = Alarm.builder().hour(h).minutes(m).build(); + for (int i = 0; i < 7; i++) { + a.setRecurring(i, true); + } + long calculatedRingTime; + if (h <= hC) { + if (m <= mC) { + calculatedRingTime = (23-hC+h)*3600000 + (60-mC+m)*60000; + } else { + calculatedRingTime = (m-mC)*60000; + if (h < hC) { + calculatedRingTime += (24-hC+h)*3600000; + } + } + } else { + if (m <= mC) { + calculatedRingTime = (h-hC-1)*3600000+(60-mC+m)*60000; + } else { + calculatedRingTime = (h-hC)*3600000+(m-mC)*60000; + } + } + now.setTimeInMillis(now.getTimeInMillis() + calculatedRingTime); + now.set(SECOND, 0); + now.set(MILLISECOND, 0); + assertEquals(a.ringsAt(), now.getTimeInMillis()); + // VERY IMPORTANT TO RESET AT THE END!!!! THIS TOOK A WHOLE FUCKING DAY OF BUG HUNTING!!! + now.setTimeInMillis(System.currentTimeMillis()); + } + } + } + @Test public void snoozeAlarm_AssertEquals_SnoozingUntilMillis_CorrespondsToWallClock() { Calendar cal = new GregorianCalendar();