aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVarun Shah <varunshah@google.com>2020-09-14 08:27:48 -0700
committerVarun Shah <varunshah@google.com>2020-10-26 23:34:49 -0700
commit7bb1f3b0ebe44e28fbd3e34da7e6995d0c7a2d82 (patch)
tree93cd82bab57f67d200ad32e5eb7bd92a0000a781
parenta2910ddf2e208436c4716f71efdf3efb792aff0b (diff)
downloadcalendar-7bb1f3b0ebe44e28fbd3e34da7e6995d0c7a2d82.tar.gz
Migrate Time class to use Calendar APIs.
Migrate the internal Time class from using the android.text.format.Time APIs to using the java.util.Calendar APIs. Note: The dst flag was removed in this CL because all member variables were made private and can only be accessed by getters and setters. This prevents unexpected behavior to occur around the dst transition border with the current usages of #normalize and #toMillis. See APPLY_DST_CHANGE_LOGIC flag documentation for more information. Bug: 159019762 Test: atest TimeTest Test: atest CalendarCommonTests Test: atest CalendarProviderTests Test: atest CtsCalendarProviderTestCases Test: atest CtsCalendarcommon2TestCases Change-Id: I31562d335a23b1f96888c121937b12daf0af3325
-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);
}
}
}