Adds incomplete Calendar implementation

This commit is contained in:
konsoletyper 2014-05-19 17:40:16 +04:00
parent fdf216a400
commit 9df49fad0a
7 changed files with 1571 additions and 9 deletions

View File

@ -0,0 +1,26 @@
/*
* Copyright 2014 Alexey Andreev.
*
* 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.
*/
package org.teavm.classlib.impl.unicode;
/**
*
* @author Alexey Andreev <konsoletyper@gmail.com>
*/
public class CLDRHelper {
public static String getCode(String language, String country) {
return !country.isEmpty() ? language + "-" + country : language;
}
}

View File

@ -0,0 +1,33 @@
/*
* Copyright 2014 Alexey Andreev.
*
* 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.
*/
package org.teavm.classlib.java.util;
import java.io.IOException;
import org.teavm.codegen.SourceWriter;
import org.teavm.javascript.ni.Generator;
import org.teavm.javascript.ni.GeneratorContext;
import org.teavm.model.MethodReference;
/**
*
* @author Alexey Andreev
*/
public class CalendarNativeGenerator implements Generator {
@Override
public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException {
}
}

View File

@ -17,6 +17,9 @@ package org.teavm.classlib.java.util;
import java.io.IOException; import java.io.IOException;
import org.teavm.codegen.SourceWriter; import org.teavm.codegen.SourceWriter;
import org.teavm.dependency.DependencyChecker;
import org.teavm.dependency.DependencyPlugin;
import org.teavm.dependency.MethodDependency;
import org.teavm.javascript.ni.Generator; import org.teavm.javascript.ni.Generator;
import org.teavm.javascript.ni.GeneratorContext; import org.teavm.javascript.ni.GeneratorContext;
import org.teavm.model.MethodReference; import org.teavm.model.MethodReference;
@ -25,7 +28,7 @@ import org.teavm.model.MethodReference;
* *
* @author Alexey Andreev <konsoletyper@gmail.com> * @author Alexey Andreev <konsoletyper@gmail.com>
*/ */
public class DateNativeGenerator implements Generator { public class DateNativeGenerator implements Generator, DependencyPlugin {
@Override @Override
public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException { public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException {
switch (methodRef.getName()) { switch (methodRef.getName()) {
@ -55,6 +58,24 @@ public class DateNativeGenerator implements Generator {
case "setSeconds": case "setSeconds":
generateSetMethod(context, writer, methodRef.getName()); generateSetMethod(context, writer, methodRef.getName());
break; break;
case "toString":
case "toGMTString":
generateToString(context, writer, methodRef.getName());
break;
case "toLocaleFormat":
generateToLocaleFormat(context, writer);
break;
}
}
@Override
public void methodAchieved(DependencyChecker checker, MethodDependency method) {
switch (method.getMethod().getName()) {
case "toString":
case "toLocaleFormat":
case "toGMTString":
method.getResult().propagate("java.lang.String");
break;
} }
} }
@ -90,4 +111,15 @@ public class DateNativeGenerator implements Generator {
writer.append("return date.").append(methodName).append("(").append(context.getParameterName(2)).append(");") writer.append("return date.").append(methodName).append("(").append(context.getParameterName(2)).append(");")
.softNewLine(); .softNewLine();
} }
private void generateToString(GeneratorContext context, SourceWriter writer, String method) throws IOException {
writer.append("return $rt_str(new Date(").append(context.getParameterName(1)).append(").").append(method)
.append("());").softNewLine();
}
private void generateToLocaleFormat(GeneratorContext context, SourceWriter writer) throws IOException {
writer.append("return $rt_str(new Date(").append(context.getParameterName(1))
.append(").toLocaleFormat($rt_ustr(").append(context.getParameterName(2)).append(")));")
.softNewLine();
}
} }

View File

