com.adobe.xfa.ut.LcDate Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of aem-sdk-api Show documentation
Show all versions of aem-sdk-api Show documentation
The Adobe Experience Manager SDK
The newest version!
/*
* ADOBE CONFIDENTIAL
*
* Copyright 2005 Adobe Systems Incorporated All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains the property of
* Adobe Systems Incorporated and its suppliers, if any. The intellectual and
* technical concepts contained herein are proprietary to Adobe Systems
* Incorporated and its suppliers and may be covered by U.S. and Foreign
* Patents, patents in process, and are protected by trade secret or copyright
* law. Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained from
* Adobe Systems Incorporated.
*/
package com.adobe.xfa.ut;
import java.util.Calendar;
import java.util.TimeZone;
/**
* The LcDate class defines objects in support of
* XFA date picture patterns.
*
*
* The date is internally represented as the number of days from the epoch,
* which is Jan 1, 1900, i.e., day 1 is Jan 1, 1900.
*
*
* Date picture patterns are used to parse and format date strings.
* Here are the metasymbols that form valid date picture patterns:
*
* - D
*
- a one or two digit (1-31) day of month.
*
- DD
*
- a two digit (01-31) day of month.
*
- J
*
- a one, two or three digit (1-366) day of year.
*
- JJJ
*
- a three digit (001-366) day of year.
*
- E
*
- a one digit (1-7) day of week.
*
- EEE
*
- an abbreviated weekday name of the ambient locale.
*
- EEEE
*
- a full weekday name of the ambient locale.
*
- G
*
- a era name of the ambient locale.
*
- M
*
- a one or two digit (1-12) month of year.
*
- MM
*
- a two digit (01-12) month of year.
*
- MMM
*
- an abbreviated month name of the ambient locale.
*
- MMMM
*
- a full month name of the ambient locale.
*
- w
*
- a one digit (0-5) week of the month. Week 1 of a month is the earliest
* set of four contiguous days in that month that ends on a Saturday.
*
- WW
*
- a two digit (01-53) ISO8601 week of the year. Week 1 of a year is the
* week containing January 4.
*
- YY
*
- a two digit (00-99) year.
*
- YYYY
*
- a four digit (1900-2999) year.
*
*
* Here's a snippet of code illustrating the use of {@link LcDate} to reformat
* a date string
*
*
*
* import com.adobe.xfa.ut.LcDate; // for defn of LcDate.
* ...
* LcDate today = new LcDate("", DEFAULT_CENTURY_SPLIT);
* String s = today.format("EEEE', the 'D' of 'MMMM', 'YYYY");
* System.out.println(s);
* LcDate date = new LcDate("28/2/2000", "D/M/YYYY",
* "", DEFAULT_CENTURY_SPLIT);
* date += 30;
* if (date.isValid())
* System.out.println(date.format(LcDate.getDateFormat(4, "pt_BR")););
*
*
*
* @author Mike P. Tardif
*
* @exclude from published api.
*/
public class LcDate {
/**
* An static inner class to represent CJK era info.
*/
private static class CJKTable {
final int nEpochStart; // start of era epoch in years.
final int nEraStart; // start of era in days.
final String sEraSyms[]; // era symbols.
CJKTable(int nEra, int nEpoch, String[] sSyms) {
nEraStart = nEra;
nEpochStart = nEpoch;
sEraSyms = sSyms;
}
}
/**
* An inner class to represent locale sensitive
* date symbols.
*/
static class Symbols {
final String abbrMonthName[] = new String[12];
final String abbrWeekdayName[] = new String[7];
final String altEraName[] = new String[4];
final String eraName[] = new String[2];
final String monthName[] = new String[12];
final String weekdayName[] = new String[7];
char zeroDigit;
}
/**
* ISO8601/XFA date pattern string: YYYYMMDD.
*/
public static final String DATE_FMT1 = "YYYYMMDD";
/**
* Alternate ISO8601/XFA date pattern string: YYYY-MM-DD.
*/
public static final String DATE_FMT2 = "YYYY-MM-DD";
/**
* LcDate pattern symbols: DJMEY.
*/
public static final String DATE_PICTURE_SYMBOLS = "DJMEeYWwGgt";
/**
* Default century split year: 30. This corresponds to the year
* 1930.
*/
public static final int DEFAULT_CENTURY_SPLIT = 30;
/**
* Default LcDate pattern string for English_US locale: MMM D, YYYY.
*/
public static final String DEFAULT_DATE_FMT = "MMM D, YYYY";
/**
* Chinese (China) Eras.
*/
static final CJKTable[] gCnEra = {
new CJKTable(1, 26, new String[] { "\u5149\u7eea" }), // GuangXu: 1875/01/01 - 1908/12/31
new CJKTable(3288, 1, new String[] { "\u5ba3\u7edf" }), // XuanTong: 1909/01/01 - 1911/12/31
new CJKTable(4383, 1, new String[] { "\u6c11\u56fd" }), // MinGuo: 1912/01/01 - 1949/09/30
new CJKTable(18171, 1949, new String[] { "" }) // unamed: 1949/10/01 - present
};
/**
* Chinese (Hong Kong) Eras.
*/
static final CJKTable[] gHkEra = {
new CJKTable(1, 26, new String[] { "\u5149\u7dd2" }), // GuangXu: 1875/01/01 - 1908/12/31
new CJKTable(3288, 1, new String[] { "\u5ba3\u7d71" }), // XuanTong: 1909/01/01 - 1911/12/31
new CJKTable(4383, 1, new String[] { "\u6c11\u570b" }), // MinGuo: 1912/01/01 - present (zh_TW)
new CJKTable(18171, 1949, new String[] { "" }) // unamed: 1949/10/01 - present (zh_HK, zh_MO)
};
/**
* Japanese (Imperial) Eras.
*/
static final CJKTable[] gJpEra= {
new CJKTable(1, 33, new String[] { "M", "\uff2d", "\u660e", "\u660e\u6cbb", "\337e" }), // Meiji: 1868/09/08 - 1912/07/29
new CJKTable(4594, 1, new String[] { "T", "\uff34", "\u5927", "\u5927\u6b63", "\u337d" }), // Taisho: 1912/07/30 - 1926/12/24
new CJKTable(9855, 1, new String[] { "S", "\uff33", "\u662d", "\u662d\u548c", "\u337c" }), // Showa: 1926/12/25 - 1989/01/07
new CJKTable(32515, 1, new String[] { "H", "\uff28", "\u5e73", "\u5e73\u6210", "\u337b" }) // Heisei: 1989/01/08 - present
};
/**
* Korean Eras.
*/
static final CJKTable[] gKrEra = {
new CJKTable(1, 4233, /* 1900 + 2333 */ new String[] { "\ub2e8\uae30" /* Hangual */, "\u6a80\u7d00" /* Hanja */})
};
/**
* Thai Eras.
*/
static final CJKTable[] gThEra = {
new CJKTable(1, 2442, /* 1900 + 542 */ new String[] { "B.E.", "\u0e1e.\u0e28.", "\u0e1e\u0e38\u0e17\u0e18\u0e28\u0e31\u0e01\u0e23\u0e32\u0e0a" }) // Buddist Era
};
/**
* Chinese (Taiwan) Eras.
*/
static final CJKTable[] gTwEra = {
new CJKTable(1, 26, new String[] { "\u5149\u7dd2" }), // GuangXu: 1875/01/01 - 1908/12/31
new CJKTable(3288, 1, new String[] { "\u5ba3\u7d71" }), // XuanTong: 1909/01/01 - 1911/12/31
new CJKTable(4383, 1, new String[] { "\u6c11\u570b" }) // MinGuo: 1912/01/01 - present
};
/*
* The cumulative number of days in a (non-leap) year
* at the start of each month.
*/
static final int monthDays[] = {
/* Jan 1 */0,
/* Feb 1 */0 + 31,
/* Mar 1 */31 + 28,
/* Apr 1 */59 + 31,
/* May 1 */90 + 30,
/* Jun 1 */120 + 31,
/* Jul 1 */151 + 30,
/* Aug 1 */181 + 31,
/* Sep 1 */212 + 31,
/* Oct 1 */243 + 30,
/* Nov 1 */273 + 31,
/* Dec 1 */304 + 30,
/* Jan 1 */334 + 31
};
/*
* Get locale-specific era table given the locale.
*
* @param locale
* a locale
*
* @return
* a CJKTable of era symbols.
*/
private static CJKTable[] getAltEraTable(LcLocale locale) {
if (locale.isJapanese())
return gJpEra;
else if (locale.isKorean())
return gKrEra;
else if (locale.isTraditionalChinese())
return gTwEra;
else if (locale.getName().equals(LcLocale.Chinese_HongKong))
return gHkEra;
else if (locale.isChinese())
return gCnEra;
else if (locale.isThai())
return gThEra;
return null;
}
/**
* Gets the date pattern in the given style for the given locale.
*
* @param style
* a style value:
*
* - 0
*
- requests the locale specific default-style date pattern,
*
- 1
*
- requests the locale specific short-style date pattern,
*
- 2
*
- requests the locale specific medium-style date pattern,
*
- 3
*
- requests the locale specific long-style date pattern, and
*
- 4
*
- requests the locale specific full-style date pattern.
*
* Any other value requests the default-style date pattern.
* @param locale
* a locale string. When empty, it will default
* to the default locale.
*/
public static String getDateFormat(int style, String locale) {
return new LcData(locale).getDateFormat(style);
}
/*
* Get the start of an epoch (in years) given a locale-specific era table
* and the start of an era.
*
* @param pEraTbl
* a locale specific era table.
* @param eraStart
* the starting value of an era.
*
* @return
* the start of an epoch in years.
*/
private static int getEpochEra(CJKTable[] pEraTbl, int eraStart) {
assert(pEraTbl != null);
for (int i = 0; i < pEraTbl.length; i++)
if (pEraTbl[i].nEraStart == eraStart)
return pEraTbl[i].nEpochStart;
return 0;
}
/**
* Gets the localized date pattern in the given style for the given locale.
*
* @param style
* a style value:
*
* - 0
*
- requests the locale specific default-style date pattern,
*
- 1
*
- requests the locale specific short-style date pattern,
*
- 2
*
- requests the locale specific medium-style date pattern,
*
- 3
*
- requests the locale specific long-style date pattern, and
*
- 4
*
- requests the locale specific full-style date pattern.
*
* Any other value requests the default-style date pattern.
* @param locale
* a locale string. When empty, it will default
* to the default locale.
*/
public static String getLocalDateFormat(int style, String locale) {
return new LcData(locale).getLocalDateFormat(style);
}
/*
* Get the start of the next era (in days)
* given a locale-specific era table and the start of an era.
*/
private static int getNextEra(CJKTable[] pEraTbl, int eraStart) {
assert(pEraTbl != null);
for (int i = 0; i < pEraTbl.length; i++)
if (pEraTbl[i].nEraStart > eraStart)
return pEraTbl[i].nEraStart;
return Integer.MAX_VALUE;
}
/*
* Determine if the given year is a leap year.
*
* @param year
* a year since 1900.
*
* @return
* boolean true if its a leap year, and false otherwise.
*/
private static boolean isLeap(int year) {
return ((year & 3) == 0 && (year % 100 != 0 || year % 400 == 0));
}
private static int P(int y) {
return ((y) + (y) / 4 - (y) / 100 + (y) / 400);
}
/*
* Calculate the number of days from the epoch to the start of the given
* year.
*
* @param year
* a year since 1900.
*
* @return
* the number of days from the epoch to the start of year.
*/
private static int startOfYear(int year) {
if (year == 0)
return 0;
//
// Account properly for leap years.
// Note: 1900 is year 1, so offset accordingly!
//
int days = year * 365 // days per year
+ (year - 1) / 4 // plus leap days except
- (year - 1) / 100 // centurial years except
+ (year + 299) / 400; // centurial years divisible by 400
return days;
}
/**
* The parsed a 2-digit year.
*/
int m2DigitYear;
/**
* The local time zone adjustment
*/
int mAdjustment;
/**
* The parsed alternate era.
*/
int mAltEra;
/**
* The presence of an ISO week pattern.
*/
boolean mbISOWeekSeen;
/**
* The century split year.
*/
int mCenturySplit;
/**
* The parsed day of the month.
*/
int mDayOfMonth;
/**
* The parsed day of the week.
*/
int mDayOfWeek;
/**
* The parsed day of the year.
*/
int mDayOfYear;
/**
* The number of days from the epoch.
*/
int mDays;
/**
* The parsed era.
*/
int mEra;
/**
* The date's locale.
*/
protected LcLocale mLocale;
/**
* The parsed month of the year.
*/
int mMonthOfYear;
/**
* The date's locale sensitive symbols.
*/
final Symbols mSymbols = new Symbols();
/**
* The validity of this date.
*/
boolean mValid;
/**
* The parsed week of the month.
*/
int mWeekOfMonth;
/**
* The parsed week of the year.
*/
int mWeekOfYear;
/**
* The parsed year of the era.
*/
int mYearOfEra;
/**
* Instantiates an LcDate object from the given number of days from the
* epoch and in the locale given. The epoch is such that day 1 corresponds
* to Jan 1, 1900.
*
* @param days
* the number of days from the epoch.
* @param locale
* a locale string. When empty, it will default to the default
* locale.
* @param centurySplit
* a century split year.
*/
public LcDate(int days, String locale /* = "" */,
int centurySplit /* = DEFAULT_CENTURY_SPLIT */) {
String sLocale = StringUtils.isEmpty(locale) ? LcLocale.getLocale() : locale;
mLocale = new LcLocale(sLocale);
if (! mLocale.isValid())
mLocale = new LcLocale(LcLocale.DEFAULT_LOCALE);
setDateSymbols(mLocale.getIsoName());
mCenturySplit = centurySplit;
mAdjustment = 0;
mDays = days;
mValid = (mDays > 0);
}
/**
* Instantiates an LcDate object from today's date and in the locale given.
*
* @param locale
* a locale string. When empty, it will default to the default
* locale.
* @param centurySplit
* a century split year.
*/
public LcDate(String locale /* = "" */,
int centurySplit /* = DEFAULT_CENTURY_SPLIT */) {
Calendar today = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
int day = today.get(Calendar.DAY_OF_MONTH);
int month = today.get(Calendar.MONTH) + 1;
int year = today.get(Calendar.YEAR);
String sLocale = StringUtils.isEmpty(locale) ? LcLocale.getLocale() : locale;
mLocale = new LcLocale(sLocale);
if (! mLocale.isValid())
mLocale = new LcLocale(LcLocale.DEFAULT_LOCALE);
setDateSymbols(mLocale.getIsoName());
mCenturySplit = centurySplit;
mAdjustment = 0;
mDays = epoch(day, month, year);
mValid = (mDays > 0);
}
/**
* Instantiates an LcDate object from the given date in the pattern given
* and in the locale given.
*
* @param date
* a date string.
* @param pat
* a date pattern string used to parse the given date.
* @param locale
* a locale string. When empty, it will default to the default
* locale.
* @param centurySplit
* a century split year.
*/
public LcDate(String date, String pat, String locale /* = "" */,
int centurySplit /* = DEFAULT_CENTURY_SPLIT */) {
String sLocale = StringUtils.isEmpty(locale) ? LcLocale.getLocale() : locale;
mLocale = new LcLocale(sLocale);
if (! mLocale.isValid())
mLocale = new LcLocale(LcLocale.DEFAULT_LOCALE);
setDateSymbols(mLocale.getIsoName());
mCenturySplit = centurySplit;
mAdjustment = 0;
if (parse(date, pat)) {
//
// If alternate era Then adjust year to start of alternate epoch.
//
CJKTable[] pEraTbl = null;
if (mAltEra >= 0) {
pEraTbl = getAltEraTable(mLocale);
LcDate oEra = new LcDate(mAltEra,
mLocale.getIsoName(), mCenturySplit);
if (mYearOfEra == 1
&& 0 <= mDayOfYear && mDayOfYear < oEra.getYearDay())
mDayOfYear += 366;
mYearOfEra += oEra.getYear();
int nEpoch = getEpochEra(pEraTbl, mAltEra);
if (m2DigitYear >= 0)
mYearOfEra += (nEpoch / 100) * 100;
mYearOfEra -= nEpoch;
if (mLocale.isThai()) {
if (m2DigitYear >= 0 && mYearOfEra % 100 < mCenturySplit)
mYearOfEra += 100;
//
// After 1940, Thai years begin on Jan 1'st. Before 1941,
// Thai years began on Apr 1'st, which means year 2483 B.E.
// had only nine months.
//
if (mYearOfEra > 1940
|| mYearOfEra <= 1940 && mMonthOfYear > 3)
mYearOfEra--;
}
mYearOfEra -= 1900;
}
else {
//
// Normalize the year to the number of years since 1900.
// For Y2K, any 2 digit years before the century split year
// is presumed to be in the 21'st century, as are any
// 2 digit years when the CENTURY_SPLIT is zero.
//
if (m2DigitYear >= 0 && mYearOfEra < 100 && mCenturySplit == 0)
mYearOfEra += 100;
else if (m2DigitYear >= 0 && mYearOfEra < mCenturySplit)
mYearOfEra += 100;
else if (mYearOfEra >= 100)
mYearOfEra -= 1900;
}
//
// Fix for Watson 1084938. For epochWeekMonth(), map Day Of Week
// to ISO days (1 == Mon).
//
if (mDayOfYear >= 0 && mYearOfEra >= 0)
mDays = epoch(mDayOfYear, mYearOfEra);
else if (mDayOfWeek >= 0 && mWeekOfYear >= 0 && mYearOfEra >= 0)
mDays = epochWeekYear((mDayOfWeek + 5) % 7 + 1,
mWeekOfYear, mYearOfEra);
else if (mWeekOfMonth >= 0 && mMonthOfYear >= 0 && mYearOfEra >= 0)
mDays = epochWeekMonth(mDayOfWeek, mWeekOfMonth,
mMonthOfYear, mYearOfEra);
else /* if (mDayOfMonth >= 0 && mMonthOfYear >= 0 && mYearOfEra >= 0) */
mDays = epoch(mDayOfMonth, mMonthOfYear, mYearOfEra);
if (mAltEra >= 0) {
if (mDays >= getNextEra(pEraTbl, mAltEra))
mDays = 0;
else if (mDays < mAltEra)
mDays = 0;
}
}
else {
mDays = 0;
}
mValid = (mDays > 0);
}
/**
* Adds the given number of days to this object.
*
* @param nDays
* the number of days to add.
* @return
* this modified object.
*/
public LcDate add(int nDays) {
if (mValid)
mDays += nDays;
mValid = (mDays > 0);
return this;
}
/**
* Calculates the number of days from the epoch given the year and day
* of year.
*
* @param day
* a day (1-366) of year.
* @param year
* a year (0-99, 1900-YYYY) of era.
* @return
* the number of days from the epoch,
* or 0 upon an invalid day, year.
*/
private int epoch(int day, int year) {
//
// Validate given year.
//
if (year < 0)
return 0;
//
// Validate given day.
//
if (day < 1 || 366 < day)
return 0;
else if (! isLeap(year + 1900) && day == 366)
return 0;
//
// Return number of days from the epoch.
//
return startOfYear(year) + day;
}
/**
* Calculates the number of days from the epoch given a day, month and year.
* @param day
* a day (1-31) of month.
* @param month
* a month (1-12) of year.
* @param year
* a year (0-99 or 1900-YYYY) of era.
* @return
* the number of days from the epoch,
* or 0 upon an invalid day, month, year.
*/
int epoch(int day, int month, int year) {
//
// Validate given year.
//
if (year < 0)
return 0;
//
// Validate given month.
//
if (month < 1 || 12 < month)
return 0;
//
// Validate given day.
//
int monthEnd = monthDays[month] - monthDays[month - 1];
if (month == 2 && isLeap(year + 1900))
monthEnd++;
if (day < 1 || monthEnd < day) {
return 0;
}
//
// Return number of days from the epoch.
//
int days = startOfYear(year) + monthDays[month - 1] + day;
if (month > 2 && isLeap(year + 1900))
days++;
return days;
}
/*
* Calculates the number of days from the epoch given
* a week, month and year. Week 1 of a month is the earliest set of 4
* contiguous days in that month, ending on the day before Sunday.
* Week 1 of a month may be shorter than 7 days, and need not start on
* Sunday,
*
* For example, the first week of Jan 1998 is Sunday, Jan 4 through
* Saturday, Jan 10. Thursday, Jan 1 through Saturday, Jan 3 belongs
* to week 0.
*
* @param day
* a day (1-7) of week.
* @param week
* a week (1-5) of month.
* @param month
* a month (1-12) of year.
* @param year
* a year (YYYY - 1900) of era.
* @return
* the number of days from the epoch, or 0 upon an invalid
* day, month, year.
*/
private int epochWeekMonth(int day, int week, int month, int year) {
//
// Validate given year.
//
if (year < 0)
return 0;
//
// Validate given month.
//
if (month < 1 || 12 < month)
return 0;
//
// Validate given week.
//
if (week < 0 || 5 < week)
return 0;
//
// Find first day of month.
//
int fdm = startOfYear(year) + monthDays[month - 1];
if (month > 2 && isLeap(1900 + year))
fdm++;
//
// Find first Sunday of month.
//
int sunday;
int dow = fdm % 7 + 1;
if (dow >= 4)
sunday = 8 - dow; // Skip the first partial week.
else
sunday = 1 - dow; // This may be zero or negative.
int d = sunday + (week - 1) * 7;
if (day > 0)
d += (day - 1); // Incorporated given day of week.
return fdm + d;
}
/*
* Calculates the number of days from the epoch given
* an ISO day, ISO week and ISO year.
*
* @param ISOday
* an ISO day (1-7) of ISO week (Mon=1).
* @param ISOweek
* an ISO week (1-53) of year.
* @param ISOyear
* an ISO year (YYYY - 1900) of era.
* @return
* the number of days from the epoch, or 0 upon an invalid
* day, month, year.
*/
private int epochWeekYear(int ISOday, int ISOweek, int ISOyear) {
//
// Validate given year.
//
if (ISOyear < 0)
return 0;
//
// Validate given week.
//
if (ISOweek < 1 || 53 < ISOweek)
return 0;
//
// Validate given day.
//
if (ISOday < 1 || 7 < ISOday)
return 0;
int y = (1900 + ISOyear);
//
// Validate that given week is truly long ISO year:
// see "The Mathematics of the ISO 8601 Calendar" by R.H. van Gent
// available at http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
//
if (ISOweek == 53) {
if (P(y) % 7 != 4 && P(y - 1) % 7 != 3)
return 0;
}
//
// Compute day of the week Jan1 falls on.
//
int Y = (y - 1) % 100;
int C = (y - 1) - Y;
int G = Y + Y / 4;
int Jan1 = 1 + (((((C / 100) % 4) * 5) + G) % 7);
int days;
//
// If ISO date falls on previous year.
//
if (ISOweek == 1 && Jan1 <= 4 && ISOday < Jan1) {
days = startOfYear(ISOyear - 1) + monthDays[11]
+ 32 - (Jan1 - ISOday);
if (isLeap(y - 1))
days++;
}
//
// Else ISO date falls on given year.
//
else {
int doy = (ISOweek - 1) * 7;
if (Jan1 <= 4)
doy += ISOday - (Jan1 - 1);
else
doy += ISOday + (8 - Jan1);
int eoy = isLeap(y) ? 366 : 365;
if (doy > eoy)
days = startOfYear(ISOyear + 1) + (doy - eoy);
else
days = startOfYear(ISOyear) + doy;
}
return days;
}
/**
* Formats this object according to the given a date pattern string.
*
* @param pat
* a date pattern string.
* @return
* the date string formatted according to the given pattern string,
* upon success, and the empty string, upon error.
*/
public String format(String pat) {
if (!mValid)
return "";
//
// Reset seen states.
//
mbISOWeekSeen = false;
//
// Pre-scan pattern for subelements of interest.
//
scan(pat);
mAltEra = -1;
StringBuilder sBuf = new StringBuilder();
char prevChr = 0;
int chrCnt = 0;
boolean inQuoted = false;
boolean inQuoteQuoted = false;
//
// Foreach each character of the pattern Do ...
//
int patLen = pat.length();
for (int i = 0; i < patLen; i++) {
char chr = pat.charAt(i);
String sub;
//
// If seen a quote within a quoted string ...
//
if (inQuoteQuoted) {
if (chr == '\'') { // cases like '...''
sBuf.append(chr);
chrCnt = 0;
} else { // cases like '...'M
inQuoted = false;
chrCnt = 1;
prevChr = chr;
}
inQuoteQuoted = false;
}
//
// Elif within a quoted string ...
//
else if (inQuoted) {
if (chr == '\'') { // cases like '...'
inQuoteQuoted = true;
} else { // cases like '...M
sBuf.append(chr);
}
chrCnt++;
}
//
// Elif start of a quoted string ...
//
else if (chr == '\'') {
if (chrCnt > 0) { // cases like ...M'
sub = subFormat(prevChr, chrCnt);
if (sub.length() == 0)
return "";
if (!sub.equals("\u001f")) // handle empty Chinese era name
sBuf.append(sub);
chrCnt = 0;
prevChr = 0;
}
inQuoted = true;
}
//
// Elif start of a metacharacter ...
//
else if (DateTimeUtil.matchChr(DATE_PICTURE_SYMBOLS, chr)
|| ('a' <= chr && chr <= 'z' || 'A' <= chr && chr <= 'Z')) {
if (chr != prevChr) {
if (chrCnt > 0) { // cases like YYM
sub = subFormat(prevChr, chrCnt);
if (sub.length() == 0)
return "";
if (!sub.equals("\u001f")) // handle empty Chinese era name
sBuf.append(sub);
chrCnt = 0;
}
prevChr = chr;
}
chrCnt++;
}
//
// Elif start of a literal ...
//
else {
if (chrCnt > 0) { // cases like MM-
sub = subFormat(prevChr, chrCnt);
if (sub.length() == 0)
return "";
if (!sub.equals("\u001f"))
sBuf.append(sub);
chrCnt = 0;
}
prevChr = 0;
if (chr == '?' || chr == '*' || chr == '+')
sBuf.append(' ');
else
sBuf.append(chr);
}
}
//
// Ensure quoted string is terminated.
//
if (inQuoteQuoted)
inQuoted = false;
if (inQuoted)
return "";
//
// Format any remaining items in the pattern.
//
if (prevChr > 0 && chrCnt > 0) {
String sub = subFormat(prevChr, chrCnt);
if (sub.length() == 0)
return "";
if (!sub.equals("\u001f"))
sBuf.append(sub);
}
return sBuf.toString();
}
/**
* Gets the number of days since the epoch.
*
* @return
* the number of days, or 0 if this object is invalid.
*/
public int getDays() {
return mDays + mAdjustment;
}
/**
* Gets the ISO8601 week of the year. ISO8601 defines the week as always
* starting with Monday being day 1 and finishing with Sunday being day 7.
* Therefore, the days of an ISO week can be in two different calendar
* years; and, because a calendar year has one or two more than 52x7=364
* days, an ISO year has either 52 or 53 weeks.
*
* ISO defines the first week of a year as the week containing Jan 4!
*
* @return
* the week of the year in the range 1-53, or -1 upon an invalid
* date.
*/
public int getISOWeek() {
if (! mValid)
return -1;
int year = getYear();
int doy = getDays() - startOfYear(year - 1900);
//
// Find weekday (Mon = 1, Sun = 7) of Jan1.
//
int Y = (year - 1) % 100;
int C = (year - 1) - Y;
int G = Y + Y / 4;
int Jan1 = 1 + (((((C / 100) % 4) * 5) + G) % 7);
int h = doy + (Jan1 - 1);
//
// Given Gregorian year, month, day compute ISO year, week, day.
// Credit Rick McCarty (http://personal.ecu.edu/mccartyr/ISOwdALG.txt)
// for algorithm.
//
int ISOweek;
//
// Find ISO day.
//
int ISOday = 1 + (h - 1) % 7;
//
// If year, month, day falls in ISOyear year - 1 Then
// find if ISOweek 52 or 53.
//
if (doy <= (8 - Jan1) && Jan1 > 4) {
if (Jan1 == 5 || (Jan1 == 6 && isLeap(year - 1)))
ISOweek = 53;
else
ISOweek = 52;
}
else {
//
// If year, month, day falls in ISOyear year + 1 Then
// find if ISOweek 1.
//
if (((isLeap(year) ? 366 : 365) - doy) < (4 - ISOday)) {
ISOweek = 1;
}
//
// If year, month, day falls in ISOyear year Then
// find if ISOweek 1 through 53.
//
else {
ISOweek = (doy + ( 7 - ISOday) + (Jan1 - 1)) / 7;
if (Jan1 > 4)
ISOweek--;
}
}
return ISOweek;
};
/**
* Gets the ISO8601 year of the era.
*
* @return
* the year of the era in the range 1900-YYYY,
* or -1 upon an invalid date.
*/
public int getISOYear() {
if (! mValid)
return -1;
int year = getYear();
int doy = getDays() - startOfYear(year - 1900);
//
// Find weekday (Mon = 1, Sun = 7) of Jan1.
//
int Y = (year - 1) % 100;
int C = (year - 1) - Y;
int G = Y + Y / 4;
int Jan1 = 1 + (((((C / 100) % 4) * 5) + G) % 7);
int h = doy + (Jan1 - 1);
//
// Given Gregorian year, month, day compute ISO year, week, day.
// Credit Rick McCarty (http://personal.ecu.edu/mccartyr/ISOwdALG.txt)
// for algorithm.
//
int ISOyear;
//
// Find ISO day.
//
int ISOday = 1 + (h - 1) % 7;
//
// If year, month, day falls in ISOyear year - 1 Then
// find if ISOweek 52 or 53.
//
if (doy <= (8 - Jan1) && Jan1 > 4) {
ISOyear = year - 1;
}
else {
ISOyear = year;
//
// If year, month, day falls in ISOyear year + 1 Then
// find if ISOweek 1.
//
if (((isLeap(year) ? 366 : 365) - doy) < (4 - ISOday))
ISOyear = year + 1;
}
return ISOyear;
}
/**
* Gets the month of the year.
*
* @return
* the month of the year in the range 1-12, where (1 = January), or
* -1 upon an invalid date.
*/
public int getMonth() {
if (!mValid)
return -1;
int year = getYear();
int leapCorrector = isLeap(year) ? 1 : 0;
int daysInYear = getDays() - startOfYear(year - 1900);
int month = 12;
while (daysInYear <= monthDays[month - 1] + leapCorrector) {
month--;
if (month <= 2)
leapCorrector = 0;
}
return month;
}
/**
* Gets the day of the month.
*
* @return
* the day of the month in the range 1-31, or -1 upon an invalid
* date.
*/
public int getMonthDay() {
if (!mValid)
return -1;
int year = getYear();
int month = getMonth();
int daysInYear = getDays() - startOfYear(year - 1900);
int day = daysInYear - monthDays[month - 1];
if (month > 2 && isLeap(year))
day--;
return day;
}
/**
* Gets the day of the week.
*
* @return
* the day of the week in the range of values 1-7, where (1 =
* Sunday), or -1 upon an invalid date.
*/
public int getWeekDay() {
if (! mValid)
return -1;
return (getDays() % 7 + 1); // day 1 (Jan 1, 1900) fell on a Monday.
}
/**
* Gets the week of the month. Week 1 of a month is the earliest set of 4
* contiguous days in that month, ending on the day before Sunday. Unlike
* week 1 of a year, week 1 of a month may be shorter than 7 days, need
* not start on Sunday, and will not include days of the previous month.
*
* For example, the first week of Jan 1998 is Sunday, Jan 4 through
* Saturday, Jan 10. Thursday, Jan 1 through Saturday, Jan 3 belongs
* to week 0.
*
* @return
* the week of the month in the range 0-5, or -1 upon an invalid
* date.
*/
public int getWeekMonth() {
if (! mValid)
return -1;
int dom = getMonthDay();
int dow = getWeekDay();
//
// Find first Sunday of month.
//
int sunday = (dom - dow) % 7 + 1;
if (sunday <= 0)
sunday += 7;
//
// Find week of month. If the first week is long enough, then count it.
//
int week = (dom - sunday + 7) / 7;
if (sunday > 4)
week++;
return week;
}
/**
* Gets the year of the era.
*
* @return
* the year of the era in the range 1900-YYYY,
* or -1 upon an invalid date.
*/
public int getYear() {
if (!mValid)
return -1;
int year = 1900;
int day = getDays();
int daysInYear;
while (day > (daysInYear = isLeap(year) ? 366 : 365)) {
year++;
day -= daysInYear;
}
return year;
}
/**
* Gets the day of the year.
*
* @return
* the day of the year in the range 1-366, or -1 upon an invalid
* date.
*/
public int getYearDay() {
if (! mValid)
return -1;
int year = getYear();
int daysInYear = getDays() - startOfYear(year - 1900);
return daysInYear;
}
/**
* Determines if this object is valid.
*
* @return
* boolean true if valid, and false otherwise.
*/
public boolean isValid() {
return mValid;
}
/**
* Parses the given string according to the date pattern given.
*
* @param str
* the date string to parse.
* @param pat
* the pattern string to parse.
*
* @return
* boolean true if successfully parsed, and false otherwise.
*/
boolean parse(String str, String pat) {
int strPos = 0;
char prevChr = 0;
int chrCnt = 0;
boolean inQuoted = false;
boolean inQuoteQuoted = false;
int strLen = str.length();
int patLen = pat.length();
int parseRes;
//
// Reset parsed date sub-elements.
//
mDayOfWeek = -1;
mDayOfMonth = -1;
mDayOfYear = -1;
mMonthOfYear = -1;
mYearOfEra = -1;
mWeekOfMonth = -1;
mWeekOfYear = -1;
m2DigitYear = -1;
mEra = -1;
mAltEra = -1;
//
// Foreach each character of the pattern Do ...
//
for (int i = 0; i < patLen; ) {
char chr = pat.charAt(i); i++;
boolean fw = (0xFF01 <= chr && chr <= 0xFF5E);
if (strPos >= strLen) {
if (inQuoted && chr == '\'') {
inQuoteQuoted = true;
break;
}
return false;
}
//
// If seen a quote within a quoted string ...
//
if (inQuoteQuoted) {
if (chr == '\'') { // cases like '...''
if (! DateTimeUtil.matchChr(str, strPos, chr, fw))
return false;
strPos += 1;
chrCnt = 0;
}
else { // cases like '...'M
inQuoted = false;
chrCnt = 1;
prevChr = chr;
}
inQuoteQuoted = false;
}
//
// Elif within a quoted string ...
//
else if (inQuoted) {
if (chr == '\'') { // cases like '...'
inQuoteQuoted = true;
}
else { // cases like '...M
if (! DateTimeUtil.matchChr(str, strPos, chr, fw))
return false;
strPos += 1;
}
chrCnt++;
}
//
// Elif start of a quoted string ...
//
else if (chr == '\'') {
if (chrCnt > 0) { // cases like ...M'
parseRes = subParse(str, strPos, prevChr, chrCnt);
if (parseRes < 0)
return false;
strPos = parseRes;
chrCnt = 0;
prevChr = 0;
}
inQuoted = true;
}
//
// Elif start of a metacharacter ...
//
else if (DateTimeUtil.matchChr(DATE_PICTURE_SYMBOLS, chr)
|| ('a' <= chr && chr <= 'z' || 'A' <= chr && chr <= 'Z')) {
if (chr != prevChr) {
if (chrCnt > 0) { // cases like YYM
parseRes = subParse(str, strPos, prevChr, chrCnt);
if (parseRes < 0)
return false;
strPos = parseRes;
chrCnt = 0;
}
prevChr = chr;
}
chrCnt++;
}
//
// Elif start of a literal ...
//
else {
if (chrCnt > 0) { // cases like MM-
parseRes = subParse(str, strPos, prevChr, chrCnt);
if (parseRes < 0)
return false;
strPos = parseRes;
chrCnt = 0;
prevChr = 0;
}
if (chr == '?') {
if (strPos < strLen
&& Character.isDefined(str.charAt(strPos)))
strPos += 1;
}
else if (chr == '+') {
if (strPos >= strLen
|| ! Character.isWhitespace(str.charAt(strPos)))
return false;
strPos += 1;
while (strPos < strLen
&& Character.isWhitespace(str.charAt(strPos)))
strPos += 1;
}
else if (chr == '*') {
while (strPos < strLen
&& Character.isWhitespace(str.charAt(strPos)))
strPos += 1;
}
else if (strPos < strLen && str.charAt(strPos) == chr) {
strPos += 1;
}
else {
return false;
}
}
}
//
// Ensure quoted string is terminated.
//
if (inQuoteQuoted)
inQuoted = false;
if (inQuoted)
return false;
//
// Parse any remaining items in the pattern.
//
if (prevChr > 0 && chrCnt > 0) {
parseRes = subParse(str, strPos, prevChr, chrCnt);
if (parseRes < 0)
return false;
strPos = parseRes;
}
//
// Ensure there's no more source to parse.
//
if (strPos != strLen)
return false;
//
// Adjust day of month if none were parsed.
// Relaxed rule for roach #666377.
//
if (mDayOfYear < 0 && mDayOfMonth < 0)
mDayOfMonth = 1;
//
// Fix for Watson 1084938. If seen Week Of Year then offset accordingly.
//
if (mDayOfWeek < 0)
mDayOfWeek = (mWeekOfYear < 0) ? 1 : 2;
//
// Ensure sufficient data was supplied for a valid date.
//
if ((mDayOfYear < 0 || mYearOfEra < 0)
&& (mDayOfWeek < 0 || mWeekOfYear < 0 || mYearOfEra < 0)
&& (mDayOfMonth < 0 || mMonthOfYear < 0 || mYearOfEra < 0))
return false;
//
// Reset alternate era if in current Simplified Chinese era.
//
if (mLocale.isSimplifiedChinese() && mAltEra >= 18171)
mAltEra = -1;
//
// Thwart nonsense alternate era values. Fix to roach 668310.
//
if (mAltEra >= 0 && mYearOfEra < 1)
return false;
return true;
}
/**
* Scans a date pattern for subelements of interest. Flag if any
* ISOWeek patterns were seen.
*
* @param pat
* a date pattern.
* @return
* boolean true is the pattern is valid.
*/
private boolean scan(String pat) {
char prevChr = 0;
int chrCnt = 0;
boolean inQuoted = false;
boolean inQuoteQuoted = false;
int patLen = pat.length();
//
// Foreach each character of the pattern Do ...
//
for (int i = 0; i < patLen;) {
char chr = pat.charAt(i); i++;
//
// If seen a quote within a quoted string ...
//
if (inQuoteQuoted) {
if (chr == '\'') { // cases like '...''
chrCnt = 0;
} else { // cases like '...'M
inQuoted = false;
chrCnt = 1;
prevChr = chr;
}
inQuoteQuoted = false;
}
//
// Elif within a quoted string ...
//
else if (inQuoted) {
if (chr == '\'') { // cases like '...'
inQuoteQuoted = true;
}
chrCnt++;
}
//
// Elif start of a quoted string ...
//
else if (chr == '\'') {
if (chrCnt > 0) { // cases like ...M'
if (! subScan(prevChr, chrCnt))
return false;
chrCnt = 0;
prevChr = 0;
}
inQuoted = true;
}
//
// Elif start of a metacharacter ...
//
else if (DateTimeUtil.matchChr(DATE_PICTURE_SYMBOLS, chr)
|| ('a' <= chr && chr <= 'z' || 'A' <= chr && chr <= 'Z')) {
if (chr != prevChr) {
if (chrCnt > 0) { // cases like YYM
if (! subScan(prevChr, chrCnt))
return false;
chrCnt = 0;
}
prevChr = chr;
}
chrCnt++;
}
//
// Elif start of a literal ...
//
else {
if (chrCnt > 0) { // cases like MM-
if (! subScan(prevChr, chrCnt))
return false;
chrCnt = 0;
prevChr = 0;
}
}
}
//
// Ensure quoted string is terminated.
//
if (inQuoteQuoted)
inQuoted = false;
if (inQuoted)
return false;
//
// Scan any remaining items in the pattern.
//
if (prevChr > 0 && chrCnt > 0) {
if (! subScan(prevChr, chrCnt))
return false;
}
return true;
}
/**
* Sets locale-specific date symbols given the locale.
*
* @param locale
* a locale
*/
void setDateSymbols(String locale) {
LcData oData = new LcData(locale);
for (int i = 0; i < 12; i++) {
mSymbols.abbrMonthName[i] = oData.getAbbrMonthName(i);
mSymbols.monthName[i] = oData.getMonthName(i);
}
for (int i = 0; i < 7; i++) {
mSymbols.abbrWeekdayName[i] = oData.getAbbrWeekdayName(i);
mSymbols.weekdayName[i] = oData.getWeekDayName(i);
}
for (int i = 0; i < 2; i++) {
mSymbols.eraName[i] = oData.getEraName(i);
}
mSymbols.zeroDigit = oData.getZeroSymbol().charAt(0);
}
/**
* Sets this object to operate on Greenwich Mean date, which is the
* default. Any subsequent calls to the format method will result in date
* strings that are expressed in Greenwich Mean date.
*/
public void setGMDate() {
Calendar today = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
int day = today.get(Calendar.DAY_OF_MONTH);
int month = today.get(Calendar.MONTH) + 1;
int year = today.get(Calendar.YEAR);
mAdjustment = epoch(day, month, year) - mDays;
}
/**
* Sets this object to operate on local date as opposed to Greenwich
* Mean date. Any subsequent calls to the format method will result in date
* strings that are expressed in local date.
*/
public void setLocalDate() {
mAdjustment = 0;
}
/*
* Formats a sub-element of this object given the number of
* occurances of a date pattern metacharacter.
*
* @param chr
* a date pattern metacharacter.
* @param chrCnt
* the number of consecutive occurances of the metacharacter.
* @return
* the formatted sub-element.
*/
@FindBugsSuppress(code="SF")
String subFormat(char chr, int chrCnt) {
StringBuilder sBuf = new StringBuilder();
boolean fw = (0xFF01 <= chr && chr <= 0xFF5E);
int tr = 0;
int d = 0;
int nEraStyle = 0;
CJKTable pEraTbl[] = getAltEraTable(mLocale);
//
// Re-classify 'g' pictures depending upon locale and locale era.
// Identify style of era.
//
if (chr == 'g' || chr == 0xFF47) { // Alternate Era
if (mLocale.isJapanese()) {
if (chrCnt == 1)
nEraStyle = fw ? 1 : 0;
else if (chrCnt == 2)
nEraStyle = fw ? 4 : 2;
else if (chrCnt == 3)
nEraStyle = 3;
} else if (mLocale.isKoreanHani()) {
nEraStyle = 1;
} else if (mLocale.isThai()) {
nEraStyle = (chrCnt == 3) ? 2 : (chrCnt == 2) ? 1 : 0;
} else /* if (mLocale.isKorean() || mLocale.isChinese()) */ {
nEraStyle = 0;
}
if (pEraTbl == null || pEraTbl[0].sEraSyms[nEraStyle] == null) {
chr = 'G';
chrCnt = 1;
}
}
//
// Identify locale's table of numeric ideographic characters.
//
char[] pCJK_Num = null;
if (mLocale.isJapanese())
pCJK_Num = DateTimeUtil.Kanji_Num;
else if (mLocale.isTraditionalChinese())
pCJK_Num = DateTimeUtil.Hanja_Num;
else if (mLocale.getName().equals(LcLocale.Chinese_HongKong))
pCJK_Num = DateTimeUtil.Hanja_Num;
else if (mLocale.isChinese())
pCJK_Num = DateTimeUtil.Kanji_Num;
else if (mLocale.isKoreanHani())
pCJK_Num = DateTimeUtil.Hanja_Num;
else if (mLocale.isKorean())
pCJK_Num = DateTimeUtil.Hangul_Num;
//
// Handle each supported LcDate sub-element.
//
int value = 0;
switch (chr) {
case 0xFF24: // Fullwidth 'D' Day Of Month.
value = getMonthDay();
switch (chrCnt) {
case 3:
case 4:
if (mLocale.isJapanese())
tr = (chrCnt == 4) ? - 1 : 0;
else if (mLocale.isKorean())
tr = -1;
else if (mLocale.isChinese())
tr = 1;
sBuf.append(DateTimeUtil.fmtNum(2, value, pCJK_Num, tr));
break;
}
/* FALLTHRU */
case 'D': // Day Of Month.
if (chr == 'D')
value = getMonthDay();
switch (chrCnt) {
case 1:
sBuf.append(DateTimeUtil.fmtPlainNum(2, value, fw,
mSymbols.zeroDigit));
break;
case 2:
sBuf.append(DateTimeUtil.fmtNum(chrCnt, value, fw,
mSymbols.zeroDigit));
break;
}
break;
case 'J':
case 0xFF2A: // Day Of Year.
value = getYearDay();
switch (chrCnt) {
case 1:
sBuf.append(DateTimeUtil.fmtPlainNum(3, value, fw,
mSymbols.zeroDigit));
break;
case 3:
sBuf.append(DateTimeUtil.fmtNum(chrCnt, value, fw,
mSymbols.zeroDigit));
break;
}
break;
case 'E':
case 0xFF25: // Day Of Week (1 == Sun)
value = getWeekDay();
switch (chrCnt) {
case 1:
sBuf.append(DateTimeUtil.fmtNum(chrCnt, value, fw,
mSymbols.zeroDigit));
break;
case 3:
sBuf.append(DateTimeUtil.fmtStr(
mSymbols.abbrWeekdayName[value - 1], false));
break;
case 4:
sBuf.append(DateTimeUtil.fmtStr(
mSymbols.weekdayName[value - 1], false));
break;
}
break;
case 'e':
case 0xFF45: // Alternate Day Of Week (1 == Mon)
value = (getWeekDay() + 5) % 7 + 1;
switch (chrCnt) {
case 1:
sBuf.append(DateTimeUtil.fmtNum(chrCnt, value, fw,
mSymbols.zeroDigit));
break;
}
break;
case 0xFF2D: // Fullwidth 'M' Month of Year.
value = getMonth();
switch (chrCnt) {
case 3:
case 4:
if (mLocale.isJapanese())
tr = (chrCnt == 4) ? -1 : 0;
else if (mLocale.isKorean())
tr = -1;
else if (mLocale.isChinese())
tr = 1;
sBuf.append(DateTimeUtil.fmtNum(2, value, pCJK_Num, tr));
break;
}
/* FALLTHRU */
case 'M': // Month of Year.
if (chr == 'M')
value = getMonth();
switch (chrCnt) {
case 1:
sBuf.append(DateTimeUtil.fmtPlainNum(2, value, fw,
mSymbols.zeroDigit));
break;
case 2:
sBuf.append(DateTimeUtil.fmtNum(chrCnt, value, fw,
mSymbols.zeroDigit));
break;
case 3:
if (chr == 'M')
sBuf.append(DateTimeUtil.fmtStr(
mSymbols.abbrMonthName[value - 1], false));
break;
case 4:
if (chr == 'M')
sBuf.append(DateTimeUtil.fmtStr(
mSymbols.monthName[value - 1], false));
break;
}
break;
case 'w':
case 0xFF57: // Week Of Month.
value = getWeekMonth();
switch (chrCnt) {
case 1:
sBuf.append(DateTimeUtil.fmtPlainNum(1, value, fw,
mSymbols.zeroDigit));
break;
}
break;
case 'W':
case 0xFF37: // Week Of Year.
value = getISOWeek();
switch (chrCnt) {
case 2:
sBuf.append(DateTimeUtil.fmtNum(chrCnt, value, fw,
mSymbols.zeroDigit));
break;
}
break;
case 0xFF39: // Fullwidth 'Y' Year of Era.
value = (mbISOWeekSeen) ? getISOYear() : getYear();
if (mAltEra >= 0) {
LcDate oEra = new LcDate(mAltEra, mLocale.getIsoName(),
mCenturySplit);
value += getEpochEra(pEraTbl, mAltEra) - oEra.getYear();
}
switch (chrCnt) {
case 3:
case 5:
if (mLocale.isJapanese()) {
tr = (chrCnt == 5) ? -1 : 0;
d = (mAltEra >= 0) ? 2 : 4;
} else if (mLocale.isKorean()) {
tr = -1;
d = 4;
} else if (mLocale.isTraditionalChinese()) {
tr = (mAltEra >= 0) ? 1 : 0;
d = 4;
} else if (mLocale.isChinese()) {
tr = (mAltEra >= 0 && getDays() < 18171) ? 1 : 0;
d = 4;
}
sBuf.append(DateTimeUtil.fmtNum(d, value, pCJK_Num, tr));
if (sBuf.toString().equals("\u4e00")) { // handle year one of era.
sBuf.setLength(0);
sBuf.append("\u5143");
}
break;
}
/* FALLTHRU */
case 'Y': // Year of Era.
if (chr == 'Y') {
value = (mbISOWeekSeen) ? getISOYear() : getYear();
if (mAltEra >= 0) {
LcDate oEra = new LcDate(mAltEra, mLocale.getIsoName(),
mCenturySplit);
value += getEpochEra(pEraTbl, mAltEra) - oEra.getYear();
//
// Before 1941, Thai years began on Apr 1'st.
//
if (mLocale.isThai()) {
if (value > 2482 || value <= 2482 && getMonth() > 3)
value++;
}
}
}
if (mAltEra >= 0) {
//
// Thwart nonsense alternate era values. Fix to roach 667143
// and roach 667648.
//
if (mLocale.isJapanese()) {
if (chrCnt == 4)
chrCnt = 2;
if (value > 99)
chrCnt = 0;
} else if (mLocale.isKorean()) {
if (0 < chrCnt && chrCnt < 3)
chrCnt = 4;
if (value > 9999)
chrCnt = 0;
} else if (mLocale.isTraditionalChinese()) {
if (chrCnt == 2 || chrCnt == 4)
chrCnt = (value > 99) ? 4 : 2;
else if (chrCnt == 1)
chrCnt = (value > 99) ? 4 : (value > 9) ? 2 : 1;
} else if (mLocale.isChinese()) {
if (chrCnt == 4)
chrCnt = (value > 99) ? 4 : 2;
}
}
switch (chrCnt) {
case 1:
value %= 1000;
sBuf.append(DateTimeUtil.fmtPlainNum(2, value, fw,
mSymbols.zeroDigit));
break;
case 2:
value %= 100;
sBuf.append(DateTimeUtil.fmtNum(chrCnt, value, fw,
mSymbols.zeroDigit));
break;
case 4:
if (mLocale.isTraditionalChinese())
chrCnt = (value > 999) ? 4 : 3;
sBuf.append(DateTimeUtil.fmtNum(chrCnt, value, fw,
mSymbols.zeroDigit));
break;
}
break;
case 'G': // Era.
if (mAltEra >= 0)
mAltEra = -1;
switch (chrCnt) {
case 1:
sBuf.append(DateTimeUtil.fmtStr(mSymbols.eraName[1], fw));
break;
}
break;
case 'g':
case 0xFF47: // Alternate Era
switch (chrCnt) {
case 1:
case 2:
case 3:
value = getDays();
for (int i = 0; i < pEraTbl.length; i++) {
if (pEraTbl[i].nEraStart <= value) {
mAltEra = pEraTbl[i].nEraStart;
sBuf.setLength(0);
sBuf.append(DateTimeUtil.fmtStr(
pEraTbl[i].sEraSyms[nEraStyle], fw));
if (sBuf.length() == 0) // handle empty Chinese era name
sBuf.append('\u001f');
}
}
break;
}
break;
case 't': // tab.
while (chrCnt-- > 0) {
sBuf.append('\t');
}
break;
default:
sBuf.append(chr);
break;
}
return sBuf.toString();
}
/*
* Parses a sub-element at a given position of the given string,
* given the number of occurances of a date pattern metacharacter.
* of the metacharacter.
*
* @param src
* the date string to parse.
* @param srcPos
* the starting parsing position within the date string.
* @param chr
* a date pattern metacharacter.
* @param chrCnt
* the number of consecutive occurances of the metacharacter.
* @return
* the ending parsing position within the date string if
* successfully parsed, and -1 otherwise.
*/
@FindBugsSuppress(code="SF")
int subParse(String src, int srcPos, char chr, int chrCnt) {
int len;
int idx;
int curPos = srcPos;
boolean fw = (0xFF01 <= chr && chr <= 0xFF5E);
boolean tr = false;
int nEraStyle = 0;
CJKTable[] pEraTbl = getAltEraTable(mLocale);
//
// Re-classify 'g' pictures depending upon locale and locale era.
// Identify style of era.
//
if (chr == 'g' || chr == 0xFF47) { // Alternate Era
if (mLocale.isJapanese()) {
if (chrCnt == 1)
nEraStyle = fw ? 1 : 0;
else if (chrCnt == 2)
nEraStyle = fw ? 4 : 2;
else if (chrCnt == 3)
nEraStyle = 3;
}
else if (mLocale.isKoreanHani()) {
nEraStyle = 1;
}
else if (mLocale.isThai()) {
nEraStyle = (chrCnt == 3) ? 2 : (chrCnt == 2) ? 1 : 0;
}
else /* if (mLocale.isKorean() || mLocale.isChinese()) */ {
nEraStyle = 0;
}
if (pEraTbl == null || pEraTbl[0].sEraSyms.length <= nEraStyle) {
chr = 'G';
chrCnt = 1;
}
else {
int nEras = pEraTbl.length;
assert(nEras <= mSymbols.altEraName.length);
for (int i = 0; i < nEras; i++) {
mSymbols.altEraName[i] = pEraTbl[i].sEraSyms[nEraStyle];
}
}
}
//
// Identify locale's table of numeric ideographic characters.
//
char[] pCJK_Num = null;
if (mLocale.isJapanese())
pCJK_Num = DateTimeUtil.Kanji_Num;
else if (mLocale.isTraditionalChinese())
pCJK_Num = DateTimeUtil.Hanja_Num;
else if (mLocale.getName().equals(LcLocale.Chinese_HongKong))
pCJK_Num = DateTimeUtil.Hanja_Num;
else if (mLocale.isChinese())
pCJK_Num = DateTimeUtil.Kanji_Num;
else if (mLocale.isKoreanHani())
pCJK_Num = DateTimeUtil.Hanja_Num;
else if (mLocale.isKorean())
pCJK_Num = DateTimeUtil.Hangul_Num;
//
// Handle each supported LcDate sub-element.
//
switch (chr) {
case 0xFF24: // Fullwidth 'D' Day Of Month
if (mDayOfMonth >= 0 || mDayOfYear >= 0)
return -1;
switch (chrCnt) {
case 3: case 4:
if (mLocale.isJapanese())
tr = (chrCnt == 4);
else if (mLocale.isKorean())
tr = true;
else if (mLocale.isChinese())
tr = true;
len = DateTimeUtil.matchNum(src, srcPos, srcPos + 2,
pCJK_Num, tr);
if (len <= 0)
return -1;
curPos = DateTimeUtil.incPos(src, srcPos, len);
mDayOfMonth = DateTimeUtil.getNum(src, srcPos, curPos,
pCJK_Num, tr);
srcPos = curPos;
break;
}
/*FALLTHRU*/
case 'D': // Day Of Month
if (chr == 'D')
if (mDayOfMonth >= 0 || mDayOfYear >= 0)
return -1;
switch (chrCnt) {
case 1: case 2:
len = DateTimeUtil.matchNum(src, srcPos, srcPos + 2,
fw, mSymbols.zeroDigit);
if (len < chrCnt)
return -1;
curPos = DateTimeUtil.incPos(src, srcPos, len);
mDayOfMonth = DateTimeUtil.getNum(src, srcPos, curPos,
fw, mSymbols.zeroDigit);
srcPos = curPos;
break;
default:
if (chr == 'Y')
return -1;
break;
}
break;
case 'J': case 0xFF2A: // Day Of Year
if (mDayOfYear >= 0 || mDayOfMonth >= 0 || mMonthOfYear >= 0)
return -1;
switch (chrCnt) {
case 1:
len = DateTimeUtil.matchNum(src, srcPos, srcPos + 3,
fw, mSymbols.zeroDigit);
if (len <= 0)
return -1;
break;
case 3:
len = DateTimeUtil.matchNum(src, srcPos, srcPos + 3,
fw, mSymbols.zeroDigit);
if (len != 3)
return -1;
break;
default:
return -1;
}
curPos = DateTimeUtil.incPos(src, srcPos, len);
mDayOfYear = DateTimeUtil.getNum(src, srcPos, curPos,
fw, mSymbols.zeroDigit);
srcPos = curPos;
break;
case 'E': case 0xFF25: // Day Of Week (1 == Sun)
if (mDayOfWeek >= 0)
return -1;
switch (chrCnt) {
case 1:
len = DateTimeUtil.matchNum(src, srcPos, srcPos + 2,
fw, mSymbols.zeroDigit);
if (len <= 0)
return -1;
curPos = DateTimeUtil.incPos(src, srcPos, len);
mDayOfWeek = DateTimeUtil.getNum(src, srcPos, curPos,
fw, mSymbols.zeroDigit);
break;
case 3:
idx = DateTimeUtil.matchName(src, srcPos,
mSymbols.abbrWeekdayName, true);
if (idx < 0)
return -1;
mDayOfWeek = idx;
curPos += mSymbols.abbrWeekdayName[idx].length();
break;
case 4:
idx = DateTimeUtil.matchName(src, srcPos,
mSymbols.weekdayName, true);
if (idx < 0)
return -1;
mDayOfWeek = idx;
curPos += mSymbols.weekdayName[idx].length();
break;
default:
return -1;
}
srcPos = curPos;
break;
case 'e': case 0xFF45: // Alternate Day Of Week (1 == Mon).
if (mDayOfWeek >= 0)
return -1;
switch (chrCnt) {
case 1:
len = DateTimeUtil.matchNum(src, srcPos, srcPos + 2,
fw, mSymbols.zeroDigit);
if (len <= 0)
return -1;
curPos = DateTimeUtil.incPos(src, srcPos, len);
mDayOfWeek = DateTimeUtil.getNum(src, srcPos, curPos,
fw, mSymbols.zeroDigit);
//
// Fix for Watson 1084938. Normalize given to ensure that
// 1 == Sun.
//
mDayOfWeek = mDayOfWeek % 7 + 1;
srcPos = curPos;
break;
default:
return -1;
}
srcPos = curPos;
break;
case 0xFF2D: // Fullwidth 'M' Month of Year
if (mMonthOfYear >= 0 || mDayOfYear >= 0)
return -1;
switch (chrCnt) {
case 3: case 4:
if (mLocale.isJapanese())
tr = (chrCnt == 4);
else if (mLocale.isKorean())
tr = true;
else if (mLocale.isChinese())
tr = true;
len = DateTimeUtil.matchNum(src, srcPos, srcPos + 2,
pCJK_Num, tr);
if (len <= 0)
return -1;
curPos = DateTimeUtil.incPos(src, srcPos, len);
mMonthOfYear = DateTimeUtil.getNum(src, srcPos, curPos,
pCJK_Num, tr);
srcPos = curPos;
break;
}
/*FALLTHRU*/
case 'M': // Month of Year
if (chr == 'M')
if (mMonthOfYear >= 0 || mDayOfYear >= 0)
return -1;
switch (chrCnt) {
case 1: case 2:
len = DateTimeUtil.matchNum(src, srcPos, srcPos + 2,
fw, mSymbols.zeroDigit);
if (len < chrCnt)
return -1;
curPos = DateTimeUtil.incPos(src, srcPos, len);
mMonthOfYear = DateTimeUtil.getNum(src, srcPos, curPos,
fw, mSymbols.zeroDigit);
srcPos = curPos;
break;
case 3:
if (chr == 'M') {
idx = DateTimeUtil.matchName(src, srcPos,
mSymbols.abbrMonthName, true);
if (idx < 0)
return -1;
mMonthOfYear = idx + 1;
curPos += mSymbols.abbrMonthName[idx].length();
srcPos = curPos;
}
break;
case 4:
if (chr == 'M') {
idx = DateTimeUtil.matchName(src, srcPos,
mSymbols.monthName, true);
if (idx < 0)
return -1;
mMonthOfYear = idx + 1;
curPos += mSymbols.monthName[idx].length();
srcPos = curPos;
}
break;
default:
return -1;
}
break;
case 'w': case 0xFF57: // Week of Month
if (mWeekOfMonth >= 0 || mWeekOfYear >= 0)
return -1;
if (mDayOfYear >= 0 || mDayOfMonth >= 0)
return -1;
switch (chrCnt) {
case 1:
len = DateTimeUtil.matchNum(src, srcPos, srcPos + 2,
fw, mSymbols.zeroDigit);
if (len <= 0)
return -1;
break;
default:
return -1;
}
curPos = DateTimeUtil.incPos(src, srcPos, len);
mWeekOfMonth = DateTimeUtil.getNum(src, srcPos, curPos,
fw, mSymbols.zeroDigit);
srcPos = curPos;
break;
case 'W': case 0xFF37: // Week of Year
if (mWeekOfYear >= 0 || mWeekOfMonth >= 0)
return -1;
if (mDayOfYear >= 0 || mDayOfMonth >= 0 || mMonthOfYear >= 0)
return -1;
switch (chrCnt) {
case 2:
len = DateTimeUtil.matchNum(src, srcPos, srcPos + 2,
fw, mSymbols.zeroDigit);
if (len != 2)
return -1;
break;
default:
return -1;
}
curPos = DateTimeUtil.incPos(src, srcPos, len);
mWeekOfYear = DateTimeUtil.getNum(src, srcPos, curPos,
fw, mSymbols.zeroDigit);
srcPos = curPos;
break;
case 0xFF39: // Fullwidth 'Y' Year of Era
if (mYearOfEra >= 0)
return -1;
switch (chrCnt) {
case 3: case 5:
if (mLocale.isJapanese())
tr = (chrCnt == 5);
else if (mLocale.isKorean())
tr = true;
else if (mLocale.isTraditionalChinese())
tr = (mAltEra >= 0);
else if (mLocale.isChinese())
tr = (0 <= mAltEra && mAltEra < 18171);
len = DateTimeUtil.matchNum(src, srcPos, srcPos + 4,
pCJK_Num, tr);
if (len <= 0)
return -1;
curPos = srcPos;
if (len == 1 && src.charAt(curPos++) == 0x5143) {
mYearOfEra = 1;
}
else {
curPos = DateTimeUtil.incPos(src, srcPos, len);
mYearOfEra = DateTimeUtil.getNum(src, srcPos, curPos,
pCJK_Num, tr);
}
srcPos = curPos;
break;
}
/*FALLTHRU*/
case 'Y': // Year of Era
if (chr == 'Y')
if (mYearOfEra >= 0)
return -1;
switch (chrCnt) {
case 1: case 2:
// If in Chinese zh_TW locale and in current era Then
// allow a 3-digit year of era.
if (mLocale.isTraditionalChinese() && mAltEra >= 4383)
len = DateTimeUtil.matchNum(src, srcPos, srcPos + 3,
fw, mSymbols.zeroDigit);
// Elsif in Chinese zh_CN and zh_HK locales and in current era
// Then allow a 4-digit year of era.
else if (mLocale.isSimplifiedChinese() && mAltEra >= 18171)
len = DateTimeUtil.matchNum(src, srcPos, srcPos + 4,
fw, mSymbols.zeroDigit);
else
len = DateTimeUtil.matchNum(src, srcPos, srcPos + 2,
fw, mSymbols.zeroDigit);
if (len < chrCnt)
return -1;
curPos = DateTimeUtil.incPos(src, srcPos, len);
mYearOfEra = DateTimeUtil.getNum(src, srcPos, curPos,
fw, mSymbols.zeroDigit);
srcPos = curPos;
m2DigitYear++;
break;
case 4:
len = DateTimeUtil.matchNum(src, srcPos, srcPos + 4,
fw, mSymbols.zeroDigit);
if (len <= 0 || len < chrCnt && mAltEra < 0)
return -1;
curPos = DateTimeUtil.incPos(src, srcPos, len);
mYearOfEra = DateTimeUtil.getNum(src, srcPos, curPos,
fw, mSymbols.zeroDigit);
srcPos = curPos;
break;
default:
if (chr == 'Y')
return -1;
break;
}
break;
case 'G': // Era
if (mEra >= 0)
return -1;
switch (chrCnt) {
case 1:
idx = DateTimeUtil.matchName(src, srcPos,
mSymbols.eraName, true);
if (idx != 1)
return -1;
mEra = idx;
len = mSymbols.eraName[idx].length();
break;
default:
return -1;
}
srcPos += len;
break;
case 'g': case 0xFF47: // Alternate Era
if (mAltEra >= 0)
return -1;
switch (chrCnt) {
case 1: case 2: case 3:
idx = DateTimeUtil.matchName(src, srcPos,
mSymbols.altEraName, false);
if (idx < 0)
return -1;
mAltEra = pEraTbl[idx].nEraStart;
len = mSymbols.altEraName[idx].length();
srcPos += len;
break;
default:
return -1;
}
break;
case 't': // tab
while (chrCnt-- > 0) {
if (src.charAt(srcPos) != '\t')
return -1;
srcPos += 1;
}
break;
default:
if (! DateTimeUtil.matchChr(src, srcPos, chr, fw))
return -1;
srcPos += 1;
break;
}
return srcPos;
}
/*
* Scans a sub-element of a date pattern given the number of occurances
* of a date pattern metacharacter.
*
* @param chr
* a date pattern metacharacter.
* @param chrCnt
* the number of consecutive occurances of the metacharacter.
* @return
* boolean true if the date sub-element is valid.
*/
private boolean subScan(char chr, int chrCnt) {
switch (chr) {
case 'W':
case 0xFF37: // Week of Year
switch (chrCnt) {
case 2:
mbISOWeekSeen = true;
break;
default:
return false;
}
break;
default:
break;
}
return true;
}
/**
* Formats this object according to the default pattern.
*
* @return
* the date string formatted according to the default pattern,
* upon success, and the empty string, upon error.
*/
public String toString() {
return format(DEFAULT_DATE_FMT);
}
}