All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.adobe.xfa.ut.LcDate Maven / Gradle / Ivy

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); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy