private static synchronized void initCache() { if (CACHE.size() <= 0) { for (FormatTokenEnum token : FormatTokenEnum.values()) { List<Character> tokenKeys = new ArrayList<Character>(); if (token.name().contains("_")) { String[] tokens = token.name().split("_"); for (String tokenLets : tokens) { tokenKeys.add(tokenLets.toUpperCase().charAt(0)); } } else { tokenKeys.add(token.name().toUpperCase().charAt(0)); } for (Character tokenKey : tokenKeys) { List<FormatTokenEnum> l = CACHE.get(tokenKey); if (l == null) { l = new ArrayList<FormatTokenEnum>(1); CACHE.put(tokenKey, l); } l.add(token); } } } }
@Override public void parse(ToDateParser params, FormatTokenEnum formatTokenEnum, String formatTokenStr) { Calendar result = params.getResultCalendar(); String s = params.getInputStr(); String inputFragmentStr = null; int dateNr = 0; switch (formatTokenEnum) { case MONTH: inputFragmentStr = setByName(result, params, Calendar.MONTH, Calendar.LONG); break; case Q /*NOT supported yet*/: throwException(params, format("token '%s' not supported yet.", formatTokenEnum.name())); break; case MON: inputFragmentStr = setByName(result, params, Calendar.MONTH, Calendar.SHORT); break; case MM: // Note: In Calendar Month go from 0 - 11 inputFragmentStr = matchStringOrThrow(PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum); dateNr = parseInt(inputFragmentStr); result.set(Calendar.MONTH, dateNr - 1); break; case RM: dateNr = 0; for (String monthName : ROMAN_MONTH) { dateNr++; int len = monthName.length(); if (s.length() >= len && monthName.equalsIgnoreCase(s.substring(0, len))) { result.set(Calendar.MONTH, dateNr); inputFragmentStr = monthName; break; } } if (inputFragmentStr == null || inputFragmentStr.isEmpty()) { throwException( params, format( "Issue happened when parsing token '%s'. " + "Expected one of: %s", formatTokenEnum.name(), Arrays.toString(ROMAN_MONTH))); } break; default: throw new IllegalArgumentException( format( "%s: Internal Error. Unhandled case: %s", this.getClass().getSimpleName(), formatTokenEnum)); } params.remove(inputFragmentStr, formatTokenStr); }
@Override public void parse(ToDateParser params, FormatTokenEnum formatTokenEnum, String formatTokenStr) { Calendar result = params.getResultCalendar(); String inputFragmentStr = null; int dateNr = 0; switch (formatTokenEnum) { case SYYYY: case YYYY: case IYYY: inputFragmentStr = matchStringOrThrow(PATTERN_FOUR_DIGITS, params, formatTokenEnum); dateNr = parseInt(inputFragmentStr); // Gregorian calendar does not have a year 0. // 0 = 0001 BC, -1 = 0002 BC, ... so we adjust result.set(Calendar.YEAR, dateNr >= 0 ? dateNr : dateNr + 1); break; case YYY: case IYY: inputFragmentStr = matchStringOrThrow(PATTERN_THREE_DIGITS, params, formatTokenEnum); dateNr = parseInt(inputFragmentStr); // Gregorian calendar does not have a year 0. // 0 = 0001 BC, -1 = 0002 BC, ... so we adjust result.set(Calendar.YEAR, dateNr >= 0 ? dateNr : dateNr + 1); break; case RRRR: inputFragmentStr = matchStringOrThrow(PATTERN_TWO_DIGITS, params, formatTokenEnum); dateNr = parseInt(inputFragmentStr); dateNr += dateNr < 50 ? 2000 : 1900; result.set(Calendar.YEAR, dateNr); break; case RR: Calendar calendar = Calendar.getInstance(); int cc = calendar.get(Calendar.YEAR) / 100; inputFragmentStr = matchStringOrThrow(PATTERN_TWO_DIGITS, params, formatTokenEnum); dateNr = parseInt(inputFragmentStr) + cc * 100; result.set(Calendar.YEAR, dateNr); break; case EE /*NOT supported yet*/: throwException(params, format("token '%s' not supported yet.", formatTokenEnum.name())); break; case E /*NOT supported yet*/: throwException(params, format("token '%s' not supported yet.", formatTokenEnum.name())); break; case YY: case IY: inputFragmentStr = matchStringOrThrow(PATTERN_TWO_DIGITS, params, formatTokenEnum); dateNr = parseInt(inputFragmentStr); // Gregorian calendar does not have a year 0. // 0 = 0001 BC, -1 = 0002 BC, ... so we adjust result.set(Calendar.YEAR, dateNr >= 0 ? dateNr : dateNr + 1); break; case SCC: case CC: inputFragmentStr = matchStringOrThrow(PATTERN_TWO_DIGITS, params, formatTokenEnum); dateNr = parseInt(inputFragmentStr) * 100; result.set(Calendar.YEAR, dateNr); break; case Y: case I: inputFragmentStr = matchStringOrThrow(PATTERN_ONE_DIGIT, params, formatTokenEnum); dateNr = parseInt(inputFragmentStr); // Gregorian calendar does not have a year 0. // 0 = 0001 BC, -1 = 0002 BC, ... so we adjust result.set(Calendar.YEAR, dateNr >= 0 ? dateNr : dateNr + 1); break; case BC_AD: inputFragmentStr = matchStringOrThrow(PATTERN_BC_AD, params, formatTokenEnum); if (inputFragmentStr.toUpperCase().startsWith("B")) { result.set(Calendar.ERA, GregorianCalendar.BC); } else { result.set(Calendar.ERA, GregorianCalendar.AD); } break; default: throw new IllegalArgumentException( format( "%s: Internal Error. Unhandled case: %s", this.getClass().getSimpleName(), formatTokenEnum)); } params.remove(inputFragmentStr, formatTokenStr); }
/** The format tokens. */ static enum FormatTokenEnum { // 4-digit year YYYY(PARSLET_YEAR), // 4-digit year with sign (- = B.C.) SYYYY(PARSLET_YEAR), // 4-digit year based on the ISO standard (?) IYYY(PARSLET_YEAR), YYY(PARSLET_YEAR), IYY(PARSLET_YEAR), YY(PARSLET_YEAR), IY(PARSLET_YEAR), // Two-digit century with with sign (- = B.C.) SCC(PARSLET_YEAR), // Two-digit century. CC(PARSLET_YEAR), // 2-digit -> 4-digit year 0-49 -> 20xx , 50-99 -> 19xx RRRR(PARSLET_YEAR), // last 2-digit of the year using "current" century value. RR(PARSLET_YEAR), // Meridian indicator BC_AD(PARSLET_YEAR, PATTERN_BC_AD), // Full Name of month MONTH(PARSLET_MONTH), // Abbreviated name of month. MON(PARSLET_MONTH), // Month (01-12; JAN = 01). MM(PARSLET_MONTH), // Roman numeral month (I-XII; JAN = I). RM(PARSLET_MONTH), // Day of year (1-366). DDD(PARSLET_DAY), // Name of day. DAY(PARSLET_DAY), // Day of month (1-31). DD(PARSLET_DAY), // Abbreviated name of day. DY(PARSLET_DAY), HH24(PARSLET_TIME), HH12(PARSLET_TIME), // Hour of day (1-12). HH(PARSLET_TIME), // Min MI(PARSLET_TIME), // Seconds past midnight (0-86399) SSSSS(PARSLET_TIME), SS(PARSLET_TIME), // Fractional seconds FF(PARSLET_TIME, PATTERN_FF), // Time zone hour. TZH(PARSLET_TIME), // Time zone minute. TZM(PARSLET_TIME), // Time zone region ID TZR(PARSLET_TIME), // Daylight savings information. Example: // PST (for US/Pacific standard time); TZD(PARSLET_TIME), // Meridian indicator AM_PM(PARSLET_TIME, PATTERN_AM_PM), // NOT supported yet - // Full era name (Japanese Imperial, ROC Official, // and Thai Buddha calendars). EE(PARSLET_YEAR), // NOT supported yet - // Abbreviated era name (Japanese Imperial, // ROC Official, and Thai Buddha calendars). E(PARSLET_YEAR), Y(PARSLET_YEAR), I(PARSLET_YEAR), // Quarter of year (1, 2, 3, 4; JAN-MAR = 1). Q(PARSLET_MONTH), // Day of week (1-7). D(PARSLET_DAY), // NOT supported yet - // Julian day; the number of days since Jan 1, 4712 BC. J(PARSLET_DAY); private static final List<FormatTokenEnum> EMPTY_LIST = new ArrayList<FormatTokenEnum>(0); private static final Map<Character, List<FormatTokenEnum>> CACHE = new HashMap<Character, List<FormatTokenEnum>>(FormatTokenEnum.values().length); private final ToDateParslet toDateParslet; private final Pattern patternToUse; FormatTokenEnum(ToDateParslet toDateParslet, Pattern patternToUse) { this.toDateParslet = toDateParslet; this.patternToUse = patternToUse; } FormatTokenEnum(ToDateParslet toDateParslet) { this.toDateParslet = toDateParslet; patternToUse = Pattern.compile(format("^(%s)", name()), Pattern.CASE_INSENSITIVE); } /** * Optimization: Only return a list of {@link FormatTokenEnum} that share the same 1st char * using the 1st char of the 'to parse' formatStr. Or return empty list if no match. */ static List<FormatTokenEnum> getTokensInQuestion(String formatStr) { List<FormatTokenEnum> result = EMPTY_LIST; if (CACHE.size() <= 0) { initCache(); } if (formatStr != null && formatStr.length() > 0) { Character key = Character.toUpperCase(formatStr.charAt(0)); result = CACHE.get(key); } if (result == null) { result = EMPTY_LIST; } return result; } private static synchronized void initCache() { if (CACHE.size() <= 0) { for (FormatTokenEnum token : FormatTokenEnum.values()) { List<Character> tokenKeys = new ArrayList<Character>(); if (token.name().contains("_")) { String[] tokens = token.name().split("_"); for (String tokenLets : tokens) { tokenKeys.add(tokenLets.toUpperCase().charAt(0)); } } else { tokenKeys.add(token.name().toUpperCase().charAt(0)); } for (Character tokenKey : tokenKeys) { List<FormatTokenEnum> l = CACHE.get(tokenKey); if (l == null) { l = new ArrayList<FormatTokenEnum>(1); CACHE.put(tokenKey, l); } l.add(token); } } } } /** * Parse the format-string with passed token of {@link FormatTokenEnum}. If token matches return * true, otherwise false. */ boolean parseFormatStrWithToken(ToDateParser params) { Matcher matcher = patternToUse.matcher(params.getFormatStr()); boolean foundToken = matcher.find(); if (foundToken) { String formatTokenStr = matcher.group(1); toDateParslet.parse(params, this, formatTokenStr); } return foundToken; } }
@Override public void parse(ToDateParser params, FormatTokenEnum formatTokenEnum, String formatTokenStr) { Calendar result = params.getResultCalendar(); String inputFragmentStr = null; int dateNr = 0; switch (formatTokenEnum) { case HH24: inputFragmentStr = matchStringOrThrow(PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum); dateNr = parseInt(inputFragmentStr); result.set(Calendar.HOUR_OF_DAY, dateNr); break; case HH12: case HH: inputFragmentStr = matchStringOrThrow(PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum); dateNr = parseInt(inputFragmentStr); result.set(Calendar.HOUR, dateNr); break; case MI: inputFragmentStr = matchStringOrThrow(PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum); dateNr = parseInt(inputFragmentStr); result.set(Calendar.MINUTE, dateNr); break; case SS: inputFragmentStr = matchStringOrThrow(PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum); dateNr = parseInt(inputFragmentStr); result.set(Calendar.SECOND, dateNr); break; case SSSSS: inputFragmentStr = matchStringOrThrow(PATTERN_NUMBER, params, formatTokenEnum); dateNr = parseInt(inputFragmentStr); result.set(Calendar.HOUR_OF_DAY, 0); result.set(Calendar.MINUTE, 0); result.set(Calendar.SECOND, dateNr); break; case FF: inputFragmentStr = matchStringOrThrow(PATTERN_NUMBER, params, formatTokenEnum); String paddedRightNrStr = format("%-9s", inputFragmentStr).replace(' ', '0'); paddedRightNrStr = paddedRightNrStr.substring(0, 9); Double nineDigits = Double.parseDouble(paddedRightNrStr); params.setNanos(nineDigits.intValue()); dateNr = (int) Math.round(nineDigits / 1000000.0); result.set(Calendar.MILLISECOND, dateNr); break; case AM_PM: inputFragmentStr = matchStringOrThrow(PATTERN_AM_PM, params, formatTokenEnum); if (inputFragmentStr.toUpperCase().startsWith("A")) { result.set(Calendar.AM_PM, Calendar.AM); } else { result.set(Calendar.AM_PM, Calendar.PM); } break; case TZH: inputFragmentStr = matchStringOrThrow(PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum); dateNr = parseInt(inputFragmentStr); TimeZone tz = result.getTimeZone(); int offsetMillis = tz.getRawOffset(); // purge min and sec offsetMillis = (offsetMillis / MILLIS_IN_HOUR) * MILLIS_IN_HOUR; tz.setRawOffset(offsetMillis + dateNr); result.setTimeZone(tz); break; case TZM: inputFragmentStr = matchStringOrThrow(PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum); dateNr = parseInt(inputFragmentStr); tz = result.getTimeZone(); offsetMillis = tz.getRawOffset(); // purge hour offsetMillis = offsetMillis % MILLIS_IN_HOUR; tz.setRawOffset(dateNr * MILLIS_IN_HOUR + offsetMillis); result.setTimeZone(tz); break; case TZR: // Example: US/Pacific String s = params.getInputStr(); tz = result.getTimeZone(); for (String tzName : TimeZone.getAvailableIDs()) { int length = tzName.length(); if (s.length() >= length && tzName.equalsIgnoreCase(s.substring(0, length))) { tz.setID(tzName); result.setTimeZone(tz); inputFragmentStr = tzName; break; } } break; case TZD: // Must correspond with TZR region. Example: PST (for US/Pacific // standard time) throwException(params, format("token '%s' not supported yet.", formatTokenEnum.name())); break; default: throw new IllegalArgumentException( format( "%s: Internal Error. Unhandled case: %s", this.getClass().getSimpleName(), formatTokenEnum)); } params.remove(inputFragmentStr, formatTokenStr); }