am fc6346d4
: Merge "Rewriting android.text.format.Time without the native _tz functions"
* commit 'fc6346d461c9ed9b499643db830885241cc82838': Rewriting android.text.format.Time without the native _tz functions
This commit is contained in:
@ -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;
|
||||
}
|
||||
calculator.copyFieldsFromTime(this);
|
||||
return calculator.format(format);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
519
core/java/android/text/format/TimeFormatter.java
Normal file
519
core/java/android/text/format/TimeFormatter.java
Normal file
@ -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);
|
||||
}
|
||||
}
|
@ -18,7 +18,11 @@ package android.util;
|
||||
|
||||
public class TimeFormatException extends RuntimeException
|
||||
{
|
||||
TimeFormatException(String s)
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public TimeFormatException(String s)
|
||||
{
|
||||
super(s);
|
||||
}
|
||||
|
@ -22,7 +22,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 \
|
||||
@ -76,7 +75,6 @@ LOCAL_SRC_FILES:= \
|
||||
android_net_TrafficStats.cpp \
|
||||
android_net_wifi_WifiNative.cpp \
|
||||
android_nio_utils.cpp \
|
||||
android_text_format_Time.cpp \
|
||||
android_util_AssetManager.cpp \
|
||||
android_util_Binder.cpp \
|
||||
android_util_EventLog.cpp \
|
||||
|
@ -132,7 +132,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);
|
||||
@ -1216,7 +1215,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),
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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
|
Reference in New Issue
Block a user