From f013e1afd1e68af5e3b868c26a653bbfb39538f8 Mon Sep 17 00:00:00 2001 From: The Android Open Source Project Date: Wed, 17 Dec 2008 18:05:43 -0800 Subject: Code drop from //branches/cupcake/...@124589 --- core/jni/android_text_format_Time.cpp | 623 ++++++++++++++++++++++++++++++++++ 1 file changed, 623 insertions(+) create mode 100644 core/jni/android_text_format_Time.cpp (limited to 'core/jni/android_text_format_Time.cpp') diff --git a/core/jni/android_text_format_Time.cpp b/core/jni/android_text_format_Time.cpp new file mode 100644 index 0000000..8e41ec7 --- /dev/null +++ b/core/jni/android_text_format_Time.cpp @@ -0,0 +1,623 @@ +/* +** 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 +#include +#include + +#include "jni.h" +#include "utils/misc.h" +#include "android_runtime/AndroidRuntime.h" +#include +#include +#include + +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_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 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->GetIntField(o, g_allDayField); + if (allDay && + ((t->t.tm_sec !=0) || (t->t.tm_min != 0) || (t->t.tm_hour != 0))) { + char msg[100]; + sprintf(msg, "allDay is true but sec, min, hour are not 0."); + jniThrowException(env, "java/lang/IllegalArgumentException", msg); + 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 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 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->GetIntField(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) +{ + Time t; + struct strftime_locale locale; + jclass timeClass = env->FindClass("android/text/format/Time"); + jstring js_mon[12], js_month[12], js_wday[7], js_weekday[7]; + jstring js_X_fmt, js_x_fmt, js_c_fmt, js_am, js_pm, js_date_fmt; + jobjectArray ja; + + if (!java2time(env, &t, This)) return env->NewStringUTF(""); + + ja = (jobjectArray) env->GetStaticObjectField(timeClass, g_shortMonthsField); + for (int i = 0; i < 12; i++) { + js_mon[i] = (jstring) 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++) { + js_month[i] = (jstring) env->GetObjectArrayElement(ja, i); + locale.month[i] = env->GetStringUTFChars(js_month[i], NULL); + } + + ja = (jobjectArray) env->GetStaticObjectField(timeClass, g_shortWeekdaysField); + for (int i = 0; i < 7; i++) { + js_wday[i] = (jstring) env->GetObjectArrayElement(ja, i); + locale.wday[i] = env->GetStringUTFChars(js_wday[i], NULL); + } + + ja = (jobjectArray) env->GetStaticObjectField(timeClass, g_longWeekdaysField); + for (int i = 0; i < 7; i++) { + js_weekday[i] = (jstring) env->GetObjectArrayElement(ja, i); + locale.weekday[i] = env->GetStringUTFChars(js_weekday[i], NULL); + } + + js_X_fmt = (jstring) env->GetStaticObjectField(timeClass, g_timeOnlyFormatField); + locale.X_fmt = env->GetStringUTFChars(js_X_fmt, NULL); + + js_x_fmt = (jstring) env->GetStaticObjectField(timeClass, g_dateOnlyFormatField); + locale.x_fmt = env->GetStringUTFChars(js_x_fmt, NULL); + + js_c_fmt = (jstring) env->GetStaticObjectField(timeClass, g_dateTimeFormatField); + locale.c_fmt = env->GetStringUTFChars(js_c_fmt, NULL); + + js_am = (jstring) env->GetStaticObjectField(timeClass, g_amField); + locale.am = env->GetStringUTFChars(js_am, NULL); + + js_pm = (jstring) env->GetStaticObjectField(timeClass, g_pmField); + locale.pm = env->GetStringUTFChars(js_pm, NULL); + + js_date_fmt = (jstring) 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) + + for (int i = 0; i < 12; i++) { + env->ReleaseStringUTFChars(js_mon[i], locale.mon[i]); + env->ReleaseStringUTFChars(js_month[i], locale.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->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); + + 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 result; +} + +static void android_text_format_Time_set(JNIEnv* env, jobject This, jlong millis) +{ + env->SetBooleanField(This, g_allDayField, JNI_FALSE); + Time t; + if (!java2time(env, &t, This)) return; + 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 jchar *s, int spos, int mul, + bool *thrown) +{ + jchar c = s[spos]; + if (c >= '0' && c <= '9') { + return (c - '0') * mul; + } else { + char msg[100]; + sprintf(msg, "Parse error at pos=%d", spos); + jniThrowException(env, "android/util/TimeFormatException", msg); + *thrown = true; + return 0; + } +} + +static bool check_char(JNIEnv* env, const jchar *s, int spos, jchar expected) +{ + jchar c = s[spos]; + if (c != expected) { + char msg[100]; + sprintf(msg, "Unexpected %c at pos=%d. Expected %c.", c, spos, + expected); + jniThrowException(env, "android/util/TimeFormatException", msg); + return false; + } + return true; +} + + +static jboolean android_text_format_Time_parse(JNIEnv* env, jobject This, jstring strObj) +{ + jsize len = env->GetStringLength(strObj); + const jchar *s = env->GetStringChars(strObj, NULL); + + bool thrown = false; + int n; + jboolean inUtc = false; + + if (len < 8) { + char msg[100]; + sprintf(msg, "String too short -- expected at least 8 characters."); + jniThrowException(env, "android/util/TimeFormatException", msg); + return false; + } + + // year + 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 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 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 false; + env->SetIntField(This, g_mdayField, n); + + if (len > 8) { + // T + if (!check_char(env, s, 8, 'T')) return 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 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 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 false; + env->SetIntField(This, g_secField, n); + + if (len > 15) { + // Z + if (!check_char(env, s, 15, 'Z')) return false; + inUtc = 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); + + env->ReleaseStringChars(strObj, s); + return inUtc; +} + +static jboolean android_text_format_Time_parse3339(JNIEnv* env, + jobject This, + jstring strObj) +{ + jsize len = env->GetStringLength(strObj); + const jchar *s = env->GetStringChars(strObj, NULL); + + bool thrown = false; + int n; + jboolean inUtc = false; + + // year + 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 false; + env->SetIntField(This, g_yearField, n); + + // - + if (!check_char(env, s, 4, '-')) return false; + + // month + n = get_char(env, s, 5, 10, &thrown); + n += get_char(env, s, 6, 1, &thrown); + --n; + if (thrown) return false; + env->SetIntField(This, g_monField, n); + + // - + if (!check_char(env, s, 7, '-')) return false; + + // day + n = get_char(env, s, 8, 10, &thrown); + n += get_char(env, s, 9, 1, &thrown); + if (thrown) return false; + env->SetIntField(This, g_mdayField, n); + + if (len >= 17) { + // T + if (!check_char(env, s, 10, 'T')) return 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 false; + int hour = n; + // env->SetIntField(This, g_hourField, n); + + // : + if (!check_char(env, s, 13, ':')) return false; + + // minute + n = get_char(env, s, 14, 10, &thrown); + n += get_char(env, s, 15, 1, &thrown); + if (thrown) return false; + int minute = n; + // env->SetIntField(This, g_minField, n); + + // : + if (!check_char(env, s, 16, ':')) return false; + + // second + n = get_char(env, s, 17, 10, &thrown); + n += get_char(env, s, 18, 1, &thrown); + if (thrown) return false; + env->SetIntField(This, g_secField, n); + + // skip the '.XYZ' -- we don't care about subsecond precision. + int offset = 0; + if (len >= 23) { + char c = s[23]; + + // 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: + char msg[100]; + sprintf(msg, "Unexpected %c at position 19. Expected + or -", + c); + jniThrowException(env, "android/util/TimeFormatException", msg); + return false; + } + inUtc = true; + + if (offset != 0) { + // hour + n = get_char(env, s, 24, 10, &thrown); + n += get_char(env, s, 25, 1, &thrown); + if (thrown) return false; + n *= offset; + hour += n; + + // : + if (!check_char(env, s, 26, ':')) return false; + + // minute + n = get_char(env, s, 27, 10, &thrown); + n += get_char(env, s, 28, 1, &thrown); + if (thrown) return 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); + + env->ReleaseStringChars(strObj, s); + 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 }, + { "compare", "(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_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_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;"); + + return AndroidRuntime::registerNativeMethods(env, "android/text/format/Time", gMethods, NELEM(gMethods)); +} + +}; // namespace android + -- cgit v1.1