Home | History | Annotate | Download | only in jni
      1 /*
      2 ** Copyright 2006, The Android Open Source Project
      3 **
      4 ** Licensed under the Apache License, Version 2.0 (the "License");
      5 ** you may not use this file except in compliance with the License.
      6 ** You may obtain a copy of the License at
      7 **
      8 **     http://www.apache.org/licenses/LICENSE-2.0
      9 **
     10 ** Unless required by applicable law or agreed to in writing, software
     11 ** distributed under the License is distributed on an "AS IS" BASIS,
     12 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13 ** See the License for the specific language governing permissions and
     14 ** limitations under the License.
     15 */
     16 
     17 #define LOG_TAG "Log_println"
     18 
     19 #include <utils/Log.h>
     20 #include <utils/String8.h>
     21 #include <assert.h>
     22 
     23 #include "jni.h"
     24 #include "utils/misc.h"
     25 #include "android_runtime/AndroidRuntime.h"
     26 #include "TimeUtils.h"
     27 #include <nativehelper/JNIHelp.h>
     28 #include <cutils/tztime.h>
     29 
     30 namespace android {
     31 
     32 static jfieldID g_allDayField = 0;
     33 static jfieldID g_secField = 0;
     34 static jfieldID g_minField = 0;
     35 static jfieldID g_hourField = 0;
     36 static jfieldID g_mdayField = 0;
     37 static jfieldID g_monField = 0;
     38 static jfieldID g_yearField = 0;
     39 static jfieldID g_wdayField = 0;
     40 static jfieldID g_ydayField = 0;
     41 static jfieldID g_isdstField = 0;
     42 static jfieldID g_gmtoffField = 0;
     43 static jfieldID g_timezoneField = 0;
     44 
     45 static jfieldID g_shortMonthsField = 0;
     46 static jfieldID g_longMonthsField = 0;
     47 static jfieldID g_longStandaloneMonthsField = 0;
     48 static jfieldID g_shortWeekdaysField = 0;
     49 static jfieldID g_longWeekdaysField = 0;
     50 static jfieldID g_timeOnlyFormatField = 0;
     51 static jfieldID g_dateOnlyFormatField = 0;
     52 static jfieldID g_dateTimeFormatField = 0;
     53 static jfieldID g_amField = 0;
     54 static jfieldID g_pmField = 0;
     55 static jfieldID g_dateCommandField = 0;
     56 static jfieldID g_localeField = 0;
     57 
     58 static jclass g_timeClass = NULL;
     59 
     60 static inline bool java2time(JNIEnv* env, Time* t, jobject o)
     61 {
     62     t->t.tm_sec = env->GetIntField(o, g_secField);
     63     t->t.tm_min = env->GetIntField(o, g_minField);
     64     t->t.tm_hour = env->GetIntField(o, g_hourField);
     65     t->t.tm_mday = env->GetIntField(o, g_mdayField);
     66     t->t.tm_mon = env->GetIntField(o, g_monField);
     67     t->t.tm_year = (env->GetIntField(o, g_yearField))-1900;
     68     t->t.tm_wday = env->GetIntField(o, g_wdayField);
     69     t->t.tm_yday = env->GetIntField(o, g_ydayField);
     70     t->t.tm_isdst = env->GetIntField(o, g_isdstField);
     71     t->t.tm_gmtoff = env->GetLongField(o, g_gmtoffField);
     72     bool allDay = env->GetIntField(o, g_allDayField);
     73     if (allDay &&
     74 	((t->t.tm_sec !=0) || (t->t.tm_min != 0) || (t->t.tm_hour != 0))) {
     75         char msg[100];
     76 	sprintf(msg, "allDay is true but sec, min, hour are not 0.");
     77 	jniThrowException(env, "java/lang/IllegalArgumentException", msg);
     78 	return false;
     79     }
     80     return true;
     81 }
     82 
     83 static inline void time2java(JNIEnv* env, jobject o, const Time &t)
     84 {
     85     env->SetIntField(o, g_secField, t.t.tm_sec);
     86     env->SetIntField(o, g_minField, t.t.tm_min);
     87     env->SetIntField(o, g_hourField, t.t.tm_hour);
     88     env->SetIntField(o, g_mdayField, t.t.tm_mday);
     89     env->SetIntField(o, g_monField, t.t.tm_mon);
     90     env->SetIntField(o, g_yearField, t.t.tm_year+1900);
     91     env->SetIntField(o, g_wdayField, t.t.tm_wday);
     92     env->SetIntField(o, g_ydayField, t.t.tm_yday);
     93     env->SetIntField(o, g_isdstField, t.t.tm_isdst);
     94     env->SetLongField(o, g_gmtoffField, t.t.tm_gmtoff);
     95 }
     96 
     97 #define ACQUIRE_TIMEZONE(This, t) \
     98     jstring timezoneString_##This \
     99             = (jstring) env->GetObjectField(This, g_timezoneField); \
    100     t.timezone = env->GetStringUTFChars(timezoneString_##This, NULL);
    101 
    102 #define RELEASE_TIMEZONE(This, t) \
    103     env->ReleaseStringUTFChars(timezoneString_##This, t.timezone);
    104 
    105 
    106 // ============================================================================
    107 
    108 static jlong android_text_format_Time_normalize(JNIEnv* env, jobject This,
    109                                            jboolean ignoreDst)
    110 {
    111     Time t;
    112     if (!java2time(env, &t, This)) return 0L;
    113     ACQUIRE_TIMEZONE(This, t)
    114 
    115     int64_t result = t.toMillis(ignoreDst != 0);
    116 
    117     time2java(env, This, t);
    118     RELEASE_TIMEZONE(This, t)
    119 
    120     return result;
    121 }
    122 
    123 static void android_text_format_Time_switchTimezone(JNIEnv* env, jobject This,
    124                             jstring timezoneObject)
    125 {
    126     Time t;
    127     if (!java2time(env, &t, This)) return;
    128     ACQUIRE_TIMEZONE(This, t)
    129 
    130     const char* timezone = env->GetStringUTFChars(timezoneObject, NULL);
    131 
    132     t.switchTimezone(timezone);
    133 
    134     time2java(env, This, t);
    135     env->ReleaseStringUTFChars(timezoneObject, timezone);
    136     RELEASE_TIMEZONE(This, t)
    137 
    138     // we do this here because there's no point in reallocating the string
    139     env->SetObjectField(This, g_timezoneField, timezoneObject);
    140 }
    141 
    142 static jint android_text_format_Time_compare(JNIEnv* env, jobject clazz,
    143                             jobject aObject, jobject bObject)
    144 {
    145     Time a, b;
    146 
    147     if (!java2time(env, &a, aObject)) return 0;
    148     ACQUIRE_TIMEZONE(aObject, a)
    149 
    150     if (!java2time(env, &b, bObject)) return 0;
    151     ACQUIRE_TIMEZONE(bObject, b)
    152 
    153     int result = Time::compare(a, b);
    154 
    155     RELEASE_TIMEZONE(aObject, a)
    156     RELEASE_TIMEZONE(bObject, b)
    157 
    158     return result;
    159 }
    160 
    161 static jstring android_text_format_Time_format2445(JNIEnv* env, jobject This)
    162 {
    163     Time t;
    164     if (!java2time(env, &t, This)) return env->NewStringUTF("");
    165     bool allDay = env->GetIntField(This, g_allDayField);
    166 
    167     if (!allDay) {
    168         ACQUIRE_TIMEZONE(This, t)
    169         bool inUtc = strcmp("UTC", t.timezone) == 0;
    170         short buf[16];
    171         t.format2445(buf, true);
    172         RELEASE_TIMEZONE(This, t)
    173         if (inUtc) {
    174             // The letter 'Z' is appended to the end so allow for one
    175             // more character in the buffer.
    176             return env->NewString((jchar*)buf, 16);
    177         } else {
    178             return env->NewString((jchar*)buf, 15);
    179         }
    180     } else {
    181         short buf[8];
    182         t.format2445(buf, false);
    183         return env->NewString((jchar*)buf, 8);
    184     }
    185 }
    186 
    187 static jstring android_text_format_Time_format(JNIEnv* env, jobject This,
    188                             jstring formatObject)
    189 {
    190     // We only teardown and setup our 'locale' struct and other state
    191     // when the Java-side locale changed.  This is safe to do here
    192     // without locking because we're always called from Java code
    193     // synchronized on the class instance.
    194     static jobject js_locale_previous = NULL;
    195     static struct strftime_locale locale;
    196     static jstring js_mon[12], js_month[12], js_wday[7], js_weekday[7];
    197     static jstring js_standalone_month[12];
    198     static jstring js_X_fmt, js_x_fmt, js_c_fmt, js_am, js_pm, js_date_fmt;
    199 
    200     Time t;
    201     if (!java2time(env, &t, This)) return env->NewStringUTF("");
    202 
    203     jclass timeClass = g_timeClass;
    204     jobject js_locale = (jobject) env->GetStaticObjectField(timeClass, g_localeField);
    205     if (js_locale_previous != js_locale) {
    206         if (js_locale_previous != NULL) {
    207             // Free the old one.
    208             for (int i = 0; i < 12; i++) {
    209                 env->ReleaseStringUTFChars(js_mon[i], locale.mon[i]);
    210                 env->ReleaseStringUTFChars(js_month[i], locale.month[i]);
    211                 env->ReleaseStringUTFChars(js_standalone_month[i], locale.standalone_month[i]);
    212                 env->DeleteGlobalRef(js_mon[i]);
    213                 env->DeleteGlobalRef(js_month[i]);
    214                 env->DeleteGlobalRef(js_standalone_month[i]);
    215             }
    216 
    217             for (int i = 0; i < 7; i++) {
    218                 env->ReleaseStringUTFChars(js_wday[i], locale.wday[i]);
    219                 env->ReleaseStringUTFChars(js_weekday[i], locale.weekday[i]);
    220                 env->DeleteGlobalRef(js_wday[i]);
    221                 env->DeleteGlobalRef(js_weekday[i]);
    222             }
    223 
    224             env->ReleaseStringUTFChars(js_X_fmt, locale.X_fmt);
    225             env->ReleaseStringUTFChars(js_x_fmt, locale.x_fmt);
    226             env->ReleaseStringUTFChars(js_c_fmt, locale.c_fmt);
    227             env->ReleaseStringUTFChars(js_am, locale.am);
    228             env->ReleaseStringUTFChars(js_pm, locale.pm);
    229             env->ReleaseStringUTFChars(js_date_fmt, locale.date_fmt);
    230             env->DeleteGlobalRef(js_X_fmt);
    231             env->DeleteGlobalRef(js_x_fmt);
    232             env->DeleteGlobalRef(js_c_fmt);
    233             env->DeleteGlobalRef(js_am);
    234             env->DeleteGlobalRef(js_pm);
    235             env->DeleteGlobalRef(js_date_fmt);
    236         }
    237         js_locale_previous = js_locale;
    238 
    239         jobjectArray ja;
    240         ja = (jobjectArray) env->GetStaticObjectField(timeClass, g_shortMonthsField);
    241         for (int i = 0; i < 12; i++) {
    242             js_mon[i] = (jstring) env->NewGlobalRef(env->GetObjectArrayElement(ja, i));
    243             locale.mon[i] = env->GetStringUTFChars(js_mon[i], NULL);
    244         }
    245 
    246         ja = (jobjectArray) env->GetStaticObjectField(timeClass, g_longMonthsField);
    247         for (int i = 0; i < 12; i++) {
    248             js_month[i] = (jstring) env->NewGlobalRef(env->GetObjectArrayElement(ja, i));
    249             locale.month[i] = env->GetStringUTFChars(js_month[i], NULL);
    250         }
    251 
    252         ja = (jobjectArray) env->GetStaticObjectField(timeClass, g_longStandaloneMonthsField);
    253         for (int i = 0; i < 12; i++) {
    254             js_standalone_month[i] = (jstring) env->NewGlobalRef(env->GetObjectArrayElement(ja, i));
    255             locale.standalone_month[i] = env->GetStringUTFChars(js_standalone_month[i], NULL);
    256         }
    257 
    258         ja = (jobjectArray) env->GetStaticObjectField(timeClass, g_shortWeekdaysField);
    259         for (int i = 0; i < 7; i++) {
    260             js_wday[i] = (jstring) env->NewGlobalRef(env->GetObjectArrayElement(ja, i));
    261             locale.wday[i] = env->GetStringUTFChars(js_wday[i], NULL);
    262         }
    263 
    264         ja = (jobjectArray) env->GetStaticObjectField(timeClass, g_longWeekdaysField);
    265         for (int i = 0; i < 7; i++) {
    266             js_weekday[i] = (jstring) env->NewGlobalRef(env->GetObjectArrayElement(ja, i));
    267             locale.weekday[i] = env->GetStringUTFChars(js_weekday[i], NULL);
    268         }
    269 
    270         js_X_fmt = (jstring) env->NewGlobalRef(env->GetStaticObjectField(
    271                                                        timeClass, g_timeOnlyFormatField));
    272         locale.X_fmt = env->GetStringUTFChars(js_X_fmt, NULL);
    273 
    274         js_x_fmt = (jstring) env->NewGlobalRef(env->GetStaticObjectField(
    275                                                        timeClass, g_dateOnlyFormatField));
    276         locale.x_fmt = env->GetStringUTFChars(js_x_fmt, NULL);
    277 
    278         js_c_fmt = (jstring) env->NewGlobalRef(env->GetStaticObjectField(
    279                                                        timeClass, g_dateTimeFormatField));
    280         locale.c_fmt = env->GetStringUTFChars(js_c_fmt, NULL);
    281 
    282         js_am = (jstring) env->NewGlobalRef(env->GetStaticObjectField(
    283                                                     timeClass, g_amField));
    284         locale.am = env->GetStringUTFChars(js_am, NULL);
    285 
    286         js_pm = (jstring) env->NewGlobalRef(env->GetStaticObjectField(
    287                                                     timeClass, g_pmField));
    288         locale.pm = env->GetStringUTFChars(js_pm, NULL);
    289 
    290         js_date_fmt = (jstring) env->NewGlobalRef(env->GetStaticObjectField(
    291                                                           timeClass, g_dateCommandField));
    292         locale.date_fmt = env->GetStringUTFChars(js_date_fmt, NULL);
    293     }
    294 
    295     ACQUIRE_TIMEZONE(This, t)
    296 
    297     const char* format = env->GetStringUTFChars(formatObject, NULL);
    298 
    299     String8 r = t.format(format, &locale);
    300 
    301     env->ReleaseStringUTFChars(formatObject, format);
    302     RELEASE_TIMEZONE(This, t)
    303 
    304     return env->NewStringUTF(r.string());
    305 }
    306 
    307 
    308 static jstring android_text_format_Time_toString(JNIEnv* env, jobject This)
    309 {
    310     Time t;
    311     if (!java2time(env, &t, This)) return env->NewStringUTF("");;
    312     ACQUIRE_TIMEZONE(This, t)
    313 
    314     String8 r = t.toString();
    315 
    316     RELEASE_TIMEZONE(This, t)
    317 
    318     return env->NewStringUTF(r.string());
    319 }
    320 
    321 static void android_text_format_Time_setToNow(JNIEnv* env, jobject This)
    322 {
    323     env->SetBooleanField(This, g_allDayField, JNI_FALSE);
    324     Time t;
    325     ACQUIRE_TIMEZONE(This, t)
    326 
    327     t.setToNow();
    328 
    329     time2java(env, This, t);
    330     RELEASE_TIMEZONE(This, t)
    331 }
    332 
    333 static jlong android_text_format_Time_toMillis(JNIEnv* env, jobject This,
    334                                         jboolean ignoreDst)
    335 {
    336     Time t;
    337     if (!java2time(env, &t, This)) return 0L;
    338     ACQUIRE_TIMEZONE(This, t)
    339 
    340     int64_t result = t.toMillis(ignoreDst != 0);
    341 
    342     RELEASE_TIMEZONE(This, t)
    343 
    344     return result;
    345 }
    346 
    347 static void android_text_format_Time_set(JNIEnv* env, jobject This, jlong millis)
    348 {
    349     env->SetBooleanField(This, g_allDayField, JNI_FALSE);
    350     Time t;
    351     ACQUIRE_TIMEZONE(This, t)
    352 
    353     t.set(millis);
    354 
    355     time2java(env, This, t);
    356     RELEASE_TIMEZONE(This, t)
    357 }
    358 
    359 
    360 // ============================================================================
    361 // Just do this here because it's not worth recreating the strings
    362 
    363 static int get_char(JNIEnv* env, const jchar *s, int spos, int mul,
    364                     bool *thrown)
    365 {
    366     jchar c = s[spos];
    367     if (c >= '0' && c <= '9') {
    368         return (c - '0') * mul;
    369     } else {
    370         if (!*thrown) {
    371             char msg[100];
    372             sprintf(msg, "Parse error at pos=%d", spos);
    373             jniThrowException(env, "android/util/TimeFormatException", msg);
    374             *thrown = true;
    375         }
    376         return 0;
    377     }
    378 }
    379 
    380 static bool check_char(JNIEnv* env, const jchar *s, int spos, jchar expected)
    381 {
    382     jchar c = s[spos];
    383     if (c != expected) {
    384         char msg[100];
    385 	sprintf(msg, "Unexpected character 0x%02x at pos=%d.  Expected %c.", c, spos,
    386 		expected);
    387 	jniThrowException(env, "android/util/TimeFormatException", msg);
    388 	return false;
    389     }
    390     return true;
    391 }
    392 
    393 
    394 static jboolean android_text_format_Time_parse(JNIEnv* env, jobject This, jstring strObj)
    395 {
    396     jsize len = env->GetStringLength(strObj);
    397     const jchar *s = env->GetStringChars(strObj, NULL);
    398 
    399     bool thrown = false;
    400     int n;
    401     jboolean inUtc = false;
    402 
    403     if (len < 8) {
    404         char msg[100];
    405         sprintf(msg, "String too short -- expected at least 8 characters.");
    406 	jniThrowException(env, "android/util/TimeFormatException", msg);
    407 	return false;
    408     }
    409 
    410     // year
    411     n = get_char(env, s, 0, 1000, &thrown);
    412     n += get_char(env, s, 1, 100, &thrown);
    413     n += get_char(env, s, 2, 10, &thrown);
    414     n += get_char(env, s, 3, 1, &thrown);
    415     if (thrown) return false;
    416     env->SetIntField(This, g_yearField, n);
    417 
    418     // month
    419     n = get_char(env, s, 4, 10, &thrown);
    420     n += get_char(env, s, 5, 1, &thrown);
    421     n--;
    422     if (thrown) return false;
    423     env->SetIntField(This, g_monField, n);
    424 
    425     // day of month
    426     n = get_char(env, s, 6, 10, &thrown);
    427     n += get_char(env, s, 7, 1, &thrown);
    428     if (thrown) return false;
    429     env->SetIntField(This, g_mdayField, n);
    430 
    431     if (len > 8) {
    432         // T
    433         if (!check_char(env, s, 8, 'T')) return false;
    434         env->SetBooleanField(This, g_allDayField, JNI_FALSE);
    435 
    436         // hour
    437         n = get_char(env, s, 9, 10, &thrown);
    438         n += get_char(env, s, 10, 1, &thrown);
    439         if (thrown) return false;
    440         env->SetIntField(This, g_hourField, n);
    441 
    442         // min
    443         n = get_char(env, s, 11, 10, &thrown);
    444         n += get_char(env, s, 12, 1, &thrown);
    445         if (thrown) return false;
    446         env->SetIntField(This, g_minField, n);
    447 
    448         // sec
    449         n = get_char(env, s, 13, 10, &thrown);
    450         n += get_char(env, s, 14, 1, &thrown);
    451         if (thrown) return false;
    452         env->SetIntField(This, g_secField, n);
    453 
    454         if (len > 15) {
    455             // Z
    456             if (!check_char(env, s, 15, 'Z')) return false;
    457 	    inUtc = true;
    458         }
    459     } else {
    460         env->SetBooleanField(This, g_allDayField, JNI_TRUE);
    461         env->SetIntField(This, g_hourField, 0);
    462         env->SetIntField(This, g_minField, 0);
    463         env->SetIntField(This, g_secField, 0);
    464     }
    465 
    466     env->SetIntField(This, g_wdayField, 0);
    467     env->SetIntField(This, g_ydayField, 0);
    468     env->SetIntField(This, g_isdstField, -1);
    469     env->SetLongField(This, g_gmtoffField, 0);
    470 
    471     env->ReleaseStringChars(strObj, s);
    472     return inUtc;
    473 }
    474 
    475 static jboolean android_text_format_Time_parse3339(JNIEnv* env,
    476                                            jobject This,
    477                                            jstring strObj)
    478 {
    479     jsize len = env->GetStringLength(strObj);
    480     const jchar *s = env->GetStringChars(strObj, NULL);
    481 
    482     bool thrown = false;
    483     int n;
    484     jboolean inUtc = false;
    485 
    486     if (len < 10) {
    487         jniThrowException(env, "android/util/TimeFormatException",
    488                 "Time input is too short; must be at least 10 characters");
    489         return false;
    490     }
    491 
    492     // year
    493     n = get_char(env, s, 0, 1000, &thrown);
    494     n += get_char(env, s, 1, 100, &thrown);
    495     n += get_char(env, s, 2, 10, &thrown);
    496     n += get_char(env, s, 3, 1, &thrown);
    497     if (thrown) return false;
    498     env->SetIntField(This, g_yearField, n);
    499 
    500     // -
    501     if (!check_char(env, s, 4, '-')) return false;
    502 
    503     // month
    504     n = get_char(env, s, 5, 10, &thrown);
    505     n += get_char(env, s, 6, 1, &thrown);
    506     --n;
    507     if (thrown) return false;
    508     env->SetIntField(This, g_monField, n);
    509 
    510     // -
    511     if (!check_char(env, s, 7, '-')) return false;
    512 
    513     // day
    514     n = get_char(env, s, 8, 10, &thrown);
    515     n += get_char(env, s, 9, 1, &thrown);
    516     if (thrown) return false;
    517     env->SetIntField(This, g_mdayField, n);
    518 
    519     if (len >= 19) {
    520         // T
    521         if (!check_char(env, s, 10, 'T')) return false;
    522 
    523 	env->SetBooleanField(This, g_allDayField, JNI_FALSE);
    524         // hour
    525         n = get_char(env, s, 11, 10, &thrown);
    526         n += get_char(env, s, 12, 1, &thrown);
    527         if (thrown) return false;
    528 	int hour = n;
    529         // env->SetIntField(This, g_hourField, n);
    530 
    531 	// :
    532 	if (!check_char(env, s, 13, ':')) return false;
    533 
    534 	// minute
    535         n = get_char(env, s, 14, 10, &thrown);
    536         n += get_char(env, s, 15, 1, &thrown);
    537         if (thrown) return false;
    538 	int minute = n;
    539         // env->SetIntField(This, g_minField, n);
    540 
    541 	// :
    542 	if (!check_char(env, s, 16, ':')) return false;
    543 
    544 	// second
    545         n = get_char(env, s, 17, 10, &thrown);
    546         n += get_char(env, s, 18, 1, &thrown);
    547         if (thrown) return false;
    548         env->SetIntField(This, g_secField, n);
    549 
    550         // skip the '.XYZ' -- we don't care about subsecond precision.
    551         int tz_index = 19;
    552         if (tz_index < len && s[tz_index] == '.') {
    553             do {
    554                 tz_index++;
    555             } while (tz_index < len
    556                 && s[tz_index] >= '0'
    557                 && s[tz_index] <= '9');
    558         }
    559 
    560         int offset = 0;
    561         if (len > tz_index) {
    562             char c = s[tz_index];
    563 
    564 	    // NOTE: the offset is meant to be subtracted to get from local time
    565 	    // to UTC.  we therefore use 1 for '-' and -1 for '+'.
    566 	    switch (c) {
    567 	    case 'Z':
    568 	        // Zulu time -- UTC
    569 	        offset = 0;
    570 		break;
    571 	    case '-':
    572                 offset = 1;
    573 	        break;
    574 	    case '+':
    575                 offset = -1;
    576 	        break;
    577 	    default:
    578 	        char msg[100];
    579 	        sprintf(msg, "Unexpected character 0x%02x at position %d.  Expected + or -",
    580 			c, tz_index);
    581 	        jniThrowException(env, "android/util/TimeFormatException", msg);
    582 	        return false;
    583 	    }
    584             inUtc = true;
    585 
    586 	    if (offset != 0) {
    587 	        if (len < tz_index + 6) {
    588 	            char msg[100];
    589 	            sprintf(msg, "Unexpected length; should be %d characters", tz_index + 6);
    590 	            jniThrowException(env, "android/util/TimeFormatException", msg);
    591 	            return false;
    592 	        }
    593 
    594 	        // hour
    595 	        n = get_char(env, s, tz_index + 1, 10, &thrown);
    596 		n += get_char(env, s, tz_index + 2, 1, &thrown);
    597 		if (thrown) return false;
    598 		n *= offset;
    599 		hour += n;
    600 
    601 		// :
    602 		if (!check_char(env, s, tz_index + 3, ':')) return false;
    603 
    604 		// minute
    605 		n = get_char(env, s, tz_index + 4, 10, &thrown);
    606 		n += get_char(env, s, tz_index + 5, 1, &thrown);
    607 		if (thrown) return false;
    608 		n *= offset;
    609 		minute += n;
    610 	    }
    611 	}
    612 	env->SetIntField(This, g_hourField, hour);
    613         env->SetIntField(This, g_minField, minute);
    614 
    615 	if (offset != 0) {
    616 	    // we need to normalize after applying the hour and minute offsets
    617 	    android_text_format_Time_normalize(env, This, false /* use isdst */);
    618 	    // The timezone is set to UTC in the calling Java code.
    619 	}
    620     } else {
    621 	env->SetBooleanField(This, g_allDayField, JNI_TRUE);
    622         env->SetIntField(This, g_hourField, 0);
    623         env->SetIntField(This, g_minField, 0);
    624         env->SetIntField(This, g_secField, 0);
    625     }
    626 
    627     env->SetIntField(This, g_wdayField, 0);
    628     env->SetIntField(This, g_ydayField, 0);
    629     env->SetIntField(This, g_isdstField, -1);
    630     env->SetLongField(This, g_gmtoffField, 0);
    631 
    632     env->ReleaseStringChars(strObj, s);
    633     return inUtc;
    634 }
    635 
    636 // ============================================================================
    637 /*
    638  * JNI registration.
    639  */
    640 static JNINativeMethod gMethods[] = {
    641     /* name, signature, funcPtr */
    642     { "normalize",               "(Z)J",                                        (void*)android_text_format_Time_normalize },
    643     { "switchTimezone",          "(Ljava/lang/String;)V",                       (void*)android_text_format_Time_switchTimezone },
    644     { "compare",                 "(Landroid/text/format/Time;Landroid/text/format/Time;)I",     (void*)android_text_format_Time_compare },
    645     { "format1",                 "(Ljava/lang/String;)Ljava/lang/String;",      (void*)android_text_format_Time_format },
    646     { "format2445",              "()Ljava/lang/String;",                        (void*)android_text_format_Time_format2445 },
    647     { "toString",                "()Ljava/lang/String;",                        (void*)android_text_format_Time_toString },
    648     { "nativeParse",             "(Ljava/lang/String;)Z",                       (void*)android_text_format_Time_parse },
    649     { "nativeParse3339",         "(Ljava/lang/String;)Z",                       (void*)android_text_format_Time_parse3339 },
    650     { "setToNow",                "()V",                                         (void*)android_text_format_Time_setToNow },
    651     { "toMillis",                "(Z)J",                                        (void*)android_text_format_Time_toMillis },
    652     { "set",                     "(J)V",                                        (void*)android_text_format_Time_set }
    653 };
    654 
    655 int register_android_text_format_Time(JNIEnv* env)
    656 {
    657     jclass timeClass = env->FindClass("android/text/format/Time");
    658 
    659     g_timeClass = (jclass) env->NewGlobalRef(timeClass);
    660 
    661     g_allDayField = env->GetFieldID(timeClass, "allDay", "Z");
    662     g_secField = env->GetFieldID(timeClass, "second", "I");
    663     g_minField = env->GetFieldID(timeClass, "minute", "I");
    664     g_hourField = env->GetFieldID(timeClass, "hour", "I");
    665     g_mdayField = env->GetFieldID(timeClass, "monthDay", "I");
    666     g_monField = env->GetFieldID(timeClass, "month", "I");
    667     g_yearField = env->GetFieldID(timeClass, "year", "I");
    668     g_wdayField = env->GetFieldID(timeClass, "weekDay", "I");
    669     g_ydayField = env->GetFieldID(timeClass, "yearDay", "I");
    670     g_isdstField = env->GetFieldID(timeClass, "isDst", "I");
    671     g_gmtoffField = env->GetFieldID(timeClass, "gmtoff", "J");
    672     g_timezoneField = env->GetFieldID(timeClass, "timezone", "Ljava/lang/String;");
    673 
    674     g_shortMonthsField = env->GetStaticFieldID(timeClass, "sShortMonths", "[Ljava/lang/String;");
    675     g_longMonthsField = env->GetStaticFieldID(timeClass, "sLongMonths", "[Ljava/lang/String;");
    676     g_longStandaloneMonthsField = env->GetStaticFieldID(timeClass, "sLongStandaloneMonths", "[Ljava/lang/String;");
    677     g_shortWeekdaysField = env->GetStaticFieldID(timeClass, "sShortWeekdays", "[Ljava/lang/String;");
    678     g_longWeekdaysField = env->GetStaticFieldID(timeClass, "sLongWeekdays", "[Ljava/lang/String;");
    679     g_timeOnlyFormatField = env->GetStaticFieldID(timeClass, "sTimeOnlyFormat", "Ljava/lang/String;");
    680     g_dateOnlyFormatField = env->GetStaticFieldID(timeClass, "sDateOnlyFormat", "Ljava/lang/String;");
    681     g_dateTimeFormatField = env->GetStaticFieldID(timeClass, "sDateTimeFormat", "Ljava/lang/String;");
    682     g_amField = env->GetStaticFieldID(timeClass, "sAm", "Ljava/lang/String;");
    683     g_pmField = env->GetStaticFieldID(timeClass, "sPm", "Ljava/lang/String;");
    684     g_dateCommandField = env->GetStaticFieldID(timeClass, "sDateCommand", "Ljava/lang/String;");
    685     g_localeField = env->GetStaticFieldID(timeClass, "sLocale", "Ljava/util/Locale;");
    686 
    687     return AndroidRuntime::registerNativeMethods(env, "android/text/format/Time", gMethods, NELEM(gMethods));
    688 }
    689 
    690 }; // namespace android
    691