aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/com/android/calendarcommon2/EventRecurrence.java5
-rw-r--r--src/com/android/calendarcommon2/RecurrenceProcessor.java155
-rw-r--r--src/com/android/calendarcommon2/RecurrenceSet.java43
-rw-r--r--src/com/android/calendarcommon2/Time.java570
-rw-r--r--tests/src/com/android/calendarcommon2/RRuleTest.java3
-rw-r--r--tests/src/com/android/calendarcommon2/RecurrenceProcessorTest.java34
-rw-r--r--tests/src/com/android/calendarcommon2/TimeTest.java539
7 files changed, 894 insertions, 455 deletions
diff --git a/src/com/android/calendarcommon2/EventRecurrence.java b/src/com/android/calendarcommon2/EventRecurrence.java
index df0c6eb..d54f2fe 100644
--- a/src/com/android/calendarcommon2/EventRecurrence.java
+++ b/src/com/android/calendarcommon2/EventRecurrence.java
@@ -18,7 +18,6 @@ package com.android.calendarcommon2;
import android.text.TextUtils;
import android.util.Log;
-import android.util.TimeFormatException;
import java.util.Calendar;
import java.util.HashMap;
@@ -475,7 +474,7 @@ public class EventRecurrence {
EventRecurrence er = (EventRecurrence) obj;
return (startDate == null ?
- er.startDate == null : Time.compare(startDate, er.startDate) == 0) &&
+ er.startDate == null : startDate.compareTo(er.startDate) == 0) &&
freq == er.freq &&
(until == null ? er.until == null : until.equals(er.until)) &&
count == er.count &&
@@ -739,7 +738,7 @@ public class EventRecurrence {
// Parse the time to validate it. The result isn't retained.
Time until = new Time();
until.parse(value);
- } catch (TimeFormatException tfe) {
+ } catch (IllegalArgumentException iae) {
throw new InvalidFormatException("Invalid UNTIL value: " + value);
}
}
diff --git a/src/com/android/calendarcommon2/RecurrenceProcessor.java b/src/com/android/calendarcommon2/RecurrenceProcessor.java
index 2707eec..24decce 100644
--- a/src/com/android/calendarcommon2/RecurrenceProcessor.java
+++ b/src/com/android/calendarcommon2/RecurrenceProcessor.java
@@ -92,7 +92,7 @@ public class RecurrenceProcessor
} else if (rrule.until != null) {
// according to RFC 2445, until must be in UTC.
mIterator.parse(rrule.until);
- long untilTime = mIterator.toMillis(false /* use isDst */);
+ long untilTime = mIterator.toMillis();
if (untilTime > lastTime) {
lastTime = untilTime;
}
@@ -128,9 +128,8 @@ public class RecurrenceProcessor
// The expansion might not contain any dates if the exrule or
// exdates cancel all the generated dates.
long[] dates = expand(dtstart, recur,
- dtstart.toMillis(false /* use isDst */) /* range start */,
- (maxtime != null) ?
- maxtime.toMillis(false /* use isDst */) : -1 /* range end */);
+ dtstart.toMillis() /* range start */,
+ (maxtime != null) ? maxtime.toMillis() : -1 /* range end */);
// The expansion might not contain any dates if exrule or exdates
// cancel all the generated dates.
@@ -200,7 +199,7 @@ public class RecurrenceProcessor
// BYMONTH
if (r.bymonthCount > 0) {
found = listContains(r.bymonth, r.bymonthCount,
- iterator.month + 1);
+ iterator.getMonth() + 1);
if (!found) {
return 1;
}
@@ -222,7 +221,7 @@ public class RecurrenceProcessor
// BYYEARDAY
if (r.byyeardayCount > 0) {
found = listContains(r.byyearday, r.byyeardayCount,
- iterator.yearDay, iterator.getActualMaximum(Time.YEAR_DAY));
+ iterator.getYearDay(), iterator.getActualMaximum(Time.YEAR_DAY));
if (!found) {
return 3;
}
@@ -230,7 +229,7 @@ public class RecurrenceProcessor
// BYMONTHDAY
if (r.bymonthdayCount > 0 ) {
found = listContains(r.bymonthday, r.bymonthdayCount,
- iterator.monthDay,
+ iterator.getDay(),
iterator.getActualMaximum(Time.MONTH_DAY));
if (!found) {
return 4;
@@ -242,7 +241,7 @@ byday:
if (r.bydayCount > 0) {
int a[] = r.byday;
int N = r.bydayCount;
- int v = EventRecurrence.timeDay2Day(iterator.weekDay);
+ int v = EventRecurrence.timeDay2Day(iterator.getWeekDay());
for (int i=0; i<N; i++) {
if (a[i] == v) {
break byday;
@@ -254,7 +253,7 @@ byday:
if (EventRecurrence.HOURLY >= freq) {
// BYHOUR
found = listContains(r.byhour, r.byhourCount,
- iterator.hour,
+ iterator.getHour(),
iterator.getActualMaximum(Time.HOUR));
if (!found) {
return 6;
@@ -263,7 +262,7 @@ byday:
if (EventRecurrence.MINUTELY >= freq) {
// BYMINUTE
found = listContains(r.byminute, r.byminuteCount,
- iterator.minute,
+ iterator.getMinute(),
iterator.getActualMaximum(Time.MINUTE));
if (!found) {
return 7;
@@ -272,7 +271,7 @@ byday:
if (EventRecurrence.SECONDLY >= freq) {
// BYSECOND
found = listContains(r.bysecond, r.bysecondCount,
- iterator.second,
+ iterator.getSecond(),
iterator.getActualMaximum(Time.SECOND));
if (!found) {
return 8;
@@ -325,7 +324,7 @@ bysetpos:
* (day of the month - 1) mod 7, and then make sure it's positive. We can simplify
* that with some algebra.
*/
- int dotw = (instance.weekDay - instance.monthDay + 36) % 7;
+ int dotw = (instance.getWeekDay() - instance.getDay() + 36) % 7;
/*
* The byday[] values are specified as bits, so we can just OR them all
@@ -367,14 +366,14 @@ bysetpos:
if (index > daySetLength) {
continue; // out of range
}
- if (daySet[index-1] == instance.monthDay) {
+ if (daySet[index-1] == instance.getDay()) {
return true;
}
} else if (index < 0) {
if (daySetLength + index < 0) {
continue; // out of range
}
- if (daySet[daySetLength + index] == instance.monthDay) {
+ if (daySet[daySetLength + index] == instance.getDay()) {
return true;
}
} else {
@@ -428,29 +427,29 @@ bysetpos:
boolean get(Time iterator, int day)
{
- int realYear = iterator.year;
- int realMonth = iterator.month;
+ int realYear = iterator.getYear();
+ int realMonth = iterator.getMonth();
Time t = null;
if (SPEW) {
Log.i(TAG, "get called with iterator=" + iterator
- + " " + iterator.month
- + "/" + iterator.monthDay
- + "/" + iterator.year + " day=" + day);
+ + " " + iterator.getMonth()
+ + "/" + iterator.getDay()
+ + "/" + iterator.getYear() + " day=" + day);
}
if (day < 1 || day > 28) {
// if might be past the end of the month, we need to normalize it
t = mTime;
t.set(day, realMonth, realYear);
unsafeNormalize(t);
- realYear = t.year;
- realMonth = t.month;
- day = t.monthDay;
+ realYear = t.getYear();
+ realMonth = t.getMonth();
+ day = t.getDay();
if (SPEW) {
- Log.i(TAG, "normalized t=" + t + " " + t.month
- + "/" + t.monthDay
- + "/" + t.year);
+ Log.i(TAG, "normalized t=" + t + " " + t.getMonth()
+ + "/" + t.getDay()
+ + "/" + t.getYear());
}
}
@@ -465,9 +464,9 @@ bysetpos:
t.set(day, realMonth, realYear);
unsafeNormalize(t);
if (SPEW) {
- Log.i(TAG, "set t=" + t + " " + t.month
- + "/" + t.monthDay
- + "/" + t.year
+ Log.i(TAG, "set t=" + t + " " + t.getMonth()
+ + "/" + t.getDay()
+ + "/" + t.getYear()
+ " realMonth=" + realMonth + " mMonth=" + mMonth);
}
}
@@ -506,11 +505,11 @@ bysetpos:
count = r.bydayCount;
if (count > 0) {
// calculate the day of week for the first of this month (first)
- j = generated.monthDay;
+ j = generated.getDay();
while (j >= 8) {
j -= 7;
}
- first = generated.weekDay;
+ first = generated.getWeekDay();
if (first >= j) {
first = first - j + 1;
} else {
@@ -630,13 +629,13 @@ bysetpos:
* UTC milliseconds; use -1 for the entire range.
* @return an array of dates, each date is in UTC milliseconds
* @throws DateException
- * @throws android.util.TimeFormatException if recur cannot be parsed
+ * @throws IllegalArgumentException if recur cannot be parsed
*/
public long[] expand(Time dtstart,
RecurrenceSet recur,
long rangeStartMillis,
long rangeEndMillis) throws DateException {
- String timezone = dtstart.timezone;
+ String timezone = dtstart.getTimezone();
mIterator.clear(timezone);
mGenerated.clear(timezone);
@@ -702,7 +701,7 @@ bysetpos:
int i = 0;
for (Long val: dtSet) {
setTimeFromLongValue(mIterator, val);
- dates[i++] = mIterator.toMillis(true /* ignore isDst */);
+ dates[i++] = mIterator.toMillis();
}
return dates;
}
@@ -727,7 +726,7 @@ bysetpos:
* @param add Whether or not we should add to out, or remove from out.
* @param out the TreeSet you'd like to fill with the events
* @throws DateException
- * @throws android.util.TimeFormatException if r cannot be parsed.
+ * @throws IllegalArgumentException if r cannot be parsed.
*/
public void expand(Time dtstart,
EventRecurrence r,
@@ -826,7 +825,7 @@ bysetpos:
// we'll skip months if it's greater than 28.
// XXX Do we generate days for MONTHLY w/ BYHOUR? If so,
// we need to do this then too.
- iterator.monthDay = 1;
+ iterator.setDay(1);
}
}
@@ -846,7 +845,7 @@ bysetpos:
// We need the "until" year/month/day values to be in the same
// timezone as all the generated dates so that we can compare them
// using the values returned by normDateTimeComparisonValue().
- until.switchTimezone(dtstart.timezone);
+ until.switchTimezone(dtstart.getTimezone());
untilDateValue = normDateTimeComparisonValue(until);
} else {
untilDateValue = Long.MAX_VALUE;
@@ -875,17 +874,17 @@ bysetpos:
unsafeNormalize(iterator);
- int iteratorYear = iterator.year;
- int iteratorMonth = iterator.month + 1;
- int iteratorDay = iterator.monthDay;
- int iteratorHour = iterator.hour;
- int iteratorMinute = iterator.minute;
- int iteratorSecond = iterator.second;
+ int iteratorYear = iterator.getYear();
+ int iteratorMonth = iterator.getMonth() + 1;
+ int iteratorDay = iterator.getDay();
+ int iteratorHour = iterator.getHour();
+ int iteratorMinute = iterator.getMinute();
+ int iteratorSecond = iterator.getSecond();
// year is never expanded -- there is no BYYEAR
generated.set(iterator);
- if (SPEW) Log.i(TAG, "year=" + generated.year);
+ if (SPEW) Log.i(TAG, "year=" + generated.getYear());
do { // month
int month = usebymonth
@@ -922,9 +921,9 @@ bysetpos:
* Thursday. If weeks started on Mondays, we would only
* need to move back (2 - 1 + 7) % 7 = 1 day.
*/
- int weekStartAdj = (iterator.weekDay -
+ int weekStartAdj = (iterator.getWeekDay() -
EventRecurrence.day2TimeDay(r.wkst) + 7) % 7;
- dayIndex = iterator.monthDay - weekStartAdj;
+ dayIndex = iterator.getDay() - weekStartAdj;
lastDayToExamine = dayIndex + 6;
} else {
lastDayToExamine = generated
@@ -1064,35 +1063,21 @@ bysetpos:
// We don't want to "generate" dates with the iterator.
// XXX: We do this for days, because there is a varying number of days
// per month
- int oldDay = iterator.monthDay;
+ int oldDay = iterator.getDay();
generated.set(iterator); // just using generated as a temporary.
int n = 1;
while (true) {
int value = freqAmount * n;
switch (freqField) {
case Time.SECOND:
- iterator.second += value;
- break;
case Time.MINUTE:
- iterator.minute += value;
- break;
case Time.HOUR:
- iterator.hour += value;
- break;
case Time.MONTH_DAY:
- iterator.monthDay += value;
- break;
case Time.MONTH:
- iterator.month += value;
- break;
case Time.YEAR:
- iterator.year += value;
- break;
case Time.WEEK_DAY:
- iterator.monthDay += value;
- break;
case Time.YEAR_DAY:
- iterator.monthDay += value;
+ iterator.add(freqField, value);
break;
default:
throw new RuntimeException("bad field=" + freqField);
@@ -1102,7 +1087,7 @@ bysetpos:
if (freqField != Time.YEAR && freqField != Time.MONTH) {
break;
}
- if (iterator.monthDay == oldDay) {
+ if (iterator.getDay() == oldDay) {
break;
}
n++;
@@ -1135,12 +1120,12 @@ bysetpos:
* This method does not modify the fields isDst, or gmtOff.
*/
static void unsafeNormalize(Time date) {
- int second = date.second;
- int minute = date.minute;
- int hour = date.hour;
- int monthDay = date.monthDay;
- int month = date.month;
- int year = date.year;
+ int second = date.getSecond();
+ int minute = date.getMinute();
+ int hour = date.getHour();
+ int monthDay = date.getDay();
+ int month = date.getMonth();
+ int year = date.getYear();
int addMinutes = ((second < 0) ? (second - 59) : second) / 60;
second -= addMinutes * 60;
@@ -1201,14 +1186,14 @@ bysetpos:
// At this point, monthDay <= the length of the current month and is
// in the range [1,31].
- date.second = second;
- date.minute = minute;
- date.hour = hour;
- date.monthDay = monthDay;
- date.month = month;
- date.year = year;
- date.weekDay = weekDay(year, month, monthDay);
- date.yearDay = yearDay(year, month, monthDay);
+ date.setSecond(second);
+ date.setMinute(minute);
+ date.setHour(hour);
+ date.setDay(monthDay);
+ date.setMonth(month);
+ date.setYear(year);
+ date.setWeekDay(weekDay(year, month, monthDay));
+ date.setYearDay(yearDay(year, month, monthDay));
}
/**
@@ -1299,17 +1284,17 @@ bysetpos:
private static final long normDateTimeComparisonValue(Time normalized) {
// 37 bits for the year, 4 bits for the month, 5 bits for the monthDay,
// 5 bits for the hour, 6 bits for the minute, 6 bits for the second.
- return ((long)normalized.year << 26) + (normalized.month << 22)
- + (normalized.monthDay << 17) + (normalized.hour << 12)
- + (normalized.minute << 6) + normalized.second;
+ return ((long)normalized.getYear() << 26) + (normalized.getMonth() << 22)
+ + (normalized.getDay() << 17) + (normalized.getHour() << 12)
+ + (normalized.getMinute() << 6) + normalized.getSecond();
}
private static final void setTimeFromLongValue(Time date, long val) {
- date.year = (int) (val >> 26);
- date.month = (int) (val >> 22) & 0xf;
- date.monthDay = (int) (val >> 17) & 0x1f;
- date.hour = (int) (val >> 12) & 0x1f;
- date.minute = (int) (val >> 6) & 0x3f;
- date.second = (int) (val & 0x3f);
+ date.setYear((int) (val >> 26));
+ date.setMonth((int) (val >> 22) & 0xf);
+ date.setDay((int) (val >> 17) & 0x1f);
+ date.setHour((int) (val >> 12) & 0x1f);
+ date.setMinute((int) (val >> 6) & 0x3f);
+ date.setSecond((int) (val & 0x3f));
}
}
diff --git a/src/com/android/calendarcommon2/RecurrenceSet.java b/src/com/android/calendarcommon2/RecurrenceSet.java
index 6c153db..e42c0e9 100644
--- a/src/com/android/calendarcommon2/RecurrenceSet.java
+++ b/src/com/android/calendarcommon2/RecurrenceSet.java
@@ -21,7 +21,6 @@ import android.database.Cursor;
import android.provider.CalendarContract;
import android.text.TextUtils;
import android.util.Log;
-import android.util.TimeFormatException;
import java.util.ArrayList;
import java.util.List;
@@ -161,14 +160,14 @@ public class RecurrenceSet {
// The timezone is updated to UTC if the time string specified 'Z'.
try {
time.parse(rawDates[i]);
- } catch (TimeFormatException e) {
+ } catch (IllegalArgumentException e) {
throw new EventRecurrence.InvalidFormatException(
- "TimeFormatException thrown when parsing time " + rawDates[i]
+ "IllegalArgumentException thrown when parsing time " + rawDates[i]
+ " in recurrence " + recurrence);
}
- dates[i] = time.toMillis(false /* use isDst */);
- time.timezone = tz;
+ dates[i] = time.toMillis();
+ time.setTimezone(tz);
}
return dates;
}
@@ -195,8 +194,9 @@ public class RecurrenceSet {
// NOTE: the timezone may be null, if this is a floating time.
String tzid = tzidParam == null ? null : tzidParam.value;
Time start = new Time(tzidParam == null ? Time.TIMEZONE_UTC : tzid);
- boolean inUtc = start.parse(dtstart);
- boolean allDay = start.allDay;
+ start.parse(dtstart);
+ boolean inUtc = dtstart.length() == 16 && dtstart.charAt(15) == 'Z';
+ boolean allDay = start.isAllDay();
// We force TimeZone to UTC for "all day recurring events" as the server is sending no
// TimeZone in DTSTART for them
@@ -223,9 +223,9 @@ public class RecurrenceSet {
}
if (allDay) {
- start.timezone = Time.TIMEZONE_UTC;
+ start.setTimezone(Time.TIMEZONE_UTC);
}
- long millis = start.toMillis(false /* use isDst */);
+ long millis = start.toMillis();
values.put(CalendarContract.Events.DTSTART, millis);
if (millis == -1) {
if (false) {
@@ -242,7 +242,7 @@ public class RecurrenceSet {
values.put(CalendarContract.Events.DURATION, duration);
values.put(CalendarContract.Events.ALL_DAY, allDay ? 1 : 0);
return true;
- } catch (TimeFormatException e) {
+ } catch (IllegalArgumentException e) {
// Something is wrong with the format of this event
Log.i(TAG,"Failed to parse event: " + component.toString());
return false;
@@ -300,10 +300,10 @@ public class RecurrenceSet {
// TODO: android.pim.Time really should take care of this for us.
if (allDay) {
dtstartProp.addParameter(new ICalendar.Parameter("VALUE", "DATE"));
- dtstartTime.allDay = true;
- dtstartTime.hour = 0;
- dtstartTime.minute = 0;
- dtstartTime.second = 0;
+ dtstartTime.setAllDay(true);
+ dtstartTime.setHour(0);
+ dtstartTime.setMinute(0);
+ dtstartTime.setSecond(0);
}
dtstartProp.setValue(dtstartTime.format2445());
@@ -359,10 +359,10 @@ public static boolean populateComponent(ContentValues values,
// TODO: android.pim.Time really should take care of this for us.
if (allDay) {
dtstartProp.addParameter(new ICalendar.Parameter("VALUE", "DATE"));
- dtstartTime.allDay = true;
- dtstartTime.hour = 0;
- dtstartTime.minute = 0;
- dtstartTime.second = 0;
+ dtstartTime.setAllDay(true);
+ dtstartTime.setHour(0);
+ dtstartTime.setMinute(0);
+ dtstartTime.setSecond(0);
}
dtstartProp.setValue(dtstartTime.format2445());
@@ -479,14 +479,13 @@ public static boolean populateComponent(ContentValues values,
ICalendar.Parameter endTzidParameter =
dtendProperty.getFirstParameter("TZID");
String endTzid = (endTzidParameter == null)
- ? start.timezone : endTzidParameter.value;
+ ? start.getTimezone() : endTzidParameter.value;
Time end = new Time(endTzid);
end.parse(dtendProperty.getValue());
- long durationMillis = end.toMillis(false /* use isDst */)
- - start.toMillis(false /* use isDst */);
+ long durationMillis = end.toMillis() - start.toMillis();
long durationSeconds = (durationMillis / 1000);
- if (start.allDay && (durationSeconds % 86400) == 0) {
+ if (start.isAllDay() && (durationSeconds % 86400) == 0) {
return "P" + (durationSeconds / 86400) + "D"; // Server wants this instead of P86400S
} else {
return "P" + durationSeconds + "S";
diff --git a/src/com/android/calendarcommon2/Time.java b/src/com/android/calendarcommon2/Time.java
index 08189cc..f0af248 100644
--- a/src/com/android/calendarcommon2/Time.java
+++ b/src/com/android/calendarcommon2/Time.java
@@ -15,6 +15,12 @@
*/
package com.android.calendarcommon2;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+import java.util.TimeZone;
+
/**
* Helper class to make migration out of android.text.format.Time smoother.
*/
@@ -22,124 +28,155 @@ public class Time {
public static final String TIMEZONE_UTC = "UTC";
+ private static final int EPOCH_JULIAN_DAY = 2440588;
+ private static final long HOUR_IN_MILLIS = 60 * 60 * 1000;
+ private static final long DAY_IN_MILLIS = 24 * HOUR_IN_MILLIS;
+
+ private static final String FORMAT_ALL_DAY_PATTERN = "yyyyMMdd";
+ private static final String FORMAT_TIME_PATTERN = "yyyyMMdd'T'HHmmss";
+ private static final String FORMAT_TIME_UTC_PATTERN = "yyyyMMdd'T'HHmmss'Z'";
+ private static final String FORMAT_LOG_TIME_PATTERN = "EEE, MMM dd, yyyy hh:mm a";
+
/*
* Define symbolic constants for accessing the fields in this class. Used in
* getActualMaximum().
*/
- public static final int SECOND = android.text.format.Time.SECOND;
- public static final int MINUTE = android.text.format.Time.MINUTE;
- public static final int HOUR = android.text.format.Time.HOUR;
- public static final int MONTH_DAY = android.text.format.Time.MONTH_DAY;
- public static final int MONTH = android.text.format.Time.MONTH;
- public static final int YEAR = android.text.format.Time.YEAR;
- public static final int WEEK_DAY = android.text.format.Time.WEEK_DAY;
- public static final int YEAR_DAY = android.text.format.Time.YEAR_DAY;
- public static final int WEEK_NUM = android.text.format.Time.WEEK_NUM;
-
- public static final int SUNDAY = android.text.format.Time.SUNDAY;
- public static final int MONDAY = android.text.format.Time.MONDAY;
- public static final int TUESDAY = android.text.format.Time.TUESDAY;
- public static final int WEDNESDAY = android.text.format.Time.WEDNESDAY;
- public static final int THURSDAY = android.text.format.Time.THURSDAY;
- public static final int FRIDAY = android.text.format.Time.FRIDAY;
- public static final int SATURDAY = android.text.format.Time.SATURDAY;
-
- private final android.text.format.Time mInstance;
-
- public int year;
- public int month;
- public int hour;
- public int minute;
- public int second;
-
- public int yearDay;
- public int monthDay;
- public int weekDay;
-
- public String timezone;
- public int isDst;
- public long gmtoff;
- public boolean allDay;
+ public static final int SECOND = 1;
+ public static final int MINUTE = 2;
+ public static final int HOUR = 3;
+ public static final int MONTH_DAY = 4;
+ public static final int MONTH = 5;
+ public static final int YEAR = 6;
+ public static final int WEEK_DAY = 7;
+ public static final int YEAR_DAY = 8;
+ public static final int WEEK_NUM = 9;
+
+ public static final int SUNDAY = 0;
+ public static final int MONDAY = 1;
+ public static final int TUESDAY = 2;
+ public static final int WEDNESDAY = 3;
+ public static final int THURSDAY = 4;
+ public static final int FRIDAY = 5;
+ public static final int SATURDAY = 6;
+
+ private final GregorianCalendar mCalendar;
+
+ private int year;
+ private int month;
+ private int monthDay;
+ private int hour;
+ private int minute;
+ private int second;
+
+ private int yearDay;
+ private int weekDay;
+
+ private String timezone;
+ private boolean allDay;
+
+ /**
+ * Enabling this flag will apply appropriate dst transition logic when calling either
+ * {@code toMillis()} or {@code normalize()} and their respective *ApplyDst() equivalents. <br>
+ * When this flag is enabled, the following calls would be considered equivalent:
+ * <ul>
+ * <li>{@code a.t.f.Time#normalize(true)} and {@code #normalize()}</li>
+ * <li>{@code a.t.f.Time#toMillis(true)} and {@code #toMillis()}</li>
+ * <li>{@code a.t.f.Time#normalize(false)} and {@code #normalizeApplyDst()}</li>
+ * <li>{@code a.t.f.Time#toMillis(false)} and {@code #toMillisApplyDst()}</li>
+ * </ul>
+ * When the flag is disabled, both {@code toMillis()} and {@code normalize()} will ignore any
+ * dst transitions unless minutes or hours were added to the time (the default behavior of the
+ * a.t.f.Time class). <br>
+ *
+ * NOTE: currently, this flag is disabled because there are no direct manipulations of the day,
+ * hour, or minute fields. All of the accesses are correctly done via setters and they rely on
+ * a private normalize call in their respective classes to achieve their expected behavior.
+ * Additionally, using any of the {@code #set()} methods or {@code #parse()} will result in
+ * normalizing by ignoring DST, which is what the default behavior is for the a.t.f.Time class.
+ */
+ static final boolean APPLY_DST_CHANGE_LOGIC = false;
+ private int mDstChangedByField = -1;
public Time() {
- mInstance = new android.text.format.Time();
- readFields();
+ this(TimeZone.getDefault().getID());
}
public Time(String timezone) {
- mInstance = new android.text.format.Time(timezone);
- readFields();
+ if (timezone == null) {
+ throw new NullPointerException("timezone cannot be null.");
+ }
+ this.timezone = timezone;
+ // Although the process's default locale is used here, #clear() will explicitly set the
+ // first day of the week to MONDAY to match with the expected a.t.f.Time implementation.
+ mCalendar = new GregorianCalendar(getTimeZone(), Locale.getDefault());
+ clear(this.timezone);
}
- private void readFields() {
- year = mInstance.year;
- month = mInstance.month;
- hour = mInstance.hour;
- minute = mInstance.minute;
- second = mInstance.second;
-
- yearDay = mInstance.yearDay;
- monthDay = mInstance.monthDay;
- weekDay = mInstance.weekDay;
-
- timezone = mInstance.timezone;
- isDst = mInstance.isDst;
- gmtoff = mInstance.gmtoff;
- allDay = mInstance.allDay;
+ private void readFieldsFromCalendar() {
+ year = mCalendar.get(Calendar.YEAR);
+ month = mCalendar.get(Calendar.MONTH);
+ monthDay = mCalendar.get(Calendar.DAY_OF_MONTH);
+ hour = mCalendar.get(Calendar.HOUR_OF_DAY);
+ minute = mCalendar.get(Calendar.MINUTE);
+ second = mCalendar.get(Calendar.SECOND);
}
- private void writeFields() {
- mInstance.year = year;
- mInstance.month = month;
- mInstance.hour = hour;
- mInstance.minute = minute;
- mInstance.second = second;
+ private void writeFieldsToCalendar() {
+ clearCalendar();
+ mCalendar.set(year, month, monthDay, hour, minute, second);
+ mCalendar.set(Calendar.MILLISECOND, 0);
+ }
- mInstance.yearDay = yearDay;
- mInstance.monthDay = monthDay;
- mInstance.weekDay = weekDay;
+ private boolean isInDst() {
+ return mCalendar.getTimeZone().inDaylightTime(mCalendar.getTime());
+ }
- mInstance.timezone = timezone;
- mInstance.isDst = isDst;
- mInstance.gmtoff = gmtoff;
- mInstance.allDay = allDay;
+ public void add(int field, int amount) {
+ final boolean wasDstBefore = isInDst();
+ mCalendar.add(getCalendarField(field), amount);
+ if (APPLY_DST_CHANGE_LOGIC && wasDstBefore != isInDst()
+ && (field == MONTH_DAY || field == HOUR || field == MINUTE)) {
+ mDstChangedByField = field;
+ }
}
public void set(long millis) {
- writeFields();
- mInstance.set(millis);
- readFields();
+ clearCalendar();
+ mCalendar.setTimeInMillis(millis);
+ readFieldsFromCalendar();
}
public void set(Time other) {
- other.writeFields();
- mInstance.set(other.mInstance);
- readFields();
+ clearCalendar();
+ mCalendar.setTimeZone(other.getTimeZone());
+ mCalendar.setTimeInMillis(other.mCalendar.getTimeInMillis());
+ readFieldsFromCalendar();
}
public void set(int day, int month, int year) {
- writeFields();
- mInstance.set(day, month, year);
- readFields();
+ clearCalendar();
+ mCalendar.set(year, month, day);
+ readFieldsFromCalendar();
}
public void set(int second, int minute, int hour, int day, int month, int year) {
- writeFields();
- mInstance.set(second, minute, hour, day, month, year);
- readFields();
- }
-
- public void setToNow() {
- writeFields();
- mInstance.setToNow();
- readFields();
+ clearCalendar();
+ mCalendar.set(year, month, day, hour, minute, second);
+ readFieldsFromCalendar();
}
public long setJulianDay(int julianDay) {
- writeFields();
- final long ms = mInstance.setJulianDay(julianDay);
- readFields();
- return ms;
+ long millis = (julianDay - EPOCH_JULIAN_DAY) * DAY_IN_MILLIS;
+ mCalendar.setTimeInMillis(millis);
+ readFieldsFromCalendar();
+
+ // adjust day approximation, set the time to 12am, and re-normalize
+ monthDay += julianDay - getJulianDay(millis, getGmtOffset());
+ hour = 0;
+ minute = 0;
+ second = 0;
+ writeFieldsToCalendar();
+ return normalize();
}
public static int getJulianDay(long begin, long gmtOff) {
@@ -147,72 +184,349 @@ public class Time {
}
public int getWeekNumber() {
- writeFields();
- final int num = mInstance.getWeekNumber();
- readFields();
- return num;
+ return mCalendar.get(Calendar.WEEK_OF_YEAR);
+ }
+
+ private int getCalendarField(int field) {
+ switch (field) {
+ case SECOND: return Calendar.SECOND;
+ case MINUTE: return Calendar.MINUTE;
+ case HOUR: return Calendar.HOUR_OF_DAY;
+ case MONTH_DAY: return Calendar.DAY_OF_MONTH;
+ case MONTH: return Calendar.MONTH;
+ case YEAR: return Calendar.YEAR;
+ case WEEK_DAY: return Calendar.DAY_OF_WEEK;
+ case YEAR_DAY: return Calendar.DAY_OF_YEAR;
+ case WEEK_NUM: return Calendar.WEEK_OF_YEAR;
+ default:
+ throw new RuntimeException("bad field=" + field);
+ }
}
public int getActualMaximum(int field) {
- writeFields();
- return mInstance.getActualMaximum(field);
+ return mCalendar.getActualMaximum(getCalendarField(field));
}
public void switchTimezone(String timezone) {
- writeFields();
- mInstance.switchTimezone(timezone);
- readFields();
+ long msBefore = mCalendar.getTimeInMillis();
+ mCalendar.setTimeZone(TimeZone.getTimeZone(timezone));
+ mCalendar.setTimeInMillis(msBefore);
+ mDstChangedByField = -1;
+ readFieldsFromCalendar();
}
- public long normalize(boolean ignoreDst) {
- writeFields();
- final long ms = mInstance.normalize(ignoreDst);
- readFields();
+ /**
+ * @param apply whether to apply dst logic on the ms or not; if apply is true, it is equivalent
+ * to calling the normalize or toMillis APIs in a.t.f.Time with ignoreDst=false
+ */
+ private long getDstAdjustedMillis(boolean apply, long ms) {
+ if (APPLY_DST_CHANGE_LOGIC) {
+ if (apply && mDstChangedByField == MONTH_DAY) {
+ return isInDst() ? (ms + HOUR_IN_MILLIS) : (ms - HOUR_IN_MILLIS);
+ } else if (!apply && (mDstChangedByField == HOUR || mDstChangedByField == MINUTE)) {
+ return isInDst() ? (ms - HOUR_IN_MILLIS) : (ms + HOUR_IN_MILLIS);
+ }
+ }
return ms;
}
- public boolean parse(String time) {
- writeFields();
- boolean success = mInstance.parse(time);
- readFields();
- return success;
+ private long normalizeInternal() {
+ final long ms = mCalendar.getTimeInMillis();
+ readFieldsFromCalendar();
+ return ms;
}
- public boolean parse3339(String time) {
- writeFields();
- boolean success = mInstance.parse3339(time);
- readFields();
- return success;
+ public long normalize() {
+ return getDstAdjustedMillis(false, normalizeInternal());
}
- public String format(String format) {
- writeFields();
- return (new android.text.format.Time(mInstance)).format(format);
+ long normalizeApplyDst() {
+ return getDstAdjustedMillis(true, normalizeInternal());
+ }
+
+ public void parse(String time) {
+ if (time == null) {
+ throw new NullPointerException("time string is null");
+ }
+ parseInternal(time);
+ writeFieldsToCalendar();
}
public String format2445() {
- writeFields();
- return (new android.text.format.Time(mInstance)).format2445();
+ writeFieldsToCalendar();
+ final SimpleDateFormat sdf = new SimpleDateFormat(
+ allDay ? FORMAT_ALL_DAY_PATTERN
+ : (TIMEZONE_UTC.equals(getTimezone()) ? FORMAT_TIME_UTC_PATTERN
+ : FORMAT_TIME_PATTERN));
+ sdf.setTimeZone(getTimeZone());
+ return sdf.format(mCalendar.getTime());
}
- public String format3339(boolean allDay) {
- writeFields();
- return (new android.text.format.Time(mInstance)).format3339(allDay);
+ public long toMillis() {
+ return getDstAdjustedMillis(false, mCalendar.getTimeInMillis());
+ }
+
+ long toMillisApplyDst() {
+ return getDstAdjustedMillis(true, mCalendar.getTimeInMillis());
}
- public long toMillis(boolean ignoreDst) {
- writeFields();
- return mInstance.toMillis(ignoreDst);
+ private TimeZone getTimeZone() {
+ return timezone != null ? TimeZone.getTimeZone(timezone) : TimeZone.getDefault();
}
- public static int compare(Time a, Time b) {
- a.writeFields();
- b.writeFields();
- return android.text.format.Time.compare(a.mInstance, b.mInstance);
+ public int compareTo(Time other) {
+ return mCalendar.compareTo(other.mCalendar);
+ }
+
+ private void clearCalendar() {
+ mDstChangedByField = -1;
+ mCalendar.clear();
+ mCalendar.set(Calendar.HOUR_OF_DAY, 0); // HOUR_OF_DAY doesn't get reset with #clear
+ mCalendar.setTimeZone(getTimeZone());
+ // set fields for week number computation according to ISO 8601.
+ mCalendar.setFirstDayOfWeek(Calendar.MONDAY);
+ mCalendar.setMinimalDaysInFirstWeek(4);
}
public void clear(String timezoneId) {
- mInstance.clear(timezoneId);
- readFields();
+ clearCalendar();
+ readFieldsFromCalendar();
+ setTimezone(timezoneId);
+ }
+
+ public int getYear() {
+ return mCalendar.get(Calendar.YEAR);
+ }
+
+ public void setYear(int year) {
+ this.year = year;
+ mCalendar.set(Calendar.YEAR, year);
+ }
+
+ public int getMonth() {
+ return mCalendar.get(Calendar.MONTH);
+ }
+
+ public void setMonth(int month) {
+ this.month = month;
+ mCalendar.set(Calendar.MONTH, month);
+ }
+
+ public int getDay() {
+ return mCalendar.get(Calendar.DAY_OF_MONTH);
+ }
+
+ public void setDay(int day) {
+ this.monthDay = day;
+ mCalendar.set(Calendar.DAY_OF_MONTH, day);
+ }
+
+ public int getHour() {
+ return mCalendar.get(Calendar.HOUR_OF_DAY);
+ }
+
+ public void setHour(int hour) {
+ this.hour = hour;
+ mCalendar.set(Calendar.HOUR_OF_DAY, hour);
+ }
+
+ public int getMinute() {
+ return mCalendar.get(Calendar.MINUTE);
+ }
+
+ public void setMinute(int minute) {
+ this.minute = minute;
+ mCalendar.set(Calendar.MINUTE, minute);
+ }
+
+ public int getSecond() {
+ return mCalendar.get(Calendar.SECOND);
+ }
+
+ public void setSecond(int second) {
+ this.second = second;
+ mCalendar.set(Calendar.SECOND, second);
+ }
+
+ public String getTimezone() {
+ return mCalendar.getTimeZone().getID();
+ }
+
+ public void setTimezone(String timezone) {
+ this.timezone = timezone;
+ mCalendar.setTimeZone(getTimeZone());
+ }
+
+ public int getYearDay() {
+ // yearDay in a.t.f.Time's implementation starts from 0, whereas Calendar's starts from 1.
+ return mCalendar.get(Calendar.DAY_OF_YEAR) - 1;
+ }
+
+ public void setYearDay(int yearDay) {
+ this.yearDay = yearDay;
+ // yearDay in a.t.f.Time's implementation starts from 0, whereas Calendar's starts from 1.
+ mCalendar.set(Calendar.DAY_OF_YEAR, yearDay + 1);
+ }
+
+ public int getWeekDay() {
+ // weekDay in a.t.f.Time's implementation starts from 0, whereas Calendar's starts from 1.
+ return mCalendar.get(Calendar.DAY_OF_WEEK) - 1;
+ }
+
+ public void setWeekDay(int weekDay) {
+ this.weekDay = weekDay;
+ // weekDay in a.t.f.Time's implementation starts from 0, whereas Calendar's starts from 1.
+ mCalendar.set(Calendar.DAY_OF_WEEK, weekDay + 1);
+ }
+
+ public boolean isAllDay() {
+ return allDay;
+ }
+
+ public void setAllDay(boolean allDay) {
+ this.allDay = allDay;
+ }
+
+ public long getGmtOffset() {
+ return mCalendar.getTimeZone().getOffset(mCalendar.getTimeInMillis()) / 1000;
+ }
+
+ private void parseInternal(String s) {
+ int len = s.length();
+ if (len < 8) {
+ throw new IllegalArgumentException("String is too short: \"" + s +
+ "\" Expected at least 8 characters.");
+ } else if (len > 8 && len < 15) {
+ throw new IllegalArgumentException("String is too short: \"" + s
+ + "\" If there are more than 8 characters there must be at least 15.");
+ }
+
+ // year
+ int n = getChar(s, 0, 1000);
+ n += getChar(s, 1, 100);
+ n += getChar(s, 2, 10);
+ n += getChar(s, 3, 1);
+ year = n;
+
+ // month
+ n = getChar(s, 4, 10);
+ n += getChar(s, 5, 1);
+ n--;
+ month = n;
+
+ // day of month
+ n = getChar(s, 6, 10);
+ n += getChar(s, 7, 1);
+ monthDay = n;
+
+ if (len > 8) {
+ checkChar(s, 8, 'T');
+ allDay = false;
+
+ // hour
+ n = getChar(s, 9, 10);
+ n += getChar(s, 10, 1);
+ hour = n;
+
+ // min
+ n = getChar(s, 11, 10);
+ n += getChar(s, 12, 1);
+ minute = n;
+
+ // sec
+ n = getChar(s, 13, 10);
+ n += getChar(s, 14, 1);
+ second = n;
+
+ if (len > 15) {
+ // Z
+ checkChar(s, 15, 'Z');
+ timezone = TIMEZONE_UTC;
+ }
+ } else {
+ allDay = true;
+ hour = 0;
+ minute = 0;
+ second = 0;
+ }
+
+ weekDay = 0;
+ yearDay = 0;
+ }
+
+ private void checkChar(String s, int spos, char expected) {
+ final char c = s.charAt(spos);
+ if (c != expected) {
+ throw new IllegalArgumentException(String.format(
+ "Unexpected character 0x%02d at pos=%d. Expected 0x%02d (\'%c\').",
+ (int) c, spos, (int) expected, expected));
+ }
+ }
+
+ private int getChar(String s, int spos, int mul) {
+ final char c = s.charAt(spos);
+ if (Character.isDigit(c)) {
+ return Character.getNumericValue(c) * mul;
+ } else {
+ throw new IllegalArgumentException("Parse error at pos=" + spos);
+ }
+ }
+
+ // NOTE: only used for outputting time to error logs
+ public String format() {
+ final SimpleDateFormat sdf =
+ new SimpleDateFormat(FORMAT_LOG_TIME_PATTERN, Locale.getDefault());
+ return sdf.format(mCalendar.getTime());
+ }
+
+ // NOTE: only used in tests
+ public boolean parse3339(String time) {
+ android.text.format.Time tmp = generateInstance();
+ boolean success = tmp.parse3339(time);
+ copyAndWriteInstance(tmp);
+ return success;
+ }
+
+ // NOTE: only used in tests
+ public String format3339(boolean allDay) {
+ return generateInstance().format3339(allDay);
+ }
+
+ private android.text.format.Time generateInstance() {
+ android.text.format.Time tmp = new android.text.format.Time(timezone);
+ tmp.set(second, minute, hour, monthDay, month, year);
+
+ tmp.yearDay = yearDay;
+ tmp.weekDay = weekDay;
+
+ tmp.timezone = timezone;
+ tmp.gmtoff = getGmtOffset();
+ tmp.allDay = allDay;
+ tmp.set(mCalendar.getTimeInMillis());
+ if (tmp.allDay && (tmp.hour != 0 || tmp.minute != 0 || tmp.second != 0)) {
+ // Time SDK expects hour, minute, second to be 0 if allDay is true
+ tmp.hour = 0;
+ tmp.minute = 0;
+ tmp.second = 0;
+ }
+
+ return tmp;
+ }
+
+ private void copyAndWriteInstance(android.text.format.Time time) {
+ year = time.year;
+ month = time.month;
+ monthDay = time.monthDay;
+ hour = time.hour;
+ minute = time.minute;
+ second = time.second;
+
+ yearDay = time.yearDay;
+ weekDay = time.weekDay;
+
+ timezone = time.timezone;
+ allDay = time.allDay;
+
+ writeFieldsToCalendar();
}
}
diff --git a/tests/src/com/android/calendarcommon2/RRuleTest.java b/tests/src/com/android/calendarcommon2/RRuleTest.java
index a473263..18217a3 100644
--- a/tests/src/com/android/calendarcommon2/RRuleTest.java
+++ b/tests/src/com/android/calendarcommon2/RRuleTest.java
@@ -114,8 +114,7 @@ public class RRuleTest extends TestCase {
RecurrenceProcessor rp = new RecurrenceProcessor();
RecurrenceSet recur = new RecurrenceSet(rrule, rdate, exrule, exdate);
- long[] out = rp.expand(dtstart, recur, rangeStart.toMillis(false /* use isDst */),
- rangeEnd.toMillis(false /* use isDst */));
+ long[] out = rp.expand(dtstart, recur, rangeStart.toMillis(), rangeEnd.toMillis());
if (METHOD_TRACE) {
Debug.stopMethodTracing();
diff --git a/tests/src/com/android/calendarcommon2/RecurrenceProcessorTest.java b/tests/src/com/android/calendarcommon2/RecurrenceProcessorTest.java
index 0687ec4..3cd9177 100644
--- a/tests/src/com/android/calendarcommon2/RecurrenceProcessorTest.java
+++ b/tests/src/com/android/calendarcommon2/RecurrenceProcessorTest.java
@@ -23,7 +23,6 @@ import android.test.suitebuilder.annotation.MediumTest;
import android.test.suitebuilder.annotation.SmallTest;
import android.text.TextUtils;
import android.util.Log;
-import android.util.TimeFormatException;
import junit.framework.TestCase;
import java.util.TreeSet;
@@ -105,8 +104,7 @@ public class RecurrenceProcessorTest extends TestCase {
RecurrenceProcessor rp = new RecurrenceProcessor();
RecurrenceSet recur = new RecurrenceSet(rrule, rdate, exrule, exdate);
- long[] out = rp.expand(dtstart, recur, rangeStart.toMillis(false /* use isDst */),
- rangeEnd.toMillis(false /* use isDst */));
+ long[] out = rp.expand(dtstart, recur, rangeStart.toMillis(), rangeEnd.toMillis());
if (METHOD_TRACE) {
Debug.stopMethodTracing();
@@ -149,12 +147,12 @@ public class RecurrenceProcessorTest extends TestCase {
if (lastOccur != -1) {
outCal.set(lastOccur);
lastStr = outCal.format2445();
- lastMillis = outCal.toMillis(true /* ignore isDst */);
+ lastMillis = outCal.toMillis();
}
if (last != null && last.length() > 0) {
Time expectedLast = new Time(tz);
expectedLast.parse(last);
- expectedMillis = expectedLast.toMillis(true /* ignore isDst */);
+ expectedMillis = expectedLast.toMillis();
}
if (lastMillis != expectedMillis) {
if (SPEW) {
@@ -597,7 +595,7 @@ public class RecurrenceProcessorTest extends TestCase {
"20060219T100000"
}, "20060220T020001");
fail("Bad UNTIL string failed to throw exception");
- } catch (TimeFormatException e) {
+ } catch (IllegalArgumentException e) {
// expected
}
}
@@ -2459,8 +2457,8 @@ public class RecurrenceProcessorTest extends TestCase {
dtstart.parse("20010101T000000");
rangeStart.parse("20010101T000000");
rangeEnd.parse("20090101T000000");
- long rangeStartMillis = rangeStart.toMillis(false /* use isDst */);
- long rangeEndMillis = rangeEnd.toMillis(false /* use isDst */);
+ long rangeStartMillis = rangeStart.toMillis();
+ long rangeEndMillis = rangeEnd.toMillis();
long startTime = System.currentTimeMillis();
for (int iterations = 0; iterations < 5; iterations++) {
@@ -2503,12 +2501,12 @@ public class RecurrenceProcessorTest extends TestCase {
long startTime = System.currentTimeMillis();
for (int i = 0; i < ITERATIONS; i++) {
- date.month += 1;
- date.monthDay += 100;
- date.normalize(true);
- date.month -= 1;
- date.monthDay -= 100;
- date.normalize(true);
+ date.add(Time.MONTH, 1);
+ date.add(Time.MONTH_DAY, 100);
+ date.normalize();
+ date.add(Time.MONTH, -1);
+ date.add(Time.MONTH_DAY, -100);
+ date.normalize();
}
long endTime = System.currentTimeMillis();
@@ -2520,11 +2518,11 @@ public class RecurrenceProcessorTest extends TestCase {
date.parse("20090404T100000");
startTime = System.currentTimeMillis();
for (int i = 0; i < ITERATIONS; i++) {
- date.month += 1;
- date.monthDay += 100;
+ date.add(Time.MONTH, 1);
+ date.add(Time.MONTH_DAY, 100);
RecurrenceProcessor.unsafeNormalize(date);
- date.month -= 1;
- date.monthDay -= 100;
+ date.add(Time.MONTH, -1);
+ date.add(Time.MONTH_DAY, -100);
RecurrenceProcessor.unsafeNormalize(date);
}
diff --git a/tests/src/com/android/calendarcommon2/TimeTest.java b/tests/src/com/android/calendarcommon2/TimeTest.java
index 600506a..df27c4f 100644
--- a/tests/src/com/android/calendarcommon2/TimeTest.java
+++ b/tests/src/com/android/calendarcommon2/TimeTest.java
@@ -29,9 +29,19 @@ import junit.framework.TestCase;
public class TimeTest extends TestCase {
@SmallTest
+ public void testNullTimezone() {
+ try {
+ Time t = new Time(null);
+ fail("expected a null timezone to throw an exception.");
+ } catch (NullPointerException npe) {
+ // expected.
+ }
+ }
+
+ @SmallTest
public void testTimezone() {
Time t = new Time(Time.TIMEZONE_UTC);
- assertEquals(Time.TIMEZONE_UTC, t.timezone);
+ assertEquals(Time.TIMEZONE_UTC, t.getTimezone());
}
@SmallTest
@@ -39,23 +49,40 @@ public class TimeTest extends TestCase {
Time t = new Time(Time.TIMEZONE_UTC);
String newTimezone = "America/Los_Angeles";
t.switchTimezone(newTimezone);
- assertEquals(newTimezone, t.timezone);
+ assertEquals(newTimezone, t.getTimezone());
}
@SmallTest
public void testGetActualMaximum() {
Time t = new Time(Time.TIMEZONE_UTC);
- t.set(0, 0, 2020);
+ t.set(1, 0, 2020);
assertEquals(59, t.getActualMaximum(Time.SECOND));
assertEquals(59, t.getActualMaximum(Time.MINUTE));
assertEquals(23, t.getActualMaximum(Time.HOUR));
assertEquals(31, t.getActualMaximum(Time.MONTH_DAY));
assertEquals(11, t.getActualMaximum(Time.MONTH));
- assertEquals(2037, t.getActualMaximum(Time.YEAR));
- assertEquals(6, t.getActualMaximum(Time.WEEK_DAY));
- assertEquals(365, t.getActualMaximum(Time.YEAR_DAY)); // 2020 is a leap year
- t.set(0, 0, 2019);
- assertEquals(364, t.getActualMaximum(Time.YEAR_DAY));
+ assertEquals(7, t.getActualMaximum(Time.WEEK_DAY));
+ assertEquals(366, t.getActualMaximum(Time.YEAR_DAY)); // 2020 is a leap year
+ t.set(1, 0, 2019);
+ assertEquals(365, t.getActualMaximum(Time.YEAR_DAY));
+ }
+
+ @SmallTest
+ public void testAdd() {
+ Time t = new Time(Time.TIMEZONE_UTC);
+ t.set(0, 0, 0, 1, 0, 2020);
+ t.add(Time.SECOND, 1);
+ assertEquals(1, t.getSecond());
+ t.add(Time.MINUTE, 1);
+ assertEquals(1, t.getMinute());
+ t.add(Time.HOUR, 1);
+ assertEquals(1, t.getHour());
+ t.add(Time.MONTH_DAY, 1);
+ assertEquals(2, t.getDay());
+ t.add(Time.MONTH, 1);
+ assertEquals(1, t.getMonth());
+ t.add(Time.YEAR, 1);
+ assertEquals(2021, t.getYear());
}
@SmallTest
@@ -63,37 +90,40 @@ public class TimeTest extends TestCase {
Time t = new Time(Time.TIMEZONE_UTC);
t.clear(Time.TIMEZONE_UTC);
- assertEquals(Time.TIMEZONE_UTC, t.timezone);
- assertFalse(t.allDay);
- assertEquals(0, t.second);
- assertEquals(0, t.minute);
- assertEquals(0, t.hour);
- assertEquals(0, t.monthDay);
- assertEquals(0, t.month);
- assertEquals(0, t.year);
- assertEquals(0, t.weekDay);
- assertEquals(0, t.yearDay);
- assertEquals(0, t.gmtoff);
- assertEquals(-1, t.isDst);
+ assertEquals(Time.TIMEZONE_UTC, t.getTimezone());
+ assertFalse(t.isAllDay());
+ assertEquals(0, t.getSecond());
+ assertEquals(0, t.getMinute());
+ assertEquals(0, t.getHour());
+ assertEquals(1, t.getDay()); // default for Calendar is 1
+ assertEquals(0, t.getMonth());
+ assertEquals(1970, t.getYear());
+ assertEquals(4, t.getWeekDay()); // 1970 Jan 1 --> Thursday
+ assertEquals(0, t.getYearDay());
+ assertEquals(0, t.getGmtOffset());
}
@SmallTest
public void testCompare() {
Time a = new Time(Time.TIMEZONE_UTC);
Time b = new Time("America/Los_Angeles");
- assertTrue(Time.compare(a, b) < 0);
- }
+ assertTrue(a.compareTo(b) < 0);
- @SmallTest
- public void testFormat() {
- Time t = new Time(Time.TIMEZONE_UTC);
- assertEquals("19700101T000000", t.format("%Y%m%dT%H%M%S"));
+ Time c = new Time("Asia/Calcutta");
+ assertTrue(a.compareTo(c) > 0);
+
+ Time d = new Time(Time.TIMEZONE_UTC);
+ assertEquals(0, a.compareTo(d));
}
@SmallTest
public void testFormat2445() {
- Time t = new Time(Time.TIMEZONE_UTC);
+ Time t = new Time();
+ assertEquals("19700101T000000", t.format2445());
+ t.setTimezone(Time.TIMEZONE_UTC);
assertEquals("19700101T000000Z", t.format2445());
+ t.setAllDay(true);
+ assertEquals("19700101", t.format2445());
}
@SmallTest
@@ -105,26 +135,63 @@ public class TimeTest extends TestCase {
}
@SmallTest
- public void testMillis0() {
+ public void testToMillis() {
Time t = new Time(Time.TIMEZONE_UTC);
+ t.set(1, 0, 0, 1, 0, 1970);
+ assertEquals(1000L, t.toMillis());
+
t.set(0, 0, 0, 1, 1, 2020);
- assertEquals(1580515200000L, t.toMillis(true));
+ assertEquals(1580515200000L, t.toMillis());
t.set(1, 0, 0, 1, 1, 2020);
- assertEquals(1580515201000L, t.toMillis(true));
+ assertEquals(1580515201000L, t.toMillis());
+
+ t.set(1, 0, 2020);
+ assertEquals(1577836800000L, t.toMillis());
+ t.set(1, 1, 2020);
+ assertEquals(1580515200000L, t.toMillis());
}
@SmallTest
- public void testMillis1() {
+ public void testToMillis_overflow() {
Time t = new Time(Time.TIMEZONE_UTC);
- t.set(1, 0, 0, 1, 0, 1970);
- assertEquals(1000L, t.toMillis(true));
+ t.set(32, 0, 2020);
+ assertEquals(1580515200000L, t.toMillis());
+ assertEquals(1, t.getDay());
+ assertEquals(1, t.getMonth());
}
@SmallTest
public void testParse() {
Time t = new Time(Time.TIMEZONE_UTC);
- assertTrue(t.parse("20201010T160000Z"));
- assertFalse(t.parse("12345678T901234"));
+ t.parse("20201010T160000Z");
+ assertEquals(2020, t.getYear());
+ assertEquals(9, t.getMonth());
+ assertEquals(10, t.getDay());
+ assertEquals(16, t.getHour());
+ assertEquals(0, t.getMinute());
+ assertEquals(0, t.getSecond());
+
+ t.parse("20200220");
+ assertEquals(2020, t.getYear());
+ assertEquals(1, t.getMonth());
+ assertEquals(20, t.getDay());
+ assertEquals(0, t.getHour());
+ assertEquals(0, t.getMinute());
+ assertEquals(0, t.getSecond());
+
+ try {
+ t.parse("invalid");
+ fail();
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+
+ try {
+ t.parse("20201010Z160000");
+ fail();
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
}
@SmallTest
@@ -132,51 +199,58 @@ public class TimeTest extends TestCase {
Time t = new Time(Time.TIMEZONE_UTC);
t.parse3339("1980-05-23");
- if (!t.allDay || t.year != 1980 || t.month != 4 || t.monthDay != 23) {
+ if (!t.isAllDay() || t.getYear() != 1980 || t.getMonth() != 4 || t.getDay() != 23) {
fail("Did not parse all-day date correctly");
}
t.parse3339("1980-05-23T09:50:50");
- if (t.allDay || t.year != 1980 || t.month != 4 || t.monthDay != 23 || t.hour != 9
- || t.minute != 50 || t.second != 50 || t.gmtoff != 0) {
+ if (t.isAllDay() || t.getYear() != 1980 || t.getMonth() != 4 || t.getDay() != 23
+ || t.getHour() != 9 || t.getMinute() != 50 || t.getSecond() != 50
+ || t.getGmtOffset() != 0) {
fail("Did not parse timezone-offset-less date correctly");
}
t.parse3339("1980-05-23T09:50:50Z");
- if (t.allDay || t.year != 1980 || t.month != 4 || t.monthDay != 23 || t.hour != 9
- || t.minute != 50 || t.second != 50 || t.gmtoff != 0) {
+ if (t.isAllDay() || t.getYear() != 1980 || t.getMonth() != 4 || t.getDay() != 23
+ || t.getHour() != 9 || t.getMinute() != 50 || t.getSecond() != 50
+ || t.getGmtOffset() != 0) {
fail("Did not parse UTC date correctly");
}
t.parse3339("1980-05-23T09:50:50.0Z");
- if (t.allDay || t.year != 1980 || t.month != 4 || t.monthDay != 23 || t.hour != 9
- || t.minute != 50 || t.second != 50 || t.gmtoff != 0) {
+ if (t.isAllDay() || t.getYear() != 1980 || t.getMonth() != 4 || t.getDay() != 23
+ || t.getHour() != 9 || t.getMinute() != 50 || t.getSecond() != 50
+ || t.getGmtOffset() != 0) {
fail("Did not parse UTC date correctly");
}
t.parse3339("1980-05-23T09:50:50.12Z");
- if (t.allDay || t.year != 1980 || t.month != 4 || t.monthDay != 23 || t.hour != 9
- || t.minute != 50 || t.second != 50 || t.gmtoff != 0) {
+ if (t.isAllDay() || t.getYear() != 1980 || t.getMonth() != 4 || t.getDay() != 23
+ || t.getHour() != 9 || t.getMinute() != 50 || t.getSecond() != 50
+ || t.getGmtOffset() != 0) {
fail("Did not parse UTC date correctly");
}
t.parse3339("1980-05-23T09:50:50.123Z");
- if (t.allDay || t.year != 1980 || t.month != 4 || t.monthDay != 23 || t.hour != 9
- || t.minute != 50 || t.second != 50 || t.gmtoff != 0) {
+ if (t.isAllDay() || t.getYear() != 1980 || t.getMonth() != 4 || t.getDay() != 23
+ || t.getHour() != 9 || t.getMinute() != 50 || t.getSecond() != 50
+ || t.getGmtOffset() != 0) {
fail("Did not parse UTC date correctly");
}
// the time should be normalized to UTC
t.parse3339("1980-05-23T09:50:50-01:05");
- if (t.allDay || t.year != 1980 || t.month != 4 || t.monthDay != 23 || t.hour != 10
- || t.minute != 55 || t.second != 50 || t.gmtoff != 0) {
+ if (t.isAllDay() || t.getYear() != 1980 || t.getMonth() != 4 || t.getDay() != 23
+ || t.getHour() != 10 || t.getMinute() != 55 || t.getSecond() != 50
+ || t.getGmtOffset() != 0) {
fail("Did not parse timezone-offset date correctly");
}
// the time should be normalized to UTC
t.parse3339("1980-05-23T09:50:50.123-01:05");
- if (t.allDay || t.year != 1980 || t.month != 4 || t.monthDay != 23 || t.hour != 10
- || t.minute != 55 || t.second != 50 || t.gmtoff != 0) {
+ if (t.isAllDay() || t.getYear() != 1980 || t.getMonth() != 4 || t.getDay() != 23
+ || t.getHour() != 10 || t.getMinute() != 55 || t.getSecond() != 50
+ || t.getGmtOffset() != 0) {
fail("Did not parse timezone-offset date correctly");
}
@@ -203,74 +277,93 @@ public class TimeTest extends TestCase {
}
@SmallTest
- public void testSet0() {
+ public void testSet_millis() {
Time t = new Time(Time.TIMEZONE_UTC);
t.set(1000L);
- assertEquals(1970, t.year);
- assertEquals(1, t.second);
+ assertEquals(1970, t.getYear());
+ assertEquals(1, t.getSecond());
t.set(2000L);
- assertEquals(2, t.second);
- assertEquals(0, t.minute);
+ assertEquals(2, t.getSecond());
+ assertEquals(0, t.getMinute());
t.set(1000L * 60);
- assertEquals(1, t.minute);
- assertEquals(0, t.hour);
+ assertEquals(1, t.getMinute());
+ assertEquals(0, t.getHour());
t.set(1000L * 60 * 60);
- assertEquals(1, t.hour);
- assertEquals(1, t.monthDay);
+ assertEquals(1, t.getHour());
+ assertEquals(1, t.getDay());
t.set((1000L * 60 * 60 * 24) + 1000L);
- assertEquals(2, t.monthDay);
- assertEquals(1970, t.year);
+ assertEquals(2, t.getDay());
+ assertEquals(1970, t.getYear());
}
@SmallTest
- public void testSet1() {
+ public void testSet_dayMonthYear() {
Time t = new Time(Time.TIMEZONE_UTC);
t.set(1, 2, 2021);
- assertEquals(1, t.monthDay);
- assertEquals(2, t.month);
- assertEquals(2021, t.year);
+ assertEquals(1, t.getDay());
+ assertEquals(2, t.getMonth());
+ assertEquals(2021, t.getYear());
}
@SmallTest
- public void testSet2() {
+ public void testSet_secondMinuteHour() {
Time t = new Time(Time.TIMEZONE_UTC);
t.set(1, 2, 3, 4, 5, 2021);
- assertEquals(1, t.second);
- assertEquals(2, t.minute);
- assertEquals(3, t.hour);
- assertEquals(4, t.monthDay);
- assertEquals(5, t.month);
- assertEquals(2021, t.year);
+ assertEquals(1, t.getSecond());
+ assertEquals(2, t.getMinute());
+ assertEquals(3, t.getHour());
+ assertEquals(4, t.getDay());
+ assertEquals(5, t.getMonth());
+ assertEquals(2021, t.getYear());
+ }
+
+ @SmallTest
+ public void testSet_overflow() {
+ // Jan 32nd --> Feb 1st
+ Time t = new Time(Time.TIMEZONE_UTC);
+ t.set(32, 0, 2020);
+ assertEquals(1, t.getDay());
+ assertEquals(1, t.getMonth());
+ assertEquals(2020, t.getYear());
+
+ t = new Time(Time.TIMEZONE_UTC);
+ t.set(5, 10, 15, 32, 0, 2020);
+ assertEquals(5, t.getSecond());
+ assertEquals(10, t.getMinute());
+ assertEquals(15, t.getHour());
+ assertEquals(1, t.getDay());
+ assertEquals(1, t.getMonth());
+ assertEquals(2020, t.getYear());
}
@SmallTest
- public void testSet3() {
+ public void testSet_other() {
Time t = new Time(Time.TIMEZONE_UTC);
t.set(1, 2, 3, 4, 5, 2021);
Time t2 = new Time();
t2.set(t);
- assertEquals(Time.TIMEZONE_UTC, t2.timezone);
- assertEquals(1, t2.second);
- assertEquals(2, t2.minute);
- assertEquals(3, t2.hour);
- assertEquals(4, t2.monthDay);
- assertEquals(5, t2.month);
- assertEquals(2021, t2.year);
+ assertEquals(Time.TIMEZONE_UTC, t2.getTimezone());
+ assertEquals(1, t2.getSecond());
+ assertEquals(2, t2.getMinute());
+ assertEquals(3, t2.getHour());
+ assertEquals(4, t2.getDay());
+ assertEquals(5, t2.getMonth());
+ assertEquals(2021, t2.getYear());
}
@SmallTest
public void testSetToNow() {
- Time t = new Time(Time.TIMEZONE_UTC);
- t.setToNow();
- long ms = t.toMillis(true);
long now = System.currentTimeMillis();
- // ensure millis returned are within 1 second of when they were set
- assertTrue(ms < now && ms > (now - 1000));
+ Time t = new Time(Time.TIMEZONE_UTC);
+ t.set(now);
+ long ms = t.toMillis();
+ // ensure time is within 1 second because of rounding errors
+ assertTrue("now: " + now + "; actual: " + ms, Math.abs(ms - now) < 1000);
}
@SmallTest
@@ -280,6 +373,14 @@ public class TimeTest extends TestCase {
assertEquals(1, t.getWeekNumber());
t.set(1, 1, 2020);
assertEquals(5, t.getWeekNumber());
+
+ // ensure ISO 8601 standards are met: weeks start on Monday and the first week has at least
+ // 4 days in it (the year's first Thursday or Jan 4th)
+ for (int i = 1; i <= 8; i++) {
+ t.set(i, 0, 2020);
+ // Jan 6th is the first Monday in 2020 so that would be week 2
+ assertEquals(i < 6 ? 1 : 2, t.getWeekNumber());
+ }
}
private static class DateTest {
@@ -288,7 +389,6 @@ public class TimeTest extends TestCase {
public int day1;
public int hour1;
public int minute1;
- public int dst1;
public int offset;
@@ -297,7 +397,6 @@ public class TimeTest extends TestCase {
public int day2;
public int hour2;
public int minute2;
- public int dst2;
public DateTest(int year1, int month1, int day1, int hour1, int minute1,
int offset, int year2, int month2, int day2, int hour2, int minute2) {
@@ -306,49 +405,30 @@ public class TimeTest extends TestCase {
this.day1 = day1;
this.hour1 = hour1;
this.minute1 = minute1;
- this.dst1 = -1;
this.offset = offset;
this.year2 = year2;
this.month2 = month2;
this.day2 = day2;
this.hour2 = hour2;
this.minute2 = minute2;
- this.dst2 = -1;
- }
-
- public DateTest(int year1, int month1, int day1, int hour1, int minute1, int dst1,
- int offset, int year2, int month2, int day2, int hour2, int minute2,
- int dst2) {
- this.year1 = year1;
- this.month1 = month1;
- this.day1 = day1;
- this.hour1 = hour1;
- this.minute1 = minute1;
- this.dst1 = dst1;
- this.offset = offset;
- this.year2 = year2;
- this.month2 = month2;
- this.day2 = day2;
- this.hour2 = hour2;
- this.minute2 = minute2;
- this.dst2 = dst2;
}
public boolean equals(Time time) {
- return time.year == year2 && time.month == month2 && time.monthDay == day2
- && time.hour == hour2 && time.minute == minute2;
+ return time.getYear() == year2 && time.getMonth() == month2 && time.getDay() == day2
+ && time.getHour() == hour2 && time.getMinute() == minute2;
}
+ }
- public boolean equalsWithDst(Time time) {
- return time.year == year2 && time.month == month2 && time.monthDay == day2
- && time.hour == hour2 && time.minute == minute2 && time.isDst == dst2;
- }
+ @SmallTest
+ public void testNormalize() {
+ Time t = new Time(Time.TIMEZONE_UTC);
+ t.parse("20060432T010203");
+ assertEquals(1146531723000L, t.normalize());
}
/* These tests assume that DST changes on Nov 4, 2007 at 2am (to 1am). */
// The "offset" field in "dayTests" represents days.
- // Use normalize(true) with these tests to change the date by 1 day.
// Note: the month numbers are 0-relative, so Jan=0, Feb=1,...Dec=11
private DateTest[] dayTests = {
// Nov 4, 12am + 0 day = Nov 4, 12am
@@ -376,11 +456,12 @@ public class TimeTest extends TestCase {
};
// The "offset" field in "minuteTests" represents minutes.
- // Use normalize(false) with these tests.
// Note: the month numbers are 0-relative, so Jan=0, Feb=1,...Dec=11
private DateTest[] minuteTests = {
// Nov 4, 12am + 0 minutes = Nov 4, 12am
new DateTest(2007, 10, 4, 0, 0, 0, 2007, 10, 4, 0, 0),
+ // Nov 4, 12am + 60 minutes = Nov 4, 1am
+ new DateTest(2007, 10, 4, 0, 0, 60, 2007, 10, 4, 1, 0),
// Nov 5, 12am + 0 minutes = Nov 5, 12am
new DateTest(2007, 10, 5, 0, 0, 0, 2007, 10, 5, 0, 0),
// Nov 3, 12am + 60 minutes = Nov 3, 1am
@@ -391,20 +472,24 @@ public class TimeTest extends TestCase {
new DateTest(2007, 10, 5, 0, 0, 60, 2007, 10, 5, 1, 0),
// Nov 3, 1am + 60 minutes = Nov 3, 2am
new DateTest(2007, 10, 3, 1, 0, 60, 2007, 10, 3, 2, 0),
+ // Nov 4, 12:59am (PDT) + 2 minutes = Nov 4, 1:01am (PDT)
+ new DateTest(2007, 10, 4, 0, 59, 2, 2007, 10, 4, 1, 1),
+ // Nov 4, 12:59am (PDT) + 62 minutes = Nov 4, 1:01am (PST)
+ new DateTest(2007, 10, 4, 0, 59, 62, 2007, 10, 4, 1, 1),
+ // Nov 4, 12:30am (PDT) + 120 minutes = Nov 4, 1:30am (PST)
+ new DateTest(2007, 10, 4, 0, 30, 120, 2007, 10, 4, 1, 30),
+ // Nov 4, 12:30am (PDT) + 90 minutes = Nov 4, 1:00am (PST)
+ new DateTest(2007, 10, 4, 0, 30, 90, 2007, 10, 4, 1, 0),
// Nov 4, 1am (PDT) + 30 minutes = Nov 4, 1:30am (PDT)
- new DateTest(2007, 10, 4, 1, 0, 1, 30, 2007, 10, 4, 1, 30, 1),
- // Nov 4, 1am (PDT) + 60 minutes = Nov 4, 1am (PST)
- new DateTest(2007, 10, 4, 1, 0, 1, 60, 2007, 10, 4, 1, 0, 0),
+ new DateTest(2007, 10, 4, 1, 0, 30, 2007, 10, 4, 1, 30),
// Nov 4, 1:30am (PDT) + 15 minutes = Nov 4, 1:45am (PDT)
- new DateTest(2007, 10, 4, 1, 30, 1, 15, 2007, 10, 4, 1, 45, 1),
- // Nov 4, 1:30am (PDT) + 30 minutes = Nov 4, 1:00am (PST)
- new DateTest(2007, 10, 4, 1, 30, 1, 30, 2007, 10, 4, 1, 0, 0),
- // Nov 4, 1:30am (PDT) + 60 minutes = Nov 4, 1:30am (PST)
- new DateTest(2007, 10, 4, 1, 30, 1, 60, 2007, 10, 4, 1, 30, 0),
+ new DateTest(2007, 10, 4, 1, 30, 15, 2007, 10, 4, 1, 45),
+ // Mar 11, 1:30am (PST) + 30 minutes = Mar 11, 3am (PDT)
+ new DateTest(2007, 2, 11, 1, 30, 30, 2007, 2, 11, 3, 0),
// Nov 4, 1:30am (PST) + 15 minutes = Nov 4, 1:45am (PST)
- new DateTest(2007, 10, 4, 1, 30, 0, 15, 2007, 10, 4, 1, 45, 0),
+ new DateTest(2007, 10, 4, 1, 30, 15, 2007, 10, 4, 1, 45),
// Nov 4, 1:30am (PST) + 30 minutes = Nov 4, 2:00am (PST)
- new DateTest(2007, 10, 4, 1, 30, 0, 30, 2007, 10, 4, 2, 0, 0),
+ new DateTest(2007, 10, 4, 1, 30, 30, 2007, 10, 4, 2, 0),
// Nov 5, 1am + 60 minutes = Nov 5, 2am
new DateTest(2007, 10, 5, 1, 0, 60, 2007, 10, 5, 2, 0),
// Nov 3, 2am + 60 minutes = Nov 3, 3am
@@ -415,46 +500,35 @@ public class TimeTest extends TestCase {
new DateTest(2007, 10, 4, 2, 0, 60, 2007, 10, 4, 3, 0),
// Nov 5, 2am + 60 minutes = Nov 5, 3am
new DateTest(2007, 10, 5, 2, 0, 60, 2007, 10, 5, 3, 0),
+ // NOTE: Calendar assumes 1am PDT == 1am PST, the two are not distinct, hence why the transition boundary itself has no tests
};
- @SmallTest
- public void testNormalize0() {
- Time t = new Time(Time.TIMEZONE_UTC);
- t.parse("20060432T010203");
- assertEquals(1146531723000L, t.normalize(false));
- }
-
@MediumTest
- public void testNormalize1() {
+ public void testNormalize_dst() {
Time local = new Time("America/Los_Angeles");
int len = dayTests.length;
for (int index = 0; index < len; index++) {
DateTest test = dayTests[index];
local.set(0, test.minute1, test.hour1, test.day1, test.month1, test.year1);
- // call normalize() to make sure that isDst is set
- local.normalize(false);
- local.monthDay += test.offset;
- local.normalize(true);
+ local.add(Time.MONTH_DAY, test.offset);
if (!test.equals(local)) {
String expectedTime = String.format("%d-%02d-%02d %02d:%02d",
test.year2, test.month2, test.day2, test.hour2, test.minute2);
String actualTime = String.format("%d-%02d-%02d %02d:%02d",
- local.year, local.month, local.monthDay, local.hour, local.minute);
+ local.getYear(), local.getMonth(), local.getDay(), local.getHour(),
+ local.getMinute());
fail("Expected: " + expectedTime + "; Actual: " + actualTime);
}
local.set(0, test.minute1, test.hour1, test.day1, test.month1, test.year1);
- // call normalize() to make sure that isDst is set
- local.normalize(false);
- local.monthDay += test.offset;
- long millis = local.toMillis(true);
- local.set(millis);
+ local.add(Time.MONTH_DAY, test.offset);
if (!test.equals(local)) {
String expectedTime = String.format("%d-%02d-%02d %02d:%02d",
test.year2, test.month2, test.day2, test.hour2, test.minute2);
String actualTime = String.format("%d-%02d-%02d %02d:%02d",
- local.year, local.month, local.monthDay, local.hour, local.minute);
+ local.getYear(), local.getMonth(), local.getDay(), local.getHour(),
+ local.getMinute());
fail("Expected: " + expectedTime + "; Actual: " + actualTime);
}
}
@@ -463,40 +537,109 @@ public class TimeTest extends TestCase {
for (int index = 0; index < len; index++) {
DateTest test = minuteTests[index];
local.set(0, test.minute1, test.hour1, test.day1, test.month1, test.year1);
- local.isDst = test.dst1;
- // call normalize() to make sure that isDst is set
- local.normalize(false);
- if (test.dst2 == -1) test.dst2 = local.isDst;
- local.minute += test.offset;
- local.normalize(false);
- if (!test.equalsWithDst(local)) {
- String expectedTime = String.format("%d-%02d-%02d %02d:%02d isDst: %d",
- test.year2, test.month2, test.day2, test.hour2, test.minute2, test.dst2);
- String actualTime = String.format("%d-%02d-%02d %02d:%02d isDst: %d",
- local.year, local.month, local.monthDay, local.hour, local.minute,
- local.isDst);
+ local.add(Time.MINUTE, test.offset);
+ if (!test.equals(local)) {
+ String expectedTime = String.format("%d-%02d-%02d %02d:%02d",
+ test.year2, test.month2, test.day2, test.hour2, test.minute2);
+ String actualTime = String.format("%d-%02d-%02d %02d:%02d",
+ local.getYear(), local.getMonth(), local.getDay(), local.getHour(),
+ local.getMinute());
fail("Expected: " + expectedTime + "; Actual: " + actualTime);
}
local.set(0, test.minute1, test.hour1, test.day1, test.month1, test.year1);
- local.isDst = test.dst1;
- // call normalize() to make sure that isDst is set
- local.normalize(false);
- if (test.dst2 == -1) test.dst2 = local.isDst;
- local.minute += test.offset;
- long millis = local.toMillis(false);
- local.set(millis);
- if (!test.equalsWithDst(local)) {
- String expectedTime = String.format("%d-%02d-%02d %02d:%02d isDst: %d",
- test.year2, test.month2, test.day2, test.hour2, test.minute2, test.dst2);
- String actualTime = String.format("%d-%02d-%02d %02d:%02d isDst: %d",
- local.year, local.month, local.monthDay, local.hour, local.minute,
- local.isDst);
+ local.add(Time.MINUTE, test.offset);
+ if (!test.equals(local)) {
+ String expectedTime = String.format("%d-%02d-%02d %02d:%02d",
+ test.year2, test.month2, test.day2, test.hour2, test.minute2);
+ String actualTime = String.format("%d-%02d-%02d %02d:%02d",
+ local.getYear(), local.getMonth(), local.getDay(), local.getHour(),
+ local.getMinute());
fail("Expected: " + expectedTime + "; Actual: " + actualTime);
}
}
}
+ @SmallTest
+ public void testNormalize_overflow() {
+ Time t = new Time(Time.TIMEZONE_UTC);
+ t.set(32, 0, 2020);
+ t.normalize();
+ assertEquals(1, t.getDay());
+ assertEquals(1, t.getMonth());
+ }
+
+ @SmallTest
+ public void testDstBehavior_addDays_ignoreDst() {
+ Time time = new Time("America/Los_Angeles");
+ time.set(4, 10, 2007); // set to Nov 4, 2007, 12am
+ assertEquals(1194159600000L, time.normalize());
+ time.add(Time.MONTH_DAY, 1); // changes to Nov 5, 2007, 12am
+ assertEquals(1194249600000L, time.toMillis());
+
+ time = new Time("America/Los_Angeles");
+ time.set(11, 2, 2007); // set to Mar 11, 2007, 12am
+ assertEquals(1173600000000L, time.normalize());
+ time.add(Time.MONTH_DAY, 1); // changes to Mar 12, 2007, 12am
+ assertEquals(1173682800000L, time.toMillis());
+ }
+
+ @SmallTest
+ public void testDstBehavior_addDays_applyDst() {
+ if (!Time.APPLY_DST_CHANGE_LOGIC) {
+ return;
+ }
+ Time time = new Time("America/Los_Angeles");
+ time.set(4, 10, 2007); // set to Nov 4, 2007, 12am
+ assertEquals(1194159600000L, time.normalizeApplyDst());
+ time.add(Time.MONTH_DAY, 1); // changes to Nov 4, 2007, 11pm (fall back)
+ assertEquals(1194246000000L, time.toMillisApplyDst());
+
+ time = new Time("America/Los_Angeles");
+ time.set(11, 2, 2007); // set to Mar 11, 2007, 12am
+ assertEquals(1173600000000L, time.normalizeApplyDst());
+ time.add(Time.MONTH_DAY, 1); // changes to Mar 12, 2007, 1am (roll forward)
+ assertEquals(1173686400000L, time.toMillisApplyDst());
+ }
+
+ @SmallTest
+ public void testDstBehavior_addHours_ignoreDst() {
+ // Note: by default, Calendar applies DST logic if adding hours or minutes but not if adding
+ // days, hence in this test, only if the APPLY_DST_CHANGE_LOGIC flag is false, then the time
+ // is adjusted with DST
+ Time time = new Time("America/Los_Angeles");
+ time.set(4, 10, 2007); // set to Nov 4, 2007, 12am
+ assertEquals(1194159600000L, time.normalize());
+ time.add(Time.HOUR, 24); // changes to Nov 5, 2007, 12am
+ assertEquals(Time.APPLY_DST_CHANGE_LOGIC ? 1194249600000L : 1194246000000L,
+ time.toMillis());
+
+ time = new Time("America/Los_Angeles");
+ time.set(11, 2, 2007); // set to Mar 11, 2007, 12am
+ assertEquals(1173600000000L, time.normalize());
+ time.add(Time.HOUR, 24); // changes to Mar 12, 2007, 12am
+ assertEquals(Time.APPLY_DST_CHANGE_LOGIC ? 1173682800000L : 1173686400000L,
+ time.toMillis());
+ }
+
+ @SmallTest
+ public void testDstBehavior_addHours_applyDst() {
+ if (!Time.APPLY_DST_CHANGE_LOGIC) {
+ return;
+ }
+ Time time = new Time("America/Los_Angeles");
+ time.set(4, 10, 2007); // set to Nov 4, 2007, 12am
+ assertEquals(1194159600000L, time.normalizeApplyDst());
+ time.add(Time.HOUR, 24); // changes to Nov 4, 2007, 11pm (fall back)
+ assertEquals(1194246000000L, time.toMillisApplyDst());
+
+ time = new Time("America/Los_Angeles");
+ time.set(11, 2, 2007); // set to Mar 11, 2007, 12am
+ assertEquals(1173600000000L, time.normalizeApplyDst());
+ time.add(Time.HOUR, 24); // changes to Mar 12, 2007, 1am (roll forward)
+ assertEquals(1173686400000L, time.toMillisApplyDst());
+ }
+
// Timezones that cover the world.
// Some GMT offsets occur more than once in case some cities decide to change their GMT offset.
private static final String[] mTimeZones = {
@@ -562,32 +705,33 @@ public class TimeTest extends TestCase {
@MediumTest
public void testGetJulianDay() {
- Time time = new Time();
-
- // for a random day in the year 2020 and for a random timezone, get the Julian day for 12am
- // and then check that if we change the time we get the same Julian day.
- int monthDay = (int) (Math.random() * 365) + 1;
- int zoneIndex = (int) (Math.random() * mTimeZones.length);
- time.set(0, 0, 0, monthDay, 0, 2020);
- time.timezone = mTimeZones[zoneIndex];
- long millis = time.normalize(true);
-
- int julianDay = Time.getJulianDay(millis, time.gmtoff);
-
- // change the time during the day and check that we get the same Julian day.
- for (int hour = 0; hour < 24; hour++) {
- for (int minute = 0; minute < 60; minute += 15) {
- time.set(0, minute, hour, monthDay, 0, 2020);
- millis = time.normalize(true);
- int day = Time.getJulianDay(millis, time.gmtoff);
- assertEquals(day, julianDay);
+ Time time = new Time(Time.TIMEZONE_UTC);
+
+ // for 30 random days in the year 2020 and for a random timezone, get the Julian day for
+ // 12am and then check that if we change the time we get the same Julian day.
+ for (int i = 0; i < 30; i++) {
+ int monthDay = (int) (Math.random() * 365) + 1;
+ int zoneIndex = (int) (Math.random() * mTimeZones.length);
+ time.setTimezone(mTimeZones[zoneIndex]);
+ time.set(0, 0, 0, monthDay, 0, 2020);
+
+ int julianDay = Time.getJulianDay(time.normalize(), time.getGmtOffset());
+
+ // change the time during the day and check that we get the same Julian day.
+ for (int hour = 0; hour < 24; hour++) {
+ for (int minute = 0; minute < 60; minute += 15) {
+ time.set(0, minute, hour, monthDay, 0, 2020);
+ int day = Time.getJulianDay(time.normalize(), time.getGmtOffset());
+ assertEquals(day, julianDay);
+ time.clear(Time.TIMEZONE_UTC);
+ }
}
}
}
@MediumTest
public void testSetJulianDay() {
- Time time = new Time();
+ Time time = new Time(Time.TIMEZONE_UTC);
// for each day in the year 2020, pick a random timezone, and verify that we can
// set the Julian day correctly.
@@ -596,22 +740,23 @@ public class TimeTest extends TestCase {
// leave the "month" as zero because we are changing the "monthDay" from 1 to 366.
// the call to normalize() will then change the "month" (but we don't really care).
time.set(0, 0, 0, monthDay, 0, 2020);
- time.timezone = mTimeZones[zoneIndex];
- long millis = time.normalize(true);
- int julianDay = Time.getJulianDay(millis, time.gmtoff);
+ time.setTimezone(mTimeZones[zoneIndex]);
+ long millis = time.normalize();
+ int julianDay = Time.getJulianDay(millis, time.getGmtOffset());
time.setJulianDay(julianDay);
// some places change daylight saving time at 12am and so there is no 12am on some days
// in some timezones - in those cases, the time is set to 1am.
// some examples: Africa/Cairo, America/Sao_Paulo, Atlantic/Azores
- assertTrue(time.hour == 0 || time.hour == 1);
- assertEquals(0, time.minute);
- assertEquals(0, time.second);
+ assertTrue(time.getHour() == 0 || time.getHour() == 1);
+ assertEquals(0, time.getMinute());
+ assertEquals(0, time.getSecond());
- millis = time.toMillis(false);
- int day = Time.getJulianDay(millis, time.gmtoff);
+ millis = time.toMillis();
+ int day = Time.getJulianDay(millis, time.getGmtOffset());
assertEquals(day, julianDay);
+ time.clear(Time.TIMEZONE_UTC);
}
}
}