diff options
author | Neil Fuller <nfuller@google.com> | 2014-07-22 16:13:26 +0000 |
---|---|---|
committer | Android Git Automerger <android-git-automerger@android.com> | 2014-07-22 16:13:26 +0000 |
commit | 5a5f8ca870c1b28093be9e4a61399da56c27e7c2 (patch) | |
tree | a7ca9d3a79a6dfc09d2f9b4a30d4222537f1a8a4 /core | |
parent | c3e6b211f6893c9c930cfc8e41eb29fd9136c2ee (diff) | |
parent | cc7c4f882435e6971450e0bdb6a335f312af704d (diff) | |
download | frameworks_base-5a5f8ca870c1b28093be9e4a61399da56c27e7c2.zip frameworks_base-5a5f8ca870c1b28093be9e4a61399da56c27e7c2.tar.gz frameworks_base-5a5f8ca870c1b28093be9e4a61399da56c27e7c2.tar.bz2 |
am 2b49b6af: am 1a429047: am fc6346d4: Merge "Rewriting android.text.format.Time without the native _tz functions"
* commit '2b49b6afa60724c8ad0383e732b116fffa6b1758':
Rewriting android.text.format.Time without the native _tz functions
Diffstat (limited to 'core')
-rw-r--r-- | core/java/android/text/format/Time.java | 677 | ||||
-rw-r--r-- | core/java/android/text/format/TimeFormatter.java | 519 | ||||
-rw-r--r-- | core/java/android/util/TimeFormatException.java | 6 | ||||
-rw-r--r-- | core/jni/Android.mk | 2 | ||||
-rw-r--r-- | core/jni/AndroidRuntime.cpp | 2 | ||||
-rw-r--r-- | core/jni/Time.cpp | 199 | ||||
-rw-r--r-- | core/jni/TimeUtils.h | 89 | ||||
-rw-r--r-- | core/jni/android_text_format_Time.cpp | 689 |
8 files changed, 1086 insertions, 1097 deletions
diff --git a/core/java/android/text/format/Time.java b/core/java/android/text/format/Time.java index f34e746..aa6ad20 100644 --- a/core/java/android/text/format/Time.java +++ b/core/java/android/text/format/Time.java @@ -16,19 +16,38 @@ package android.text.format; -import android.content.res.Resources; +import android.util.TimeFormatException; +import java.io.IOException; import java.util.Locale; import java.util.TimeZone; -import libcore.icu.LocaleData; +import libcore.util.ZoneInfo; +import libcore.util.ZoneInfoDB; /** * An alternative to the {@link java.util.Calendar} and * {@link java.util.GregorianCalendar} classes. An instance of the Time class represents * a moment in time, specified with second precision. It is modelled after - * struct tm, and in fact, uses struct tm to implement most of the - * functionality. + * struct tm. This class is not thread-safe and does not consider leap seconds. + * + * <p>This class has a number of issues and it is recommended that + * {@link java.util.GregorianCalendar} is used instead. + * + * <p>Known issues: + * <ul> + * <li>For historical reasons when performing time calculations all arithmetic currently takes + * place using 32-bit integers. This limits the reliable time range representable from 1902 + * until 2037.See the wikipedia article on the + * <a href="http://en.wikipedia.org/wiki/Year_2038_problem">Year 2038 problem</a> for details. + * Do not rely on this behavior; it may change in the future. + * </li> + * <li>Calling {@link #switchTimezone(String)} on a date that cannot exist, such as a wall time + * that was skipped due to a DST transition, will result in a date in 1969 (i.e. -1, or 1 second + * before 1st Jan 1970 UTC).</li> + * <li>Much of the formatting / parsing assumes ASCII text and is therefore not suitable for + * use with non-ASCII scripts.</li> + * </ul> */ public class Time { private static final String Y_M_D_T_H_M_S_000 = "%Y-%m-%dT%H:%M:%S.000"; @@ -106,7 +125,7 @@ public class Time { public int isDst; /** - * Offset from UTC (in seconds). + * Offset in seconds from UTC including any DST offset. */ public long gmtoff; @@ -137,41 +156,20 @@ public class Time { public static final int FRIDAY = 5; public static final int SATURDAY = 6; - /* - * The Locale for which date formatting strings have been loaded. - */ - private static Locale sLocale; - private static String[] sShortMonths; - private static String[] sLongMonths; - private static String[] sLongStandaloneMonths; - private static String[] sShortWeekdays; - private static String[] sLongWeekdays; - private static String sTimeOnlyFormat; - private static String sDateOnlyFormat; - private static String sDateTimeFormat; - private static String sAm; - private static String sPm; - private static char sZeroDigit; - - // Referenced by native code. - private static String sDateCommand = "%a %b %e %H:%M:%S %Z %Y"; + // An object that is reused for date calculations. + private TimeCalculator calculator; /** * Construct a Time object in the timezone named by the string * argument "timezone". The time is initialized to Jan 1, 1970. - * @param timezone string containing the timezone to use. + * @param timezoneId string containing the timezone to use. * @see TimeZone */ - public Time(String timezone) { - if (timezone == null) { - throw new NullPointerException("timezone is null!"); + public Time(String timezoneId) { + if (timezoneId == null) { + throw new NullPointerException("timezoneId is null!"); } - this.timezone = timezone; - this.year = 1970; - this.monthDay = 1; - // Set the daylight-saving indicator to the unknown value -1 so that - // it will be recomputed. - this.isDst = -1; + initialize(timezoneId); } /** @@ -179,7 +177,7 @@ public class Time { * Jan 1, 1970. */ public Time() { - this(TimeZone.getDefault().getID()); + initialize(TimeZone.getDefault().getID()); } /** @@ -189,9 +187,23 @@ public class Time { * @param other */ public Time(Time other) { + initialize(other.timezone); set(other); } + /** Initialize the Time to 00:00:00 1/1/1970 in the specified timezone. */ + private void initialize(String timezoneId) { + this.timezone = timezoneId; + this.year = 1970; + this.monthDay = 1; + // Set the daylight-saving indicator to the unknown value -1 so that + // it will be recomputed. + this.isDst = -1; + + // A reusable object that performs the date/time calculations. + calculator = new TimeCalculator(timezoneId); + } + /** * Ensures the values in each field are in range. For example if the * current value of this calendar is March 32, normalize() will convert it @@ -208,14 +220,26 @@ public class Time { * * @return the UTC milliseconds since the epoch */ - native public long normalize(boolean ignoreDst); + public long normalize(boolean ignoreDst) { + calculator.copyFieldsFromTime(this); + long timeInMillis = calculator.toMillis(ignoreDst); + calculator.copyFieldsToTime(this); + return timeInMillis; + } /** * Convert this time object so the time represented remains the same, but is * instead located in a different timezone. This method automatically calls - * normalize() in some cases + * normalize() in some cases. + * + * <p>This method can return incorrect results if the date / time cannot be normalized. */ - native public void switchTimezone(String timezone); + public void switchTimezone(String timezone) { + calculator.copyFieldsFromTime(this); + calculator.switchTimeZone(timezone); + calculator.copyFieldsToTime(this); + this.timezone = timezone; + } private static final int[] DAYS_PER_MONTH = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; @@ -265,13 +289,13 @@ public class Time { /** * Clears all values, setting the timezone to the given timezone. Sets isDst * to a negative value to mean "unknown". - * @param timezone the timezone to use. + * @param timezoneId the timezone to use. */ - public void clear(String timezone) { - if (timezone == null) { + public void clear(String timezoneId) { + if (timezoneId == null) { throw new NullPointerException("timezone is null!"); } - this.timezone = timezone; + this.timezone = timezoneId; this.allDay = false; this.second = 0; this.minute = 0; @@ -304,12 +328,12 @@ public class Time { } else if (b == null) { throw new NullPointerException("b == null"); } + a.calculator.copyFieldsFromTime(a); + b.calculator.copyFieldsFromTime(b); - return nativeCompare(a, b); + return TimeCalculator.compare(a.calculator, b.calculator); } - private static native int nativeCompare(Time a, Time b); - /** * Print the current value given the format string provided. See man * strftime for what means what. The final string must be less than 256 @@ -318,61 +342,21 @@ public class Time { * @return a String containing the current time expressed in the current locale. */ public String format(String format) { - synchronized (Time.class) { - Locale locale = Locale.getDefault(); - - if (sLocale == null || locale == null || !(locale.equals(sLocale))) { - LocaleData localeData = LocaleData.get(locale); - - sAm = localeData.amPm[0]; - sPm = localeData.amPm[1]; - sZeroDigit = localeData.zeroDigit; - - sShortMonths = localeData.shortMonthNames; - sLongMonths = localeData.longMonthNames; - sLongStandaloneMonths = localeData.longStandAloneMonthNames; - sShortWeekdays = localeData.shortWeekdayNames; - sLongWeekdays = localeData.longWeekdayNames; - - Resources r = Resources.getSystem(); - sTimeOnlyFormat = r.getString(com.android.internal.R.string.time_of_day); - sDateOnlyFormat = r.getString(com.android.internal.R.string.month_day_year); - sDateTimeFormat = r.getString(com.android.internal.R.string.date_and_time); - - sLocale = locale; - } - - String result = format1(format); - if (sZeroDigit != '0') { - result = localizeDigits(result); - } - return result; - } - } - - native private String format1(String format); - - // TODO: unify this with java.util.Formatter's copy. - private String localizeDigits(String s) { - int length = s.length(); - int offsetToLocalizedDigits = sZeroDigit - '0'; - StringBuilder result = new StringBuilder(length); - for (int i = 0; i < length; ++i) { - char ch = s.charAt(i); - if (ch >= '0' && ch <= '9') { - ch += offsetToLocalizedDigits; - } - result.append(ch); - } - return result.toString(); + calculator.copyFieldsFromTime(this); + return calculator.format(format); } - /** * Return the current time in YYYYMMDDTHHMMSS<tz> format */ @Override - native public String toString(); + public String toString() { + // toString() uses its own TimeCalculator rather than the shared one. Otherwise crazy stuff + // happens during debugging when the debugger calls toString(). + TimeCalculator calculator = new TimeCalculator(this.timezone); + calculator.copyFieldsFromTime(this); + return calculator.toStringInternal(); + } /** * Parses a date-time string in either the RFC 2445 format or an abbreviated @@ -414,7 +398,7 @@ public class Time { if (s == null) { throw new NullPointerException("time string is null"); } - if (nativeParse(s)) { + if (parseInternal(s)) { timezone = TIMEZONE_UTC; return true; } @@ -424,7 +408,94 @@ public class Time { /** * Parse a time in the current zone in YYYYMMDDTHHMMSS format. */ - native private boolean nativeParse(String s); + private boolean parseInternal(String s) { + int len = s.length(); + if (len < 8) { + throw new TimeFormatException("String is too short: \"" + s + + "\" Expected at least 8 characters."); + } + + boolean inUtc = false; + + // 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) { + if (len < 15) { + throw new TimeFormatException( + "String is too short: \"" + s + + "\" If there are more than 8 characters there must be at least" + + " 15."); + } + 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'); + inUtc = true; + } + } else { + allDay = true; + hour = 0; + minute = 0; + second = 0; + } + + weekDay = 0; + yearDay = 0; + isDst = -1; + gmtoff = 0; + return inUtc; + } + + private void checkChar(String s, int spos, char expected) { + char c = s.charAt(spos); + if (c != expected) { + throw new TimeFormatException(String.format( + "Unexpected character 0x%02d at pos=%d. Expected 0x%02d (\'%c\').", + (int) c, spos, (int) expected, expected)); + } + } + + private static int getChar(String s, int spos, int mul) { + char c = s.charAt(spos); + if (Character.isDigit(c)) { + return Character.getNumericValue(c) * mul; + } else { + throw new TimeFormatException("Parse error at pos=" + spos); + } + } /** * Parse a time in RFC 3339 format. This method also parses simple dates @@ -461,14 +532,140 @@ public class Time { if (s == null) { throw new NullPointerException("time string is null"); } - if (nativeParse3339(s)) { + if (parse3339Internal(s)) { timezone = TIMEZONE_UTC; return true; } return false; } - native private boolean nativeParse3339(String s); + private boolean parse3339Internal(String s) { + int len = s.length(); + if (len < 10) { + throw new TimeFormatException("String too short --- expected at least 10 characters."); + } + boolean inUtc = false; + + // year + int n = getChar(s, 0, 1000); + n += getChar(s, 1, 100); + n += getChar(s, 2, 10); + n += getChar(s, 3, 1); + year = n; + + checkChar(s, 4, '-'); + + // month + n = getChar(s, 5, 10); + n += getChar(s, 6, 1); + --n; + month = n; + + checkChar(s, 7, '-'); + + // day + n = getChar(s, 8, 10); + n += getChar(s, 9, 1); + monthDay = n; + + if (len >= 19) { + // T + checkChar(s, 10, 'T'); + allDay = false; + + // hour + n = getChar(s, 11, 10); + n += getChar(s, 12, 1); + + // Note that this.hour is not set here. It is set later. + int hour = n; + + checkChar(s, 13, ':'); + + // minute + n = getChar(s, 14, 10); + n += getChar(s, 15, 1); + // Note that this.minute is not set here. It is set later. + int minute = n; + + checkChar(s, 16, ':'); + + // second + n = getChar(s, 17, 10); + n += getChar(s, 18, 1); + second = n; + + // skip the '.XYZ' -- we don't care about subsecond precision. + + int tzIndex = 19; + if (tzIndex < len && s.charAt(tzIndex) == '.') { + do { + tzIndex++; + } while (tzIndex < len && Character.isDigit(s.charAt(tzIndex))); + } + + int offset = 0; + if (len > tzIndex) { + char c = s.charAt(tzIndex); + // NOTE: the offset is meant to be subtracted to get from local time + // to UTC. we therefore use 1 for '-' and -1 for '+'. + switch (c) { + case 'Z': + // Zulu time -- UTC + offset = 0; + break; + case '-': + offset = 1; + break; + case '+': + offset = -1; + break; + default: + throw new TimeFormatException(String.format( + "Unexpected character 0x%02d at position %d. Expected + or -", + (int) c, tzIndex)); + } + inUtc = true; + + if (offset != 0) { + if (len < tzIndex + 6) { + throw new TimeFormatException( + String.format("Unexpected length; should be %d characters", + tzIndex + 6)); + } + + // hour + n = getChar(s, tzIndex + 1, 10); + n += getChar(s, tzIndex + 2, 1); + n *= offset; + hour += n; + + // minute + n = getChar(s, tzIndex + 4, 10); + n += getChar(s, tzIndex + 5, 1); + n *= offset; + minute += n; + } + } + this.hour = hour; + this.minute = minute; + + if (offset != 0) { + normalize(false); + } + } else { + allDay = true; + this.hour = 0; + this.minute = 0; + this.second = 0; + } + + this.weekDay = 0; + this.yearDay = 0; + this.isDst = -1; + this.gmtoff = 0; + return inUtc; + } /** * Returns the timezone string that is currently set for the device. @@ -480,7 +677,9 @@ public class Time { /** * Sets the time of the given Time object to the current time. */ - native public void setToNow(); + public void setToNow() { + set(System.currentTimeMillis()); + } /** * Converts this time to milliseconds. Suitable for interacting with the @@ -530,7 +729,10 @@ public class Time { * to read back the same milliseconds that you set with {@link #set(long)} * or {@link #set(Time)} or after parsing a date string. */ - native public long toMillis(boolean ignoreDst); + public long toMillis(boolean ignoreDst) { + calculator.copyFieldsFromTime(this); + return calculator.toMillis(ignoreDst); + } /** * Sets the fields in this Time object given the UTC milliseconds. After @@ -539,15 +741,23 @@ public class Time { * * @param millis the time in UTC milliseconds since the epoch. */ - native public void set(long millis); + public void set(long millis) { + allDay = false; + calculator.timezone = timezone; + calculator.setTimeInMillis(millis); + calculator.copyFieldsToTime(this); + } /** - * Format according to RFC 2445 DATETIME type. + * Format according to RFC 2445 DATE-TIME type. * - * <p> - * The same as format("%Y%m%dT%H%M%S"). + * <p>The same as format("%Y%m%dT%H%M%S"), or format("%Y%m%dT%H%M%SZ") for a Time with a + * timezone set to "UTC". */ - native public String format2445(); + public String format2445() { + calculator.copyFieldsFromTime(this); + return calculator.format2445(!allDay); + } /** * Copy the value of that to this Time object. No normalization happens. @@ -682,7 +892,6 @@ public class Time { * Otherwise, if the timezone is UTC, expresses the time as Y-M-D-T-H-M-S UTC</p> * <p> * Otherwise the time is expressed the time as Y-M-D-T-H-M-S +- GMT</p> - * @param allDay * @return string in the RFC 3339 format. */ public String format3339(boolean allDay) { @@ -693,7 +902,7 @@ public class Time { } else { String base = format(Y_M_D_T_H_M_S_000); String sign = (gmtoff < 0) ? "-" : "+"; - int offset = (int)Math.abs(gmtoff); + int offset = (int) Math.abs(gmtoff); int minutes = (offset % 3600) / 60; int hours = offset / 3600; @@ -714,16 +923,18 @@ public class Time { } /** - * Computes the Julian day number, given the UTC milliseconds - * and the offset (in seconds) from UTC. The Julian day for a given - * date will be the same for every timezone. For example, the Julian - * day for July 1, 2008 is 2454649. This is the same value no matter - * what timezone is being used. The Julian day is useful for testing - * if two events occur on the same day and for determining the relative - * time of an event from the present ("yesterday", "3 days ago", etc.). + * Computes the Julian day number for a point in time in a particular + * timezone. The Julian day for a given date is the same for every + * timezone. For example, the Julian day for July 1, 2008 is 2454649. * - * <p> - * Use {@link #toMillis(boolean)} to get the milliseconds. + * <p>Callers must pass the time in UTC millisecond (as can be returned + * by {@link #toMillis(boolean)} or {@link #normalize(boolean)}) + * and the offset from UTC of the timezone in seconds (as might be in + * {@link #gmtoff}). + * + * <p>The Julian day is useful for testing if two events occur on the + * same calendar date and for determining the relative time of an event + * from the present ("yesterday", "3 days ago", etc.). * * @param millis the time in UTC milliseconds * @param gmtoff the offset from UTC in seconds @@ -810,4 +1021,240 @@ public class Time { public static int getJulianMondayFromWeeksSinceEpoch(int week) { return MONDAY_BEFORE_JULIAN_EPOCH + week * 7; } + + /** + * A class that handles date/time calculations. + * + * This class originated as a port of a native C++ class ("android.Time") to pure Java. It is + * separate from the enclosing class because some methods copy the result of calculations back + * to the enclosing object, but others do not: thus separate state is retained. + */ + private static class TimeCalculator { + public final ZoneInfo.WallTime wallTime; + public String timezone; + + // Information about the current timezone. + private ZoneInfo zoneInfo; + + public TimeCalculator(String timezoneId) { + this.zoneInfo = lookupZoneInfo(timezoneId); + this.wallTime = new ZoneInfo.WallTime(); + } + + public long toMillis(boolean ignoreDst) { + if (ignoreDst) { + wallTime.setIsDst(-1); + } + + int r = wallTime.mktime(zoneInfo); + if (r == -1) { + return -1; + } + return r * 1000L; + } + + public void setTimeInMillis(long millis) { + // Preserve old 32-bit Android behavior. + int intSeconds = (int) (millis / 1000); + + updateZoneInfoFromTimeZone(); + wallTime.localtime(intSeconds, zoneInfo); + } + + public String format(String format) { + if (format == null) { + format = "%c"; + } + TimeFormatter formatter = new TimeFormatter(); + return formatter.format(format, wallTime, zoneInfo); + } + + private void updateZoneInfoFromTimeZone() { + if (!zoneInfo.getID().equals(timezone)) { + this.zoneInfo = lookupZoneInfo(timezone); + } + } + + private static ZoneInfo lookupZoneInfo(String timezoneId) { + try { + ZoneInfo zoneInfo = ZoneInfoDB.getInstance().makeTimeZone(timezoneId); + if (zoneInfo == null) { + zoneInfo = ZoneInfoDB.getInstance().makeTimeZone("GMT"); + } + if (zoneInfo == null) { + throw new AssertionError("GMT not found: \"" + timezoneId + "\""); + } + return zoneInfo; + } catch (IOException e) { + // This should not ever be thrown. + throw new AssertionError("Error loading timezone: \"" + timezoneId + "\"", e); + } + } + + public void switchTimeZone(String timezone) { + int seconds = wallTime.mktime(zoneInfo); + this.timezone = timezone; + updateZoneInfoFromTimeZone(); + wallTime.localtime(seconds, zoneInfo); + } + + public String format2445(boolean hasTime) { + char[] buf = new char[hasTime ? 16 : 8]; + int n = wallTime.getYear(); + + buf[0] = toChar(n / 1000); + n %= 1000; + buf[1] = toChar(n / 100); + n %= 100; + buf[2] = toChar(n / 10); + n %= 10; + buf[3] = toChar(n); + + n = wallTime.getMonth() + 1; + buf[4] = toChar(n / 10); + buf[5] = toChar(n % 10); + + n = wallTime.getMonthDay(); + buf[6] = toChar(n / 10); + buf[7] = toChar(n % 10); + + if (!hasTime) { + return new String(buf, 0, 8); + } + + buf[8] = 'T'; + + n = wallTime.getHour(); + buf[9] = toChar(n / 10); + buf[10] = toChar(n % 10); + + n = wallTime.getMinute(); + buf[11] = toChar(n / 10); + buf[12] = toChar(n % 10); + + n = wallTime.getSecond(); + buf[13] = toChar(n / 10); + buf[14] = toChar(n % 10); + + if (TIMEZONE_UTC.equals(timezone)) { + // The letter 'Z' is appended to the end. + buf[15] = 'Z'; + return new String(buf, 0, 16); + } else { + return new String(buf, 0, 15); + } + } + + private char toChar(int n) { + return (n >= 0 && n <= 9) ? (char) (n + '0') : ' '; + } + + /** + * A method that will return the state of this object in string form. Note: it has side + * effects and so has deliberately not been made the default {@link #toString()}. + */ + public String toStringInternal() { + // This implementation possibly displays the un-normalized fields because that is + // what it has always done. + return String.format("%04d%02d%02dT%02d%02d%02d%s(%d,%d,%d,%d,%d)", + wallTime.getYear(), + wallTime.getMonth() + 1, + wallTime.getMonthDay(), + wallTime.getHour(), + wallTime.getMinute(), + wallTime.getSecond(), + timezone, + wallTime.getWeekDay(), + wallTime.getYearDay(), + wallTime.getGmtOffset(), + wallTime.getIsDst(), + toMillis(false /* use isDst */) / 1000 + ); + + } + + public static int compare(TimeCalculator aObject, TimeCalculator bObject) { + if (aObject.timezone.equals(bObject.timezone)) { + // If the timezones are the same, we can easily compare the two times. + int diff = aObject.wallTime.getYear() - bObject.wallTime.getYear(); + if (diff != 0) { + return diff; + } + + diff = aObject.wallTime.getMonth() - bObject.wallTime.getMonth(); + if (diff != 0) { + return diff; + } + + diff = aObject.wallTime.getMonthDay() - bObject.wallTime.getMonthDay(); + if (diff != 0) { + return diff; + } + + diff = aObject.wallTime.getHour() - bObject.wallTime.getHour(); + if (diff != 0) { + return diff; + } + + diff = aObject.wallTime.getMinute() - bObject.wallTime.getMinute(); + if (diff != 0) { + return diff; + } + + diff = aObject.wallTime.getSecond() - bObject.wallTime.getSecond(); + if (diff != 0) { + return diff; + } + + return 0; + } else { + // Otherwise, convert to milliseconds and compare that. This requires that object be + // normalized. Note: For dates that do not exist: toMillis() can return -1, which + // can be confused with a valid time. + long am = aObject.toMillis(false /* use isDst */); + long bm = bObject.toMillis(false /* use isDst */); + long diff = am - bm; + return (diff < 0) ? -1 : ((diff > 0) ? 1 : 0); + } + + } + + public void copyFieldsToTime(Time time) { + time.second = wallTime.getSecond(); + time.minute = wallTime.getMinute(); + time.hour = wallTime.getHour(); + time.monthDay = wallTime.getMonthDay(); + time.month = wallTime.getMonth(); + time.year = wallTime.getYear(); + + // Read-only fields that are derived from other information above. + time.weekDay = wallTime.getWeekDay(); + time.yearDay = wallTime.getYearDay(); + + // < 0: DST status unknown, 0: is not in DST, 1: is in DST + time.isDst = wallTime.getIsDst(); + // This is in seconds and includes any DST offset too. + time.gmtoff = wallTime.getGmtOffset(); + } + + public void copyFieldsFromTime(Time time) { + wallTime.setSecond(time.second); + wallTime.setMinute(time.minute); + wallTime.setHour(time.hour); + wallTime.setMonthDay(time.monthDay); + wallTime.setMonth(time.month); + wallTime.setYear(time.year); + wallTime.setWeekDay(time.weekDay); + wallTime.setYearDay(time.yearDay); + wallTime.setIsDst(time.isDst); + wallTime.setGmtOffset((int) time.gmtoff); + + if (time.allDay && (time.second != 0 || time.minute != 0 || time.hour != 0)) { + throw new IllegalArgumentException("allDay is true but sec, min, hour are not 0."); + } + + timezone = time.timezone; + updateZoneInfoFromTimeZone(); + } + } } diff --git a/core/java/android/text/format/TimeFormatter.java b/core/java/android/text/format/TimeFormatter.java new file mode 100644 index 0000000..ec79b36 --- /dev/null +++ b/core/java/android/text/format/TimeFormatter.java @@ -0,0 +1,519 @@ +/* + * Based on the UCB version of strftime.c with the copyright notice appearing below. + */ + +/* +** Copyright (c) 1989 The Regents of the University of California. +** All rights reserved. +** +** Redistribution and use in source and binary forms are permitted +** provided that the above copyright notice and this paragraph are +** duplicated in all such forms and that any documentation, +** advertising materials, and other materials related to such +** distribution and use acknowledge that the software was developed +** by the University of California, Berkeley. The name of the +** University may not be used to endorse or promote products derived +** from this software without specific prior written permission. +** THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED +** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +*/ +package android.text.format; + +import android.content.res.Resources; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.Formatter; +import java.util.Locale; +import java.util.TimeZone; +import libcore.icu.LocaleData; +import libcore.util.ZoneInfo; + +/** + * Formatting logic for {@link Time}. Contains a port of Bionic's broken strftime_tz to Java. The + * main issue with this implementation is the treatment of characters as ASCII, despite returning + * localized (UTF-16) strings from the LocaleData. + * + * <p>This class is not thread safe. + */ +class TimeFormatter { + // An arbitrary value outside the range representable by a byte / ASCII character code. + private static final int FORCE_LOWER_CASE = 0x100; + + private static final int SECSPERMIN = 60; + private static final int MINSPERHOUR = 60; + private static final int DAYSPERWEEK = 7; + private static final int MONSPERYEAR = 12; + private static final int HOURSPERDAY = 24; + private static final int DAYSPERLYEAR = 366; + private static final int DAYSPERNYEAR = 365; + + /** + * The Locale for which the cached LocaleData and formats have been loaded. + */ + private static Locale sLocale; + private static LocaleData sLocaleData; + private static String sTimeOnlyFormat; + private static String sDateOnlyFormat; + private static String sDateTimeFormat; + + private final LocaleData localeData; + private final String dateTimeFormat; + private final String timeOnlyFormat; + private final String dateOnlyFormat; + private final Locale locale; + + private StringBuilder outputBuilder; + private Formatter outputFormatter; + + public TimeFormatter() { + synchronized (TimeFormatter.class) { + Locale locale = Locale.getDefault(); + + if (sLocale == null || !(locale.equals(sLocale))) { + sLocale = locale; + sLocaleData = LocaleData.get(locale); + + Resources r = Resources.getSystem(); + sTimeOnlyFormat = r.getString(com.android.internal.R.string.time_of_day); + sDateOnlyFormat = r.getString(com.android.internal.R.string.month_day_year); + sDateTimeFormat = r.getString(com.android.internal.R.string.date_and_time); + } + + this.dateTimeFormat = sDateTimeFormat; + this.timeOnlyFormat = sTimeOnlyFormat; + this.dateOnlyFormat = sDateOnlyFormat; + this.locale = locale; + localeData = sLocaleData; + } + } + + /** + * Format the specified {@code wallTime} using {@code pattern}. The output is returned. + */ + public String format(String pattern, ZoneInfo.WallTime wallTime, ZoneInfo zoneInfo) { + try { + StringBuilder stringBuilder = new StringBuilder(); + + outputBuilder = stringBuilder; + outputFormatter = new Formatter(stringBuilder, locale); + + formatInternal(pattern, wallTime, zoneInfo); + String result = stringBuilder.toString(); + // This behavior is the source of a bug since some formats are defined as being + // in ASCII. Generally localization is very broken. + if (localeData.zeroDigit != '0') { + result = localizeDigits(result); + } + return result; + } finally { + outputBuilder = null; + outputFormatter = null; + } + } + + private String localizeDigits(String s) { + int length = s.length(); + int offsetToLocalizedDigits = localeData.zeroDigit - '0'; + StringBuilder result = new StringBuilder(length); + for (int i = 0; i < length; ++i) { + char ch = s.charAt(i); + if (ch >= '0' && ch <= '9') { + ch += offsetToLocalizedDigits; + } + result.append(ch); + } + return result.toString(); + } + + /** + * Format the specified {@code wallTime} using {@code pattern}. The output is written to + * {@link #outputBuilder}. + */ + private void formatInternal(String pattern, ZoneInfo.WallTime wallTime, ZoneInfo zoneInfo) { + // Convert to ASCII bytes to be compatible with old implementation behavior. + byte[] bytes = pattern.getBytes(StandardCharsets.US_ASCII); + if (bytes.length == 0) { + return; + } + + ByteBuffer formatBuffer = ByteBuffer.wrap(bytes); + while (formatBuffer.remaining() > 0) { + boolean outputCurrentByte = true; + char currentByteAsChar = convertToChar(formatBuffer.get(formatBuffer.position())); + if (currentByteAsChar == '%') { + outputCurrentByte = handleToken(formatBuffer, wallTime, zoneInfo); + } + if (outputCurrentByte) { + currentByteAsChar = convertToChar(formatBuffer.get(formatBuffer.position())); + outputBuilder.append(currentByteAsChar); + } + + formatBuffer.position(formatBuffer.position() + 1); + } + } + + private boolean handleToken(ByteBuffer formatBuffer, ZoneInfo.WallTime wallTime, + ZoneInfo zoneInfo) { + + // The byte at formatBuffer.position() is expected to be '%' at this point. + int modifier = 0; + while (formatBuffer.remaining() > 1) { + // Increment the position then get the new current byte. + formatBuffer.position(formatBuffer.position() + 1); + char currentByteAsChar = convertToChar(formatBuffer.get(formatBuffer.position())); + switch (currentByteAsChar) { + case 'A': + modifyAndAppend((wallTime.getWeekDay() < 0 + || wallTime.getWeekDay() >= DAYSPERWEEK) + ? "?" : localeData.longWeekdayNames[wallTime.getWeekDay() + 1], + modifier); + return false; + case 'a': + modifyAndAppend((wallTime.getWeekDay() < 0 + || wallTime.getWeekDay() >= DAYSPERWEEK) + ? "?" : localeData.shortWeekdayNames[wallTime.getWeekDay() + 1], + modifier); + return false; + case 'B': + if (modifier == '-') { + modifyAndAppend((wallTime.getMonth() < 0 + || wallTime.getMonth() >= MONSPERYEAR) + ? "?" + : localeData.longStandAloneMonthNames[wallTime.getMonth()], + modifier); + } else { + modifyAndAppend((wallTime.getMonth() < 0 + || wallTime.getMonth() >= MONSPERYEAR) + ? "?" : localeData.longMonthNames[wallTime.getMonth()], + modifier); + } + return false; + case 'b': + case 'h': + modifyAndAppend((wallTime.getMonth() < 0 || wallTime.getMonth() >= MONSPERYEAR) + ? "?" : localeData.shortMonthNames[wallTime.getMonth()], + modifier); + return false; + case 'C': + outputYear(wallTime.getYear(), true, false, modifier); + return false; + case 'c': + formatInternal(dateTimeFormat, wallTime, zoneInfo); + return false; + case 'D': + formatInternal("%m/%d/%y", wallTime, zoneInfo); + return false; + case 'd': + outputFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), + wallTime.getMonthDay()); + return false; + case 'E': + case 'O': + // C99 locale modifiers are not supported. + continue; + case '_': + case '-': + case '0': + case '^': + case '#': + modifier = currentByteAsChar; + continue; + case 'e': + outputFormatter.format(getFormat(modifier, "%2d", "%2d", "%d", "%02d"), + wallTime.getMonthDay()); + return false; + case 'F': + formatInternal("%Y-%m-%d", wallTime, zoneInfo); + return false; + case 'H': + outputFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), + wallTime.getHour()); + return false; + case 'I': + int hour = (wallTime.getHour() % 12 != 0) ? (wallTime.getHour() % 12) : 12; + outputFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), hour); + return false; + case 'j': + int yearDay = wallTime.getYearDay() + 1; + outputFormatter.format(getFormat(modifier, "%03d", "%3d", "%d", "%03d"), + yearDay); + return false; + case 'k': + outputFormatter.format(getFormat(modifier, "%2d", "%2d", "%d", "%02d"), + wallTime.getHour()); + return false; + case 'l': + int n2 = (wallTime.getHour() % 12 != 0) ? (wallTime.getHour() % 12) : 12; + outputFormatter.format(getFormat(modifier, "%2d", "%2d", "%d", "%02d"), n2); + return false; + case 'M': + outputFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), + wallTime.getMinute()); + return false; + case 'm': + outputFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), + wallTime.getMonth() + 1); + return false; + case 'n': + modifyAndAppend("\n", modifier); + return false; + case 'p': + modifyAndAppend((wallTime.getHour() >= (HOURSPERDAY / 2)) ? localeData.amPm[1] + : localeData.amPm[0], modifier); + return false; + case 'P': + modifyAndAppend((wallTime.getHour() >= (HOURSPERDAY / 2)) ? localeData.amPm[1] + : localeData.amPm[0], FORCE_LOWER_CASE); + return false; + case 'R': + formatInternal("%H:%M", wallTime, zoneInfo); + return false; + case 'r': + formatInternal("%I:%M:%S %p", wallTime, zoneInfo); + return false; + case 'S': + outputFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), + wallTime.getSecond()); + return false; + case 's': + int timeInSeconds = wallTime.mktime(zoneInfo); + modifyAndAppend(Integer.toString(timeInSeconds), modifier); + return false; + case 'T': + formatInternal("%H:%M:%S", wallTime, zoneInfo); + return false; + case 't': + modifyAndAppend("\t", modifier); + return false; + case 'U': + outputFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), + (wallTime.getYearDay() + DAYSPERWEEK - wallTime.getWeekDay()) + / DAYSPERWEEK); + return false; + case 'u': + int day = (wallTime.getWeekDay() == 0) ? DAYSPERWEEK : wallTime.getWeekDay(); + outputFormatter.format("%d", day); + return false; + case 'V': /* ISO 8601 week number */ + case 'G': /* ISO 8601 year (four digits) */ + case 'g': /* ISO 8601 year (two digits) */ + { + int year = wallTime.getYear(); + int yday = wallTime.getYearDay(); + int wday = wallTime.getWeekDay(); + int w; + while (true) { + int len = isLeap(year) ? DAYSPERLYEAR : DAYSPERNYEAR; + // What yday (-3 ... 3) does the ISO year begin on? + int bot = ((yday + 11 - wday) % DAYSPERWEEK) - 3; + // What yday does the NEXT ISO year begin on? + int top = bot - (len % DAYSPERWEEK); + if (top < -3) { + top += DAYSPERWEEK; + } + top += len; + if (yday >= top) { + ++year; + w = 1; + break; + } + if (yday >= bot) { + w = 1 + ((yday - bot) / DAYSPERWEEK); + break; + } + --year; + yday += isLeap(year) ? DAYSPERLYEAR : DAYSPERNYEAR; + } + if (currentByteAsChar == 'V') { + outputFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), w); + } else if (currentByteAsChar == 'g') { + outputYear(year, false, true, modifier); + } else { + outputYear(year, true, true, modifier); + } + return false; + } + case 'v': + formatInternal("%e-%b-%Y", wallTime, zoneInfo); + return false; + case 'W': + int n = (wallTime.getYearDay() + DAYSPERWEEK - ( + wallTime.getWeekDay() != 0 ? (wallTime.getWeekDay() - 1) + : (DAYSPERWEEK - 1))) / DAYSPERWEEK; + outputFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), n); + return false; + case 'w': + outputFormatter.format("%d", wallTime.getWeekDay()); + return false; + case 'X': + formatInternal(timeOnlyFormat, wallTime, zoneInfo); + return false; + case 'x': + formatInternal(dateOnlyFormat, wallTime, zoneInfo); + return false; + case 'y': + outputYear(wallTime.getYear(), false, true, modifier); + return false; + case 'Y': + outputYear(wallTime.getYear(), true, true, modifier); + return false; + case 'Z': + if (wallTime.getIsDst() < 0) { + return false; + } + boolean isDst = wallTime.getIsDst() != 0; + modifyAndAppend(zoneInfo.getDisplayName(isDst, TimeZone.SHORT), modifier); + return false; + case 'z': { + if (wallTime.getIsDst() < 0) { + return false; + } + int diff = wallTime.getGmtOffset(); + String sign; + if (diff < 0) { + sign = "-"; + diff = -diff; + } else { + sign = "+"; + } + modifyAndAppend(sign, modifier); + diff /= SECSPERMIN; + diff = (diff / MINSPERHOUR) * 100 + (diff % MINSPERHOUR); + outputFormatter.format(getFormat(modifier, "%04d", "%4d", "%d", "%04d"), diff); + return false; + } + case '+': + formatInternal("%a %b %e %H:%M:%S %Z %Y", wallTime, zoneInfo); + return false; + case '%': + // If conversion char is undefined, behavior is undefined. Print out the + // character itself. + default: + return true; + } + } + return true; + } + + private void modifyAndAppend(CharSequence str, int modifier) { + switch (modifier) { + case FORCE_LOWER_CASE: + for (int i = 0; i < str.length(); i++) { + outputBuilder.append(brokenToLower(str.charAt(i))); + } + break; + case '^': + for (int i = 0; i < str.length(); i++) { + outputBuilder.append(brokenToUpper(str.charAt(i))); + } + break; + case '#': + for (int i = 0; i < str.length(); i++) { + char c = str.charAt(i); + if (brokenIsUpper(c)) { + c = brokenToLower(c); + } else if (brokenIsLower(c)) { + c = brokenToUpper(c); + } + outputBuilder.append(c); + } + break; + default: + outputBuilder.append(str); + + } + } + + private void outputYear(int value, boolean outputTop, boolean outputBottom, int modifier) { + int lead; + int trail; + + final int DIVISOR = 100; + trail = value % DIVISOR; + lead = value / DIVISOR + trail / DIVISOR; + trail %= DIVISOR; + if (trail < 0 && lead > 0) { + trail += DIVISOR; + --lead; + } else if (lead < 0 && trail > 0) { + trail -= DIVISOR; + ++lead; + } + if (outputTop) { + if (lead == 0 && trail < 0) { + modifyAndAppend("-0", modifier); + } else { + outputFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), lead); + } + } + if (outputBottom) { + int n = ((trail < 0) ? -trail : trail); + outputFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), n); + } + } + + private static String getFormat(int modifier, String normal, String underscore, String dash, + String zero) { + switch (modifier) { + case '_': + return underscore; + case '-': + return dash; + case '0': + return zero; + } + return normal; + } + + private static boolean isLeap(int year) { + return (((year) % 4) == 0 && (((year) % 100) != 0 || ((year) % 400) == 0)); + } + + /** + * A broken implementation of {@link Character#isUpperCase(char)} that assumes ASCII in order to + * be compatible with the old native implementation. + */ + private static boolean brokenIsUpper(char toCheck) { + return toCheck >= 'A' && toCheck <= 'Z'; + } + + /** + * A broken implementation of {@link Character#isLowerCase(char)} that assumes ASCII in order to + * be compatible with the old native implementation. + */ + private static boolean brokenIsLower(char toCheck) { + return toCheck >= 'a' && toCheck <= 'z'; + } + + /** + * A broken implementation of {@link Character#toLowerCase(char)} that assumes ASCII in order to + * be compatible with the old native implementation. + */ + private static char brokenToLower(char input) { + if (input >= 'A' && input <= 'Z') { + return (char) (input - 'A' + 'a'); + } + return input; + } + + /** + * A broken implementation of {@link Character#toUpperCase(char)} that assumes ASCII in order to + * be compatible with the old native implementation. + */ + private static char brokenToUpper(char input) { + if (input >= 'a' && input <= 'z') { + return (char) (input - 'a' + 'A'); + } + return input; + } + + /** + * Safely convert a byte containing an ASCII character to a char, even for character codes + * > 127. + */ + private static char convertToChar(byte b) { + return (char) (b & 0xFF); + } +} diff --git a/core/java/android/util/TimeFormatException.java b/core/java/android/util/TimeFormatException.java index d7a898b..f520523 100644 --- a/core/java/android/util/TimeFormatException.java +++ b/core/java/android/util/TimeFormatException.java @@ -18,7 +18,11 @@ package android.util; public class TimeFormatException extends RuntimeException { - TimeFormatException(String s) + + /** + * @hide + */ + public TimeFormatException(String s) { super(s); } diff --git a/core/jni/Android.mk b/core/jni/Android.mk index f65aab5..f003680 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -23,7 +23,6 @@ LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPES LOCAL_SRC_FILES:= \ AndroidRuntime.cpp \ - Time.cpp \ com_android_internal_content_NativeLibraryHelper.cpp \ com_google_android_gles_jni_EGLImpl.cpp \ com_google_android_gles_jni_GLImpl.cpp.arm \ @@ -81,7 +80,6 @@ LOCAL_SRC_FILES:= \ android_net_NetUtils.cpp \ android_net_TrafficStats.cpp \ android_nio_utils.cpp \ - android_text_format_Time.cpp \ android_util_AssetManager.cpp \ android_util_Binder.cpp \ android_util_EventLog.cpp \ diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 92a8fca..3a02ab9 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -144,7 +144,6 @@ extern int register_android_database_SQLiteConnection(JNIEnv* env); extern int register_android_database_SQLiteGlobal(JNIEnv* env); extern int register_android_database_SQLiteDebug(JNIEnv* env); extern int register_android_nio_utils(JNIEnv* env); -extern int register_android_text_format_Time(JNIEnv* env); extern int register_android_os_Debug(JNIEnv* env); extern int register_android_os_MessageQueue(JNIEnv* env); extern int register_android_os_Parcel(JNIEnv* env); @@ -1223,7 +1222,6 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_util_EventLog), REG_JNI(register_android_util_Log), REG_JNI(register_android_util_FloatMath), - REG_JNI(register_android_text_format_Time), REG_JNI(register_android_content_AssetManager), REG_JNI(register_android_content_StringBlock), REG_JNI(register_android_content_XmlBlock), diff --git a/core/jni/Time.cpp b/core/jni/Time.cpp deleted file mode 100644 index f3037f3..0000000 --- a/core/jni/Time.cpp +++ /dev/null @@ -1,199 +0,0 @@ -#include "TimeUtils.h" -#include <stdio.h> -#include <cutils/tztime.h> - -namespace android { - -static void -dump(const Time& t) -{ - #ifdef HAVE_TM_GMTOFF - long tm_gmtoff = t.t.tm_gmtoff; - #else - long tm_gmtoff = 0; - #endif - printf("%04d-%02d-%02d %02d:%02d:%02d (%d,%ld,%d,%d)\n", - t.t.tm_year+1900, t.t.tm_mon+1, t.t.tm_mday, - t.t.tm_hour, t.t.tm_min, t.t.tm_sec, - t.t.tm_isdst, tm_gmtoff, t.t.tm_wday, t.t.tm_yday); -} - -Time::Time() -{ - t.tm_sec = 0; - t.tm_min = 0; - t.tm_hour = 0; - t.tm_mday = 0; - t.tm_mon = 0; - t.tm_year = 0; - t.tm_wday = 0; - t.tm_yday = 0; - t.tm_isdst = -1; // we don't know, so let the C library determine - #ifdef HAVE_TM_GMTOFF - t.tm_gmtoff = 0; - #endif -} - - -#define COMPARE_FIELD(field) do { \ - int diff = a.t.field - b.t.field; \ - if (diff != 0) return diff; \ - } while(0) - -int -Time::compare(Time& a, Time& b) -{ - if (0 == strcmp(a.timezone, b.timezone)) { - // if the timezones are the same, we can easily compare the two - // times. Otherwise, convert to milliseconds and compare that. - // This requires that object be normalized. - COMPARE_FIELD(tm_year); - COMPARE_FIELD(tm_mon); - COMPARE_FIELD(tm_mday); - COMPARE_FIELD(tm_hour); - COMPARE_FIELD(tm_min); - COMPARE_FIELD(tm_sec); - return 0; - } else { - int64_t am = a.toMillis(false /* use isDst */); - int64_t bm = b.toMillis(false /* use isDst */); - int64_t diff = am-bm; - return (diff < 0) ? -1 : ((diff > 0) ? 1 : 0); - } -} - -static const int DAYS_PER_MONTH[] = { - 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 - }; - -static inline int days_this_month(int year, int month) -{ - int n = DAYS_PER_MONTH[month]; - if (n != 28) { - return n; - } else { - int y = year; - return ((y%4)==0&&((y%100)!=0||(y%400)==0)) ? 29 : 28; - } -} - -void -Time::switchTimezone(const char* timezone) -{ - time_t seconds = mktime_tz(&(this->t), this->timezone); - localtime_tz(&seconds, &(this->t), timezone); -} - -String8 -Time::format(const char *format, const struct strftime_locale *locale) const -{ - char buf[257]; - int n = strftime_tz(buf, 257, format, &(this->t), locale); - if (n > 0) { - return String8(buf); - } else { - return String8(); - } -} - -static inline short -tochar(int n) -{ - return (n >= 0 && n <= 9) ? ('0'+n) : ' '; -} - -static inline short -next_char(int *m, int k) -{ - int n = *m / k; - *m = *m % k; - return tochar(n); -} - -void -Time::format2445(short* buf, bool hasTime) const -{ - int n; - - n = t.tm_year+1900; - buf[0] = next_char(&n, 1000); - buf[1] = next_char(&n, 100); - buf[2] = next_char(&n, 10); - buf[3] = tochar(n); - - n = t.tm_mon+1; - buf[4] = next_char(&n, 10); - buf[5] = tochar(n); - - n = t.tm_mday; - buf[6] = next_char(&n, 10); - buf[7] = tochar(n); - - if (hasTime) { - buf[8] = 'T'; - - n = t.tm_hour; - buf[9] = next_char(&n, 10); - buf[10] = tochar(n); - - n = t.tm_min; - buf[11] = next_char(&n, 10); - buf[12] = tochar(n); - - n = t.tm_sec; - buf[13] = next_char(&n, 10); - buf[14] = tochar(n); - bool inUtc = strcmp("UTC", timezone) == 0; - if (inUtc) { - buf[15] = 'Z'; - } - } -} - -String8 -Time::toString() const -{ - String8 str; - char* s = str.lockBuffer(150); - #ifdef HAVE_TM_GMTOFF - long tm_gmtoff = t.tm_gmtoff; - #else - long tm_gmtoff = 0; - #endif - sprintf(s, "%04d%02d%02dT%02d%02d%02d%s(%d,%d,%ld,%d,%d)", - t.tm_year+1900, t.tm_mon+1, t.tm_mday, t.tm_hour, t.tm_min, - t.tm_sec, timezone, t.tm_wday, t.tm_yday, tm_gmtoff, t.tm_isdst, - (int)(((Time*)this)->toMillis(false /* use isDst */)/1000)); - str.unlockBuffer(); - return str; -} - -void -Time::setToNow() -{ - time_t seconds; - time(&seconds); - localtime_tz(&seconds, &(this->t), this->timezone); -} - -int64_t -Time::toMillis(bool ignoreDst) -{ - if (ignoreDst) { - this->t.tm_isdst = -1; - } - int64_t r = mktime_tz(&(this->t), this->timezone); - if (r == -1) - return -1; - return r * 1000; -} - -void -Time::set(int64_t millis) -{ - time_t seconds = millis / 1000; - localtime_tz(&seconds, &(this->t), this->timezone); -} - -}; // namespace android - diff --git a/core/jni/TimeUtils.h b/core/jni/TimeUtils.h deleted file mode 100644 index b19e021..0000000 --- a/core/jni/TimeUtils.h +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2005 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ANDROID_TIME_H -#define ANDROID_TIME_H - -#include <time.h> -#include <cutils/tztime.h> -#include <stdint.h> -#include <sys/types.h> -#include <sys/time.h> -#include <utils/String8.h> -#include <utils/String16.h> - -namespace android { - -/* - * This class is the core implementation of the android.util.Time java - * class. It doesn't implement some of the methods that are implemented - * in Java. They could be done here, but it's not expected that this class - * will be used. If that assumption is incorrect, feel free to update this - * file. The reason to do it here is to not mix the implementation of this - * class and the jni glue code. - */ -class Time -{ -public: - struct tm t; - - // this object doesn't own this string - const char *timezone; - - enum { - SEC = 1, - MIN = 2, - HOUR = 3, - MDAY = 4, - MON = 5, - YEAR = 6, - WDAY = 7, - YDAY = 8 - }; - - static int compare(Time& a, Time& b); - - Time(); - - void switchTimezone(const char *timezone); - String8 format(const char *format, const struct strftime_locale *locale) const; - void format2445(short* buf, bool hasTime) const; - String8 toString() const; - void setToNow(); - int64_t toMillis(bool ignoreDst); - void set(int64_t millis); - - inline void set(int sec, int min, int hour, int mday, int mon, int year, - int isdst) - { - this->t.tm_sec = sec; - this->t.tm_min = min; - this->t.tm_hour = hour; - this->t.tm_mday = mday; - this->t.tm_mon = mon; - this->t.tm_year = year; - this->t.tm_isdst = isdst; -#ifdef HAVE_TM_GMTOFF - this->t.tm_gmtoff = 0; -#endif - this->t.tm_wday = 0; - this->t.tm_yday = 0; - } -}; - -}; // namespace android - -#endif // ANDROID_TIME_H diff --git a/core/jni/android_text_format_Time.cpp b/core/jni/android_text_format_Time.cpp deleted file mode 100644 index 28a8a5d..0000000 --- a/core/jni/android_text_format_Time.cpp +++ /dev/null @@ -1,689 +0,0 @@ -/* -** Copyright 2006, The Android Open Source Project -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -*/ - -#define LOG_TAG "Log_println" - -#include <utils/Log.h> -#include <utils/String8.h> -#include <assert.h> - -#include "jni.h" -#include "utils/misc.h" -#include "android_runtime/AndroidRuntime.h" -#include "ScopedStringChars.h" -#include "TimeUtils.h" -#include <nativehelper/JNIHelp.h> -#include <cutils/tztime.h> - -namespace android { - -static jfieldID g_allDayField = 0; -static jfieldID g_secField = 0; -static jfieldID g_minField = 0; -static jfieldID g_hourField = 0; -static jfieldID g_mdayField = 0; -static jfieldID g_monField = 0; -static jfieldID g_yearField = 0; -static jfieldID g_wdayField = 0; -static jfieldID g_ydayField = 0; -static jfieldID g_isdstField = 0; -static jfieldID g_gmtoffField = 0; -static jfieldID g_timezoneField = 0; - -static jfieldID g_shortMonthsField = 0; -static jfieldID g_longMonthsField = 0; -static jfieldID g_longStandaloneMonthsField = 0; -static jfieldID g_shortWeekdaysField = 0; -static jfieldID g_longWeekdaysField = 0; -static jfieldID g_timeOnlyFormatField = 0; -static jfieldID g_dateOnlyFormatField = 0; -static jfieldID g_dateTimeFormatField = 0; -static jfieldID g_amField = 0; -static jfieldID g_pmField = 0; -static jfieldID g_dateCommandField = 0; -static jfieldID g_localeField = 0; - -static jclass g_timeClass = NULL; - -static inline bool java2time(JNIEnv* env, Time* t, jobject o) -{ - t->t.tm_sec = env->GetIntField(o, g_secField); - t->t.tm_min = env->GetIntField(o, g_minField); - t->t.tm_hour = env->GetIntField(o, g_hourField); - t->t.tm_mday = env->GetIntField(o, g_mdayField); - t->t.tm_mon = env->GetIntField(o, g_monField); - t->t.tm_year = (env->GetIntField(o, g_yearField))-1900; - t->t.tm_wday = env->GetIntField(o, g_wdayField); - t->t.tm_yday = env->GetIntField(o, g_ydayField); - t->t.tm_isdst = env->GetIntField(o, g_isdstField); - t->t.tm_gmtoff = env->GetLongField(o, g_gmtoffField); - bool allDay = env->GetBooleanField(o, g_allDayField); - if (allDay && - ((t->t.tm_sec !=0) || (t->t.tm_min != 0) || (t->t.tm_hour != 0))) { - jniThrowException(env, "java/lang/IllegalArgumentException", - "allDay is true but sec, min, hour are not 0."); - return false; - } - return true; -} - -static inline void time2java(JNIEnv* env, jobject o, const Time &t) -{ - env->SetIntField(o, g_secField, t.t.tm_sec); - env->SetIntField(o, g_minField, t.t.tm_min); - env->SetIntField(o, g_hourField, t.t.tm_hour); - env->SetIntField(o, g_mdayField, t.t.tm_mday); - env->SetIntField(o, g_monField, t.t.tm_mon); - env->SetIntField(o, g_yearField, t.t.tm_year+1900); - env->SetIntField(o, g_wdayField, t.t.tm_wday); - env->SetIntField(o, g_ydayField, t.t.tm_yday); - env->SetIntField(o, g_isdstField, t.t.tm_isdst); - env->SetLongField(o, g_gmtoffField, t.t.tm_gmtoff); -} - -#define ACQUIRE_TIMEZONE(This, t) \ - jstring timezoneString_##This \ - = (jstring) env->GetObjectField(This, g_timezoneField); \ - t.timezone = env->GetStringUTFChars(timezoneString_##This, NULL); - -#define RELEASE_TIMEZONE(This, t) \ - env->ReleaseStringUTFChars(timezoneString_##This, t.timezone); - - -// ============================================================================ - -static jlong android_text_format_Time_normalize(JNIEnv* env, jobject This, - jboolean ignoreDst) -{ - Time t; - if (!java2time(env, &t, This)) return 0L; - ACQUIRE_TIMEZONE(This, t) - - int64_t result = t.toMillis(ignoreDst != 0); - - time2java(env, This, t); - RELEASE_TIMEZONE(This, t) - - return static_cast<jlong>(result); -} - -static void android_text_format_Time_switchTimezone(JNIEnv* env, jobject This, - jstring timezoneObject) -{ - Time t; - if (!java2time(env, &t, This)) return; - ACQUIRE_TIMEZONE(This, t) - - const char* timezone = env->GetStringUTFChars(timezoneObject, NULL); - - t.switchTimezone(timezone); - - time2java(env, This, t); - env->ReleaseStringUTFChars(timezoneObject, timezone); - RELEASE_TIMEZONE(This, t) - - // we do this here because there's no point in reallocating the string - env->SetObjectField(This, g_timezoneField, timezoneObject); -} - -static jint android_text_format_Time_compare(JNIEnv* env, jobject clazz, - jobject aObject, jobject bObject) -{ - Time a, b; - - if (!java2time(env, &a, aObject)) return 0; - ACQUIRE_TIMEZONE(aObject, a) - - if (!java2time(env, &b, bObject)) return 0; - ACQUIRE_TIMEZONE(bObject, b) - - int result = Time::compare(a, b); - - RELEASE_TIMEZONE(aObject, a) - RELEASE_TIMEZONE(bObject, b) - - return static_cast<jint>(result); -} - -static jstring android_text_format_Time_format2445(JNIEnv* env, jobject This) -{ - Time t; - if (!java2time(env, &t, This)) return env->NewStringUTF(""); - bool allDay = env->GetBooleanField(This, g_allDayField); - - if (!allDay) { - ACQUIRE_TIMEZONE(This, t) - bool inUtc = strcmp("UTC", t.timezone) == 0; - short buf[16]; - t.format2445(buf, true); - RELEASE_TIMEZONE(This, t) - if (inUtc) { - // The letter 'Z' is appended to the end so allow for one - // more character in the buffer. - return env->NewString((jchar*)buf, 16); - } else { - return env->NewString((jchar*)buf, 15); - } - } else { - short buf[8]; - t.format2445(buf, false); - return env->NewString((jchar*)buf, 8); - } -} - -static jstring android_text_format_Time_format(JNIEnv* env, jobject This, - jstring formatObject) -{ - // We only teardown and setup our 'locale' struct and other state - // when the Java-side locale changed. This is safe to do here - // without locking because we're always called from Java code - // synchronized on the class instance. - static jobject js_locale_previous = NULL; - static struct strftime_locale locale; - static jstring js_mon[12], js_month[12], js_wday[7], js_weekday[7]; - static jstring js_standalone_month[12]; - static jstring js_X_fmt, js_x_fmt, js_c_fmt, js_am, js_pm, js_date_fmt; - - Time t; - if (!java2time(env, &t, This)) return env->NewStringUTF(""); - - jclass timeClass = g_timeClass; - jobject js_locale = (jobject) env->GetStaticObjectField(timeClass, g_localeField); - if (js_locale_previous != js_locale) { - if (js_locale_previous != NULL) { - // Free the old one. - for (int i = 0; i < 12; i++) { - env->ReleaseStringUTFChars(js_mon[i], locale.mon[i]); - env->ReleaseStringUTFChars(js_month[i], locale.month[i]); - env->ReleaseStringUTFChars(js_standalone_month[i], locale.standalone_month[i]); - env->DeleteGlobalRef(js_mon[i]); - env->DeleteGlobalRef(js_month[i]); - env->DeleteGlobalRef(js_standalone_month[i]); - } - - for (int i = 0; i < 7; i++) { - env->ReleaseStringUTFChars(js_wday[i], locale.wday[i]); - env->ReleaseStringUTFChars(js_weekday[i], locale.weekday[i]); - env->DeleteGlobalRef(js_wday[i]); - env->DeleteGlobalRef(js_weekday[i]); - } - - env->ReleaseStringUTFChars(js_X_fmt, locale.X_fmt); - env->ReleaseStringUTFChars(js_x_fmt, locale.x_fmt); - env->ReleaseStringUTFChars(js_c_fmt, locale.c_fmt); - env->ReleaseStringUTFChars(js_am, locale.am); - env->ReleaseStringUTFChars(js_pm, locale.pm); - env->ReleaseStringUTFChars(js_date_fmt, locale.date_fmt); - env->DeleteGlobalRef(js_X_fmt); - env->DeleteGlobalRef(js_x_fmt); - env->DeleteGlobalRef(js_c_fmt); - env->DeleteGlobalRef(js_am); - env->DeleteGlobalRef(js_pm); - env->DeleteGlobalRef(js_date_fmt); - } - js_locale_previous = js_locale; - - jobjectArray ja; - ja = (jobjectArray) env->GetStaticObjectField(timeClass, g_shortMonthsField); - for (int i = 0; i < 12; i++) { - // Calendar.JANUARY == 0. - js_mon[i] = (jstring) env->NewGlobalRef(env->GetObjectArrayElement(ja, i)); - locale.mon[i] = env->GetStringUTFChars(js_mon[i], NULL); - } - - ja = (jobjectArray) env->GetStaticObjectField(timeClass, g_longMonthsField); - for (int i = 0; i < 12; i++) { - // Calendar.JANUARY == 0. - js_month[i] = (jstring) env->NewGlobalRef(env->GetObjectArrayElement(ja, i)); - locale.month[i] = env->GetStringUTFChars(js_month[i], NULL); - } - - ja = (jobjectArray) env->GetStaticObjectField(timeClass, g_longStandaloneMonthsField); - for (int i = 0; i < 12; i++) { - // Calendar.JANUARY == 0. - js_standalone_month[i] = (jstring) env->NewGlobalRef(env->GetObjectArrayElement(ja, i)); - locale.standalone_month[i] = env->GetStringUTFChars(js_standalone_month[i], NULL); - } - - ja = (jobjectArray) env->GetStaticObjectField(timeClass, g_shortWeekdaysField); - for (int i = 0; i < 7; i++) { - // Calendar.SUNDAY == 1, and there's an empty string in element 0. - js_wday[i] = (jstring) env->NewGlobalRef(env->GetObjectArrayElement(ja, i + 1)); - locale.wday[i] = env->GetStringUTFChars(js_wday[i], NULL); - } - - ja = (jobjectArray) env->GetStaticObjectField(timeClass, g_longWeekdaysField); - for (int i = 0; i < 7; i++) { - // Calendar.SUNDAY == 1, and there's an empty string in element 0. - js_weekday[i] = (jstring) env->NewGlobalRef(env->GetObjectArrayElement(ja, i + 1)); - locale.weekday[i] = env->GetStringUTFChars(js_weekday[i], NULL); - } - - js_X_fmt = (jstring) env->NewGlobalRef(env->GetStaticObjectField( - timeClass, g_timeOnlyFormatField)); - locale.X_fmt = env->GetStringUTFChars(js_X_fmt, NULL); - - js_x_fmt = (jstring) env->NewGlobalRef(env->GetStaticObjectField( - timeClass, g_dateOnlyFormatField)); - locale.x_fmt = env->GetStringUTFChars(js_x_fmt, NULL); - - js_c_fmt = (jstring) env->NewGlobalRef(env->GetStaticObjectField( - timeClass, g_dateTimeFormatField)); - locale.c_fmt = env->GetStringUTFChars(js_c_fmt, NULL); - - js_am = (jstring) env->NewGlobalRef(env->GetStaticObjectField( - timeClass, g_amField)); - locale.am = env->GetStringUTFChars(js_am, NULL); - - js_pm = (jstring) env->NewGlobalRef(env->GetStaticObjectField( - timeClass, g_pmField)); - locale.pm = env->GetStringUTFChars(js_pm, NULL); - - js_date_fmt = (jstring) env->NewGlobalRef(env->GetStaticObjectField( - timeClass, g_dateCommandField)); - locale.date_fmt = env->GetStringUTFChars(js_date_fmt, NULL); - } - - ACQUIRE_TIMEZONE(This, t) - - const char* format = env->GetStringUTFChars(formatObject, NULL); - - String8 r = t.format(format, &locale); - - env->ReleaseStringUTFChars(formatObject, format); - RELEASE_TIMEZONE(This, t) - - return env->NewStringUTF(r.string()); -} - - -static jstring android_text_format_Time_toString(JNIEnv* env, jobject This) -{ - Time t; - if (!java2time(env, &t, This)) return env->NewStringUTF(""); - ACQUIRE_TIMEZONE(This, t) - - String8 r = t.toString(); - - RELEASE_TIMEZONE(This, t) - - return env->NewStringUTF(r.string()); -} - -static void android_text_format_Time_setToNow(JNIEnv* env, jobject This) -{ - env->SetBooleanField(This, g_allDayField, JNI_FALSE); - Time t; - ACQUIRE_TIMEZONE(This, t) - - t.setToNow(); - - time2java(env, This, t); - RELEASE_TIMEZONE(This, t) -} - -static jlong android_text_format_Time_toMillis(JNIEnv* env, jobject This, - jboolean ignoreDst) -{ - Time t; - if (!java2time(env, &t, This)) return 0L; - ACQUIRE_TIMEZONE(This, t) - - int64_t result = t.toMillis(ignoreDst != 0); - - RELEASE_TIMEZONE(This, t) - - return static_cast<jlong>(result); -} - -static void android_text_format_Time_set(JNIEnv* env, jobject This, jlong millis) -{ - env->SetBooleanField(This, g_allDayField, JNI_FALSE); - Time t; - ACQUIRE_TIMEZONE(This, t) - - t.set(millis); - - time2java(env, This, t); - RELEASE_TIMEZONE(This, t) -} - - -// ============================================================================ -// Just do this here because it's not worth recreating the strings - -static int get_char(JNIEnv* env, const ScopedStringChars& s, int spos, int mul, - bool* thrown) -{ - jchar c = s[spos]; - if (c >= '0' && c <= '9') { - return (c - '0') * mul; - } else { - if (!*thrown) { - jniThrowExceptionFmt(env, "android/util/TimeFormatException", - "Parse error at pos=%d", spos); - *thrown = true; - } - return 0; - } -} - -static bool check_char(JNIEnv* env, const ScopedStringChars& s, int spos, jchar expected) -{ - jchar c = s[spos]; - if (c != expected) { - jniThrowExceptionFmt(env, "android/util/TimeFormatException", - "Unexpected character 0x%02x at pos=%d. Expected %c.", - c, spos, expected); - return false; - } - return true; -} - - -static jboolean android_text_format_Time_parse(JNIEnv* env, jobject This, jstring strObj) -{ - jsize len = env->GetStringLength(strObj); - if (len < 8) { - jniThrowException(env, "android/util/TimeFormatException", - "String too short -- expected at least 8 characters."); - return JNI_FALSE; - } - - jboolean inUtc = JNI_FALSE; - - ScopedStringChars s(env, strObj); - - // year - int n; - bool thrown = false; - n = get_char(env, s, 0, 1000, &thrown); - n += get_char(env, s, 1, 100, &thrown); - n += get_char(env, s, 2, 10, &thrown); - n += get_char(env, s, 3, 1, &thrown); - if (thrown) return JNI_FALSE; - env->SetIntField(This, g_yearField, n); - - // month - n = get_char(env, s, 4, 10, &thrown); - n += get_char(env, s, 5, 1, &thrown); - n--; - if (thrown) return JNI_FALSE; - env->SetIntField(This, g_monField, n); - - // day of month - n = get_char(env, s, 6, 10, &thrown); - n += get_char(env, s, 7, 1, &thrown); - if (thrown) return JNI_FALSE; - env->SetIntField(This, g_mdayField, n); - - if (len > 8) { - // T - if (!check_char(env, s, 8, 'T')) return JNI_FALSE; - env->SetBooleanField(This, g_allDayField, JNI_FALSE); - - // hour - n = get_char(env, s, 9, 10, &thrown); - n += get_char(env, s, 10, 1, &thrown); - if (thrown) return JNI_FALSE; - env->SetIntField(This, g_hourField, n); - - // min - n = get_char(env, s, 11, 10, &thrown); - n += get_char(env, s, 12, 1, &thrown); - if (thrown) return JNI_FALSE; - env->SetIntField(This, g_minField, n); - - // sec - n = get_char(env, s, 13, 10, &thrown); - n += get_char(env, s, 14, 1, &thrown); - if (thrown) return JNI_FALSE; - env->SetIntField(This, g_secField, n); - - if (len > 15) { - // Z - if (!check_char(env, s, 15, 'Z')) return JNI_FALSE; - inUtc = JNI_TRUE; - } - } else { - env->SetBooleanField(This, g_allDayField, JNI_TRUE); - env->SetIntField(This, g_hourField, 0); - env->SetIntField(This, g_minField, 0); - env->SetIntField(This, g_secField, 0); - } - - env->SetIntField(This, g_wdayField, 0); - env->SetIntField(This, g_ydayField, 0); - env->SetIntField(This, g_isdstField, -1); - env->SetLongField(This, g_gmtoffField, 0); - - return inUtc; -} - -static jboolean android_text_format_Time_parse3339(JNIEnv* env, - jobject This, - jstring strObj) -{ - jsize len = env->GetStringLength(strObj); - if (len < 10) { - jniThrowException(env, "android/util/TimeFormatException", - "String too short --- expected at least 10 characters."); - return JNI_FALSE; - } - - jboolean inUtc = JNI_FALSE; - - ScopedStringChars s(env, strObj); - - // year - int n; - bool thrown = false; - n = get_char(env, s, 0, 1000, &thrown); - n += get_char(env, s, 1, 100, &thrown); - n += get_char(env, s, 2, 10, &thrown); - n += get_char(env, s, 3, 1, &thrown); - if (thrown) return JNI_FALSE; - env->SetIntField(This, g_yearField, n); - - // - - if (!check_char(env, s, 4, '-')) return JNI_FALSE; - - // month - n = get_char(env, s, 5, 10, &thrown); - n += get_char(env, s, 6, 1, &thrown); - --n; - if (thrown) return JNI_FALSE; - env->SetIntField(This, g_monField, n); - - // - - if (!check_char(env, s, 7, '-')) return JNI_FALSE; - - // day - n = get_char(env, s, 8, 10, &thrown); - n += get_char(env, s, 9, 1, &thrown); - if (thrown) return JNI_FALSE; - env->SetIntField(This, g_mdayField, n); - - if (len >= 19) { - // T - if (!check_char(env, s, 10, 'T')) return JNI_FALSE; - - env->SetBooleanField(This, g_allDayField, JNI_FALSE); - // hour - n = get_char(env, s, 11, 10, &thrown); - n += get_char(env, s, 12, 1, &thrown); - if (thrown) return JNI_FALSE; - int hour = n; - // env->SetIntField(This, g_hourField, n); - - // : - if (!check_char(env, s, 13, ':')) return JNI_FALSE; - - // minute - n = get_char(env, s, 14, 10, &thrown); - n += get_char(env, s, 15, 1, &thrown); - if (thrown) return JNI_FALSE; - int minute = n; - // env->SetIntField(This, g_minField, n); - - // : - if (!check_char(env, s, 16, ':')) return JNI_FALSE; - - // second - n = get_char(env, s, 17, 10, &thrown); - n += get_char(env, s, 18, 1, &thrown); - if (thrown) return JNI_FALSE; - env->SetIntField(This, g_secField, n); - - // skip the '.XYZ' -- we don't care about subsecond precision. - int tz_index = 19; - if (tz_index < len && s[tz_index] == '.') { - do { - tz_index++; - } while (tz_index < len - && s[tz_index] >= '0' - && s[tz_index] <= '9'); - } - - int offset = 0; - if (len > tz_index) { - char c = s[tz_index]; - - // NOTE: the offset is meant to be subtracted to get from local time - // to UTC. we therefore use 1 for '-' and -1 for '+'. - switch (c) { - case 'Z': - // Zulu time -- UTC - offset = 0; - break; - case '-': - offset = 1; - break; - case '+': - offset = -1; - break; - default: - jniThrowExceptionFmt(env, "android/util/TimeFormatException", - "Unexpected character 0x%02x at position %d. Expected + or -", - c, tz_index); - return JNI_FALSE; - } - inUtc = JNI_TRUE; - - if (offset != 0) { - if (len < tz_index + 6) { - jniThrowExceptionFmt(env, "android/util/TimeFormatException", - "Unexpected length; should be %d characters", - tz_index + 6); - return JNI_FALSE; - } - - // hour - n = get_char(env, s, tz_index + 1, 10, &thrown); - n += get_char(env, s, tz_index + 2, 1, &thrown); - if (thrown) return JNI_FALSE; - n *= offset; - hour += n; - - // : - if (!check_char(env, s, tz_index + 3, ':')) return JNI_FALSE; - - // minute - n = get_char(env, s, tz_index + 4, 10, &thrown); - n += get_char(env, s, tz_index + 5, 1, &thrown); - if (thrown) return JNI_FALSE; - n *= offset; - minute += n; - } - } - env->SetIntField(This, g_hourField, hour); - env->SetIntField(This, g_minField, minute); - - if (offset != 0) { - // we need to normalize after applying the hour and minute offsets - android_text_format_Time_normalize(env, This, false /* use isdst */); - // The timezone is set to UTC in the calling Java code. - } - } else { - env->SetBooleanField(This, g_allDayField, JNI_TRUE); - env->SetIntField(This, g_hourField, 0); - env->SetIntField(This, g_minField, 0); - env->SetIntField(This, g_secField, 0); - } - - env->SetIntField(This, g_wdayField, 0); - env->SetIntField(This, g_ydayField, 0); - env->SetIntField(This, g_isdstField, -1); - env->SetLongField(This, g_gmtoffField, 0); - - return inUtc; -} - -// ============================================================================ -/* - * JNI registration. - */ -static JNINativeMethod gMethods[] = { - /* name, signature, funcPtr */ - { "normalize", "(Z)J", (void*)android_text_format_Time_normalize }, - { "switchTimezone", "(Ljava/lang/String;)V", (void*)android_text_format_Time_switchTimezone }, - { "nativeCompare", "(Landroid/text/format/Time;Landroid/text/format/Time;)I", (void*)android_text_format_Time_compare }, - { "format1", "(Ljava/lang/String;)Ljava/lang/String;", (void*)android_text_format_Time_format }, - { "format2445", "()Ljava/lang/String;", (void*)android_text_format_Time_format2445 }, - { "toString", "()Ljava/lang/String;", (void*)android_text_format_Time_toString }, - { "nativeParse", "(Ljava/lang/String;)Z", (void*)android_text_format_Time_parse }, - { "nativeParse3339", "(Ljava/lang/String;)Z", (void*)android_text_format_Time_parse3339 }, - { "setToNow", "()V", (void*)android_text_format_Time_setToNow }, - { "toMillis", "(Z)J", (void*)android_text_format_Time_toMillis }, - { "set", "(J)V", (void*)android_text_format_Time_set } -}; - -int register_android_text_format_Time(JNIEnv* env) -{ - jclass timeClass = env->FindClass("android/text/format/Time"); - - g_timeClass = (jclass) env->NewGlobalRef(timeClass); - - g_allDayField = env->GetFieldID(timeClass, "allDay", "Z"); - g_secField = env->GetFieldID(timeClass, "second", "I"); - g_minField = env->GetFieldID(timeClass, "minute", "I"); - g_hourField = env->GetFieldID(timeClass, "hour", "I"); - g_mdayField = env->GetFieldID(timeClass, "monthDay", "I"); - g_monField = env->GetFieldID(timeClass, "month", "I"); - g_yearField = env->GetFieldID(timeClass, "year", "I"); - g_wdayField = env->GetFieldID(timeClass, "weekDay", "I"); - g_ydayField = env->GetFieldID(timeClass, "yearDay", "I"); - g_isdstField = env->GetFieldID(timeClass, "isDst", "I"); - g_gmtoffField = env->GetFieldID(timeClass, "gmtoff", "J"); - g_timezoneField = env->GetFieldID(timeClass, "timezone", "Ljava/lang/String;"); - - g_shortMonthsField = env->GetStaticFieldID(timeClass, "sShortMonths", "[Ljava/lang/String;"); - g_longMonthsField = env->GetStaticFieldID(timeClass, "sLongMonths", "[Ljava/lang/String;"); - g_longStandaloneMonthsField = env->GetStaticFieldID(timeClass, "sLongStandaloneMonths", "[Ljava/lang/String;"); - g_shortWeekdaysField = env->GetStaticFieldID(timeClass, "sShortWeekdays", "[Ljava/lang/String;"); - g_longWeekdaysField = env->GetStaticFieldID(timeClass, "sLongWeekdays", "[Ljava/lang/String;"); - g_timeOnlyFormatField = env->GetStaticFieldID(timeClass, "sTimeOnlyFormat", "Ljava/lang/String;"); - g_dateOnlyFormatField = env->GetStaticFieldID(timeClass, "sDateOnlyFormat", "Ljava/lang/String;"); - g_dateTimeFormatField = env->GetStaticFieldID(timeClass, "sDateTimeFormat", "Ljava/lang/String;"); - g_amField = env->GetStaticFieldID(timeClass, "sAm", "Ljava/lang/String;"); - g_pmField = env->GetStaticFieldID(timeClass, "sPm", "Ljava/lang/String;"); - g_dateCommandField = env->GetStaticFieldID(timeClass, "sDateCommand", "Ljava/lang/String;"); - g_localeField = env->GetStaticFieldID(timeClass, "sLocale", "Ljava/util/Locale;"); - - return AndroidRuntime::registerNativeMethods(env, "android/text/format/Time", gMethods, NELEM(gMethods)); -} - -}; // namespace android |