blob: c1dd4998c33935a492a5d247e0644d249f32d16f [file] [log] [blame]
/*
** 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 <utils/TimeUtils.h>
#include <nativehelper/JNIHelp.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 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_pim_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_pim_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_pim_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_pim_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_pim_Time_format(JNIEnv* env, jobject This,
jstring formatObject)
{
Time t;
if (!java2time(env, &t, This)) return env->NewStringUTF("");
ACQUIRE_TIMEZONE(This, t)
const char* format = env->GetStringUTFChars(formatObject, NULL);
String8 r = t.format(format);
env->ReleaseStringUTFChars(formatObject, format);
RELEASE_TIMEZONE(This, t)
return env->NewStringUTF(r.string());
}
static jstring android_pim_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_pim_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_pim_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_pim_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 void android_pim_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;
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;
env->SetIntField(This, g_yearField, n);
n = get_char(env, s, 4, 10, &thrown);
n += get_char(env, s, 5, 1, &thrown);
n--;
if (thrown) return;
env->SetIntField(This, g_monField, n);
n = get_char(env, s, 6, 10, &thrown);
n += get_char(env, s, 7, 1, &thrown);
if (thrown) return;
env->SetIntField(This, g_mdayField, n);
if (len >= 15) {
env->SetBooleanField(This, g_allDayField, JNI_FALSE);
n = get_char(env, s, 9, 10, &thrown);
n += get_char(env, s, 10, 1, &thrown);
if (thrown) return;
env->SetIntField(This, g_hourField, n);
n = get_char(env, s, 11, 10, &thrown);
n += get_char(env, s, 12, 1, &thrown);
if (thrown) return;
env->SetIntField(This, g_minField, n);
n = get_char(env, s, 13, 10, &thrown);
n += get_char(env, s, 14, 1, &thrown);
if (thrown) return;
env->SetIntField(This, g_secField, n);
} 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);
}
static jboolean android_pim_Time_parse2445(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 {
// all day
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_pim_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_pim_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_pim_Time_normalize },
{ "switchTimezone", "(Ljava/lang/String;)V", (void*)android_pim_Time_switchTimezone },
{ "compare", "(Landroid/pim/Time;Landroid/pim/Time;)I", (void*)android_pim_Time_compare },
{ "format", "(Ljava/lang/String;)Ljava/lang/String;", (void*)android_pim_Time_format },
{ "format2445", "()Ljava/lang/String;", (void*)android_pim_Time_format2445 },
{ "toString", "()Ljava/lang/String;", (void*)android_pim_Time_toString },
{ "parse", "(Ljava/lang/String;)V", (void*)android_pim_Time_parse },
{ "nativeParse2445", "(Ljava/lang/String;)Z", (void*)android_pim_Time_parse2445 },
{ "nativeParse3339", "(Ljava/lang/String;)Z", (void*)android_pim_Time_parse3339 },
{ "setToNow", "()V", (void*)android_pim_Time_setToNow },
{ "toMillis", "(Z)J", (void*)android_pim_Time_toMillis },
{ "set", "(J)V", (void*)android_pim_Time_set }
};
int register_android_pim_Time(JNIEnv* env)
{
jclass timeClass = env->FindClass("android/pim/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;");
return AndroidRuntime::registerNativeMethods(env, "android/pim/Time", gMethods, NELEM(gMethods));
}
}; // namespace android