@ -26,7 +26,9 @@ import org.teavm.javascript.Renderer;
import org.teavm.javascript.ni.Generator; import org.teavm.javascript.ni.Generator;
import org.teavm.javascript.ni.GeneratorContext; import org.teavm.javascript.ni.GeneratorContext;
import org.teavm.model.MethodReference; import org.teavm.model.MethodReference;
import com.google.gson.*; import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
/** /**
* *
@ -36,6 +38,8 @@ public class LocaleSettingsNativeGenerator implements Generator {
private ClassLoader classLoader; private ClassLoader classLoader;
private Properties properties; private Properties properties;
private Map<String, LocaleInfo> knownLocales = new LinkedHashMap<>(); private Map<String, LocaleInfo> knownLocales = new LinkedHashMap<>();
private Map<String, Integer> minDaysMap = new LinkedHashMap<>();
private Map<String, Integer> firstDayMap = new LinkedHashMap<>();
private Set<String> availableLocales = new LinkedHashSet<>(); private Set<String> availableLocales = new LinkedHashSet<>();
private Set<String> availableLanguages = new LinkedHashSet<>(); private Set<String> availableLanguages = new LinkedHashSet<>();
private Set<String> availableCountries = new LinkedHashSet<>(); private Set<String> availableCountries = new LinkedHashSet<>();
@ -83,6 +87,10 @@ public class LocaleSettingsNativeGenerator implements Generator {
if (!entry.getName().endsWith(".json")) { if (!entry.getName().endsWith(".json")) {
continue; continue;
} }
if (entry.getName().equals("supplemental/weekData.json")) {
readWeekData(input);
continue;
}
int objectIndex = entry.getName().lastIndexOf('/'); int objectIndex = entry.getName().lastIndexOf('/');
String objectName = entry.getName().substring(objectIndex + 1); String objectName = entry.getName().substring(objectIndex + 1);
String localeName = entry.getName().substring(0, objectIndex); String localeName = entry.getName().substring(0, objectIndex);
@ -125,16 +133,50 @@ public class LocaleSettingsNativeGenerator implements Generator {
private void readCountries(String localeCode, LocaleInfo locale, InputStream input) { private void readCountries(String localeCode, LocaleInfo locale, InputStream input) {
JsonObject root = (JsonObject)new JsonParser().parse(new InputStreamReader(input)); JsonObject root = (JsonObject)new JsonParser().parse(new InputStreamReader(input));
JsonObject languagesJson = root.get("main").getAsJsonObject().get(localeCode).getAsJsonObject() JsonObject countriesJson = root.get("main").getAsJsonObject().get(localeCode).getAsJsonObject()
.get("localeDisplayNames").getAsJsonObject().get("territories").getAsJsonObject(); .get("localeDisplayNames").getAsJsonObject().get("territories").getAsJsonObject();
for (Map.Entry<String, JsonElement> property : languagesJson.entrySet()) { for (Map.Entry<String, JsonElement> property : countriesJson.entrySet()) {
String language = property.getKey(); String country = property.getKey();
if (availableCountries.contains(language)) { if (availableCountries.contains(country)) {
locale.territories.put(language, property.getValue().getAsString()); locale.territories.put(country, property.getValue().getAsString());
} }
} }
} }
private void readWeekData(InputStream input) {
JsonObject root = (JsonObject)new JsonParser().parse(new InputStreamReader(input));
JsonObject weekJson = root.get("supplemental").getAsJsonObject().get("weekData").getAsJsonObject();
JsonObject minDaysJson = weekJson.get("minDays").getAsJsonObject();
for (Map.Entry<String, JsonElement> property : minDaysJson.entrySet()) {
minDaysMap.put(property.getKey(), property.getValue().getAsInt());
}
JsonObject firstDayJson = weekJson.get("firstDay").getAsJsonObject();
for (Map.Entry<String, JsonElement> property : firstDayJson.entrySet()) {
firstDayMap.put(property.getKey(), getNumericDay(property.getValue().getAsString()));
}
}
private int getNumericDay(String day) {
switch (day) {
case "sun":
return 1;
case "mon":
return 2;
case "tue":
return 3;
case "wed":
return 4;
case "thu":
return 5;
case "fri":
return 6;
case "sat":
return 7;
default:
throw new IllegalArgumentException("Can't recognize day name: " + day);
}
}
@Override @Override
public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException { public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException {
init(); init();
@ -149,6 +191,9 @@ public class LocaleSettingsNativeGenerator implements Generator {
} }
private void generateReadCLDR(SourceWriter writer) throws IOException { private void generateReadCLDR(SourceWriter writer) throws IOException {
writer.append("if (").appendClass("java.util.Locale").append("$CLDR").append("{").indent().softNewLine();
writer.append("return;").softNewLine();
writer.outdent().append("}").softNewLine();
writer.appendClass("java.util.Locale").append(".$CLDR = {").indent().softNewLine(); writer.appendClass("java.util.Locale").append(".$CLDR = {").indent().softNewLine();
boolean firstLocale = true; boolean firstLocale = true;
for (Map.Entry<String, LocaleInfo> entry : knownLocales.entrySet()) { for (Map.Entry<String, LocaleInfo> entry : knownLocales.entrySet()) {

View File

@ -0,0 +1,443 @@
/*
* Copyright 2014 Alexey Andreev.
*
* 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.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
package org.teavm.classlib.java.util;
import org.teavm.classlib.impl.unicode.CLDRHelper;
import org.teavm.classlib.java.io.TSerializable;
import org.teavm.classlib.java.lang.TCloneable;
import org.teavm.classlib.java.lang.TComparable;
public abstract class TCalendar implements TSerializable, TCloneable, TComparable<TCalendar> {
protected boolean areFieldsSet;
protected int[] fields;
protected boolean[] isSet;
protected boolean isTimeSet;
protected long time;
transient int lastTimeFieldSet;
transient int lastDateFieldSet;
private boolean lenient;
private int firstDayOfWeek;
private int minimalDaysInFirstWeek;
public static final int JANUARY = 0;
public static final int FEBRUARY = 1;
public static final int MARCH = 2;
public static final int APRIL = 3;
public static final int MAY = 4;
public static final int JUNE = 5;
public static final int JULY = 6;
public static final int AUGUST = 7;
public static final int SEPTEMBER = 8;
public static final int OCTOBER = 9;
public static final int NOVEMBER = 10;
public static final int DECEMBER = 11;
public static final int UNDECIMBER = 12;
public static final int SUNDAY = 1;
public static final int MONDAY = 2;
public static final int TUESDAY = 3;
public static final int WEDNESDAY = 4;
public static final int THURSDAY = 5;
public static final int FRIDAY = 6;
public static final int SATURDAY = 7;
public static final int ERA = 0;
public static final int YEAR = 1;
public static final int MONTH = 2;
public static final int WEEK_OF_YEAR = 3;
public static final int WEEK_OF_MONTH = 4;
public static final int DATE = 5;
public static final int DAY_OF_MONTH = 5;
public static final int DAY_OF_YEAR = 6;
public static final int DAY_OF_WEEK = 7;
public static final int DAY_OF_WEEK_IN_MONTH = 8;
public static final int AM_PM = 9;
public static final int HOUR = 10;
public static final int HOUR_OF_DAY = 11;
public static final int MINUTE = 12;
public static final int SECOND = 13;
public static final int MILLISECOND = 14;
public static final int ZONE_OFFSET = 15;
public static final int DST_OFFSET = 16;
public static final int FIELD_COUNT = 17;
public static final int AM = 0;
public static final int PM = 1;
@SuppressWarnings("nls")
private static String[] fieldNames = { "ERA=", "YEAR=", "MONTH=", "WEEK_OF_YEAR=", "WEEK_OF_MONTH=",
"DAY_OF_MONTH=", "DAY_OF_YEAR=", "DAY_OF_WEEK=", "DAY_OF_WEEK_IN_MONTH=", "AM_PM=", "HOUR=", "HOUR_OF_DAY",
"MINUTE=", "SECOND=", "MILLISECOND=", "ZONE_OFFSET=", "DST_OFFSET=" };
protected TCalendar() {
this(TLocale.getDefault());
}
protected TCalendar(TLocale locale) {
fields = new int[FIELD_COUNT];
isSet = new boolean[FIELD_COUNT];
areFieldsSet = isTimeSet = false;
setLenient(true);
initLocales();
String localeCode = CLDRHelper.getCode(locale.getLanguage(), locale.getCountry());
setFirstDayOfWeek(getFirstDayOfWeek(localeCode));
setMinimalDaysInFirstWeek(getMinimalDaysInFirstWeek(localeCode));
}
// Generated by JCLPlugin
private static native void initLocales();
// TODO: implement using CLDR
private static native int getFirstDayOfWeek(String localeCode);
// TODO: implement using CLDR
private static native int getMinimalDaysInFirstWeek(String localeCode);
abstract public void add(int field, int value);
public boolean after(Object calendar) {
if (!(calendar instanceof TCalendar)) {
return false;
}
return getTimeInMillis() > ((TCalendar) calendar).getTimeInMillis();
}
public boolean before(Object calendar) {
if (!(calendar instanceof TCalendar)) {
return false;
}
return getTimeInMillis() < ((TCalendar) calendar).getTimeInMillis();
}
public final void clear() {
for (int i = 0; i < FIELD_COUNT; i++) {
fields[i] = 0;
isSet[i] = false;
}
areFieldsSet = isTimeSet = false;
}
public final void clear(int field) {
fields[field] = 0;
isSet[field] = false;
areFieldsSet = isTimeSet = false;
}
@Override
public Object clone() {
try {
TCalendar clone = (TCalendar) super.clone();
clone.fields = fields.clone();
clone.isSet = isSet.clone();
return clone;
} catch (CloneNotSupportedException e) {
return null;
}
}
protected void complete() {
if (!isTimeSet) {
computeTime();
isTimeSet = true;
}
if (!areFieldsSet) {
computeFields();
areFieldsSet = true;
}
}
protected abstract void computeFields();
protected abstract void computeTime();
@Override
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (!(object instanceof TCalendar)) {
return false;
}
TCalendar cal = (TCalendar) object;
return getTimeInMillis() == cal.getTimeInMillis() && isLenient() == cal.isLenient() &&
getFirstDayOfWeek() == cal.getFirstDayOfWeek() &&
getMinimalDaysInFirstWeek() == cal.getMinimalDaysInFirstWeek();
}
public int get(int field) {
complete();
return fields[field];
}
public int getActualMaximum(int field) {
int value, next;
if (getMaximum(field) == (next = getLeastMaximum(field))) {
return next;
}
complete();
long orgTime = time;
set(field, next);
do {
value = next;
roll(field, true);
next = get(field);
} while (next > value);
time = orgTime;
areFieldsSet = false;
return value;
}
public int getActualMinimum(int field) {
int value, next;
if (getMinimum(field) == (next = getGreatestMinimum(field))) {
return next;
}
complete();
long orgTime = time;
set(field, next);
do {
value = next;
roll(field, false);
next = get(field);
} while (next < value);
time = orgTime;
areFieldsSet = false;
return value;
}
public static synchronized TLocale[] getAvailableLocales() {
return TLocale.getAvailableLocales();
}
public int getFirstDayOfWeek() {
return firstDayOfWeek;
}
abstract public int getGreatestMinimum(int field);
public static synchronized TCalendar getInstance() {
return new TGregorianCalendar();
}
public static synchronized TCalendar getInstance(TLocale locale) {
return new TGregorianCalendar(locale);
}
abstract public int getLeastMaximum(int field);
abstract public int getMaximum(int field);
public int getMinimalDaysInFirstWeek() {
return minimalDaysInFirstWeek;
}
abstract public int getMinimum(int field);
public final TDate getTime() {
return new TDate(getTimeInMillis());
}
public long getTimeInMillis() {
if (!isTimeSet) {
computeTime();
isTimeSet = true;
}
return time;
}
@Override
public int hashCode() {
return (isLenient() ? 1237 : 1231) + getFirstDayOfWeek() + getMinimalDaysInFirstWeek();
}
protected final int internalGet(int field) {
return fields[field];
}
public boolean isLenient() {
return lenient;
}
public final boolean isSet(int field) {
return isSet[field];
}
public void roll(int field, int value) {
boolean increment = value >= 0;
int count = increment ? value : -value;
for (int i = 0; i < count; i++) {
roll(field, increment);
}
}
abstract public void roll(int field, boolean increment);
public void set(int field, int value) {
fields[field] = value;
isSet[field] = true;
areFieldsSet = isTimeSet = false;
if (field > MONTH && field < AM_PM) {
lastDateFieldSet = field;
}
if (field == HOUR || field == HOUR_OF_DAY) {
lastTimeFieldSet = field;
}
if (field == AM_PM) {
lastTimeFieldSet = HOUR;
}
}
public final void set(int year, int month, int day) {
set(YEAR, year);
set(MONTH, month);
set(DATE, day);
}
public final void set(int year, int month, int day, int hourOfDay, int minute) {
set(year, month, day);
set(HOUR_OF_DAY, hourOfDay);
set(MINUTE, minute);
}
public final void set(int year, int month, int day, int hourOfDay, int minute, int second) {
set(year, month, day, hourOfDay, minute);
set(SECOND, second);
}
public void setFirstDayOfWeek(int value) {
firstDayOfWeek = value;
}
public void setLenient(boolean value) {
lenient = value;
}
public void setMinimalDaysInFirstWeek(int value) {
minimalDaysInFirstWeek = value;
}
public final void setTime(TDate date) {
setTimeInMillis(date.getTime());
}
public void setTimeInMillis(long milliseconds) {
if (!isTimeSet || !areFieldsSet || time != milliseconds) {
time = milliseconds;
isTimeSet = true;
areFieldsSet = false;
complete();
}
}
@Override
public String toString() {
StringBuilder result = new StringBuilder(getClass().getName() + "[time=" +
(isTimeSet ? String.valueOf(time) : "?") + ",areFieldsSet=" + areFieldsSet + ",lenient=" + lenient +
",firstDayOfWeek=" + firstDayOfWeek + ",minimalDaysInFirstWeek=" +
minimalDaysInFirstWeek);
for (int i = 0; i < FIELD_COUNT; i++) {
result.append(',');
result.append(fieldNames[i]);
result.append('=');
if (isSet[i]) {
result.append(fields[i]);
} else {
result.append('?');
}
}
result.append(']');
return result.toString();
}
@Override
public int compareTo(TCalendar anotherCalendar) {
if (null == anotherCalendar) {
throw new NullPointerException();
}
long timeInMillis = getTimeInMillis();
long anotherTimeInMillis = anotherCalendar.getTimeInMillis();
if (timeInMillis > anotherTimeInMillis) {
return 1;
}
if (timeInMillis == anotherTimeInMillis) {
return 0;
}
return -1;
}
}

View File

@ -15,14 +15,16 @@
*/ */
package org.teavm.classlib.java.util; package org.teavm.classlib.java.util;
import org.teavm.classlib.java.lang.TComparable;
import org.teavm.classlib.java.lang.TSystem; import org.teavm.classlib.java.lang.TSystem;
import org.teavm.dependency.PluggableDependency;
import org.teavm.javascript.ni.GeneratedBy; import org.teavm.javascript.ni.GeneratedBy;
/** /**
* *
* @author Alexey Andreev <konsoletyper@gmail.com> * @author Alexey Andreev <konsoletyper@gmail.com>
*/ */
public class TDate { public class TDate implements TComparable<TDate> {
private long value; private long value;
@GeneratedBy(DateNativeGenerator.class) @GeneratedBy(DateNativeGenerator.class)
@ -155,6 +157,45 @@ public class TDate {
return value > when.value; return value > when.value;
} }
@Override
public boolean equals(Object obj) {
if (!(obj instanceof TDate)) {
return false;
}
TDate other = (TDate)obj;
return value == other.value;
}
@Override
public int compareTo(TDate other) {
return Long.compare(value, other.value);
}
@Override
public int hashCode() {
return (int)value ^ (int)(value >>> 32);
}
@Override
public String toString() {
return toString(value);
}
@Deprecated
public String toLocaleString() {
return toLocaleFormat(value, "%c");
}
@Deprecated
public String toGMTString() {
return toGMTString(value);
}
@Deprecated
public int getTimezoneOffset() {
return getTimezoneOffset(value);
}
@GeneratedBy(DateNativeGenerator.class) @GeneratedBy(DateNativeGenerator.class)
private static native int getFullYear(double date); private static native int getFullYear(double date);
@ -176,7 +217,6 @@ public class TDate {
@GeneratedBy(DateNativeGenerator.class) @GeneratedBy(DateNativeGenerator.class)
private static native int getDay(double date); private static native int getDay(double date);
@GeneratedBy(DateNativeGenerator.class) @GeneratedBy(DateNativeGenerator.class)
private static native int getHours(double date); private static native int getHours(double date);
@ -203,4 +243,20 @@ public class TDate {
@GeneratedBy(DateNativeGenerator.class) @GeneratedBy(DateNativeGenerator.class)
private static native double buildNumericUTC(int year, int month, int date, int hrs, int min, int sec); private static native double buildNumericUTC(int year, int month, int date, int hrs, int min, int sec);
@GeneratedBy(DateNativeGenerator.class)
@PluggableDependency(DateNativeGenerator.class)
private static native String toString(double value);
@GeneratedBy(DateNativeGenerator.class)
@PluggableDependency(DateNativeGenerator.class)
private static native String toLocaleFormat(double value, String format);
@GeneratedBy(DateNativeGenerator.class)
@PluggableDependency(DateNativeGenerator.class)
private static native String toGMTString(double value);
@GeneratedBy(DateNativeGenerator.class)
@PluggableDependency(DateNativeGenerator.class)
static native int getTimezoneOffset(double value);
} }

View File

@ -0,0 +1,927 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
package org.teavm.classlib.java.util;
public class TGregorianCalendar extends TCalendar {
public static final int BC = 0;
public static final int AD = 1;
private static final long defaultGregorianCutover = -12219292800000l;
private long gregorianCutover = defaultGregorianCutover;
private transient int changeYear = 1582;
private transient int julianSkew = ((changeYear - 2000) / 400) + julianError() - ((changeYear - 2000) / 100);
static byte[] DaysInMonth = new byte[] { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
private static int[] DaysInYear = new int[] { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };
private static int[] maximums = new int[] { 1, 292278994, 11, 53, 6, 31, 366, 7, 6, 1, 11, 23, 59, 59, 999,
14 * 3600 * 1000, 7200000 };
private static int[] minimums = new int[] { 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, -13 * 3600 * 1000, 0 };
private static int[] leastMaximums = new int[] { 1, 292269054, 11, 50, 3, 28, 355, 7, 3, 1, 11, 23, 59, 59, 999,
50400000, 1200000 };
private boolean isCached;
private int cachedFields[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
private long nextMidnightMillis = 0L;
private long lastMidnightMillis = 0L;
private int currentYearSkew = 10;
private int lastYearSkew = 0;
public TGregorianCalendar() {
this(TLocale.getDefault());
}
public TGregorianCalendar(int year, int month, int day) {
set(year, month, day);
}
public TGregorianCalendar(int year, int month, int day, int hour, int minute) {
set(year, month, day, hour, minute);
}
public TGregorianCalendar(int year, int month, int day, int hour, int minute, int second) {
set(year, month, day, hour, minute, second);
}
TGregorianCalendar(long milliseconds) {
this(false);
setTimeInMillis(milliseconds);
}
public TGregorianCalendar(TLocale locale) {
super(locale);
setTimeInMillis(System.currentTimeMillis());
}
TGregorianCalendar(@SuppressWarnings("unused") boolean ignored) {
setFirstDayOfWeek(SUNDAY);
setMinimalDaysInFirstWeek(1);
}
@Override
public void add(int field, int value) {
if (value == 0) {
return;
}
if (field < 0 || field >= ZONE_OFFSET) {
throw new IllegalArgumentException();
}
isCached = false;
if (field == ERA) {
complete();
if (fields[ERA] == AD) {
if (value >= 0) {
return;
}
set(ERA, BC);
} else {
if (value <= 0) {
return;
}
set(ERA, AD);
}
complete();
return;
}
if (field == YEAR || field == MONTH) {
complete();
if (field == MONTH) {
int month = fields[MONTH] + value;
if (month < 0) {
value = (month - 11) / 12;
month = 12 + (month % 12);
} else {
value = month / 12;
}
set(MONTH, month % 12);
}
set(YEAR, fields[YEAR] + value);
int days = daysInMonth(isLeapYear(fields[YEAR]), fields[MONTH]);
if (fields[DATE] > days) {
set(DATE, days);
}
complete();
return;
}
long multiplier = 0;
getTimeInMillis(); // Update the time
switch (field) {
case MILLISECOND:
time += value;
break;
case SECOND:
time += value * 1000L;
break;
case MINUTE:
time += value * 60000L;
break;
case HOUR:
case HOUR_OF_DAY:
time += value * 3600000L;
break;
case AM_PM:
multiplier = 43200000L;
break;
case DATE:
case DAY_OF_YEAR:
case DAY_OF_WEEK:
multiplier = 86400000L;
break;
case WEEK_OF_YEAR:
case WEEK_OF_MONTH:
case DAY_OF_WEEK_IN_MONTH:
multiplier = 604800000L;
break;
}
if (multiplier > 0) {
int offset = getTimeZoneOffset(time);
time += value * multiplier;
int newOffset = getTimeZoneOffset(time);
// Adjust for moving over a DST boundary
if (newOffset != offset) {
time += offset - newOffset;
}
}
areFieldsSet = false;
complete();
}
@Override
public Object clone() {
TGregorianCalendar thisClone = (TGregorianCalendar) super.clone();
thisClone.cachedFields = cachedFields.clone();
return thisClone;
}
private final void fullFieldsCalc(long timeVal, int millis, int zoneOffset) {
long days = timeVal / 86400000;
if (millis < 0) {
millis += 86400000;
days--;
}
// Cannot add ZONE_OFFSET to time as it might overflow
millis += zoneOffset;
while (millis < 0) {
millis += 86400000;
days--;
}
while (millis >= 86400000) {
millis -= 86400000;
days++;
}
int dayOfYear = computeYearAndDay(days, timeVal + zoneOffset);
fields[DAY_OF_YEAR] = dayOfYear;
if (fields[YEAR] == changeYear && gregorianCutover <= timeVal + zoneOffset) {
dayOfYear += currentYearSkew;
}
int month = dayOfYear / 32;
boolean leapYear = isLeapYear(fields[YEAR]);
int date = dayOfYear - daysInYear(leapYear, month);
if (date > daysInMonth(leapYear, month)) {
date -= daysInMonth(leapYear, month);
month++;
}
fields[DAY_OF_WEEK] = mod7(days - 3) + 1;
int dstOffset = getTimeZoneOffset(timeVal);
if (fields[YEAR] > 0) {
dstOffset -= zoneOffset;
}
fields[DST_OFFSET] = dstOffset;
if (dstOffset != 0) {
long oldDays = days;
millis += dstOffset;
if (millis < 0) {
millis += 86400000;
days--;
} else if (millis >= 86400000) {
millis -= 86400000;
days++;
}
if (oldDays != days) {
dayOfYear = computeYearAndDay(days, timeVal - zoneOffset + dstOffset);
fields[DAY_OF_YEAR] = dayOfYear;
if (fields[YEAR] == changeYear && gregorianCutover <= timeVal - zoneOffset + dstOffset) {
dayOfYear += currentYearSkew;
}
month = dayOfYear / 32;
leapYear = isLeapYear(fields[YEAR]);
date = dayOfYear - daysInYear(leapYear, month);
if (date > daysInMonth(leapYear, month)) {
date -= daysInMonth(leapYear, month);
month++;
}
fields[DAY_OF_WEEK] = mod7(days - 3) + 1;
}
}
fields[MILLISECOND] = (millis % 1000);
millis /= 1000;
fields[SECOND] = (millis % 60);
millis /= 60;
fields[MINUTE] = (millis % 60);
millis /= 60;
fields[HOUR_OF_DAY] = (millis % 24);
fields[AM_PM] = fields[HOUR_OF_DAY] > 11 ? 1 : 0;
fields[HOUR] = fields[HOUR_OF_DAY] % 12;
if (fields[YEAR] <= 0) {
fields[ERA] = BC;
fields[YEAR] = -fields[YEAR] + 1;
} else {
fields[ERA] = AD;
}
fields[MONTH] = month;
fields[DATE] = date;
fields[DAY_OF_WEEK_IN_MONTH] = (date - 1) / 7 + 1;
fields[WEEK_OF_MONTH] = (date - 1 + mod7(days - date - 2 - (getFirstDayOfWeek() - 1))) / 7 + 1;
int daysFromStart = mod7(days - 3 - (fields[DAY_OF_YEAR] - 1) - (getFirstDayOfWeek() - 1));
int week = (fields[DAY_OF_YEAR] - 1 + daysFromStart) / 7 +
(7 - daysFromStart >= getMinimalDaysInFirstWeek() ? 1 : 0);
if (week == 0) {
fields[WEEK_OF_YEAR] = 7 - mod7(daysFromStart - (isLeapYear(fields[YEAR] - 1) ? 2 : 1)) >= getMinimalDaysInFirstWeek() ? 53
: 52;
} else if (fields[DAY_OF_YEAR] >= (leapYear ? 367 : 366) - mod7(daysFromStart + (leapYear ? 2 : 1))) {
fields[WEEK_OF_YEAR] = 7 - mod7(daysFromStart + (leapYear ? 2 : 1)) >= getMinimalDaysInFirstWeek() ? 1
: week;
} else {
fields[WEEK_OF_YEAR] = week;
}
}
private final void cachedFieldsCheckAndGet(long timeVal, long newTimeMillis, long newTimeMillisAdjusted,
int millis, int zoneOffset) {
int dstOffset = fields[DST_OFFSET];
if (!isCached || newTimeMillis >= nextMidnightMillis || newTimeMillis <= lastMidnightMillis ||
cachedFields[4] != zoneOffset || (dstOffset == 0 && (newTimeMillisAdjusted >= nextMidnightMillis)) ||
(dstOffset != 0 && (newTimeMillisAdjusted <= lastMidnightMillis))) {
fullFieldsCalc(timeVal, millis, zoneOffset);
isCached = false;
} else {
fields[YEAR] = cachedFields[0];
fields[MONTH] = cachedFields[1];
fields[DATE] = cachedFields[2];
fields[DAY_OF_WEEK] = cachedFields[3];
fields[ERA] = cachedFields[5];
fields[WEEK_OF_YEAR] = cachedFields[6];
fields[WEEK_OF_MONTH] = cachedFields[7];
fields[DAY_OF_YEAR] = cachedFields[8];
fields[DAY_OF_WEEK_IN_MONTH] = cachedFields[9];
}
}
private static int getTimeZoneOffset(double time) {
return TDate.getTimezoneOffset(time);
}
@Override
protected void computeFields() {
int zoneOffset = getTimeZoneOffset(time);
if (!isSet[ZONE_OFFSET]) {
fields[ZONE_OFFSET] = zoneOffset;
}
int millis = (int) (time % 86400000);
int savedMillis = millis;
int dstOffset = fields[DST_OFFSET];
// compute without a change in daylight saving time
int offset = zoneOffset + dstOffset;
long newTime = time + offset;
if (time > 0L && newTime < 0L && offset > 0) {
newTime = 0x7fffffffffffffffL;
} else if (time < 0L && newTime > 0L && offset < 0) {
newTime = 0x8000000000000000L;
}
if (isCached) {
if (millis < 0) {
millis += 86400000;
}
// Cannot add ZONE_OFFSET to time as it might overflow
millis += zoneOffset;
millis += dstOffset;
if (millis < 0) {
millis += 86400000;
} else if (millis >= 86400000) {
millis -= 86400000;
}
fields[MILLISECOND] = (millis % 1000);
millis /= 1000;
fields[SECOND] = (millis % 60);
millis /= 60;
fields[MINUTE] = (millis % 60);
millis /= 60;
fields[HOUR_OF_DAY] = (millis % 24);
millis /= 24;
fields[AM_PM] = fields[HOUR_OF_DAY] > 11 ? 1 : 0;
fields[HOUR] = fields[HOUR_OF_DAY] % 12;
long newTimeAdjusted = newTime;
if (newTime > 0L && newTimeAdjusted < 0L && dstOffset == 0) {
newTimeAdjusted = 0x7fffffffffffffffL;
} else if (newTime < 0L && newTimeAdjusted > 0L && dstOffset != 0) {
newTimeAdjusted = 0x8000000000000000L;
}
cachedFieldsCheckAndGet(time, newTime, newTimeAdjusted, savedMillis, zoneOffset);
} else {
fullFieldsCalc(time, savedMillis, zoneOffset);
}
for (int i = 0; i < FIELD_COUNT; i++) {
isSet[i] = true;
}
// Caching
if (!isCached && newTime != 0x7fffffffffffffffL && newTime != 0x8000000000000000L) {
int cacheMillis = 0;
cachedFields[0] = fields[YEAR];
cachedFields[1] = fields[MONTH];
cachedFields[2] = fields[DATE];
cachedFields[3] = fields[DAY_OF_WEEK];
cachedFields[4] = zoneOffset;
cachedFields[5] = fields[ERA];
cachedFields[6] = fields[WEEK_OF_YEAR];
cachedFields[7] = fields[WEEK_OF_MONTH];
cachedFields[8] = fields[DAY_OF_YEAR];
cachedFields[9] = fields[DAY_OF_WEEK_IN_MONTH];
cacheMillis += (23 - fields[HOUR_OF_DAY]) * 60 * 60 * 1000;
cacheMillis += (59 - fields[MINUTE]) * 60 * 1000;
cacheMillis += (59 - fields[SECOND]) * 1000;
nextMidnightMillis = newTime + cacheMillis;
cacheMillis = fields[HOUR_OF_DAY] * 60 * 60 * 1000;
cacheMillis += fields[MINUTE] * 60 * 1000;
cacheMillis += fields[SECOND] * 1000;
lastMidnightMillis = newTime - cacheMillis;
isCached = true;
}
}
@Override
protected void computeTime() {
if (!isLenient()) {
if (isSet[HOUR_OF_DAY]) {
if (fields[HOUR_OF_DAY] < 0 || fields[HOUR_OF_DAY] > 23) {
throw new IllegalArgumentException();
}
} else if (isSet[HOUR] && (fields[HOUR] < 0 || fields[HOUR] > 11)) {
throw new IllegalArgumentException();
}
if (isSet[MINUTE] && (fields[MINUTE] < 0 || fields[MINUTE] > 59)) {
throw new IllegalArgumentException();
}
if (isSet[SECOND] && (fields[SECOND] < 0 || fields[SECOND] > 59)) {
throw new IllegalArgumentException();
}
if (isSet[MILLISECOND] && (fields[MILLISECOND] < 0 || fields[MILLISECOND] > 999)) {
throw new IllegalArgumentException();
}
if (isSet[WEEK_OF_YEAR] && (fields[WEEK_OF_YEAR] < 1 || fields[WEEK_OF_YEAR] > 53)) {
throw new IllegalArgumentException();
}
if (isSet[DAY_OF_WEEK] && (fields[DAY_OF_WEEK] < 1 || fields[DAY_OF_WEEK] > 7)) {
throw new IllegalArgumentException();
}
if (isSet[DAY_OF_WEEK_IN_MONTH] && (fields[DAY_OF_WEEK_IN_MONTH] < 1 || fields[DAY_OF_WEEK_IN_MONTH] > 6)) {
throw new IllegalArgumentException();
}
if (isSet[WEEK_OF_MONTH] && (fields[WEEK_OF_MONTH] < 1 || fields[WEEK_OF_MONTH] > 6)) {
throw new IllegalArgumentException();
}
if (isSet[AM_PM] && fields[AM_PM] != AM && fields[AM_PM] != PM) {
throw new IllegalArgumentException();
}
if (isSet[HOUR] && (fields[HOUR] < 0 || fields[HOUR] > 11)) {
throw new IllegalArgumentException();
}
if (isSet[YEAR]) {
if (isSet[ERA] && fields[ERA] == BC && (fields[YEAR] < 1 || fields[YEAR] > 292269054)) {
throw new IllegalArgumentException();
} else if (fields[YEAR] < 1 || fields[YEAR] > 292278994) {
throw new IllegalArgumentException();
}
}
if (isSet[MONTH] && (fields[MONTH] < 0 || fields[MONTH] > 11)) {
throw new IllegalArgumentException();
}
}
long timeVal;
long hour = 0;
if (isSet[HOUR_OF_DAY] && lastTimeFieldSet != HOUR) {
hour = fields[HOUR_OF_DAY];
} else if (isSet[HOUR]) {
hour = (fields[AM_PM] * 12) + fields[HOUR];
}
timeVal = hour * 3600000;
if (isSet[MINUTE]) {
timeVal += ((long) fields[MINUTE]) * 60000;
}
if (isSet[SECOND]) {
timeVal += ((long) fields[SECOND]) * 1000;
}
if (isSet[MILLISECOND]) {
timeVal += fields[MILLISECOND];
}
long days;
int year = isSet[YEAR] ? fields[YEAR] : 1970;
if (isSet[ERA]) {
// Always test for valid ERA, even if the Calendar is lenient
if (fields[ERA] != BC && fields[ERA] != AD) {
throw new IllegalArgumentException();
}
if (fields[ERA] == BC) {
year = 1 - year;
}
}
boolean weekMonthSet = isSet[WEEK_OF_MONTH] || isSet[DAY_OF_WEEK_IN_MONTH];
boolean useMonth = (isSet[DATE] || isSet[MONTH] || weekMonthSet) && lastDateFieldSet != DAY_OF_YEAR;
if (useMonth && (lastDateFieldSet == DAY_OF_WEEK || lastDateFieldSet == WEEK_OF_YEAR)) {
if (isSet[WEEK_OF_YEAR] && isSet[DAY_OF_WEEK]) {
useMonth = lastDateFieldSet != WEEK_OF_YEAR && weekMonthSet && isSet[DAY_OF_WEEK];
} else if (isSet[DAY_OF_YEAR]) {
useMonth = isSet[DATE] && isSet[MONTH];
}
}
if (useMonth) {
int month = fields[MONTH];
year += month / 12;
month %= 12;
if (month < 0) {
year--;
month += 12;
}
boolean leapYear = isLeapYear(year);
days = daysFromBaseYear(year) + daysInYear(leapYear, month);
boolean useDate = isSet[DATE];
if (useDate &&
(lastDateFieldSet == DAY_OF_WEEK || lastDateFieldSet == WEEK_OF_MONTH || lastDateFieldSet == DAY_OF_WEEK_IN_MONTH)) {
useDate = !(isSet[DAY_OF_WEEK] && weekMonthSet);
}
if (useDate) {
if (!isLenient() && (fields[DATE] < 1 || fields[DATE] > daysInMonth(leapYear, month))) {
throw new IllegalArgumentException();
}
days += fields[DATE] - 1;
} else {
int dayOfWeek;
if (isSet[DAY_OF_WEEK]) {
dayOfWeek = fields[DAY_OF_WEEK] - 1;
} else {
dayOfWeek = getFirstDayOfWeek() - 1;
}
if (isSet[WEEK_OF_MONTH] && lastDateFieldSet != DAY_OF_WEEK_IN_MONTH) {
int skew = mod7(days - 3 - (getFirstDayOfWeek() - 1));
days += (fields[WEEK_OF_MONTH] - 1) * 7 + mod7(skew + dayOfWeek - (days - 3)) - skew;
} else if (isSet[DAY_OF_WEEK_IN_MONTH]) {
if (fields[DAY_OF_WEEK_IN_MONTH] >= 0) {
days += mod7(dayOfWeek - (days - 3)) + (fields[DAY_OF_WEEK_IN_MONTH] - 1) * 7;
} else {
days += daysInMonth(leapYear, month) +
mod7(dayOfWeek - (days + daysInMonth(leapYear, month) - 3)) +
fields[DAY_OF_WEEK_IN_MONTH] * 7;
}
} else if (isSet[DAY_OF_WEEK]) {
int skew = mod7(days - 3 - (getFirstDayOfWeek() - 1));
days += mod7(mod7(skew + dayOfWeek - (days - 3)) - skew);
}
}
} else {
boolean useWeekYear = isSet[WEEK_OF_YEAR] && lastDateFieldSet != DAY_OF_YEAR;
if (useWeekYear && isSet[DAY_OF_YEAR]) {
useWeekYear = isSet[DAY_OF_WEEK];
}
days = daysFromBaseYear(year);
if (useWeekYear) {
int dayOfWeek;
if (isSet[DAY_OF_WEEK]) {
dayOfWeek = fields[DAY_OF_WEEK] - 1;
} else {
dayOfWeek = getFirstDayOfWeek() - 1;
}
int skew = mod7(days - 3 - (getFirstDayOfWeek() - 1));
days += (fields[WEEK_OF_YEAR] - 1) * 7 + mod7(skew + dayOfWeek - (days - 3)) - skew;
if (7 - skew < getMinimalDaysInFirstWeek()) {
days += 7;
}
} else if (isSet[DAY_OF_YEAR]) {
if (!isLenient() &&
(fields[DAY_OF_YEAR] < 1 || fields[DAY_OF_YEAR] > (365 + (isLeapYear(year) ? 1 : 0)))) {
throw new IllegalArgumentException();
}
days += fields[DAY_OF_YEAR] - 1;
} else if (isSet[DAY_OF_WEEK]) {
days += mod7(fields[DAY_OF_WEEK] - 1 - (days - 3));
}
}
lastDateFieldSet = 0;
timeVal += days * 86400000;
// Use local time to compare with the gregorian change
if (year == changeYear && timeVal >= gregorianCutover + julianError() * 86400000L) {
timeVal -= julianError() * 86400000L;
}
long timeValWithoutDST = timeVal - getTimeZoneOffset(timeVal);
this.time = timeVal;
if (timeValWithoutDST != timeVal) {
computeFields();
areFieldsSet = true;
}
}
private int computeYearAndDay(long dayCount, long localTime) {
int year = 1970;
long days = dayCount;
if (localTime < gregorianCutover) {
days -= julianSkew;
}
int approxYears;
while ((approxYears = (int) (days / 365)) != 0) {
year = year + approxYears;
days = dayCount - daysFromBaseYear(year);
}
if (days < 0) {
year = year - 1;
days = days + daysInYear(year);
}
fields[YEAR] = year;
return (int) days + 1;
}
private long daysFromBaseYear(int iyear) {
long year = iyear;
if (year >= 1970) {
long days = (year - 1970) * 365 + ((year - 1969) / 4);
if (year > changeYear) {
days -= ((year - 1901) / 100) - ((year - 1601) / 400);
} else {
if (year == changeYear) {
days += currentYearSkew;
} else if (year == changeYear - 1) {
days += lastYearSkew;
} else {
days += julianSkew;
}
}
return days;
} else if (year <= changeYear) {
return (year - 1970) * 365 + ((year - 1972) / 4) + julianSkew;
}
return (year - 1970) * 365 + ((year - 1972) / 4) - ((year - 2000) / 100) + ((year - 2000) / 400);
}
private int daysInMonth() {
return daysInMonth(isLeapYear(fields[YEAR]), fields[MONTH]);
}
private int daysInMonth(boolean leapYear, int month) {
if (leapYear && month == FEBRUARY) {
return DaysInMonth[month] + 1;
}
return DaysInMonth[month];
}
private int daysInYear(int year) {
int daysInYear = isLeapYear(year) ? 366 : 365;
if (year == changeYear) {
daysInYear -= currentYearSkew;
}
if (year == changeYear - 1) {
daysInYear -= lastYearSkew;
}
return daysInYear;
}
private int daysInYear(boolean leapYear, int month) {
if (leapYear && month > FEBRUARY) {
return DaysInYear[month] + 1;
}
return DaysInYear[month];
}
@Override
public boolean equals(Object object) {
return super.equals(object) && gregorianCutover == ((TGregorianCalendar) object).gregorianCutover;
}
@Override
public int getActualMaximum(int field) {
int value;
if ((value = maximums[field]) == leastMaximums[field]) {
return value;
}
switch (field) {
case WEEK_OF_YEAR:
case WEEK_OF_MONTH:
isCached = false;
break;
}
complete();
long orgTime = time;
int result = 0;
switch (field) {
case WEEK_OF_YEAR:
set(DATE, 31);
set(MONTH, DECEMBER);
result = get(WEEK_OF_YEAR);
if (result == 1) {
set(DATE, 31 - 7);
result = get(WEEK_OF_YEAR);
}
areFieldsSet = false;
break;
case WEEK_OF_MONTH:
set(DATE, daysInMonth());
result = get(WEEK_OF_MONTH);
areFieldsSet = false;
break;
case DATE:
return daysInMonth();
case DAY_OF_YEAR:
return daysInYear(fields[YEAR]);
case DAY_OF_WEEK_IN_MONTH:
result = get(DAY_OF_WEEK_IN_MONTH) + ((daysInMonth() - get(DATE)) / 7);
break;
case YEAR:
TGregorianCalendar clone = (TGregorianCalendar) clone();
if (get(ERA) == AD) {
clone.setTimeInMillis(Long.MAX_VALUE);
} else {
clone.setTimeInMillis(Long.MIN_VALUE);
}
result = clone.get(YEAR);
clone.set(YEAR, get(YEAR));
if (clone.before(this)) {
result--;
}
break;
case DST_OFFSET:
result = getMaximum(DST_OFFSET);
break;
}
time = orgTime;
return result;
}
@Override
public int getActualMinimum(int field) {
return getMinimum(field);
}
@Override
public int getGreatestMinimum(int field) {
return minimums[field];
}
public final TDate getGregorianChange() {
return new TDate(gregorianCutover);
}
@Override
public int getLeastMaximum(int field) {
// return value for WEEK_OF_YEAR should make corresponding changes when
// the gregorian change date have been reset.
if (gregorianCutover != defaultGregorianCutover && field == WEEK_OF_YEAR) {
long currentTimeInMillis = time;
setTimeInMillis(gregorianCutover);
int actual = getActualMaximum(field);
setTimeInMillis(currentTimeInMillis);
return actual;
}
return leastMaximums[field];
}
@Override
public int getMaximum(int field) {
return maximums[field];
}
@Override
public int getMinimum(int field) {
return minimums[field];
}
@Override
public int hashCode() {
return super.hashCode() + ((int) (gregorianCutover >>> 32) ^ (int) gregorianCutover);
}
public boolean isLeapYear(int year) {
if (year > changeYear) {
return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
}
return year % 4 == 0;
}
private int julianError() {
return changeYear / 100 - changeYear / 400 - 2;
}
private int mod(int value, int mod) {
int rem = value % mod;
if (value < 0 && rem < 0) {
return rem + mod;
}
return rem;
}
private int mod7(long num1) {
int rem = (int) (num1 % 7);
if (num1 < 0 && rem < 0) {
return rem + 7;
}
return rem;
}
@Override
public void roll(int field, int value) {
if (value == 0) {
return;
}
if (field < 0 || field >= ZONE_OFFSET) {
throw new IllegalArgumentException();
}
isCached = false;
complete();
int days, day, mod, maxWeeks, newWeek;
int max = -1;
switch (field) {
case YEAR:
max = maximums[field];
break;
case WEEK_OF_YEAR:
days = daysInYear(fields[YEAR]);
day = DAY_OF_YEAR;
mod = mod7(fields[DAY_OF_WEEK] - fields[day] - (getFirstDayOfWeek() - 1));
maxWeeks = (days - 1 + mod) / 7 + 1;
newWeek = mod(fields[field] - 1 + value, maxWeeks) + 1;
if (newWeek == maxWeeks) {
int addDays = (newWeek - fields[field]) * 7;
if (fields[day] > addDays && fields[day] + addDays > days) {
set(field, 1);
} else {
set(field, newWeek - 1);
}
} else if (newWeek == 1) {
int week = (fields[day] - ((fields[day] - 1) / 7 * 7) - 1 + mod) / 7 + 1;
if (week > 1) {
set(field, 1);
} else {
set(field, newWeek);
}
} else {
set(field, newWeek);
}
break;
case WEEK_OF_MONTH:
days = daysInMonth();
day = DATE;
mod = mod7(fields[DAY_OF_WEEK] - fields[day] - (getFirstDayOfWeek() - 1));
maxWeeks = (days - 1 + mod) / 7 + 1;
newWeek = mod(fields[field] - 1 + value, maxWeeks) + 1;
if (newWeek == maxWeeks) {
if (fields[day] + (newWeek - fields[field]) * 7 > days) {
set(day, days);
} else {
set(field, newWeek);
}
} else if (newWeek == 1) {
int week = (fields[day] - ((fields[day] - 1) / 7 * 7) - 1 + mod) / 7 + 1;
if (week > 1) {
set(day, 1);
} else {
set(field, newWeek);
}
} else {
set(field, newWeek);
}
break;
case DATE:
max = daysInMonth();
break;
case DAY_OF_YEAR:
max = daysInYear(fields[YEAR]);
break;
case DAY_OF_WEEK:
max = maximums[field];
lastDateFieldSet = WEEK_OF_MONTH;
break;
case DAY_OF_WEEK_IN_MONTH:
max = (fields[DATE] + ((daysInMonth() - fields[DATE]) / 7 * 7) - 1) / 7 + 1;
break;
case ERA:
case MONTH:
case AM_PM:
case HOUR:
case HOUR_OF_DAY:
case MINUTE:
case SECOND:
case MILLISECOND:
set(field, mod(fields[field] + value, maximums[field] + 1));
if (field == MONTH && fields[DATE] > daysInMonth()) {
set(DATE, daysInMonth());
} else if (field == AM_PM) {
lastTimeFieldSet = HOUR;
}
break;
}
if (max != -1) {
set(field, mod(fields[field] - 1 + value, max) + 1);
}
complete();
}
@Override
public void roll(int field, boolean increment) {
roll(field, increment ? 1 : -1);
}
public void setGregorianChange(TDate date) {
gregorianCutover = date.getTime();
TGregorianCalendar cal = new TGregorianCalendar();
cal.setTime(date);
changeYear = cal.get(YEAR);
if (cal.get(ERA) == BC) {
changeYear = 1 - changeYear;
}
julianSkew = ((changeYear - 2000) / 400) + julianError() - ((changeYear - 2000) / 100);
isCached = false;
int dayOfYear = cal.get(DAY_OF_YEAR);
if (dayOfYear < julianSkew) {
currentYearSkew = dayOfYear - 1;
lastYearSkew = julianSkew - dayOfYear + 1;
} else {
lastYearSkew = 0;
currentYearSkew = julianSkew;
}
isCached = false;
}
@Override
public void setFirstDayOfWeek(int value) {
super.setFirstDayOfWeek(value);
isCached = false;
}
@Override
public void setMinimalDaysInFirstWeek(int value) {
super.setMinimalDaysInFirstWeek(value);
isCached = false;
}
}