package java.util;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OptionalDataException;
import java.io.Serializable;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PermissionCollection;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.security.ProtectionDomain;
import java.text.DateFormat;
import java.text.DateFormatSymbols;
import java.time.Instant;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import sun.util.BuddhistCalendar;
import sun.util.calendar.ZoneInfo;
import sun.util.locale.provider.CalendarDataUtility;
import sun.util.locale.provider.LocaleProviderAdapter;
import sun.util.spi.CalendarProvider;
//抽象类:实现了Serializable, Cloneable, Comparable接口
public abstract class Calendar implements Serializable, Cloneable, Comparable<Calendar> {
// Data flow in Calendar,时间量的转换
// local fields (YEAR, MONTH, DATE, HOUR, MINUTE, etc.)
// |
// |Using Calendar-specific algorithm
// V
// local standard millis
// |
// | Using TimeZone or user-set ZONE_OFFSET / DST_OFFSET
// V
// UTC millis (in time data member)
public final static int ERA = 0; //纪元
public final static int YEAR = 1; //年
public final static int MONTH = 2; //月份,JANUARY为0
public final static int WEEK_OF_YEAR = 3; //一年中第几周
public final static int WEEK_OF_MONTH = 4;//月份中的第几周
public final static int DATE = 5; //本月份中的第几天,日期
public final static int DAY_OF_MONTH = 5;//本月中的第几天,日期
public final static int DAY_OF_YEAR = 6;//本年的第几天
public final static int DAY_OF_WEEK = 7;//week第几天,星期几
public final static int DAY_OF_WEEK_IN_MONTH = 8;//本月内星期几的序号
public final static int AM_PM = 9; //时间是AM/PM
public final static int HOUR = 10; //小时 12小时制
public final static int HOUR_OF_DAY = 11;//小时 24小时制
public final static int MINUTE = 12; //分钟
public final static int SECOND = 13;//秒
public final static int MILLISECOND = 14;//毫秒
public final static int ZONE_OFFSET = 15; //GMT的原始偏移量(毫秒)
public final static int DST_OFFSET = 16;//一天的毫秒偏移量
public final static int FIELD_COUNT = 17;//属性编号值
//星期
public final static int SUNDAY = 1;
public final static int MONDAY = 2;
public final static int TUESDAY = 3;
public final static int WEDNESDAY = 4;
public final static int THURSDAY = 5;
public final static int FRIDAY = 6;
public final static int SATURDAY = 7;
//月份
public final static int JANUARY = 0;
public final static int FEBRUARY = 1;
public final static int MARCH = 2;
public final static int APRIL = 3;
public final static int MAY = 4;
public final static int JUNE = 5;
public final static int JULY = 6;
public final static int AUGUST = 7;
public final static int SEPTEMBER = 8;
public final static int OCTOBER = 9;
public final static int NOVEMBER = 10;
public final static int DECEMBER = 11;
public final static int UNDECIMBER = 12; //第13个月
//上下午
public final static int AM = 0;
public final static int PM = 1;
//时间显示风格
public static final int ALL_STYLES = 0;
static final int STANDALONE_MASK = 0x8000;
//时间显示风格
public static final int SHORT = 1;
public static final int LONG = 2;
public static final int NARROW_FORMAT = 4;
public static final int NARROW_STANDALONE = NARROW_FORMAT | STANDALONE_MASK;
public static final int SHORT_FORMAT = 1;
public static final int LONG_FORMAT = 2;
public static final int SHORT_STANDALONE = SHORT | STANDALONE_MASK;
public static final int LONG_STANDALONE = LONG | STANDALONE_MASK;
protected int fields[]; //Calendar类值都存在这个数组中
protected boolean isSet[]; //标记每个字段是否被设置
transient private int stamp[];
protected long time;
protected boolean isTimeSet;
protected boolean areFieldsSet;
transient boolean areAllFieldsSet;
private boolean lenient = true;
private TimeZone zone;
transient private boolean sharedZone = false;
private int firstDayOfWeek;
private int minimalDaysInFirstWeek;
private static final ConcurrentMap<Locale, int[]> cachedLocaleData
= new ConcurrentHashMap<>(3);
private static final int UNSET = 0;
private static final int COMPUTED = 1;
private static final int MINIMUM_USER_STAMP = 2;
static final int ALL_FIELDS = (1 << FIELD_COUNT) - 1;
private int nextStamp = MINIMUM_USER_STAMP;
static final int currentSerialVersion = 1;
private int serialVersionOnStream = currentSerialVersion;
// Proclaim serialization compatibility with JDK 1.1
static final long serialVersionUID = -1807547505821590642L;
// Mask values for calendar fields
@SuppressWarnings("PointlessBitwiseExpression")
final static int ERA_MASK = (1 << ERA);
final static int YEAR_MASK = (1 << YEAR);
final static int MONTH_MASK = (1 << MONTH);
final static int WEEK_OF_YEAR_MASK = (1 << WEEK_OF_YEAR);
final static int WEEK_OF_MONTH_MASK = (1 << WEEK_OF_MONTH);
final static int DAY_OF_MONTH_MASK = (1 << DAY_OF_MONTH);
final static int DATE_MASK = DAY_OF_MONTH_MASK;
final static int DAY_OF_YEAR_MASK = (1 << DAY_OF_YEAR);
final static int DAY_OF_WEEK_MASK = (1 << DAY_OF_WEEK);
final static int DAY_OF_WEEK_IN_MONTH_MASK = (1 << DAY_OF_WEEK_IN_MONTH);
final static int AM_PM_MASK = (1 << AM_PM);
final static int HOUR_MASK = (1 << HOUR);
final static int HOUR_OF_DAY_MASK = (1 << HOUR_OF_DAY);
final static int MINUTE_MASK = (1 << MINUTE);
final static int SECOND_MASK = (1 << SECOND);
final static int MILLISECOND_MASK = (1 << MILLISECOND);
final static int ZONE_OFFSET_MASK = (1 << ZONE_OFFSET);
final static int DST_OFFSET_MASK = (1 << DST_OFFSET);
public static class Builder {
private static final int NFIELDS = FIELD_COUNT + 1; // +1 for WEEK_YEAR
private static final int WEEK_YEAR = FIELD_COUNT;
private long instant;
private int[] fields;
private int nextStamp;
private int maxFieldIndex;
private String type;
private TimeZone zone;
private boolean lenient = true;
private Locale locale;
private int firstDayOfWeek, minimalDaysInFirstWeek;
public Builder(){
}
public Builder setInstant(long instant){
if(fields != null){
throw new IllegalStateException();
}
this.instant = instant;
nextStamp = COMPUTED;
return this;
}
public Builder setInstant(Date instant){
return setInstant(instant.getTime()); // NPE if instant == null
}
public Builder set(int field, int value){
// Note: WEEK_YEAR can't be set with this method.
if(field < 0 || field >= FIELD_COUNT){
throw new IllegalArgumentException("field is invalid");
}
if(isInstantSet()){
throw new IllegalStateException("instant has been set");
}
allocateFields();
internalSet(field, value);
return this;
}
public Builder setFields(int... fieldValuePairs){
int len = fieldValuePairs.length;
if((len % 2) != 0){
throw new IllegalArgumentException();
}
if(isInstantSet()){
throw new IllegalStateException("instant has been set");
}
if((nextStamp + len / 2) < 0){
throw new IllegalStateException("stamp counter overflow");
}
allocateFields();
for(int i = 0; i < len; ){
int field = fieldValuePairs[i++];
// Note: WEEK_YEAR can't be set with this method.
if(field < 0 || field >= FIELD_COUNT){
throw new IllegalArgumentException("field is invalid");
}
internalSet(field, fieldValuePairs[i++]);
}
return this;
}
public Builder setDate(int year, int month, int dayOfMonth){
return setFields(YEAR, year, MONTH, month, DAY_OF_MONTH, dayOfMonth);
}
public Builder setTimeOfDay(int hourOfDay, int minute, int second){
return setTimeOfDay(hourOfDay, minute, second, 0);
}
public Builder setTimeOfDay(int hourOfDay, int minute, int second, int millis){
return setFields(HOUR_OF_DAY, hourOfDay, MINUTE, minute,
SECOND, second, MILLISECOND, millis);
}
public Builder setWeekDate(int weekYear, int weekOfYear, int dayOfWeek){
allocateFields();
internalSet(WEEK_YEAR, weekYear);
internalSet(WEEK_OF_YEAR, weekOfYear);
internalSet(DAY_OF_WEEK, dayOfWeek);
return this;
}
public Builder setTimeZone(TimeZone zone){
if(zone == null){
throw new NullPointerException();
}
this.zone = zone;
return this;
}
public Builder setLenient(boolean lenient){
this.lenient = lenient;
return this;
}
public Builder setCalendarType(String type){
if(type.equals("gregorian")){ // NPE if type == null
type = "gregory";
}
if(!Calendar.getAvailableCalendarTypes().contains(type)
&& !type.equals("iso8601")){
throw new IllegalArgumentException("unknown calendar type: " + type);
}
if(this.type == null){
this.type = type;
}else{
if(!this.type.equals(type)){
throw new IllegalStateException("calendar type override");
}
}
return this;
}
public Builder setLocale(Locale locale){
if(locale == null){
throw new NullPointerException();
}
this.locale = locale;
return this;
}
public Builder setWeekDefinition(int firstDayOfWeek, int minimalDaysInFirstWeek){
if(!isValidWeekParameter(firstDayOfWeek)
|| !isValidWeekParameter(minimalDaysInFirstWeek)){
throw new IllegalArgumentException();
}
this.firstDayOfWeek = firstDayOfWeek;
this.minimalDaysInFirstWeek = minimalDaysInFirstWeek;
return this;
}
public Calendar build(){
if(locale == null){
locale = Locale.getDefault();
}
if(zone == null){
zone = TimeZone.getDefault();
}
Calendar cal;
if(type == null){
type = locale.getUnicodeLocaleType("ca");
}
if(type == null){
if(locale.getCountry() == "TH"
&& locale.getLanguage() == "th"){
type = "buddhist";
}else{
type = "gregory";
}
}
switch (type) {
case "gregory":
cal = new GregorianCalendar(zone, locale, true);
break;
case "iso8601":
GregorianCalendar gcal = new GregorianCalendar(zone, locale, true);
// make gcal a proleptic Gregorian
gcal.setGregorianChange(new Date(Long.MIN_VALUE));
// and week definition to be compatible with ISO 8601
setWeekDefinition(MONDAY, 4);
cal = gcal;
break;
case "buddhist":
cal = new BuddhistCalendar(zone, locale);
cal.clear();
break;
case "japanese":
cal = new JapaneseImperialCalendar(zone, locale, true);
break;
default:
throw new IllegalArgumentException("unknown calendar type: " + type);
}
cal.setLenient(lenient);
if(firstDayOfWeek != 0){
cal.setFirstDayOfWeek(firstDayOfWeek);
cal.setMinimalDaysInFirstWeek(minimalDaysInFirstWeek);
}
if(isInstantSet()){
cal.setTimeInMillis(instant);
cal.complete();
return cal;
}
if(fields != null){
boolean weekDate = isSet(WEEK_YEAR)
&& fields[WEEK_YEAR] > fields[YEAR];
if(weekDate && !cal.isWeekDateSupported()){
throw new IllegalArgumentException("week date is unsupported by " + type);
}
// Set the fields from the min stamp to the max stamp so that
// the fields resolution works in the Calendar.
for(int stamp = MINIMUM_USER_STAMP; stamp < nextStamp; stamp++){
for(int index = 0; index <= maxFieldIndex; index++){
if(fields[index] == stamp){
cal.set(index, fields[NFIELDS + index]);
break;
}
}
}
if(weekDate){
int weekOfYear = isSet(WEEK_OF_YEAR) ? fields[NFIELDS + WEEK_OF_YEAR] : 1;
int dayOfWeek = isSet(DAY_OF_WEEK)
? fields[NFIELDS + DAY_OF_WEEK] : cal.getFirstDayOfWeek();
cal.setWeekDate(fields[NFIELDS + WEEK_YEAR], weekOfYear, dayOfWeek);
}
cal.complete();
}
return cal;
}
private void allocateFields(){
if(fields == null){
fields = new int[NFIELDS * 2];
nextStamp = MINIMUM_USER_STAMP;
maxFieldIndex = -1;
}
}
private void internalSet(int field, int value){
fields[field] = nextStamp++;
if(nextStamp < 0){
throw new IllegalStateException("stamp counter overflow");
}
fields[NFIELDS + field] = value;
if(field > maxFieldIndex && field < WEEK_YEAR){
maxFieldIndex = field;
}
}
private boolean isInstantSet(){
return nextStamp == COMPUTED;
}
private boolean isSet(int index){
return fields != null && fields[index] > UNSET;
}
private boolean isValidWeekParameter(int value){
return value > 0 && value <= 7;
}
}
protected Calendar(){
this(TimeZone.getDefaultRef(), Locale.getDefault(Locale.Category.FORMAT));
sharedZone = true;
}
protected Calendar(TimeZone zone, Locale aLocale){
fields = new int[FIELD_COUNT];
isSet = new boolean[FIELD_COUNT];
stamp = new int[FIELD_COUNT];
this.zone = zone;
setWeekCountData(aLocale);
}
public static Calendar getInstance(){
return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT));
}
public static Calendar getInstance(TimeZone zone){
return createCalendar(zone, Locale.getDefault(Locale.Category.FORMAT));
}
public static Calendar getInstance(Locale aLocale){
return createCalendar(TimeZone.getDefault(), aLocale);
}
public static Calendar getInstance(TimeZone zone,
Locale aLocale){
return createCalendar(zone, aLocale);
}
private static Calendar createCalendar(TimeZone zone,
Locale aLocale){
CalendarProvider provider =
LocaleProviderAdapter.getAdapter(CalendarProvider.class, aLocale)
.getCalendarProvider();
if(provider != null){
try {
return provider.getInstance(zone, aLocale);
} catch (IllegalArgumentException iae) {
// fall back to the default instantiation
}
}
Calendar cal = null;
if(aLocale.hasExtensions()){
String caltype = aLocale.getUnicodeLocaleType("ca");
if(caltype != null){
switch (caltype) {
case "buddhist":
cal = new BuddhistCalendar(zone, aLocale);
break;
case "japanese":
cal = new JapaneseImperialCalendar(zone, aLocale);
break;
case "gregory":
cal = new GregorianCalendar(zone, aLocale);
break;
}
}
}
if(cal == null){
if(aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH"){
cal = new BuddhistCalendar(zone, aLocale);
}else if(aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja"
&& aLocale.getCountry() == "JP"){
cal = new JapaneseImperialCalendar(zone, aLocale);
}else{
cal = new GregorianCalendar(zone, aLocale);
}
}
return cal;
}
public static synchronized Locale[] getAvailableLocales(){
return DateFormat.getAvailableLocales();
}
protected abstract void computeTime();
protected abstract void computeFields();
public final Date getTime(){
return new Date(getTimeInMillis());
}
public final void setTime(Date date){
setTimeInMillis(date.getTime());
}
public long getTimeInMillis(){
if(!isTimeSet){
updateTime();
}
return time;
}
public void setTimeInMillis(long millis){
// If we don't need to recalculate the calendar field values,
// do nothing.
if(time == millis && isTimeSet && areFieldsSet && areAllFieldsSet
&& (zone instanceof ZoneInfo) && !((ZoneInfo) zone).isDirty()){
return;
}
time = millis;
isTimeSet = true;
areFieldsSet = fal***puteFields();
areAllFieldsSet = areFieldsSet = true;
}
public int get(int field){
complete();
return internalGet(field);
}
protected final int internalGet(int field){
return fields[field];
}
final void internalSet(int field, int value){
fields[field] = value;
}
public void set(int field, int value){
// If the fields are partially normalized, calculate all the
// fields before changing any fields.
if(areFieldsSet && !areAllFieldsSet){
computeFields();
}
internalSet(field, value);
isTimeSet = false;
areFieldsSet = false;
isSet[field] = true;
stamp[field] = nextStamp++;
if(nextStamp == Integer.MAX_VALUE){
adjustStamp();
}
}
public final void set(int year, int month, int date){
set(YEAR, year);
set(MONTH, month);
set(DATE, date);
}
public final void set(int year, int month, int date, int hourOfDay, int minute){
set(YEAR, year);
set(MONTH, month);
set(DATE, date);
set(HOUR_OF_DAY, hourOfDay);
set(MINUTE, minute);
}
public final void set(int year, int month, int date, int hourOfDay, int minute,
int second){
set(YEAR, year);
set(MONTH, month);
set(DATE, date);
set(HOUR_OF_DAY, hourOfDay);
set(MINUTE, minute);
set(SECOND, second);
}
public final void clear(){
for(int i = 0; i < fields.length; ){
stamp[i] = fields[i] = 0; // UNSET == 0
isSet[i++] = false;
}
areAllFieldsSet = areFieldsSet = false;
isTimeSet = false;
}
public final void clear(int field){
fields[field] = 0;
stamp[field] = UNSET;
isSet[field] = false;
areAllFieldsSet = areFieldsSet = false;
isTimeSet = false;
}
public final boolean isSet(int field){
return stamp[field] != UNSET;
}
public String getDisplayName(int field, int style, Locale locale){
if(!checkDisplayNameParams(field, style, SHORT, NARROW_FORMAT, locale,
ERA_MASK | MONTH_MASK | DAY_OF_WEEK_MASK | AM_PM_MASK)){
return null;
}
String calendarType = getCalendarType();
int fieldValue = get(field);
// the standalone and narrow styles are supported only through CalendarDataProviders.
if(isStandaloneStyle(style) || isNarrowFormatStyle(style)){
String val = CalendarDataUtility.retrieveFieldValueName(calendarType,
field, fieldValue,
style, locale);
// Perform fallback here to follow the CLDR rules
if(val == null){
if(isNarrowFormatStyle(style)){
val = CalendarDataUtility.retrieveFieldValueName(calendarType,
field, fieldValue,
toStandaloneStyle(style),
locale);
}else if(isStandaloneStyle(style)){
val = CalendarDataUtility.retrieveFieldValueName(calendarType,
field, fieldValue,
getBaseStyle(style),
locale);
}
}
return val;
}
DateFormatSymbols symbols = DateFormatSymbols.getInstance(locale);
String[] strings = getFieldStrings(field, style, symbols);
if(strings != null){
if(fieldValue < strings.length){
return strings[fieldValue];
}
}
return null;
}
public Map<String, Integer> getDisplayNames(int field, int style, Locale locale){
if(!checkDisplayNameParams(field, style, ALL_STYLES, NARROW_FORMAT, locale,
ERA_MASK | MONTH_MASK | DAY_OF_WEEK_MASK | AM_PM_MASK)){
return null;
}
String calendarType = getCalendarType();
if(style == ALL_STYLES || isStandaloneStyle(style) || isNarrowFormatStyle(style)){
Map<String, Integer> map;
map = CalendarDataUtility.retrieveFieldValueNames(calendarType, field, style, locale);
// Perform fallback here to follow the CLDR rules
if(map == null){
if(isNarrowFormatStyle(style)){
map = CalendarDataUtility.retrieveFieldValueNames(calendarType, field,
toStandaloneStyle(style), locale);
}else if(style != ALL_STYLES){
map = CalendarDataUtility.retrieveFieldValueNames(calendarType, field,
getBaseStyle(style), locale);
}
}
return map;
}
// SHORT or LONG
return getDisplayNamesImpl(field, style, locale);
}
private Map<String, Integer> getDisplayNamesImpl(int field, int style, Locale locale){
DateFormatSymbols symbols = DateFormatSymbols.getInstance(locale);
String[] strings = getFieldStrings(field, style, symbols);
if(strings != null){
Map<String, Integer> names = new HashMap<>();
for(int i = 0; i < strings.length; i++){
if(strings[i].length() == 0){
continue;
}
names.put(strings[i], i);
}
return names;
}
return null;
}
boolean checkDisplayNameParams(int field, int style, int minStyle, int maxStyle,
Locale locale, int fieldMask){
int baseStyle = getBaseStyle(style); // Ignore the standalone mask
if(field < 0 || field >= fields.length ||
baseStyle < minStyle || baseStyle > maxStyle){
throw new IllegalArgumentException();
}
if(locale == null){
throw new NullPointerException();
}
return isFieldSet(fieldMask, field);
}
private String[] getFieldStrings(int field, int style, DateFormatSymbols symbols){
int baseStyle = getBaseStyle(style); // ignore the standalone mask
// DateFormatSymbols doesn't support any narrow names.
if(baseStyle == NARROW_FORMAT){
return null;
}
String[] strings = null;
switch (field) {
case ERA:
strings = symbols.getEras();
break;
case MONTH:
strings = (baseStyle == LONG) ? symbols.getMonths() : symbols.getShortMonths();
break;
case DAY_OF_WEEK:
strings = (baseStyle == LONG) ? symbols.getWeekdays() : symbols.getShortWeekdays();
break;
case AM_PM:
strings = symbols.getAmPmStrings();
break;
}
return strings;
}
protected void complete(){
if(!isTimeSet){
updateTime();
}
if(!areFieldsSet || !areAllFieldsSet){
computeFields(); // fills in unset fields
areAllFieldsSet = areFieldsSet = true;
}
}
final boolean isExternallySet(int field){
return stamp[field] >= MINIMUM_USER_STAMP;
}
final int getSetStateFields(){
int mask = 0;
for(int i = 0; i < fields.length; i++){
if(stamp[i] != UNSET){
mask |= 1 << i;
}
}
return mask;
}
final void setFieldsComputed(int fieldMask){
if(fieldMask == ALL_FIELDS){
for(int i = 0; i < fields.length; i++){
stamp[i] = COMPUTED;
isSet[i] = true;
}
areFieldsSet = areAllFieldsSet = true;
}else{
for(int i = 0; i < fields.length; i++){
if((fieldMask & 1) == 1){
stamp[i] = COMPUTED;
isSet[i] = true;
}else{
if(areAllFieldsSet && !isSet[i]){
areAllFieldsSet = false;
}
}
fieldMask >>>= 1;
}
}
}
final void setFieldsNormalized(int fieldMask){
if(fieldMask != ALL_FIELDS){
for(int i = 0; i < fields.length; i++){
if((fieldMask & 1) == 0){
stamp[i] = fields[i] = 0; // UNSET == 0
isSet[i] = false;
}
fieldMask >>= 1;
}
}
// Some or all of the fields are in sync with the
// milliseconds, but the stamp values are not normalized yet.
areFieldsSet = true;
areAllFieldsSet = false;
}
final boolean isPartiallyNormalized(){
return areFieldsSet && !areAllFieldsSet;
}
final boolean isFullyNormalized(){
return areFieldsSet && areAllFieldsSet;
}
final void setUnnormalized(){
areFieldsSet = areAllFieldsSet = false;
}
static boolean isFieldSet(int fieldMask, int field){
return (fieldMask & (1 << field)) != 0;
}
final int selectFields(){
// This implementation has been taken from the GregorianCalendar class.
// The YEAR field must always be used regardless of its SET
// state because YEAR is a mandatory field to determine the date
// and the default value (EPOCH_YEAR) may change through the
// normalization process.
int fieldMask = YEAR_MASK;
if(stamp[ERA] != UNSET){
fieldMask |= ERA_MASK;
}
// Find the most recent group of fields specifying the day within
// the year. These may be any of the following combinations:
// MONTH + DAY_OF_MONTH
// MONTH + WEEK_OF_MONTH + DAY_OF_WEEK
// MONTH + DAY_OF_WEEK_IN_MONTH + DAY_OF_WEEK
// DAY_OF_YEAR
// WEEK_OF_YEAR + DAY_OF_WEEK
// We look for the most recent of the fields in each group to determine
// the age of the group. For groups involving a week-related field such
// as WEEK_OF_MONTH, DAY_OF_WEEK_IN_MONTH, or WEEK_OF_YEAR, both the
// week-related field and the DAY_OF_WEEK must be set for the group as a
// whole to be considered. (See bug 4153860 - liu 7/24/98.)
int dowStamp = stamp[DAY_OF_WEEK];
int monthStamp = stamp[MONTH];
int domStamp = stamp[DAY_OF_MONTH];
int womStamp = aggregateStamp(stamp[WEEK_OF_MONTH], dowStamp);
int dowimStamp = aggregateStamp(stamp[DAY_OF_WEEK_IN_MONTH], dowStamp);
int doyStamp = stamp[DAY_OF_YEAR];
int woyStamp = aggregateStamp(stamp[WEEK_OF_YEAR], dowStamp);
int bestStamp = domStamp;
if(womStamp > bestStamp){
bestStamp = womStamp;
}
if(dowimStamp > bestStamp){
bestStamp = dowimStamp;
}
if(doyStamp > bestStamp){
bestStamp = doyStamp;
}
if(woyStamp > bestStamp){
bestStamp = woyStamp;
}
/* No complete combination exists. Look for WEEK_OF_MONTH,
* DAY_OF_WEEK_IN_MONTH, or WEEK_OF_YEAR alone. Treat DAY_OF_WEEK alone
* as DAY_OF_WEEK_IN_MONTH.
*/
if(bestStamp == UNSET){
womStamp = stamp[WEEK_OF_MONTH];
dowimStamp = Math.max(stamp[DAY_OF_WEEK_IN_MONTH], dowStamp);
woyStamp = stamp[WEEK_OF_YEAR];
bestStamp = Math.max(Math.max(womStamp, dowimStamp), woyStamp);
/* Treat MONTH alone or no fields at all as DAY_OF_MONTH. This may
* result in bestStamp = domStamp = UNSET if no fields are set,
* which indicates DAY_OF_MONTH.
*/
if(bestStamp == UNSET){
bestStamp = domStamp = monthStamp;
}
}
if(bestStamp == domStamp ||
(bestStamp == womStamp && stamp[WEEK_OF_MONTH] >= stamp[WEEK_OF_YEAR]) ||
(bestStamp == dowimStamp && stamp[DAY_OF_WEEK_IN_MONTH] >= stamp[WEEK_OF_YEAR])){
fieldMask |= MONTH_MASK;
if(bestStamp == domStamp){
fieldMask |= DAY_OF_MONTH_MASK;
}else{
assert (bestStamp == womStamp || bestStamp == dowimStamp);
if(dowStamp != UNSET){
fieldMask |= DAY_OF_WEEK_MASK;
}
if(womStamp == dowimStamp){
// When they are equal, give the priority to
// WEEK_OF_MONTH for compatibility.
if(stamp[WEEK_OF_MONTH] >= stamp[DAY_OF_WEEK_IN_MONTH]){
fieldMask |= WEEK_OF_MONTH_MASK;
}else{
fieldMask |= DAY_OF_WEEK_IN_MONTH_MASK;
}
}else{
if(bestStamp == womStamp){
fieldMask |= WEEK_OF_MONTH_MASK;
}else{
assert (bestStamp == dowimStamp);
if(stamp[DAY_OF_WEEK_IN_MONTH] != UNSET){
fieldMask |= DAY_OF_WEEK_IN_MONTH_MASK;
}
}
}
}
}else{
assert (bestStamp == doyStamp || bestStamp == woyStamp ||
bestStamp == UNSET);
if(bestStamp == doyStamp){
fieldMask |= DAY_OF_YEAR_MASK;
}else{
assert (bestStamp == woyStamp);
if(dowStamp != UNSET){
fieldMask |= DAY_OF_WEEK_MASK;
}
fieldMask |= WEEK_OF_YEAR_MASK;
}
}
// Find the best set of fields specifying the time of day. There
// are only two possibilities here; the HOUR_OF_DAY or the
// AM_PM and the HOUR.
int hourOfDayStamp = stamp[HOUR_OF_DAY];
int hourStamp = aggregateStamp(stamp[HOUR], stamp[AM_PM]);
bestStamp = (hourStamp > hourOfDayStamp) ? hourStamp : hourOfDayStamp;
// if bestStamp is still UNSET, then take HOUR or AM_PM. (See 4846659)
if(bestStamp == UNSET){
bestStamp = Math.max(stamp[HOUR], stamp[AM_PM]);
}
// Hours
if(bestStamp != UNSET){
if(bestStamp == hourOfDayStamp){
fieldMask |= HOUR_OF_DAY_MASK;
}else{
fieldMask |= HOUR_MASK;
if(stamp[AM_PM] != UNSET){
fieldMask |= AM_PM_MASK;
}
}
}
if(stamp[MINUTE] != UNSET){
fieldMask |= MINUTE_MASK;
}
if(stamp[SECOND] != UNSET){
fieldMask |= SECOND_MASK;
}
if(stamp[MILLISECOND] != UNSET){
fieldMask |= MILLISECOND_MASK;
}
if(stamp[ZONE_OFFSET] >= MINIMUM_USER_STAMP){
fieldMask |= ZONE_OFFSET_MASK;
}
if(stamp[DST_OFFSET] >= MINIMUM_USER_STAMP){
fieldMask |= DST_OFFSET_MASK;
}
return fieldMask;
}
int getBaseStyle(int style){
return style & ~STANDALONE_MASK;
}
private int toStandaloneStyle(int style){
return style | STANDALONE_MASK;
}
private boolean isStandaloneStyle(int style){
return (style & STANDALONE_MASK) != 0;
}
private boolean isNarrowStyle(int style){
return style == NARROW_FORMAT || style == NARROW_STANDALONE;
}
private boolean isNarrowFormatStyle(int style){
return style == NARROW_FORMAT;
}
private static int aggregateStamp(int stamp_a, int stamp_b){
if(stamp_a == UNSET || stamp_b == UNSET){
return UNSET;
}
return (stamp_a > stamp_b) ? stamp_a : stamp_b;
}
public static Set<String> getAvailableCalendarTypes(){
return AvailableCalendarTypes.SET;
}
private static class AvailableCalendarTypes {
private static final Set<String> SET;
static{
Set<String> set = new HashSet<>(3);
set.add("gregory");
set.add("buddhist");
set.add("japanese");
SET = Collections.unmodifiableSet(set);
}
private AvailableCalendarTypes(){
}
}
public String getCalendarType(){
return this.getClass().getName();
}
public boolean equals(Object obj){
if(this == obj){
return true;
}
try {
Calendar that = (Calendar) obj;
return compareTo(getMillisOf(that)) == 0 &&
lenient == that.lenient &&
firstDayOfWeek == that.firstDayOfWeek &&
minimalDaysInFirstWeek == that.minimalDaysInFirstWeek &&
zone.equals(that.zone);
} catch (Exception e) {
// Note: GregorianCalendar.computeTime throws
// IllegalArgumentException if the ERA value is invalid
// even it's in lenient mode.
}
return false;
}
@Override
public int hashCode(){
// 'otheritems' represents the hash code for the previous versions.
int otheritems = (lenient ? 1 : 0)
| (firstDayOfWeek << 1)
| (minimalDaysInFirstWeek << 4)
| (zone.hashCode() << 7);
long t = getMillisOf(this);
return (int) t ^ (int) (t >> 32) ^ otheritems;
}
public boolean before(Object when){
return when instanceof Calendar
&& compareTo((Calendar) when) < 0;
}
public boolean after(Object when){
return when instanceof Calendar
&& compareTo((Calendar) when) > 0;
}
@Override
public int compareTo(Calendar anotherCalendar){
return compareTo(getMillisOf(anotherCalendar));
}
abstract public void add(int field, int amount);
abstract public void roll(int field, boolean up);
public void roll(int field, int amount){
while (amount > 0) {
roll(field, true);
amount--;
}
while (amount < 0) {
roll(field, false);
amount++;
}
}
public void setTimeZone(TimeZone value){
zone = value;
sharedZone = false;
/* Recompute the fields from the time using the new zone. This also
* works if isTimeSet is false (after a call to set()). In that case
* the time will be computed from the fields using the new zone, then
* the fields will get recomputed from that. Consider the sequence of
* calls: cal.setTimeZone(EST); cal.set(HOUR, 1); cal.setTimeZone(PST).
* Is cal set to 1 o'clock EST or 1 o'clock PST? Answer: PST. More
* generally, a call to setTimeZone() affects calls to set() BEFORE AND
* AFTER it up to the next call to complete().
*/
areAllFieldsSet = areFieldsSet = false;
}
public TimeZone getTimeZone(){
// If the TimeZone object is shared by other Calendar instances, then
// create a clone.
if(sharedZone){
zone = (TimeZone) zone.clone();
sharedZone = false;
}
return zone;
}
TimeZone getZone(){
return zone;
}
void setZoneShared(boolean shared){
sharedZone = shared;
}
public void setLenient(boolean lenient){
this.lenient = lenient;
}
public boolean isLenient(){
return lenient;
}
public void setFirstDayOfWeek(int value){
if(firstDayOfWeek == value){
return;
}
firstDayOfWeek = value;
invalidateWeekFields();
}
public int getFirstDayOfWeek(){
return firstDayOfWeek;
}
public void setMinimalDaysInFirstWeek(int value){
if(minimalDaysInFirstWeek == value){
return;
}
minimalDaysInFirstWeek = value;
invalidateWeekFields();
}
public int getMinimalDaysInFirstWeek(){
return minimalDaysInFirstWeek;
}
public boolean isWeekDateSupported(){
return false;
}
public int getWeekYear(){
throw new UnsupportedOperationException();
}
public void setWeekDate(int weekYear, int weekOfYear, int dayOfWeek){
throw new UnsupportedOperationException();
}
public int getWeeksInWeekYear(){
throw new UnsupportedOperationException();
}
abstract public int getMinimum(int field);
abstract public int getMaximum(int field);
abstract public int getGreatestMinimum(int field);
abstract public int getLeastMaximum(int field);
public int getActualMinimum(int field){
int fieldValue = getGreatestMinimum(field);
int endValue = getMinimum(field);
// if we know that the minimum value is always the same, just return it
if(fieldValue == endValue){
return fieldValue;
}
// clone the calendar so we don't mess with the real one, and set it to
// accept anything for the field values
Calendar work = (Calendar) this.clone();
work.setLenient(true);
// now try each value from getLeastMaximum() to getMaximum() one by one until
// we get a value that normalizes to another value. The last value that
// normalizes to itself is the actual minimum for the current date
int result = fieldValue;
do {
work.set(field, fieldValue);
if(work.get(field) != fieldValue){
break;
}else{
result = fieldValue;
fieldValue--;
}
} while (fieldValue >= endValue);
return result;
}
public int getActualMaximum(int field){
int fieldValue = getLeastMaximum(field);
int endValue = getMaximum(field);
// if we know that the maximum value is always the same, just return it.
if(fieldValue == endValue){
return fieldValue;
}
// clone the calendar so we don't mess with the real one, and set it to
// accept anything for the field values.
Calendar work = (Calendar) this.clone();
work.setLenient(true);
// if we're counting weeks, set the day of the week to Sunday. We know the
// last week of a month or year will contain the first day of the week.
if(field == WEEK_OF_YEAR || field == WEEK_OF_MONTH){
work.set(DAY_OF_WEEK, firstDayOfWeek);
}
// now try each value from getLeastMaximum() to getMaximum() one by one until
// we get a value that normalizes to another value. The last value that
// normalizes to itself is the actual maximum for the current date
int result = fieldValue;
do {
work.set(field, fieldValue);
if(work.get(field) != fieldValue){
break;
}else{
result = fieldValue;
fieldValue++;
}
} while (fieldValue <= endValue);
return result;
}
@Override
public Object clone(){
try {
Calendar other = (Calendar) super.clone();
other.fields = new int[FIELD_COUNT];
other.isSet = new boolean[FIELD_COUNT];
other.stamp = new int[FIELD_COUNT];
for(int i = 0; i < FIELD_COUNT; i++){
other.fields[i] = fields[i];
other.stamp[i] = stamp[i];
other.isSet[i] = isSet[i];
}
other.zone = (TimeZone) zone.clone();
return other;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
private static final String[] FIELD_NAME = {
"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"
};
static String getFieldName(int field){
return FIELD_NAME[field];
}
@Override
public String toString(){
// NOTE: BuddhistCalendar.toString() interprets the string
// produced by this method so that the Gregorian year number
// is substituted by its B.E. year value. It relies on
// "...,YEAR=<year>,..." or "...,YEAR=?,...".
StringBuilder buffer = new StringBuilder(800);
buffer.append(getClass().getName()).append('[');
appendValue(buffer, "time", isTimeSet, time);
buffer.append(",areFieldsSet=").append(areFieldsSet);
buffer.append(",areAllFieldsSet=").append(areAllFieldsSet);
buffer.append(",lenient=").append(lenient);
buffer.append(",zone=").append(zone);
appendValue(buffer, ",firstDayOfWeek", true, (long) firstDayOfWeek);
appendValue(buffer, ",minimalDaysInFirstWeek", true, (long) minimalDaysInFirstWeek);
for(int i = 0; i < FIELD_COUNT; ++i){
buffer.append(',');
appendValue(buffer, FIELD_NAME[i], isSet(i), (long) fields[i]);
}
buffer.append(']');
return buffer.toString();
}
// =======================privates===============================
private static void appendValue(StringBuilder sb, String item, boolean valid, long value){
sb.append(item).append('=');
if(valid){
sb.append(value);
}else{
sb.append('?');
}
}
private void setWeekCountData(Locale desiredLocale){
/* try to get the Locale data from the cache */
int[] data = cachedLocaleData.get(desiredLocale);
if(data == null){ /* cache miss */
data = new int[2];
data[0] = CalendarDataUtility.retrieveFirstDayOfWeek(desiredLocale);
data[1] = CalendarDataUtility.retrieveMinimalDaysInFirstWeek(desiredLocale);
cachedLocaleData.putIfAbsent(desiredLocale, data);
}
firstDayOfWeek = data[0];
minimalDaysInFirstWeek = data[1];
}
private void updateTime(){
computeTime();
// The areFieldsSet and areAllFieldsSet values are no longer
// controlled here (as of 1.5).
isTimeSet = true;
}
private int compareTo(long t){
long thisTime = getMillisOf(this);
return (thisTime > t) ? 1 : (thisTime == t) ? 0 : -1;
}
private static long getMillisOf(Calendar calendar){
if(calendar.isTimeSet){
return calendar.time;
}
Calendar cal = (Calendar) calendar.clone();
cal.setLenient(true);
return cal.getTimeInMillis();
}
private void adjustStamp(){
int max = MINIMUM_USER_STAMP;
int newStamp = MINIMUM_USER_STAMP;
for(; ; ){
int min = Integer.MAX_VALUE;
for(int i = 0; i < stamp.length; i++){
int v = stamp[i];
if(v >= newStamp && min > v){
min = v;
}
if(max < v){
max = v;
}
}
if(max != min && min == Integer.MAX_VALUE){
break;
}
for(int i = 0; i < stamp.length; i++){
if(stamp[i] == min){
stamp[i] = newStamp;
}
}
newStamp++;
if(min == max){
break;
}
}
nextStamp = newStamp;
}
private void invalidateWeekFields(){
if(stamp[WEEK_OF_MONTH] != COMPUTED &&
stamp[WEEK_OF_YEAR] != COMPUTED){
return;
}
// We have to check the new values of these fields after changing
// firstDayOfWeek and/or minimalDaysInFirstWeek. If the field values
// have been changed, then set the new values. (4822110)
Calendar cal = (Calendar) clone();
cal.setLenient(true);
cal.clear(WEEK_OF_MONTH);
cal.clear(WEEK_OF_YEAR);
if(stamp[WEEK_OF_MONTH] == COMPUTED){
int weekOfMonth = cal.get(WEEK_OF_MONTH);
if(fields[WEEK_OF_MONTH] != weekOfMonth){
fields[WEEK_OF_MONTH] = weekOfMonth;
}
}
if(stamp[WEEK_OF_YEAR] == COMPUTED){
int weekOfYear = cal.get(WEEK_OF_YEAR);
if(fields[WEEK_OF_YEAR] != weekOfYear){
fields[WEEK_OF_YEAR] = weekOfYear;
}
}
}
private synchronized void writeObject(ObjectOutputStream stream)
throws IOException{
// Try to compute the time correctly, for the future (stream
// version 2) in which we don't write out fields[] or isSet[].
if(!isTimeSet){
try {
updateTime();
} catch (IllegalArgumentException e) {}
}
// If this Calendar has a ZoneInfo, save it and set a
// SimpleTimeZone equivalent (as a single DST schedule) for
// backward compatibility.
TimeZone savedZone = null;
if(zone instanceof ZoneInfo){
SimpleTimeZone stz = ((ZoneInfo) zone).getLastRuleInstance();
if(stz == null){
stz = new SimpleTimeZone(zone.getRawOffset(), zone.getID());
}
savedZone = zone;
zone = stz;
}
// Write out the 1.1 FCS object.
stream.defaultWriteObject();
// Write out the ZoneInfo object
// 4802409: we write out even if it is null, a temporary workaround
// the real fix for bug 4844924 in corba-iiop
stream.writeObject(savedZone);
if(savedZone != null){
zone = savedZone;
}
}
private static class CalendarAccessControlContext {
private static final AccessControlContext INSTANCE;
static{
RuntimePermission perm = new RuntimePermission("accessClassInPackage.sun.util.calendar");
PermissionCollection perms = perm.newPermissionCollection();
perms.add(perm);
INSTANCE = new AccessControlContext(new ProtectionDomain[]{
new ProtectionDomain(null, perms)
});
}
private CalendarAccessControlContext(){
}
}
private void readObject(ObjectInputStream stream)
throws IOException, ClassNotFoundException{
final ObjectInputStream input = stream;
input.defaultReadObject();
stamp = new int[FIELD_COUNT];
// Starting with version 2 (not implemented yet), we expect that
// fields[], isSet[], isTimeSet, and areFieldsSet may not be
// streamed out anymore. We expect 'time' to be correct.
if(serialVersionOnStream >= 2){
isTimeSet = true;
if(fields == null){
fields = new int[FIELD_COUNT];
}
if(isSet == null){
isSet = new boolean[FIELD_COUNT];
}
}else if(serialVersionOnStream >= 0){
for(int i = 0; i < FIELD_COUNT; ++i){
stamp[i] = isSet[i] ? COMPUTED : UNSET;
}
}
serialVersionOnStream = currentSerialVersion;
// If there's a ZoneInfo object, use it for zone.
ZoneInfo zi = null;
try {
zi = AccessController.doPrivileged(
new PrivilegedExceptionAction<ZoneInfo>() {
@Override
public ZoneInfo run() throws Exception{
return (ZoneInfo) input.readObject();
}
},
CalendarAccessControlContext.INSTANCE);
} catch (PrivilegedActionException pae) {
Exception e = pae.getException();
if(!(e instanceof OptionalDataException)){
if(e instanceof RuntimeException){
throw (RuntimeException) e;
}else if(e instanceof IOException){
throw (IOException) e;
}else if(e instanceof ClassNotFoundException){
throw (ClassNotFoundException) e;
}
throw new RuntimeException(e);
}
}
if(zi != null){
zone = zi;
}
// If the deserialized object has a SimpleTimeZone, try to
// replace it with a ZoneInfo equivalent (as of 1.4) in order
// to be compatible with the SimpleTimeZone-based
// implementation as much as possible.
if(zone instanceof SimpleTimeZone){
String id = zone.getID();
TimeZone tz = TimeZone.getTimeZone(id);
if(tz != null && tz.hasSameRules(zone) && tz.getID().equals(id)){
zone = tz;
}
}
}
public final Instant toInstant(){
return Instant.ofEpochMilli(getTimeInMillis());
}
}