From de148065c28eb8cc0b9ae9a3b906889343a084a0 Mon Sep 17 00:00:00 2001 From: =?utf8?q?kaz=C3=A8de=20king?= Date: Thu, 25 Feb 2016 19:53:49 +0000 Subject: [PATCH] ICU-11872 new time formatting pattern chars b/B Merging from the branch. X-SVN-Rev: 38371 --- .../src/com/ibm/icu/impl/DayPeriodRules.java | 398 ++++++++ .../core/src/com/ibm/icu/text/DateFormat.java | 190 ++-- .../com/ibm/icu/text/DateFormatSymbols.java | 131 ++- .../com/ibm/icu/text/SimpleDateFormat.java | 444 +++++++-- .../icu/dev/test/format/DateFormatTest.java | 876 +++++++++++++----- 5 files changed, 1639 insertions(+), 400 deletions(-) create mode 100644 icu4j/main/classes/core/src/com/ibm/icu/impl/DayPeriodRules.java diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/DayPeriodRules.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/DayPeriodRules.java new file mode 100644 index 00000000000..a9244e0dae9 --- /dev/null +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/DayPeriodRules.java @@ -0,0 +1,398 @@ +/* + ******************************************************************************* + * Copyright (C) 2016, International Business Machines Corporation and + * others. All Rights Reserved. + ******************************************************************************* + */ +package com.ibm.icu.impl; + +import java.util.HashMap; +import java.util.Map; + +import com.ibm.icu.util.ICUException; +import com.ibm.icu.util.ULocale; + +public final class DayPeriodRules { + public enum DayPeriod { + MIDNIGHT, + NOON, + MORNING1, + AFTERNOON1, + EVENING1, + NIGHT1, + MORNING2, + AFTERNOON2, + EVENING2, + NIGHT2; + + public static DayPeriod[] VALUES = DayPeriod.values(); + + private static DayPeriod fromStringOrNull(CharSequence str) { + if ("midnight".contentEquals(str)) { return MIDNIGHT; } + if ("noon".contentEquals(str)) { return NOON; } + if ("morning1".contentEquals(str)) { return MORNING1; } + if ("afternoon1".contentEquals(str)) { return AFTERNOON1; } + if ("evening1".contentEquals(str)) { return EVENING1; } + if ("night1".contentEquals(str)) { return NIGHT1; } + if ("morning2".contentEquals(str)) { return MORNING2; } + if ("afternoon2".contentEquals(str)) { return AFTERNOON2; } + if ("evening2".contentEquals(str)) { return EVENING2; } + if ("night2".contentEquals(str)) { return NIGHT2; } + return null; + } + } + + private enum CutoffType { + BEFORE, + AFTER, // TODO: AFTER is deprecated in CLDR 29. Remove. + FROM, + AT; + + private static CutoffType fromStringOrNull(CharSequence str) { + if ("from".contentEquals(str)) { return CutoffType.FROM; } + if ("before".contentEquals(str)) { return CutoffType.BEFORE; } + if ("after".contentEquals(str)) { return CutoffType.AFTER; } + if ("at".contentEquals(str)) { return CutoffType.AT; } + return null; + } + } + + private static final class DayPeriodRulesData { + Map localesToRuleSetNumMap = new HashMap(); + DayPeriodRules[] rules; + int maxRuleSetNum = -1; + } + + private static final class DayPeriodRulesDataSink extends UResource.TableSink { + private DayPeriodRulesData data; + + private DayPeriodRulesDataSink(DayPeriodRulesData data) { + this.data = data; + } + + // Entry point. + @Override + public UResource.TableSink getOrCreateTableSink(UResource.Key key, int initialSize) { + if (key.contentEquals("locales")) { + return localesSink; + } else if (key.contentEquals("rules")) { + return rulesSink; + } + return null; + } + + // Locales. + private class LocalesSink extends UResource.TableSink { + @Override + public void put(UResource.Key key, UResource.Value value) { + int setNum = parseSetNum(value.getString()); + data.localesToRuleSetNumMap.put(key.toString(), setNum); + } + } + + private LocalesSink localesSink = new LocalesSink(); + + // Rules. + private class RulesSink extends UResource.TableSink { + @Override + public UResource.TableSink getOrCreateTableSink(UResource.Key key, int initialSize) { + ruleSetNum = parseSetNum(key.toString()); + data.rules[ruleSetNum] = new DayPeriodRules(); + return ruleSetSink; + } + } + private RulesSink rulesSink = new RulesSink(); + + // Rules -> "set10", e.g. + private class RuleSetSink extends UResource.TableSink { + @Override + public UResource.TableSink getOrCreateTableSink(UResource.Key key, int initialSize) { + period = DayPeriod.fromStringOrNull(key); + if (period == null) { throw new ICUException("Unknown day period in data."); } + return periodSink; + } + + @Override + public void leave() { + for (DayPeriod period : data.rules[ruleSetNum].dayPeriodForHour) { + if (period == null) { + throw new ICUException("Rules in data don't cover all 24 hours (they should)."); + } + } + } + } + private RuleSetSink ruleSetSink = new RuleSetSink(); + + // Rules -> "set10" -> "morning1", e.g. + // If multiple times exist in a cutoff (such as before{6:00, 24:00}) + // they'll be sent to the next sink. + private class PeriodSink extends UResource.TableSink { + @Override + public void put(UResource.Key key, UResource.Value value) { + cutoffType = CutoffType.fromStringOrNull(key); + addCutoff(cutoffType, value.getString()); + } + + @Override + public UResource.ArraySink getOrCreateArraySink(UResource.Key key, int initialSize) { + cutoffType = CutoffType.fromStringOrNull(key); + return cutoffSink; + } + + @Override + public void leave() { + setDayPeriodForHoursFromCutoffs(); + for (int i = 0; i < cutoffs.length; ++i) { + cutoffs[i] = 0; + } + } + } + private PeriodSink periodSink = new PeriodSink(); + + // Rules -> "set10" -> "morning1" -> "before", e.g. + // Will only enter this sink if more than one time is present for this cutoff. + private class CutoffSink extends UResource.ArraySink { + @Override + public void put(int index, UResource.Value value) { + addCutoff(cutoffType, value.getString()); + } + } + private CutoffSink cutoffSink = new CutoffSink(); + + // Members. + private int cutoffs[] = new int[25]; // [0] thru [24]; 24 is allowed is "before 24". + + // "Path" to data. + private int ruleSetNum; + private DayPeriod period; + private CutoffType cutoffType; + + // Helpers. + private void addCutoff(CutoffType type, String hourStr) { + if (type == null) { throw new ICUException("Cutoff type not recognized."); } + int hour = parseHour(hourStr); + cutoffs[hour] |= 1 << type.ordinal(); + } + + private void setDayPeriodForHoursFromCutoffs() { + DayPeriodRules rule = data.rules[ruleSetNum]; + for (int startHour = 0; startHour <= 24; ++startHour) { + // AT cutoffs must be either midnight or noon. + if ((cutoffs[startHour] & (1 << CutoffType.AT.ordinal())) > 0) { + if (startHour == 0 && period == DayPeriod.MIDNIGHT) { + rule.hasMidnight = true; + } else if (startHour == 12 && period == DayPeriod.NOON) { + rule.hasNoon = true; + } else { + throw new ICUException("AT cutoff must only be set for 0:00 or 12:00."); + } + } + + // FROM/AFTER and BEFORE must come in a pair. + if ((cutoffs[startHour] & (1 << CutoffType.FROM.ordinal())) > 0 || + (cutoffs[startHour] & (1 << CutoffType.AFTER.ordinal())) > 0) { + for (int hour = startHour + 1;; ++hour) { + if (hour == startHour) { + // We've gone around the array once and can't find a BEFORE. + throw new ICUException( + "FROM/AFTER cutoffs must have a matching BEFORE cutoff."); + } + if (hour == 25) { hour = 0; } + if ((cutoffs[hour] & (1 << CutoffType.BEFORE.ordinal())) > 0) { + rule.add(startHour, hour, period); + break; + } + } + } + } + } + + private static int parseHour(String str) { + int firstColonPos = str.indexOf(':'); + if (firstColonPos < 0 || !str.substring(firstColonPos).equals(":00")) { + throw new ICUException("Cutoff time must end in \":00\"."); + } + + String hourStr = str.substring(0, firstColonPos); + if (firstColonPos != 1 && firstColonPos != 2) { + throw new ICUException("Cutoff time must begin with h: or hh:"); + } + + int hour = Integer.parseInt(hourStr); + // parseInt() throws NumberFormatException if hourStr isn't proper. + + if (hour < 0 || hour > 24) { + throw new ICUException("Cutoff hour must be between 0 and 24, inclusive."); + } + + return hour; + } + } // DayPeriodRulesDataSink + + private static class DayPeriodRulesCountSink extends UResource.TableSink { + private DayPeriodRulesData data; + + private DayPeriodRulesCountSink(DayPeriodRulesData data) { + this.data = data; + } + + @Override + public UResource.TableSink getOrCreateTableSink(UResource.Key key, int initialSize) { + int setNum = parseSetNum(key.toString()); + if (setNum > data.maxRuleSetNum) { + data.maxRuleSetNum = setNum; + } + + return null; + } + } + + private static final DayPeriodRulesData DATA = loadData(); + + private boolean hasMidnight; + private boolean hasNoon; + private DayPeriod[] dayPeriodForHour; + + private DayPeriodRules() { + hasMidnight = false; + hasNoon = false; + dayPeriodForHour = new DayPeriod[24]; + } + + /** + * Get a DayPeriodRules object given a locale. + * If data hasn't been loaded, it will be loaded for all locales at once. + * @param locale locale for which the DayPeriodRules object is requested. + * @return a DayPeriodRules object for `locale`. + */ + public static DayPeriodRules getInstance(ULocale locale) { + String localeCode = locale.getName(); + if (localeCode.isEmpty()) { localeCode = "root"; } + + Integer ruleSetNum = null; + while (ruleSetNum == null) { + ruleSetNum = DATA.localesToRuleSetNumMap.get(localeCode); + if (ruleSetNum == null) { + localeCode = ULocale.getFallback(localeCode); + if (localeCode.isEmpty()) { + // Saves a lookup in the map. + break; + } + } else { + break; + } + } + + if (ruleSetNum == null || DATA.rules[ruleSetNum] == null) { + // Data doesn't exist for the locale requested. + return null; + } + + return DATA.rules[ruleSetNum]; + } + + public double getMidPointForDayPeriod(DayPeriod dayPeriod) { + int startHour = getStartHourForDayPeriod(dayPeriod); + int endHour = getEndHourForDayPeriod(dayPeriod); + + double midPoint = (startHour + endHour) / 2.0; + + if (startHour > endHour) { + // dayPeriod wraps around midnight. Shift midPoint by 12 hours, in the direction that + // lands it in [0, 24). + midPoint += 12; + if (midPoint >= 24) { + midPoint -= 24; + } + } + + return midPoint; + } + + private static DayPeriodRulesData loadData() { + DayPeriodRulesData data = new DayPeriodRulesData(); + ICUResourceBundle rb = (ICUResourceBundle)ICUResourceBundle.getBundleInstance( + ICUResourceBundle.ICU_BASE_NAME, + "dayPeriods", + ICUResourceBundle.ICU_DATA_CLASS_LOADER, + true); + + DayPeriodRulesCountSink countSink = new DayPeriodRulesCountSink(data); + rb.getAllTableItemsWithFallback("rules", countSink); + + data.rules = new DayPeriodRules[data.maxRuleSetNum + 1]; + DayPeriodRulesDataSink sink = new DayPeriodRulesDataSink(data); + rb.getAllTableItemsWithFallback("", sink); + + return data; + } + + private int getStartHourForDayPeriod(DayPeriod dayPeriod) throws IllegalArgumentException { + if (dayPeriod == DayPeriod.MIDNIGHT) { return 0; } + if (dayPeriod == DayPeriod.NOON) { return 12; } + + if (dayPeriodForHour[0] == dayPeriod && dayPeriodForHour[23] == dayPeriod) { + // dayPeriod wraps around midnight. Start hour is later than end hour. + for (int i = 22; i >= 1; --i) { + if (dayPeriodForHour[i] != dayPeriod) { + return (i + 1); + } + } + } else { + for (int i = 0; i <= 23; ++i) { + if (dayPeriodForHour[i] == dayPeriod) { + return i; + } + } + } + + // dayPeriod doesn't exist in rule set; throw exception. + throw new IllegalArgumentException(); + } + + private int getEndHourForDayPeriod(DayPeriod dayPeriod) { + if (dayPeriod == DayPeriod.MIDNIGHT) { return 0; } + if (dayPeriod == DayPeriod.NOON) { return 12; } + + if (dayPeriodForHour[0] == dayPeriod && dayPeriodForHour[23] == dayPeriod) { + // dayPeriod wraps around midnight. End hour is before start hour. + for (int i = 1; i <= 22; ++i) { + if (dayPeriodForHour[i] != dayPeriod) { + // i o'clock is when a new period starts, therefore when the old period ends. + return i; + } + } + } else { + for (int i = 23; i >= 0; --i) { + if (dayPeriodForHour[i] == dayPeriod) { + return (i + 1); + } + } + } + + // dayPeriod doesn't exist in rule set; throw exception. + throw new IllegalArgumentException(); + } + + // Getters. + public boolean hasMidnight() { return hasMidnight; } + public boolean hasNoon() { return hasNoon; } + public DayPeriod getDayPeriodForHour(int hour) { return dayPeriodForHour[hour]; } + + // Helpers. + private void add(int startHour, int limitHour, DayPeriod period) { + for (int i = startHour; i != limitHour; ++i) { + if (i == 24) { i = 0; } + dayPeriodForHour[i] = period; + } + } + + private static int parseSetNum(String setNumStr) { + if (!setNumStr.startsWith("set")) { + throw new ICUException("Set number should start with \"set\"."); + } + + String numStr = setNumStr.substring(3); // e.g. "set17" -> "17" + return Integer.parseInt(numStr); // This throws NumberFormatException if numStr isn't a proper number. + } +} diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/DateFormat.java b/icu4j/main/classes/core/src/com/ibm/icu/text/DateFormat.java index 7ebbe3d5a1d..adb9c48f800 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/text/DateFormat.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/DateFormat.java @@ -31,13 +31,13 @@ import com.ibm.icu.util.ULocale.Category; /** * {@icuenhanced java.text.DateFormat}.{@icu _usage_} - * + * *

* DateFormat is an abstract class for date/time formatting subclasses which formats and parses dates or time in a * language-independent manner. The date/time formatting subclass, such as SimpleDateFormat, allows for formatting * (i.e., date -> text), parsing (text -> date), and normalization. The date is represented as a Date * object or as the milliseconds since January 1, 1970, 00:00:00 GMT. - * + * *

* DateFormat helps you to format and parse dates for any locale. Your code can be completely independent of the locale * conventions for months, days of the week, or even the calendar format: lunar vs. solar. It provides many class @@ -52,10 +52,10 @@ import com.ibm.icu.util.ULocale.Category; * common pre-defined skeletons, such as {@link #MINUTE_SECOND} for something like "13:45" or {@link #YEAR_ABBR_MONTH} * for something like "Sept 2012". * - * + * *

* To format a date for the current Locale, use one of the static factory methods: - * + * *

  * myString = DateFormat.getDateInstance().format(myDate);
  * myString = DateFormat.getPatternInstance(DateFormat.YEAR_ABBR_MONTH).format(myDate);
@@ -63,7 +63,7 @@ import com.ibm.icu.util.ULocale.Category;
  * 

* If you are formatting multiple numbers, it is more efficient to get the format and use it multiple times so that the * system doesn't have to fetch the information about the local language and country conventions multiple times. - * + * *

  * DateFormat df = DateFormat.getDateInstance();
  * for (int i = 0; i < a.length; ++i) {
@@ -72,13 +72,13 @@ import com.ibm.icu.util.ULocale.Category;
  * 
*

* To format a date for a different Locale, specify it in the call to getDateInstance(). - * + * *

  * DateFormat df = DateFormat.getDateInstance(DateFormat.LONG, Locale.FRANCE);
  * 
*

* You can use a DateFormat to parse also. - * + * *

  * myDate = df.parse(myString);
  * 
@@ -93,7 +93,7 @@ import com.ibm.icu.util.ULocale.Category; *
  • LONG is longer, such as January 12, 1952 or 3:30:32pm *
  • FULL is pretty completely specified, such as Tuesday, April 12, 1952 AD or 3:30:42pm PST. * - * + * *

    * Use getPatternInstance to format with a skeleton. Typically this is with a predefined skeleton, like * {@link #YEAR_ABBR_MONTH} for something like "Sept 2012". If you don't want to use one of the predefined skeletons, @@ -111,25 +111,25 @@ import com.ibm.icu.util.ULocale.Category; * skeleton. * * - * + * *

    * You can also set the time zone on the format if you wish. If you want even more control over the format or parsing, * (or want to give your users more control), you can try casting the DateFormat you get from the factory methods to a * SimpleDateFormat. This will work for the majority of countries; just remember to put it in a try block in case you * encounter an unusual one. - * + * *

    * You can also use forms of the parse and format methods with ParsePosition and FieldPosition to allow you to *

      *
    • progressively parse through pieces of a string. *
    • align any particular field, or find out where it is for selection on the screen. *
    - * + * *

    Synchronization

    - * + * * Date formats are not synchronized. It is recommended to create separate format instances for each thread. If multiple * threads access a format concurrently, it must be synchronized externally. - * + * * @see UFormat * @see NumberFormat * @see SimpleDateFormat @@ -449,6 +449,24 @@ public abstract class DateFormat extends UFormat { @Deprecated final static int RELATED_YEAR = 34; + /** + * {@icu} FieldPosition selector for 'b' field alignment. + * No related Calendar field. + * This displays the fixed day period (am/pm/midnight/noon). + * @draft ICU 57 + * @provisional This API might change or be removed in a future release. + */ + final static int AM_PM_MIDNIGHT_NOON_FIELD = 35; + + /** + * {@icu} FieldPosition selector for 'B' field alignment. + * No related Calendar field. + * This displays the flexible day period. + * @draft ICU 57 + * @provisional This API might change or be removed in a future release. + */ + final static int FLEXIBLE_DAY_PERIOD_FIELD = 36; + /** * {@icu} FieldPosition selector time separator, * no related Calendar field. No pattern character is currently @@ -456,7 +474,7 @@ public abstract class DateFormat extends UFormat { * @draft ICU 55 * @provisional This API might change or be removed in a future release. */ - public final static int TIME_SEPARATOR = 35; + public final static int TIME_SEPARATOR = 37; /** * {@icu} Number of FieldPosition selectors for DateFormat. @@ -464,7 +482,7 @@ public abstract class DateFormat extends UFormat { * @stable ICU 3.0 */ - public final static int FIELD_COUNT = 36; + public final static int FIELD_COUNT = 38; // A previous comment for the above stated that we must have // DateFormat.FIELD_COUNT == DateFormatSymbols.patternChars.length() // but that does not seem to be the case, and in fact since there is @@ -472,31 +490,31 @@ public abstract class DateFormat extends UFormat { // currently the case that // DateFormat.FIELD_COUNT == DateFormatSymbols.patternChars.length() + 1 - + /** * boolean attributes - * + * * @stable ICU 53 */ - public enum BooleanAttribute { - /** - * indicates whitespace tolerance. Also included is trailing dot tolerance. + public enum BooleanAttribute { + /** + * indicates whitespace tolerance. Also included is trailing dot tolerance. * @stable ICU 53 */ PARSE_ALLOW_WHITESPACE, - /** - * indicates tolerance of numeric data when String data may be assumed. - * e.g. YEAR_NAME_FIELD + /** + * indicates tolerance of numeric data when String data may be assumed. + * e.g. YEAR_NAME_FIELD * @stable ICU 53 */ - PARSE_ALLOW_NUMERIC, - /** + PARSE_ALLOW_NUMERIC, + /** * indicates tolerance of pattern mismatch between input data and specified format pattern. - * e.g. accepting "September" for a month pattern of MMM ("Sep") + * e.g. accepting "September" for a month pattern of MMM ("Sep") * @draft ICU 56 * @provisional This API might change or be removed in a future release. */ - PARSE_MULTIPLE_PATTERNS_FOR_MATCH, + PARSE_MULTIPLE_PATTERNS_FOR_MATCH, /** * indicates tolerance of a partial literal match * e.g. accepting "--mon-02-march-2011" for a pattern of "'--: 'EEE-WW-MMMM-yyyy" @@ -512,11 +530,11 @@ public abstract class DateFormat extends UFormat { @Deprecated PARSE_PARTIAL_MATCH }; - + /** * boolean attributes for this instance. Inclusion in this is indicates a true condition. */ - private EnumSet booleanAttributes = EnumSet.allOf(BooleanAttribute.class); + private EnumSet booleanAttributes = EnumSet.allOf(BooleanAttribute.class); /* * Capitalization setting, hoisted to DateFormat ICU 53 @@ -1054,7 +1072,7 @@ public abstract class DateFormat extends UFormat { * @stable ICU 4.0 */ public static final String NUM_MONTH_WEEKDAY_DAY = "MEd"; - + /** * List of all of the date skeleton constants for iteration. * Note that this is fragile; be sure to add any values that are added above. @@ -1154,7 +1172,7 @@ public abstract class DateFormat extends UFormat { * @stable ICU 4.0 */ public static final String MINUTE_SECOND = "ms"; - + /** * List of all of the time skeleton constants for iteration. * Note that this is fragile; be sure to add any values that are added above. @@ -1172,7 +1190,7 @@ public abstract class DateFormat extends UFormat { HOUR_MINUTE_SECOND, HOUR24_MINUTE_SECOND, MINUTE_SECOND); - + /* * TIMEZONES */ @@ -1185,7 +1203,7 @@ public abstract class DateFormat extends UFormat { * @stable ICU 50 */ public static final String LOCATION_TZ = "VVVV"; - + /** * {@icu} Constant for generic non-location format, such as Pacific Time; * used in combinations date + time + zone, or time + zone. @@ -1194,7 +1212,7 @@ public abstract class DateFormat extends UFormat { * @stable ICU 50 */ public static final String GENERIC_TZ = "vvvv"; - + /** * {@icu} Constant for generic non-location format, abbreviated if possible, such as PT; * used in combinations date + time + zone, or time + zone. @@ -1203,7 +1221,7 @@ public abstract class DateFormat extends UFormat { * @stable ICU 50 */ public static final String ABBR_GENERIC_TZ = "v"; - + /** * {@icu} Constant for specific non-location format, such as Pacific Daylight Time; * used in combinations date + time + zone, or time + zone. @@ -1212,7 +1230,7 @@ public abstract class DateFormat extends UFormat { * @stable ICU 50 */ public static final String SPECIFIC_TZ = "zzzz"; - + /** * {@icu} Constant for specific non-location format, abbreviated if possible, such as PDT; * used in combinations date + time + zone, or time + zone. @@ -1221,7 +1239,7 @@ public abstract class DateFormat extends UFormat { * @stable ICU 50 */ public static final String ABBR_SPECIFIC_TZ = "z"; - + /** * {@icu} Constant for localized GMT/UTC format, such as GMT+8:00 or HPG-8:00; * used in combinations date + time + zone, or time + zone. @@ -1595,15 +1613,15 @@ public abstract class DateFormat extends UFormat { * lenient parsing, the parser may use heuristics to interpret inputs that * do not precisely match this object's format. Without lenient parsing, * inputs must match this object's format more closely. - *

    - * Note: ICU 53 introduced finer grained control of leniency (and added - * new control points) making the preferred method a combination of - * setCalendarLenient() & setBooleanAttribute() calls. - * This method supports prior functionality but may not support all - * future leniency control & behavior of DateFormat. For control of pre 53 leniency, - * Calendar and DateFormat whitespace & numeric tolerance, this method is safe to - * use. However, mixing leniency control via this method and modification of the - * newer attributes via setBooleanAttribute() may produce undesirable + *

    + * Note: ICU 53 introduced finer grained control of leniency (and added + * new control points) making the preferred method a combination of + * setCalendarLenient() & setBooleanAttribute() calls. + * This method supports prior functionality but may not support all + * future leniency control & behavior of DateFormat. For control of pre 53 leniency, + * Calendar and DateFormat whitespace & numeric tolerance, this method is safe to + * use. However, mixing leniency control via this method and modification of the + * newer attributes via setBooleanAttribute() may produce undesirable * results. * * @param lenient True specifies date/time interpretation to be lenient. @@ -1626,43 +1644,43 @@ public abstract class DateFormat extends UFormat { */ public boolean isLenient() { - return calendar.isLenient() + return calendar.isLenient() && getBooleanAttribute(BooleanAttribute.PARSE_ALLOW_NUMERIC) && getBooleanAttribute(BooleanAttribute.PARSE_ALLOW_WHITESPACE); } - /** - * Specifies whether date/time parsing in the encapsulated Calendar object should be lenient. + /** + * Specifies whether date/time parsing in the encapsulated Calendar object should be lenient. * With lenient parsing, the parser may use heuristics to interpret inputs that * do not precisely match this object's format. Without lenient parsing, * inputs must match this object's format more closely. - * @param lenient when true, Calendar parsing is lenient - * @see com.ibm.icu.util.Calendar#setLenient + * @param lenient when true, Calendar parsing is lenient + * @see com.ibm.icu.util.Calendar#setLenient * @stable ICU 53 - */ + */ public void setCalendarLenient(boolean lenient) { calendar.setLenient(lenient); } - - /** - * Returns whether date/time parsing in the encapsulated Calendar object is lenient. + + /** + * Returns whether date/time parsing in the encapsulated Calendar object is lenient. * @stable ICU 53 - */ + */ public boolean isCalendarLenient() { return calendar.isLenient(); } - - /** + + /** * Sets a boolean attribute for this instance. Aspects of DateFormat leniency are controlled by - * boolean attributes. - * + * boolean attributes. + * * @see BooleanAttribute * @stable ICU 53 */ - public DateFormat setBooleanAttribute(BooleanAttribute key, boolean value) + public DateFormat setBooleanAttribute(BooleanAttribute key, boolean value) { if(key.equals(DateFormat.BooleanAttribute.PARSE_PARTIAL_MATCH)) { key = DateFormat.BooleanAttribute.PARSE_PARTIAL_LITERAL_MATCH; @@ -1675,32 +1693,32 @@ public abstract class DateFormat extends UFormat { { booleanAttributes.remove(key); } - + return this; } - + /** * Returns the current value for the specified BooleanAttribute for this instance * * if attribute is missing false is returned. - * + * * @see BooleanAttribute * @stable ICU 53 */ - public boolean getBooleanAttribute(BooleanAttribute key) + public boolean getBooleanAttribute(BooleanAttribute key) { if(key == DateFormat.BooleanAttribute.PARSE_PARTIAL_MATCH) { key = DateFormat.BooleanAttribute.PARSE_PARTIAL_LITERAL_MATCH; } return booleanAttributes.contains(key); } - - + + /** * {@icu} Set a particular DisplayContext value in the formatter, - * such as CAPITALIZATION_FOR_STANDALONE. - * - * @param context The DisplayContext value to set. + * such as CAPITALIZATION_FOR_STANDALONE. + * + * @param context The DisplayContext value to set. * @stable ICU 53 */ public void setContext(DisplayContext context) { @@ -1712,7 +1730,7 @@ public abstract class DateFormat extends UFormat { /** * {@icu} Get the formatter's DisplayContext value for the specified DisplayContext.Type, * such as CAPITALIZATION. - * + * * @param type the DisplayContext.Type whose value to return * @return the current DisplayContext setting for the specified type * @stable ICU 53 @@ -1821,7 +1839,7 @@ public abstract class DateFormat extends UFormat { // Didn't have capitalizationSetting, set it to default capitalizationSetting = DisplayContext.CAPITALIZATION_NONE; } - + // if deserialized from a release that didn't have booleanAttributes, add them all if(booleanAttributes == null) { booleanAttributes = EnumSet.allOf(BooleanAttribute.class); @@ -1971,9 +1989,9 @@ public abstract class DateFormat extends UFormat { /** * Returns a date/time formatter that uses the SHORT style * for both the date and the time. - * + * * @param cal The calendar system for which a date/time format is desired. - * @param locale The locale for which the date/time format is desired. + * @param locale The locale for which the date/time format is desired. * @stable ICU 2.0 */ static final public DateFormat getInstance(Calendar cal, Locale locale) { @@ -1983,9 +2001,9 @@ public abstract class DateFormat extends UFormat { /** * Returns a date/time formatter that uses the SHORT style * for both the date and the time. - * + * * @param cal The calendar system for which a date/time format is desired. - * @param locale The locale for which the date/time format is desired. + * @param locale The locale for which the date/time format is desired. * @stable ICU 3.2 * @provisional This API might change or be removed in a future release. */ @@ -1996,7 +2014,7 @@ public abstract class DateFormat extends UFormat { /** * Returns a default date/time formatter that uses the SHORT style for both the * date and the time. - * + * * @param cal The calendar system for which a date/time format is desired. * @stable ICU 2.0 */ @@ -2054,7 +2072,7 @@ public abstract class DateFormat extends UFormat { static final public DateFormat getDateTimeInstance(Calendar cal, int dateStyle, int timeStyle) { return getDateTimeInstance(cal, dateStyle, timeStyle, ULocale.getDefault(Category.FORMAT)); } - + /** * {@icu} Returns a {@link DateFormat} object that can be used to format dates and times in * the default locale. @@ -2392,7 +2410,7 @@ public abstract class DateFormat extends UFormat { * Constant identifying the extended year field. * @stable ICU 3.8 */ - public static final Field EXTENDED_YEAR = new Field("extended year", + public static final Field EXTENDED_YEAR = new Field("extended year", Calendar.EXTENDED_YEAR); /** @@ -2428,6 +2446,20 @@ public abstract class DateFormat extends UFormat { @Deprecated public static final Field RELATED_YEAR = new Field("related year", -1); + /** + * {@icu} Constant identifying the am/pm/midnight/noon field. + * @draft ICU 57 + * @provisional This API might change or be removed in a future release. + */ + public static final Field AM_PM_MIDNIGHT_NOON = new Field("am/pm/midnight/noon", -1); + + /** + * {@icu} Constant identifying the flexible day period field. + * @draft ICU 57 + * @provisional This API might change or be removed in a future release. + */ + public static final Field FLEXIBLE_DAY_PERIOD = new Field("flexible day period", -1); + /** * Constant identifying the time separator field. * @draft ICU 55 diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/DateFormatSymbols.java b/icu4j/main/classes/core/src/com/ibm/icu/text/DateFormatSymbols.java index 243b91a51cb..1f4b6dad2ed 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/text/DateFormatSymbols.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/DateFormatSymbols.java @@ -592,7 +592,7 @@ public class DateFormatSymbols implements Serializable, Cloneable { * Unlocalized date-time pattern characters. For example: 'y', 'd', etc. * All locales use the same unlocalized pattern characters. */ - static final String patternChars = "GyMdkHmsSEDFwWahKzYeugAZvcLQqVUOXxr"; + static final String patternChars = "GyMdkHmsSEDFwWahKzYeugAZvcLQqVUOXxrbB"; /** * Localized date-time pattern characters. For example, a locale may @@ -606,6 +606,42 @@ public class DateFormatSymbols implements Serializable, Cloneable { */ String localPatternChars = null; + /** + * Localized names for abbreviated (== short) day periods. + * An array of strings, in the order of DayPeriod constants. + */ + String abbreviatedDayPeriods[] = null; + + /** + * Localized names for wide day periods. + * An array of strings, in the order of DayPeriod constants. + */ + String wideDayPeriods[] = null; + + /** + * Localized names for narrow day periods. + * An array of strings, in the order of DayPeriod constants. + */ + String narrowDayPeriods[] = null; + + /** + * Localized names for standalone abbreviated (== short) day periods. + * An array of strings, in the order of DayPeriod constants. + */ + String standaloneAbbreviatedDayPeriods[] = null; + + /** + * Localized names for standalone wide day periods. + * An array of strings, in the order of DayPeriod constants. + */ + String standaloneWideDayPeriods[] = null; + + /** + * Localized names for standalone narrow day periods. + * An array of strings, in the order of DayPeriod constants. + */ + String standaloneNarrowDayPeriods[] = null; + /* use serialVersionUID from JDK 1.1.4 for interoperability */ private static final long serialVersionUID = -5987973545549424702L; @@ -629,19 +665,19 @@ public class DateFormatSymbols implements Serializable, Cloneable { * @internal */ enum CapitalizationContextUsage { - OTHER, - MONTH_FORMAT, /* except narrow */ - MONTH_STANDALONE, /* except narrow */ - MONTH_NARROW, - DAY_FORMAT, /* except narrow */ - DAY_STANDALONE, /* except narrow */ - DAY_NARROW, - ERA_WIDE, - ERA_ABBREV, - ERA_NARROW, - ZONE_LONG, - ZONE_SHORT, - METAZONE_LONG, + OTHER, + MONTH_FORMAT, /* except narrow */ + MONTH_STANDALONE, /* except narrow */ + MONTH_NARROW, + DAY_FORMAT, /* except narrow */ + DAY_STANDALONE, /* except narrow */ + DAY_NARROW, + ERA_WIDE, + ERA_ABBREV, + ERA_NARROW, + ZONE_LONG, + ZONE_SHORT, + METAZONE_LONG, METAZONE_SHORT } @@ -954,7 +990,7 @@ public class DateFormatSymbols implements Serializable, Cloneable { /** * Returns abbreviated weekday strings; for example: "Sun", "Mon", etc. * (Note: the method name is misleading; it does not get the CLDR-style - * "short" weekday strings, e.g. "Su", "Mo", etc.) + * "short" weekday strings, e.g. "Su", "Mo", etc.) * @return the abbreviated weekday strings. Use Calendar.SUNDAY, * Calendar.MONDAY, etc. to index the result array. * @stable ICU 2.0 @@ -966,7 +1002,7 @@ public class DateFormatSymbols implements Serializable, Cloneable { /** * Sets abbreviated weekday strings; for example: "Sun", "Mon", etc. * (Note: the method name is misleading; it does not set the CLDR-style - * "short" weekday strings, e.g. "Su", "Mo", etc.) + * "short" weekday strings, e.g. "Su", "Mo", etc.) * @param newAbbrevWeekdays the new abbreviated weekday strings. The array should * be indexed by Calendar.SUNDAY, * Calendar.MONDAY, etc. @@ -1295,7 +1331,7 @@ public class DateFormatSymbols implements Serializable, Cloneable { * {@link java.text.DateFormatSymbols#getZoneStrings()}. For accessing the full * set of time zone string data used by ICU implementation, you should use * {@link TimeZoneNames} APIs instead. - * + * * @return the time zone strings. * @stable ICU 2.0 */ @@ -1339,7 +1375,7 @@ public class DateFormatSymbols implements Serializable, Cloneable { * you should customize {@link TimeZoneFormat} and set the * instance by {@link SimpleDateFormat#setTimeZoneFormat(TimeZoneFormat)} * instead. - * + * * @param newZoneStrings the new time zone strings. * @stable ICU 2.0 */ @@ -1423,6 +1459,12 @@ public class DateFormatSymbols implements Serializable, Cloneable { && Utility.arrayEquals(standaloneNarrowWeekdays, that.standaloneNarrowWeekdays) && Utility.arrayEquals(ampms, that.ampms) && Utility.arrayEquals(ampmsNarrow, that.ampmsNarrow) + && Utility.arrayEquals(abbreviatedDayPeriods, that.abbreviatedDayPeriods) + && Utility.arrayEquals(wideDayPeriods, that.wideDayPeriods) + && Utility.arrayEquals(narrowDayPeriods, that.narrowDayPeriods) + && Utility.arrayEquals(standaloneAbbreviatedDayPeriods, that.standaloneAbbreviatedDayPeriods) + && Utility.arrayEquals(standaloneWideDayPeriods, that.standaloneWideDayPeriods) + && Utility.arrayEquals(standaloneNarrowDayPeriods, that.standaloneNarrowDayPeriods) && Utility.arrayEquals(timeSeparator, that.timeSeparator) && arrayOfArrayEquals(zoneStrings, that.zoneStrings) // getDiplayName maps deprecated country and language codes to the current ones @@ -1507,10 +1549,16 @@ public class DateFormatSymbols implements Serializable, Cloneable { this.leapMonthPatterns = dfs.leapMonthPatterns; this.shortYearNames = dfs.shortYearNames; this.shortZodiacNames = dfs.shortZodiacNames; + this.abbreviatedDayPeriods = dfs.abbreviatedDayPeriods; + this.wideDayPeriods = dfs.wideDayPeriods; + this.narrowDayPeriods = dfs.narrowDayPeriods; + this.standaloneAbbreviatedDayPeriods = dfs.standaloneAbbreviatedDayPeriods; + this.standaloneWideDayPeriods = dfs.standaloneWideDayPeriods; + this.standaloneNarrowDayPeriods = dfs.standaloneNarrowDayPeriods; this.zoneStrings = dfs.zoneStrings; // always null at initialization time for now this.localPatternChars = dfs.localPatternChars; - + this.capitalization = dfs.capitalization; this.actualLocale = dfs.actualLocale; @@ -1541,7 +1589,7 @@ public class DateFormatSymbols implements Serializable, Cloneable { months = calData.getStringArray("monthNames", "wide"); shortMonths = calData.getStringArray("monthNames", "abbreviated"); narrowMonths = calData.getStringArray("monthNames", "narrow"); - + standaloneMonths = calData.getStringArray("monthNames", "stand-alone", "wide"); standaloneShortMonths = calData.getStringArray("monthNames", "stand-alone", "abbreviated"); standaloneNarrowMonths = calData.getStringArray("monthNames", "stand-alone", "narrow"); @@ -1610,6 +1658,13 @@ public class DateFormatSymbols implements Serializable, Cloneable { standaloneQuarters = calData.getStringArray("quarters", "stand-alone", "wide"); standaloneShortQuarters = calData.getStringArray("quarters", "stand-alone", "abbreviated"); + abbreviatedDayPeriods = loadDayPeriodStrings(calData, false, "abbreviated"); + wideDayPeriods = loadDayPeriodStrings(calData, false, "wide"); + narrowDayPeriods = loadDayPeriodStrings(calData, false, "narrow"); + standaloneAbbreviatedDayPeriods = loadDayPeriodStrings(calData, true, "abbreviated"); + standaloneWideDayPeriods = loadDayPeriodStrings(calData, true, "wide"); + standaloneNarrowDayPeriods = loadDayPeriodStrings(calData, true, "narrow"); + // The code for getting individual symbols in the leapMonthSymbols array is here // rather than in CalendarData because it depends on DateFormatSymbols constants... ICUResourceBundle monthPatternsBundle = null; @@ -1629,7 +1684,7 @@ public class DateFormatSymbols implements Serializable, Cloneable { leapMonthPatterns[DT_LEAP_MONTH_PATTERN_STANDALONE_NARROW] = calData.get("monthPatterns", "stand-alone", "narrow").get("leap").getString(); leapMonthPatterns[DT_LEAP_MONTH_PATTERN_NUMERIC] = calData.get("monthPatterns", "numeric", "all").get("leap").getString(); } - + ICUResourceBundle cyclicNameSetsBundle = null; try { cyclicNameSetsBundle = calData.get("cyclicNameSets"); @@ -1641,7 +1696,7 @@ public class DateFormatSymbols implements Serializable, Cloneable { shortYearNames = calData.get("cyclicNameSets", "years", "format", "abbreviated").getStringArray(); shortZodiacNames = calData.get("cyclicNameSets", "zodiacs", "format", "abbreviated").getStringArray(); } - + requestedLocale = desiredLocale; ICUResourceBundle rb = @@ -1658,7 +1713,7 @@ public class DateFormatSymbols implements Serializable, Cloneable { // TODO: obtain correct actual/valid locale later ULocale uloc = rb.getULocale(); setLocale(uloc, uloc); - + capitalization = new HashMap(); boolean[] noTransforms = new boolean[2]; noTransforms[0] = false; @@ -1722,6 +1777,36 @@ public class DateFormatSymbols implements Serializable, Cloneable { return equal; } + /** + * Loads localized names for day periods in the requested format. + * @param calData where the strings come from + * @param standalone whether the standalone version is requested + * @param width "abbreviated", "wide", or "narrow". + */ + private String[] loadDayPeriodStrings(CalendarData calData, boolean standalone, String width) { + String[] dayPeriodKeys = {"midnight", "noon", + "morning1", "afternoon1", "evening1", "night1", + "morning2", "afternoon2", "evening2", "night2"}; + + ICUResourceBundle b; + String strings[] = new String[10]; + try { + if (standalone) { + b = calData.get("dayPeriod", "standalone", width); + } else { + b = calData.get("dayPeriod", "format", width); + } + } catch (MissingResourceException e) { + return strings; // Array of null's. + } + + for (int i = 0; i < 10; ++i) { + strings[i] = b.findStringWithFallback(dayPeriodKeys[i]); // Null if string doesn't exist. + } + + return strings; + } + /* * save the input locale */ diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/SimpleDateFormat.java b/icu4j/main/classes/core/src/com/ibm/icu/text/SimpleDateFormat.java index cd0d9ff109d..6b23c532fe4 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/text/SimpleDateFormat.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/SimpleDateFormat.java @@ -25,6 +25,7 @@ import java.util.UUID; import com.ibm.icu.impl.CalendarData; import com.ibm.icu.impl.DateNumberFormat; +import com.ibm.icu.impl.DayPeriodRules; import com.ibm.icu.impl.ICUCache; import com.ibm.icu.impl.PatternProps; import com.ibm.icu.impl.SimpleCache; @@ -84,7 +85,7 @@ import com.ibm.icu.util.ULocale.Category; * G * 1..3 * AD - * Era - Replaced with the Era string for the current date. One to three letters for the + * Era - Replaced with the Era string for the current date. One to three letters for the * abbreviated form, four letters for the long (wide) form, five for the narrow form. * * @@ -198,7 +199,7 @@ import com.ibm.icu.util.ULocale.Category; * Q * 1..2 * 02 - * Quarter - Use one or two for the numerical quarter, three for the abbreviation, or four + * Quarter - Use one or two for the numerical quarter, three for the abbreviation, or four * for the full (wide) name (five for the narrow name is not yet supported). * * @@ -213,7 +214,7 @@ import com.ibm.icu.util.ULocale.Category; * q * 1..2 * 02 - * Stand-Alone Quarter - Use one or two for the numerical quarter, three for the abbreviation, + * Stand-Alone Quarter - Use one or two for the numerical quarter, three for the abbreviation, * or four for the full name (five for the narrow name is not yet supported). * * @@ -249,7 +250,7 @@ import com.ibm.icu.util.ULocale.Category; * L * 1..2 * 09 - * Stand-Alone Month - Use one or two for the numerical month, three for the abbreviation, + * Stand-Alone Month - Use one or two for the numerical month, three for the abbreviation, * four for the full (wide) name, or 5 for the narrow name. With two ("LL"), the month number is zero-padded if * necessary (e.g. "08"). * @@ -305,7 +306,7 @@ import com.ibm.icu.util.ULocale.Category; * 2451334 * Modified Julian day. This is different from the conventional Julian day number in two regards. * First, it demarcates days at local zone midnight, rather than noon GMT. Second, it is a local number; - * that is, it depends on the local time zone. It can be thought of as a single number that encompasses + * that is, it depends on the local time zone. It can be thought of as a single number that encompasses * all the date-related fields. * * @@ -314,7 +315,7 @@ import com.ibm.icu.util.ULocale.Category; * E * 1..3 * Tue - * Day of week - Use one through three letters for the short day, four for the full (wide) name, + * Day of week - Use one through three letters for the short day, four for the full (wide) name, * five for the narrow name, or six for the short name. * * @@ -533,7 +534,7 @@ import com.ibm.icu.util.ULocale.Category; * The generic location format. * Where that is unavailable, falls back to the long localized GMT format ("OOOO"; * Note: Fallback is only necessary with a GMT-style Time Zone ID, like Etc/GMT-830.)
    - * This is especially useful when presenting possible timezone choices for user selection, + * This is especially useful when presenting possible timezone choices for user selection, * since the naming is more uniform than the "v" format. * * @@ -609,7 +610,7 @@ import com.ibm.icu.util.ULocale.Category; * (Note: The seconds field is not supported by the ISO8601 specification.) * * - * + * * *

    * Any characters in the pattern that are not in the ranges of ['a'..'z'] @@ -901,7 +902,7 @@ public class SimpleDateFormat extends DateFormat { // When possessing ISO format, the ERA may be ommitted is the // year specifier is a negative number. private static final int ISOSpecialEra = -32000; - + // This prefix is designed to NEVER MATCH real text, in order to // suppress the parsing of negative numbers. Adjust as needed (if // this becomes valid Unicode). @@ -923,6 +924,16 @@ public class SimpleDateFormat extends DateFormat { */ private transient BreakIterator capitalizationBrkIter = null; + /** + * DateFormat pattern contains the minute field. + */ + private transient boolean hasMinute; + + /** + * DateFormat pattern contains the second field. + */ + private transient boolean hasSecond; + /* * Capitalization setting, introduced in ICU 50 * Special serialization, see writeObject & readObject below @@ -1118,6 +1129,8 @@ public class SimpleDateFormat extends DateFormat { if (override != null) { initNumberFormatters(locale); } + + parsePattern(); } /** @@ -1257,10 +1270,10 @@ public class SimpleDateFormat extends DateFormat { /** * {@icu} Set a particular DisplayContext value in the formatter, - * such as CAPITALIZATION_FOR_STANDALONE. Note: For getContext, see + * such as CAPITALIZATION_FOR_STANDALONE. Note: For getContext, see * DateFormat. - * - * @param context The DisplayContext value to set. + * + * @param context The DisplayContext value to set. * @stable ICU 53 */ // Here we override the DateFormat implementation in order to lazily initialize relevant items @@ -1365,11 +1378,11 @@ public class SimpleDateFormat extends DateFormat { // 0 1 2 3 4 5 6 7 8 9 : ; < = > ? -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // @ A B C D E F G H I J K L M N O - -1, 22, -1, -1, 10, 9, 11, 0, 5, -1, -1, 16, 26, 2, -1, 31, + -1, 22, 36, -1, 10, 9, 11, 0, 5, -1, -1, 16, 26, 2, -1, 31, // P Q R S T U V W X Y Z [ \ ] ^ _ -1, 27, -1, 8, -1, 30, 29, 13, 32, 18, 23, -1, -1, -1, -1, -1, // ` a b c d e f g h i j k l m n o - -1, 14, -1, 25, 3, 19, -1, 21, 15, -1, -1, 4, -1, 6, -1, -1, + -1, 14, 35, 25, 3, 19, -1, 21, 15, -1, -1, 4, -1, 6, -1, -1, // p q r s t u v w x y z { | } ~ -1, 28, 34, 7, -1, 20, 24, 12, 33, 1, 17, -1, -1, -1, -1, -1, }; @@ -1398,6 +1411,7 @@ public class SimpleDateFormat extends DateFormat { /*O*/ Calendar.ZONE_OFFSET /* also DST_OFFSET */, /*Xx*/ Calendar.ZONE_OFFSET /* also DST_OFFSET */, Calendar.ZONE_OFFSET /* also DST_OFFSET */, /*r*/ Calendar.EXTENDED_YEAR /* not an exact match */, + /*bB*/ -1, -1 /* am/pm/midnight/noon and flexible day period fields; no mapping to calendar fields */ /*:*/ -1, /* => no useful mapping to any calendar field, can't use protected Calendar.BASE_FIELD_COUNT */ }; @@ -1420,6 +1434,7 @@ public class SimpleDateFormat extends DateFormat { /*O*/ DateFormat.TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD, /*Xx*/ DateFormat.TIMEZONE_ISO_FIELD, DateFormat.TIMEZONE_ISO_LOCAL_FIELD, /*r*/ DateFormat.RELATED_YEAR, + /*bB*/ DateFormat.AM_PM_MIDNIGHT_NOON_FIELD, DateFormat.FLEXIBLE_DAY_PERIOD_FIELD, /*(no pattern character defined for this)*/ DateFormat.TIME_SEPARATOR, }; @@ -1442,6 +1457,7 @@ public class SimpleDateFormat extends DateFormat { /*O*/ DateFormat.Field.TIME_ZONE, /*Xx*/ DateFormat.Field.TIME_ZONE, DateFormat.Field.TIME_ZONE, /*r*/ DateFormat.Field.RELATED_YEAR, + /*bB*/ DateFormat.Field.AM_PM_MIDNIGHT_NOON, DateFormat.Field.FLEXIBLE_DAY_PERIOD, /*(no pattern character defined for this)*/ DateFormat.Field.TIME_SEPARATOR, }; @@ -1571,7 +1587,7 @@ public class SimpleDateFormat extends DateFormat { safeAppend(formatData.shortYearNames, value-1, buf); break; } - // else fall through to numeric year handling, do not break here + // else fall through to numeric year handling, do not break here case 1: // 'y' - YEAR case 18: // 'Y' - YEAR_WOY if ( override != null && (override.compareTo("hebr") == 0 || override.indexOf("y=hebr") >= 0) && @@ -1846,8 +1862,115 @@ public class SimpleDateFormat extends DateFormat { zeroPaddingNumber(currentNumberFormat,buf, (value/3)+1, count, maxIntCount); } break; - case 35: // TIME SEPARATOR (no pattern character currently defined, we should - // not get here but leave support in for future definition. + case 35: // 'b' - am/pm/noon/midnight + { + int hour = cal.get(Calendar.HOUR_OF_DAY); + String toAppend = null; + + // For "midnight" and "noon": + // Time, as displayed, must be exactly noon or midnight. + // This means minutes and seconds, if present, must be zero. + if ((hour == 0 || hour == 12) && + (!hasMinute || cal.get(Calendar.MINUTE) == 0) && + (!hasSecond || cal.get(Calendar.SECOND) == 0)) { + // Stealing am/pm value to use as our array index. + // It works out: am/midnight are both 0, pm/noon are both 1, + // 12 am is 12 midnight, and 12 pm is 12 noon. + value = cal.get(Calendar.AM_PM); + + if (count == 3) { + toAppend = formatData.abbreviatedDayPeriods[value]; + } else if (count == 4 || count > 5) { + toAppend = formatData.wideDayPeriods[value]; + } else { // count == 5 + toAppend = formatData.narrowDayPeriods[value]; + } + } + + if (toAppend == null) { + // Time isn't exactly midnight or noon (as displayed) or localized string doesn't + // exist for requested period. Fall back to am/pm instead. + subFormat(buf, 'a', count, beginOffset, fieldNum, capitalizationContext, pos, cal); + } else { + buf.append(toAppend); + } + + break; + } + case 36: // 'B' - flexible day period + { + // TODO: Maybe fetch the DayperiodRules during initialization (instead of at the first + // loading of an instance) if a relevant pattern character (b or B) is used. + DayPeriodRules ruleSet = DayPeriodRules.getInstance(getLocale()); + if (ruleSet == null) { + // Data doesn't exist for the locale we're looking for. + // Fall back to am/pm. + subFormat(buf, 'a', count, beginOffset, fieldNum, capitalizationContext, pos, cal); + break; + } + + // Get current display time. + int hour = cal.get(Calendar.HOUR_OF_DAY); + int minute = 0; + int second = 0; + if (hasMinute) { minute = cal.get(Calendar.MINUTE); } + if (hasSecond) { second = cal.get(Calendar.SECOND); } + + // Determine day period. + DayPeriodRules.DayPeriod periodType; + if (hour == 0 && minute == 0 && second == 0 && ruleSet.hasMidnight()) { + periodType = DayPeriodRules.DayPeriod.MIDNIGHT; + } else if (hour == 12 && minute == 0 && second == 0 && ruleSet.hasNoon()) { + periodType = DayPeriodRules.DayPeriod.NOON; + } else { + periodType = ruleSet.getDayPeriodForHour(hour); + } + + // Rule set exists, therefore periodType can't be null. + // Get localized string. + assert(periodType != null); + String toAppend = null; + + int index = periodType.ordinal(); + if (count <= 3) { + toAppend = formatData.abbreviatedDayPeriods[index]; // i.e. short + } else if (count == 4 || count > 5) { + toAppend = formatData.wideDayPeriods[index]; + } else { // count == 5 + toAppend = formatData.narrowDayPeriods[index]; + } + + // Fallback schedule: + // Midnight/Noon -> General Periods -> AM/PM. + + // Midnight/Noon -> General Periods. + if (toAppend == null && + (periodType == DayPeriodRules.DayPeriod.MIDNIGHT || + periodType == DayPeriodRules.DayPeriod.NOON)) { + periodType = ruleSet.getDayPeriodForHour(hour); + index = periodType.ordinal(); + + if (count <= 3) { + toAppend = formatData.abbreviatedDayPeriods[index]; // i.e. short + } else if (count == 4 || count > 5) { + toAppend = formatData.wideDayPeriods[index]; + } else { // count == 5 + toAppend = formatData.narrowDayPeriods[index]; + } + } + + // General Periods -> AM/PM. + if (toAppend == null) { + subFormat(buf, 'a', count, beginOffset, fieldNum, capitalizationContext, pos, cal); + } + else { + buf.append(toAppend); + } + + break; + } + case 37: // TIME SEPARATOR (no pattern character currently defined, we should + // not get here but leave support in for future definition. buf.append(formatData.getTimeSeparatorString()); break; default: @@ -2054,9 +2177,9 @@ public class SimpleDateFormat extends DateFormat { /** * Overrides superclass method and - * This method also clears per field NumberFormat instances - * previously set by {@link #setNumberFormat(String, NumberFormat)} - * + * This method also clears per field NumberFormat instances + * previously set by {@link #setNumberFormat(String, NumberFormat)} + * * @stable ICU 2.0 */ public void setNumberFormat(NumberFormat newNumberFormat) { @@ -2193,6 +2316,11 @@ public class SimpleDateFormat extends DateFormat { } int start = pos; + // Hold the day period until everything else is parsed, because we need + // the hour to interpret time correctly. + // Using an one-element array for output parameter. + Output dayPeriod = new Output(null); + Output tzTimeType = new Output(TimeType.UNKNOWN); boolean[] ambiguousYear = { false }; @@ -2202,7 +2330,7 @@ public class SimpleDateFormat extends DateFormat { int numericFieldLength = 0; // start index of numeric text run in the input text int numericStartPos = 0; - + MessageFormat numericLeapMonthFormatter = null; if (formatData.leapMonthPatterns != null && formatData.leapMonthPatterns.length >= DateFormatSymbols.DT_MONTH_PATTERN_COUNT) { numericLeapMonthFormatter = new MessageFormat(formatData.leapMonthPatterns[DateFormatSymbols.DT_LEAP_MONTH_PATTERN_NUMERIC], locale); @@ -2224,8 +2352,8 @@ public class SimpleDateFormat extends DateFormat { // try 4/2/2, 3/2/2, 2/2/2, and finally 1/2/2. if (numericFieldStart == -1) { // check if this field is followed by abutting another numeric field - if ((i + 1) < items.length - && (items[i + 1] instanceof PatternItem) + if ((i + 1) < items.length + && (items[i + 1] instanceof PatternItem) && ((PatternItem)items[i + 1]).isNumeric) { // record the first numeric field within a numeric text run numericFieldStart = i; @@ -2270,15 +2398,15 @@ public class SimpleDateFormat extends DateFormat { int s = pos; pos = subParse(text, pos, field.type, field.length, - false, true, ambiguousYear, cal, numericLeapMonthFormatter, tzTimeType); - + false, true, ambiguousYear, cal, numericLeapMonthFormatter, tzTimeType, dayPeriod); + if (pos < 0) { if (pos == ISOSpecialEra) { // era not present, in special cases allow this to continue pos = s; - if (i+1 < items.length) { - + if (i+1 < items.length) { + String patl = null; // if it will cause a class cast exception to String, we can't use it try { @@ -2290,15 +2418,15 @@ public class SimpleDateFormat extends DateFormat { calendar.setTimeZone(backupTZ); } return; - } - + } + // get next item in pattern if(patl == null) patl = (String)items[i+1]; int plen = patl.length(); int idx=0; - - // White space characters found in patten. + + // White space characters found in pattern. // Skip contiguous white spaces. while (idx < plen) { @@ -2308,7 +2436,7 @@ public class SimpleDateFormat extends DateFormat { else break; } - + // if next item in pattern is all whitespace, skip it if (idx == plen) { i++; @@ -2322,7 +2450,7 @@ public class SimpleDateFormat extends DateFormat { calendar.setTimeZone(backupTZ); } return; - } + } } } @@ -2343,7 +2471,7 @@ public class SimpleDateFormat extends DateFormat { } ++i; } - + // Special hack for trailing "." after non-numeric field. if (pos < text.length()) { char extra = text.charAt(pos); @@ -2356,6 +2484,70 @@ public class SimpleDateFormat extends DateFormat { } } + // If dayPeriod is set, use it in conjunction with hour-of-day to determine am/pm. + if (dayPeriod.value != null) { + DayPeriodRules ruleSet = DayPeriodRules.getInstance(getLocale()); + + if (!cal.isSet(Calendar.HOUR) && !cal.isSet(Calendar.HOUR_OF_DAY)) { + // If hour is not set, set time to the midpoint of current day period, overwriting + // minutes if it's set. + double midPoint = ruleSet.getMidPointForDayPeriod(dayPeriod.value); + + // Truncate midPoint toward zero to get the hour. + // Any leftover means it was a half-hour. + int midPointHour = (int) midPoint; + int midPointMinute = (midPoint - midPointHour) > 0 ? 30 : 0; + + // No need to set am/pm because hour-of-day is set last therefore takes precedence. + cal.set(Calendar.HOUR_OF_DAY, midPointHour); + cal.set(Calendar.MINUTE, midPointMinute); + } else { + int hourOfDay; + + if (cal.isSet(Calendar.HOUR_OF_DAY)) { // Hour is parsed in 24-hour format. + hourOfDay = cal.get(Calendar.HOUR_OF_DAY); + } else { // Hour is parsed in 12-hour format. + hourOfDay = cal.get(Calendar.HOUR); + // cal.get() turns 12 to 0 for 12-hour time; change 0 to 12 + // so 0 unambiguously means a 24-hour time from above. + if (hourOfDay == 0) { hourOfDay = 12; } + } + assert(0 <= hourOfDay && hourOfDay <= 23); + + + // If hour-of-day is 0 or 13 thru 23 then input time in unambiguously in 24-hour format. + if (hourOfDay == 0 || (13 <= hourOfDay && hourOfDay <= 23)) { + // Make hour-of-day take precedence over (hour + am/pm) by setting it again. + cal.set(Calendar.HOUR_OF_DAY, hourOfDay); + } else { + // We have a 12-hour time and need to choose between am and pm. + // Behave as if dayPeriod spanned 6 hours each way from its center point. + // This will parse correctly for consistent time + period (e.g. 10 at night) as + // well as provide a reasonable recovery for inconsistent time + period (e.g. + // 9 in the afternoon). + + // Assume current time is in the AM. + // - Change 12 back to 0 for easier handling of 12am. + // - Append minutes as fractional hours because e.g. 8:15 and 8:45 could be parsed + // into different half-days if center of dayPeriod is at 14:30. + // - cal.get(MINUTE) will return 0 if MINUTE is unset, which works. + if (hourOfDay == 12) { hourOfDay = 0; } + double currentHour = hourOfDay + cal.get(Calendar.MINUTE) / 60.0; + double midPointHour = ruleSet.getMidPointForDayPeriod(dayPeriod.value); + + double hoursAheadMidPoint = currentHour - midPointHour; + + // Assume current time is in the AM. + if (-6 <= hoursAheadMidPoint && hoursAheadMidPoint < 6) { + // Assumption holds; set time as such. + cal.set(Calendar.AM_PM, 0); + } else { + cal.set(Calendar.AM_PM, 1); + } + } + } + } + // At this point the fields of Calendar have been set. Calendar // will fill in default values for missing fields when the time // is computed. @@ -2612,7 +2804,7 @@ public class SimpleDateFormat extends DateFormat { } return pos; } - + static final UnicodeSet DATE_PATTERN_TYPE = new UnicodeSet("[GyYuUQqMLlwWd]").freeze(); /** @@ -2776,6 +2968,34 @@ public class SimpleDateFormat extends DateFormat { return -start; } + /** + * Similar to matchQuarterString but customized for day periods. + */ + protected int matchDayPeriodString(String text, int start, String[] data, int dataLength, + Output dayPeriod) + { + int bestMatchLength = 0, bestMatch = -1; + int matchLength = 0; + for (int i = 0; i < dataLength; ++i) { + // Only try matching if the string exists. + if (data[i] != null) { + int length = data[i].length(); + if (length > bestMatchLength && + (matchLength = regionMatchesWithOptionalDot(text, start, data[i], length)) >= 0) { + bestMatch = i; + bestMatchLength = matchLength; + } + } + } + + if (bestMatch >= 0) { + dayPeriod.value = DayPeriodRules.DayPeriod.VALUES[bestMatch]; + return start + bestMatchLength; + } + + return -start; + } + /** * Protected method that converts one field of the input string into a * numeric field value in cal. Returns -start (for @@ -2803,6 +3023,16 @@ public class SimpleDateFormat extends DateFormat { return subParse(text, start, ch, count, obeyCount, allowNegative, ambiguousYear, cal, null, null); } + /** + * Overloading to provide default argument (null) for day period. + */ + private int subParse(String text, int start, char ch, int count, + boolean obeyCount, boolean allowNegative, + boolean[] ambiguousYear, Calendar cal, + MessageFormat numericLeapMonthFormatter, Output tzTimeType) { + return subParse(text, start, ch, count, obeyCount, allowNegative, ambiguousYear, cal, null, null, null); + } + /** * Protected method that converts one field of the input string into a * numeric field value in cal. Returns -start (for @@ -2832,7 +3062,8 @@ public class SimpleDateFormat extends DateFormat { private int subParse(String text, int start, char ch, int count, boolean obeyCount, boolean allowNegative, boolean[] ambiguousYear, Calendar cal, - MessageFormat numericLeapMonthFormatter, Output tzTimeType) + MessageFormat numericLeapMonthFormatter, Output tzTimeType, + Output dayPeriod) { Number number = null; NumberFormat currentNumberFormat = null; @@ -2848,7 +3079,7 @@ public class SimpleDateFormat extends DateFormat { currentNumberFormat = getNumberFormat(ch); int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex]; // -1 if irrelevant - + if (numericLeapMonthFormatter != null) { numericLeapMonthFormatter.setFormatByArgumentIndex(0, currentNumberFormat); } @@ -2887,7 +3118,7 @@ public class SimpleDateFormat extends DateFormat { { // It would be good to unify this with the obeyCount logic below, // but that's going to be difficult. - + boolean parsedNumericLeapMonth = false; if (numericLeapMonthFormatter != null && (patternCharIndex == 2 || patternCharIndex == 26)) { // First see if we can parse month number with leap month pattern @@ -2901,7 +3132,7 @@ public class SimpleDateFormat extends DateFormat { cal.set(Calendar.IS_LEAP_MONTH, 0); } } - + if (!parsedNumericLeapMonth) { if (obeyCount) { if ((start+count) > text.length()) { @@ -2941,13 +3172,13 @@ public class SimpleDateFormat extends DateFormat { } // check return position, if it equals -start, then matchString error - // special case the return code so we don't necessarily fail out until we + // special case the return code so we don't necessarily fail out until we // verify no year information also if (ps == ~start) ps = ISOSpecialEra; - return ps; - + return ps; + case 1: // 'y' - YEAR case 18: // 'Y' - YEAR_WOY // If there are 3 or more YEAR pattern characters, this indicates @@ -3036,7 +3267,7 @@ public class SimpleDateFormat extends DateFormat { if (newStart > 0) { return newStart; } - } + } // count == 4 failed, now try count == 3 if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 3) { return (patternCharIndex == 2)? @@ -3073,25 +3304,25 @@ public class SimpleDateFormat extends DateFormat { cal.set(Calendar.MILLISECOND, value); return pos.getIndex(); case 19: // 'e' - DOW_LOCAL - if(count <= 2 || (number != null && (getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_NUMERIC))) ) { + if(count <= 2 || (number != null && (getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_NUMERIC))) ) { // i.e. e/ee or lenient and have a number cal.set(field, value); return pos.getIndex(); } // else for eee-eeeeee, fall through to EEE-EEEEEE handling - //$FALL-THROUGH$ + //$FALL-THROUGH$ case 9: { // 'E' - DAY_OF_WEEK // Want to be able to parse at least wide, abbrev, short, and narrow forms. int newStart = 0; if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 4) { if ((newStart = matchString(text, start, Calendar.DAY_OF_WEEK, formatData.weekdays, null, cal)) > 0) { // try EEEE wide return newStart; - } + } } if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 3) { if ((newStart = matchString(text, start, Calendar.DAY_OF_WEEK, formatData.shortWeekdays, null, cal)) > 0) { // try EEE abbrev return newStart; - } + } } if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 6) { if (formatData.shorterWeekdays != null) { @@ -3110,7 +3341,7 @@ public class SimpleDateFormat extends DateFormat { return newStart; } case 25: { // 'c' - STAND_ALONE_DAY_OF_WEEK - if(count == 1 || (number != null && (getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_NUMERIC))) ) { + if(count == 1 || (number != null && (getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_NUMERIC))) ) { // i.e. c or lenient and have a number cal.set(field, value); return pos.getIndex(); @@ -3120,7 +3351,7 @@ public class SimpleDateFormat extends DateFormat { if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 4) { if ((newStart = matchString(text, start, Calendar.DAY_OF_WEEK, formatData.standaloneWeekdays, null, cal)) > 0) { // try cccc wide return newStart; - } + } } if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 3) { if ((newStart = matchString(text, start, Calendar.DAY_OF_WEEK, formatData.standaloneShortWeekdays, null, cal)) > 0) { // try ccc abbrev @@ -3281,7 +3512,7 @@ public class SimpleDateFormat extends DateFormat { return ~start; } case 27: // 'Q' - QUARTER - if (count <= 2 || (number != null && getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_NUMERIC))) { + if (count <= 2 || (number != null && getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_NUMERIC))) { // i.e., Q or QQ. or lenient & have number // Don't want to parse the quarter if it is a string // while pattern uses numeric style: Q or QQ. @@ -3307,7 +3538,7 @@ public class SimpleDateFormat extends DateFormat { } case 28: // 'q' - STANDALONE QUARTER - if (count <= 2 || (number != null && getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_NUMERIC))) { + if (count <= 2 || (number != null && getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_ALLOW_NUMERIC))) { // i.e., q or qq. or lenient & have number // Don't want to parse the quarter if it is a string // while pattern uses numeric style: q or qq. @@ -3332,8 +3563,8 @@ public class SimpleDateFormat extends DateFormat { return newStart; } - case 35: // TIME SEPARATOR (no pattern character currently defined, we should - // not get here but leave support in for future definition. + case 37: // TIME SEPARATOR (no pattern character currently defined, we should + // not get here but leave support in for future definition. { // Try matching a time separator. ArrayList data = new ArrayList(3); @@ -3353,6 +3584,66 @@ public class SimpleDateFormat extends DateFormat { return matchString(text, start, -1 /* => nothing to set */, data.toArray(new String[0]), cal); } + case 35: // 'b' -- fixed day period (am/pm/midnight/noon) + { + int ampmStart = subParse(text, start, 'a', count, obeyCount, allowNegative, ambiguousYear, cal, + numericLeapMonthFormatter, tzTimeType, dayPeriod); + + if (ampmStart > 0) { + return ampmStart; + } else { + int newStart = 0; + if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 3) { + if ((newStart = matchDayPeriodString( + text, start, formatData.abbreviatedDayPeriods, 2, dayPeriod)) > 0) { + return newStart; + } + } + if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 4) { + if ((newStart = matchDayPeriodString( + text, start, formatData.wideDayPeriods, 2, dayPeriod)) > 0) { + return newStart; + } + } + if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 4) { + if ((newStart = matchDayPeriodString( + text, start, formatData.narrowDayPeriods, 2, dayPeriod)) > 0) { + return newStart; + } + } + + return newStart; + } + } + + case 36: // 'B' -- flexible day period + { + int newStart = 0; + if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 3) { + if ((newStart = matchDayPeriodString( + text, start, formatData.abbreviatedDayPeriods, + formatData.abbreviatedDayPeriods.length, dayPeriod)) > 0) { + return newStart; + } + } + if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 4) { + if ((newStart = matchDayPeriodString( + text, start, formatData.wideDayPeriods, + formatData.wideDayPeriods.length, dayPeriod)) > 0) { + return newStart; + } + } + if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_MULTIPLE_PATTERNS_FOR_MATCH) || count == 4) { + if ((newStart = matchDayPeriodString( + text, start, formatData.narrowDayPeriods, + formatData.narrowDayPeriods.length, dayPeriod)) > 0) { + return newStart; + } + } + + return newStart; + } + default: // case 3: // 'd' - DATE // case 5: // 'H' - HOUR_OF_DAY (0..23) @@ -3362,7 +3653,7 @@ public class SimpleDateFormat extends DateFormat { // case 11: // 'F' - DAY_OF_WEEK_IN_MONTH // case 12: // 'w' - WEEK_OF_YEAR // case 13: // 'W' - WEEK_OF_MONTH - // case 16: // 'K' - HOUR (0..11) + // case 16: // 'K' - HOUR (0..11) // case 20: // 'u' - EXTENDED_YEAR // case 21: // 'g' - JULIAN_DAY // case 22: // 'A' - MILLISECONDS_IN_DAY @@ -3402,7 +3693,7 @@ public class SimpleDateFormat extends DateFormat { } return false; } - + /** * Parse an integer using numberFormat. This method is semantically * const, but actually may modify fNumberFormat. @@ -3520,6 +3811,8 @@ public class SimpleDateFormat extends DateFormat { public void applyPattern(String pat) { this.pattern = pat; + parsePattern(); + setLocale(null, null); // reset parsed pattern items patternItems = null; @@ -3568,7 +3861,7 @@ public class SimpleDateFormat extends DateFormat { /** * {@icu} Gets the time zone formatter which this date/time * formatter uses to format and parse a time zone. - * + * * @return the time zone formatter which this date/time * formatter uses. * @stable ICU 49 @@ -3579,7 +3872,7 @@ public class SimpleDateFormat extends DateFormat { /** * {@icu} Allows you to set the time zone formatter. - * + * * @param tzfmt the new time zone formatter * @stable ICU 49 */ @@ -3685,11 +3978,13 @@ public class SimpleDateFormat extends DateFormat { } } } - + // if serialized pre-56 update & turned off partial match switch to new enum value if(getBooleanAttribute(DateFormat.BooleanAttribute.PARSE_PARTIAL_MATCH) == false) { setBooleanAttribute(DateFormat.BooleanAttribute.PARSE_PARTIAL_LITERAL_MATCH, false); } + + parsePattern(); } /** @@ -4051,19 +4346,19 @@ public class SimpleDateFormat extends DateFormat { * allow the user to set the NumberFormat for several fields * It can be a single field like: "y"(year) or "M"(month) * It can be several field combined together: "yMd"(year, month and date) - * Note: + * Note: * 1 symbol field is enough for multiple symbol fields (so "y" will override "yy", "yyy") * If the field is not numeric, then override has no effect (like "MMM" will use abbreviation, not numerical field) - * + * * @param fields the fields to override - * @param overrideNF the NumbeferFormat used + * @param overrideNF the NumbeferFormat used * @exception IllegalArgumentException when the fields contain invalid field * @stable ICU 54 */ public void setNumberFormat(String fields, NumberFormat overrideNF) { overrideNF.setGroupingUsed(false); String nsName = "$" + UUID.randomUUID().toString(); - + // initialize mapping if not there if (numberFormatters == null) { numberFormatters = new HashMap(); @@ -4086,7 +4381,7 @@ public class SimpleDateFormat extends DateFormat { // we can't rely on the fast numfmt where we have a partial field override. useLocalZeroPaddingNumberFormat = false; } - + /** * give the NumberFormat used for the field like 'y'(year) and 'M'(year) * @@ -4150,7 +4445,7 @@ public class SimpleDateFormat extends DateFormat { ULocale ovrLoc = new ULocale(loc.getBaseName()+"@numbers="+nsName); NumberFormat nf = NumberFormat.createInstance(ovrLoc,NumberFormat.NUMBERSTYLE); nf.setGroupingUsed(false); - + if (fullOverride) { setNumberFormat(nf); } else { @@ -4166,4 +4461,25 @@ public class SimpleDateFormat extends DateFormat { start = delimiterPosition + 1; } } + + private void parsePattern() { + hasMinute = false; + hasSecond = false; + + boolean inQuote = false; + for (int i = 0; i < pattern.length(); ++i) { + char ch = pattern.charAt(i); + if (ch == '\'') { + inQuote = !inQuote; + } + if (!inQuote) { + if (ch == 'm') { + hasMinute = true; + } + if (ch == 's') { + hasSecond = true; + } + } + } + } } diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/DateFormatTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/DateFormatTest.java index 4d156349105..ac26e44c0b4 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/DateFormatTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/DateFormatTest.java @@ -5,7 +5,7 @@ ******************************************************************************* */ -/** +/** * Port From: ICU4C v1.8.1 : format : DateFormatTest * Source File: $ICU4CRoot/source/test/intltest/dtfmttst.cpp **/ @@ -59,58 +59,60 @@ import com.ibm.icu.util.UResourceBundle; import com.ibm.icu.util.VersionInfo; public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { - + public static void main(String[] args) throws Exception { new DateFormatTest().run(args); } + + /** - * Verify that patterns have the correct values and could produce the + * Verify that patterns have the correct values and could produce the * the DateFormat instances that contain the correct localized patterns. */ public void TestPatterns() { final String[][] EXPECTED = { {DateFormat.YEAR, "y","en","y"}, - + {DateFormat.QUARTER, "QQQQ", "en", "QQQQ"}, {DateFormat.ABBR_QUARTER, "QQQ", "en", "QQQ"}, - {DateFormat.YEAR_QUARTER, "yQQQQ", "en", "QQQQ y"}, + {DateFormat.YEAR_QUARTER, "yQQQQ", "en", "QQQQ y"}, {DateFormat.YEAR_ABBR_QUARTER, "yQQQ", "en", "QQQ y"}, - + {DateFormat.MONTH, "MMMM", "en", "LLLL"}, {DateFormat.ABBR_MONTH, "MMM", "en", "LLL"}, {DateFormat.NUM_MONTH, "M", "en", "L"}, {DateFormat.YEAR_MONTH, "yMMMM","en","MMMM y"}, {DateFormat.YEAR_ABBR_MONTH, "yMMM","en","MMM y"}, - {DateFormat.YEAR_NUM_MONTH, "yM","en","M/y"}, - + {DateFormat.YEAR_NUM_MONTH, "yM","en","M/y"}, + {DateFormat.DAY, "d","en","d"}, {DateFormat.YEAR_MONTH_DAY, "yMMMMd", "en", "MMMM d, y"}, {DateFormat.YEAR_ABBR_MONTH_DAY, "yMMMd", "en", "MMM d, y"}, - {DateFormat.YEAR_NUM_MONTH_DAY, "yMd", "en", "M/d/y"}, - + {DateFormat.YEAR_NUM_MONTH_DAY, "yMd", "en", "M/d/y"}, + {DateFormat.WEEKDAY, "EEEE", "en", "cccc"}, - {DateFormat.ABBR_WEEKDAY, "E", "en", "ccc"}, + {DateFormat.ABBR_WEEKDAY, "E", "en", "ccc"}, {DateFormat.YEAR_MONTH_WEEKDAY_DAY, "yMMMMEEEEd", "en", "EEEE, MMMM d, y"}, {DateFormat.YEAR_ABBR_MONTH_WEEKDAY_DAY, "yMMMEd", "en", "EEE, MMM d, y"}, - {DateFormat.YEAR_NUM_MONTH_WEEKDAY_DAY, "yMEd", "en", "EEE, M/d/y"}, - + {DateFormat.YEAR_NUM_MONTH_WEEKDAY_DAY, "yMEd", "en", "EEE, M/d/y"}, + {DateFormat.MONTH_DAY, "MMMMd","en","MMMM d"}, {DateFormat.ABBR_MONTH_DAY, "MMMd","en","MMM d"}, {DateFormat.NUM_MONTH_DAY, "Md","en","M/d"}, - + {DateFormat.MONTH_WEEKDAY_DAY, "MMMMEEEEd","en","EEEE, MMMM d"}, {DateFormat.ABBR_MONTH_WEEKDAY_DAY, "MMMEd","en","EEE, MMM d"}, {DateFormat.NUM_MONTH_WEEKDAY_DAY, "MEd","en","EEE, M/d"}, {DateFormat.HOUR, "j", "en", "h a"}, // (fixed expected result per ticket 6872<-6626) {DateFormat.HOUR24, "H", "en", "HH"}, // (fixed expected result per ticket 6872<-6626 - + {DateFormat.MINUTE, "m", "en", "m"}, {DateFormat.HOUR_MINUTE, "jm","en","h:mm a"}, // (fixed expected result per ticket 6872<-7180) {DateFormat.HOUR24_MINUTE, "Hm", "en", "HH:mm"}, // (fixed expected result per ticket 6872<-6626) - + {DateFormat.SECOND, "s", "en", "s"}, {DateFormat.HOUR_MINUTE_SECOND, "jms","en","h:mm:ss a"}, // (fixed expected result per ticket 6872<-7180) {DateFormat.HOUR24_MINUTE_SECOND, "Hms","en","HH:mm:ss"}, // (fixed expected result per ticket 6872<-6626) @@ -124,19 +126,19 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { {DateFormat.ABBR_UTC_TZ, "ZZZZ", "en", "ZZZZ"}, {}, // marker for starting combinations - + {DateFormat.YEAR_NUM_MONTH_DAY + DateFormat.ABBR_UTC_TZ, "yMdZZZZ", "en", "M/d/y, ZZZZ"}, {DateFormat.MONTH_DAY + DateFormat.LOCATION_TZ, "MMMMdVVVV", "en", "MMMM d, VVVV"}, }; Date testDate = new Date(2012-1900, 6, 1, 14, 58, 59); // just for verbose log - + List expectedSkeletons = new ArrayList(DateFormat.DATE_SKELETONS); expectedSkeletons.addAll(DateFormat.TIME_SKELETONS); expectedSkeletons.addAll(DateFormat.ZONE_SKELETONS); boolean combinations = false; - + List testedSkeletons = new ArrayList(); - + for (int i = 0; i < EXPECTED.length; i++) { if (EXPECTED[i].length == 0) { combinations = true; @@ -151,28 +153,28 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { String expectedPattern = EXPECTED[i][1]; ULocale locale = new ULocale(EXPECTED[i][2], "", ""); if (!actualPattern.equals(expectedPattern)) { - errln("FAILURE! Expected pattern: " + expectedPattern + + errln("FAILURE! Expected pattern: " + expectedPattern + " but was: " + actualPattern); ok=false; } - - // Verify that DataFormat instances produced contain the correct + + // Verify that DataFormat instances produced contain the correct // localized patterns - DateFormat date1 = DateFormat.getPatternInstance(actualPattern, + DateFormat date1 = DateFormat.getPatternInstance(actualPattern, locale); DateFormat date2 = DateFormat.getPatternInstance(Calendar.getInstance(locale), actualPattern, locale); - + String expectedLocalPattern = EXPECTED[i][3]; String actualLocalPattern1 = ((SimpleDateFormat)date1).toLocalizedPattern(); String actualLocalPattern2 = ((SimpleDateFormat)date2).toLocalizedPattern(); if (!actualLocalPattern1.equals(expectedLocalPattern)) { - errln("FAILURE! Expected local pattern: " + expectedLocalPattern + errln("FAILURE! Expected local pattern: " + expectedLocalPattern + " but was: " + actualLocalPattern1); ok=false; - } + } if (!actualLocalPattern2.equals(expectedLocalPattern)) { - errln("FAILURE! Expected local pattern: " + expectedLocalPattern + errln("FAILURE! Expected local pattern: " + expectedLocalPattern + " but was: " + actualLocalPattern2); ok=false; } @@ -180,7 +182,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { logln(date1.format(testDate) + "\t\t" + Arrays.asList(EXPECTED[i])); } } - assertEquals("All skeletons are tested (and in an iterable list)", + assertEquals("All skeletons are tested (and in an iterable list)", new HashSet(expectedSkeletons), new HashSet(testedSkeletons)); assertEquals("All skeletons are tested (and in an iterable list), and in the right order.", expectedSkeletons, testedSkeletons); } @@ -203,10 +205,10 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { /* * A String array for the time zone ids. */ - + final String[] ids = TimeZone.getAvailableIDs(); int ids_length = ids.length; //when fixed the bug should comment it out - + /* * How many ids do we have? */ @@ -237,7 +239,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { minutes = (offset % 3600000) / 60000; seconds = (offset % 60000) / 1000; String dstOffset = sign + (hours < 10 ? "0" : "") + hours - + ":" + (minutes < 10 ? "0" : "") + minutes; + + ":" + (minutes < 10 ? "0" : "") + minutes; if (seconds != 0) { dstOffset += ":" + (seconds < 10 ? "0" : "") + seconds; } @@ -250,10 +252,10 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { */ StringBuffer fmtOffset = new StringBuffer(""); FieldPosition pos = new FieldPosition(0); - + try { fmtOffset = sdf.format(today, fmtOffset, pos); - } catch (Exception e) { + } catch (Exception e) { logln("Exception:" + e); continue; } @@ -266,42 +268,42 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { /* * Show our result. */ - + boolean ok = fmtDstOffset == null || fmtDstOffset.equals("") || fmtDstOffset.equals(dstOffset); if (ok) { logln(i + " " + ids[i] + " " + dstOffset + " " - + fmtOffset + (fmtDstOffset != null ? " ok" : " ?")); + + fmtOffset + (fmtDstOffset != null ? " ok" : " ?")); } else { errln(i + " " + ids[i] + " " + dstOffset + " " + fmtOffset + " *** FAIL ***"); } - + } } - + public void TestEquals() { - DateFormat fmtA = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.FULL); - DateFormat fmtB = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.FULL); + DateFormat fmtA = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.FULL); + DateFormat fmtB = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.FULL); if (!fmtA.equals(fmtB)) - errln("FAIL"); + errln("FAIL"); } - + /** * Test the parsing of 2-digit years. */ public void TestTwoDigitYearDSTParse() { - - SimpleDateFormat fullFmt = new SimpleDateFormat("EEE MMM dd HH:mm:ss.SSS zzz yyyy G"); - SimpleDateFormat fmt = new SimpleDateFormat("dd-MMM-yy h:mm:ss 'o''clock' a z", Locale.ENGLISH); + + SimpleDateFormat fullFmt = new SimpleDateFormat("EEE MMM dd HH:mm:ss.SSS zzz yyyy G"); + SimpleDateFormat fmt = new SimpleDateFormat("dd-MMM-yy h:mm:ss 'o''clock' a z", Locale.ENGLISH); String s = "03-Apr-04 2:20:47 o'clock AM PST"; - + /* * SimpleDateFormat(pattern, locale) Construct a SimpleDateDateFormat using * the given pattern, the locale and using the TimeZone.getDefault(); - * So it need to add the timezone offset on hour field. - * ps. the Method Calendar.getTime() used by SimpleDateFormat.parse() always + * So it need to add the timezone offset on hour field. + * ps. the Method Calendar.getTime() used by SimpleDateFormat.parse() always * return Date value with TimeZone.getDefault() [Richard/GCL] */ - + TimeZone defaultTZ = TimeZone.getDefault(); TimeZone PST = TimeZone.getTimeZone("PST"); int defaultOffset = defaultTZ.getRawOffset(); @@ -315,7 +317,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { cal.setTime(d); //DSTOffset hour += defaultTZ.inDaylightTime(d) ? 1 : 0; - + logln(s + " P> " + ((DateFormat) fullFmt).format(d)); // hr is the actual hour of day, in units of seconds // adjust for DST @@ -327,9 +329,9 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { } catch (ParseException e) { errln("Parse Error:" + e.getMessage()); } - + } - + /** * Verify that returned field position indices are correct. */ @@ -342,7 +344,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { DateFormatSymbols rootSyms = new DateFormatSymbols(new Locale("", "", "")); assertEquals("patternChars", PATTERN_CHARS, rootSyms.getLocalPatternChars()); } - + assertTrue("DATEFORMAT_FIELD_NAMES", DATEFORMAT_FIELD_NAMES.length == DateFormat.FIELD_COUNT); if(DateFormat.FIELD_COUNT != PATTERN_CHARS.length() + 1){ // +1 for missing TIME_SEPARATOR pattern char errln("Did not get the correct value for DateFormat.FIELD_COUNT. Expected: "+ PATTERN_CHARS.length() + 1); @@ -373,22 +375,22 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { "", "1997", "August", "13", "", "", "34", "12", "", "Wednesday", "", "", "", "", "PM", "2", "", "Pacific Daylight Time", "", "", "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "", "1997", "ao\u00FBt", "13", "", "14", "34", "12", "", "mercredi", "", "", "", "", "", "", "", "heure d\u2019\u00E9t\u00E9 du Pacifique", "", "", "", "", "", "", "", "", "", "", "", "", - "", "", "", "", "", "", + "", "", "", "", "", "", "", "", "AD", "1997", "8", "13", "14", "14", "34", "12", "5", "Wed", "225", "2", "33", "3", "PM", "2", "2", "PDT", "1997", "4", "1997", "2450674", "52452513", "-0700", "PT", "4", "8", "3", "3", "uslax", - "1997", "GMT-7", "-07", "-07", "1997", "", + "1997", "GMT-7", "-07", "-07", "1997", "PM", "in the afternoon", "", "Anno Domini", "1997", "August", "0013", "0014", "0014", "0034", "0012", "5130", "Wednesday", "0225", "0002", "0033", "0003", "PM", "0002", "0002", "Pacific Daylight Time", "1997", "Wednesday", "1997", "2450674", "52452513", "GMT-07:00", "Pacific Time", "Wednesday", "August", "3rd quarter", "3rd quarter", "Los Angeles Time", - "1997", "GMT-07:00", "-0700", "-0700", "1997", "", + "1997", "GMT-07:00", "-0700", "-0700", "1997", "PM", "in the afternoon", "", }; assertTrue("data size", EXPECTED.length == COUNT * DateFormat.FIELD_COUNT); @@ -470,7 +472,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { for (i = 0; i < DateFormat.FIELD_COUNT; ++i, ++exp) { pos = new FieldPosition(i); buf.setLength(0); - df.format(aug13, buf, pos); + df.format(aug13, buf, pos); field = buf.substring(pos.getBeginIndex(), pos.getEndIndex()); assertEquals("pattern#" + j + " field #" + i + " " + DATEFORMAT_FIELD_NAMES[i], EXPECTED[exp], field); @@ -491,7 +493,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { /** * This MUST be kept in sync with DateFormatSymbols.patternChars. */ - static final String PATTERN_CHARS = "GyMdkHmsSEDFwWahKzYeugAZvcLQqVUOXxr"; + static final String PATTERN_CHARS = "GyMdkHmsSEDFwWahKzYeugAZvcLQqVUOXxrbB"; /** * A list of the DateFormat.Field. @@ -533,6 +535,8 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { DateFormat.Field.TIME_ZONE, // X DateFormat.Field.TIME_ZONE, // x DateFormat.Field.RELATED_YEAR, // r + DateFormat.Field.AM_PM_MIDNIGHT_NOON, // b + DateFormat.Field.FLEXIBLE_DAY_PERIOD, // B DateFormat.Field.TIME_SEPARATOR,// (no pattern character currently specified for this) }; @@ -576,6 +580,8 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { "TIMEZONE_ISO_FIELD", "TIMEZONE_ISO_LOCAL_FIELD", "RELATED_YEAR", + "AM_PM_MIDNIGHT_NOON_FIELD", + "FLEXIBLE_DAY_PERIOD_FIELD", "TIME_SEPARATOR", }; @@ -583,7 +589,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { * General parse/format tests. Add test cases as needed. */ public void TestGeneral() { - + String DATA[] = { "yyyy MM dd HH:mm:ss.SSS", @@ -648,7 +654,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { logln("cross format/parse tests"); final String basepat = "yy/MM/dd H:mm "; - final SimpleDateFormat[] formats = { + final SimpleDateFormat[] formats = { new SimpleDateFormat(basepat + "v", en), new SimpleDateFormat(basepat + "vvvv", en), new SimpleDateFormat(basepat + "zzz", en), @@ -678,8 +684,8 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { try { Date t = formats[k].parse(test); if (!d.equals(t)) { - errln("format " + k + - " incorrectly parsed output of format " + j + + errln("format " + k + + " incorrectly parsed output of format " + j + " (" + test + "), returned " + t + " instead of " + d); } else { @@ -687,8 +693,8 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { } } catch (ParseException e) { - errln("format " + k + - " could not parse output of format " + j + + errln("format " + k + + " could not parse output of format " + j + " (" + test + ")"); } } @@ -755,7 +761,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { cal.setTimeZone(tz); String result = fmt.format(cal); if (!result.equals(info[4])) { - errln(info[0] + ";" + info[1] + ";" + info[2] + ";" + info[3] + " expected: '" + + errln(info[0] + ";" + info[1] + ";" + info[2] + ";" + info[3] + " expected: '" + info[4] + "' but got: '" + result + "'"); } } @@ -1543,7 +1549,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { * returning an appropriate error. */ public void TestPartialParse994() { - + SimpleDateFormat f = new SimpleDateFormat(); Calendar cal = Calendar.getInstance(); cal.clear(); @@ -1555,14 +1561,14 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { tryPat994(f, "yy/MM/dd HH:mm:ss", "97/01/17 ", date); tryPat994(f, "yy/MM/dd HH:mm:ss", "97/01/17", date); } - + // internal test subroutine, used by TestPartialParse994 public void tryPat994(SimpleDateFormat format, String pat, String str, Date expected) { Date Null = null; logln("Pattern \"" + pat + "\" String \"" + str + "\""); try { format.applyPattern(pat); - Date date = format.parse(str); + Date date = format.parse(str); String f = ((DateFormat) format).format(date); logln(" parse(" + str + ") -> " + date); logln(" format -> " + f); @@ -1579,7 +1585,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { e.printStackTrace(); } } - + /** * Verify the behavior of patterns in which digits for different fields run together * without intervening separators. @@ -1621,9 +1627,9 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { cal.clear(); cal.set(1997, 3 - 1, 4); _testIt917(fmt, myDate, cal.getTime()); - + } - + // internal test subroutine, used by TestRunTogetherPattern917 public void _testIt917(SimpleDateFormat fmt, String str, Date expected) { logln("pattern=" + fmt.toPattern() + " string=" + str); @@ -1637,13 +1643,13 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { if (!formatted.equals(str)) errln( "FAIL: Expected " + str); } - + /** * Verify the handling of Czech June and July, which have the unique attribute that * one is a proper prefix substring of the other. */ public void TestCzechMonths459() { - DateFormat fmt = DateFormat.getDateInstance(DateFormat.FULL, new Locale("cs", "", "")); + DateFormat fmt = DateFormat.getDateInstance(DateFormat.FULL, new Locale("cs", "", "")); logln("Pattern " + ((SimpleDateFormat) fmt).toPattern()); Calendar cal = Calendar.getInstance(); cal.clear(); @@ -1684,7 +1690,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { errln(e.getMessage()); } } - + /** * Test the handling of 'D' in patterns. */ @@ -1712,14 +1718,14 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { if (!myDate.equals(expLittleD)) errln("FAIL: Expected " + expLittleD); } - + /** * Test the day of year pattern. */ public void TestDayOfYearPattern195() { Calendar cal = Calendar.getInstance(); Date today = cal.getTime(); - int year,month,day; + int year,month,day; year = cal.get(Calendar.YEAR); month = cal.get(Calendar.MONTH); day = cal.get(Calendar.DAY_OF_MONTH); @@ -1731,7 +1737,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { tryPattern(sdf, today, null, expected); tryPattern(sdf, today, "G yyyy DDD", expected); } - + // interl test subroutine, used by TestDayOfYearPattern195 public void tryPattern(SimpleDateFormat sdf, Date d, String pattern, Date expected) { if (pattern != null) @@ -1752,12 +1758,12 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { errln(e.getMessage()); } } - + /** * Test the handling of single quotes in patterns. */ public void TestQuotePattern161() { - SimpleDateFormat formatter = new SimpleDateFormat("MM/dd/yyyy 'at' hh:mm:ss a zzz", Locale.US); + SimpleDateFormat formatter = new SimpleDateFormat("MM/dd/yyyy 'at' hh:mm:ss a zzz", Locale.US); Calendar cal = Calendar.getInstance(); cal.clear(); cal.set(1997, Calendar.AUGUST, 13, 10, 42, 28); @@ -1767,18 +1773,18 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { logln("format(" + currentTime_1 + ") = " + dateString); if (!dateString.substring(0, exp.length()).equals(exp)) errln("FAIL: Expected " + exp); - + } - + /** * Verify the correct behavior when handling invalid input strings. */ public void TestBadInput135() { - int looks[] = {DateFormat.SHORT, DateFormat.MEDIUM, DateFormat.LONG, DateFormat.FULL}; + int looks[] = {DateFormat.SHORT, DateFormat.MEDIUM, DateFormat.LONG, DateFormat.FULL}; int looks_length = looks.length; - final String[] strings = {"Mar 15", "Mar 15 1997", "asdf", "3/1/97 1:23:", "3/1/00 1:23:45 AM"}; + final String[] strings = {"Mar 15", "Mar 15 1997", "asdf", "3/1/97 1:23:", "3/1/00 1:23:45 AM"}; int strings_length = strings.length; - DateFormat full = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, Locale.US); + DateFormat full = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, Locale.US); String expected = "March 1, 2000 at 1:23:45 AM "; for (int i = 0; i < strings_length; ++i) { final String text = strings[i]; @@ -1786,14 +1792,14 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { int dateLook = looks[j]; for (int k = 0; k < looks_length; ++k) { int timeLook = looks[k]; - DateFormat df = DateFormat.getDateTimeInstance(dateLook, timeLook, Locale.US); - String prefix = text + ", " + dateLook + "/" + timeLook + ": "; + DateFormat df = DateFormat.getDateTimeInstance(dateLook, timeLook, Locale.US); + String prefix = text + ", " + dateLook + "/" + timeLook + ": "; try { Date when = df.parse(text); if (when == null) { errln(prefix + "SHOULD NOT HAPPEN: parse returned null."); continue; - } + } if (when != null) { String format; format = full.format(when); @@ -1810,14 +1816,14 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { } } } - + /** * Verify the correct behavior when parsing an array of inputs against an * array of patterns, with known results. The results are encoded after * the input strings in each row. */ public void TestBadInput135a() { - + SimpleDateFormat dateParse = new SimpleDateFormat("", Locale.US); final String ss; Date date; @@ -1839,7 +1845,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { }; final int PF_LENGTH = parseFormats.length; final int INPUT_LENGTH = inputStrings.length; - + dateParse.applyPattern("d MMMM, yyyy"); dateParse.setTimeZone(TimeZone.getDefault()); ss = "not parseable"; @@ -1887,9 +1893,9 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { } } } - + } - + /** * Test the parsing of two-digit years. */ @@ -1903,20 +1909,20 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { cal.set(50 + 1900, Calendar.JUNE, 4); parse2DigitYear(fmt, "6/4/50", cal.getTime()); } - + // internal test subroutine, used by TestTwoDigitYear public void parse2DigitYear(DateFormat fmt, String str, Date expected) { try { Date d = fmt.parse(str); logln("Parsing \""+ str+ "\" with "+ ((SimpleDateFormat) fmt).toPattern() - + " => "+ d); + + " => "+ d); if (!d.equals(expected)) errln( "FAIL: Expected " + expected); } catch (ParseException e) { errln(e.getMessage()); } } - + /** * Test the formatting of time zones. */ @@ -1937,20 +1943,20 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { } catch (Throwable t) { System.out.println(t); } - + } - + /** * Test the formatting of time zones. */ public void TestDateFormatZone146() { TimeZone saveDefault = TimeZone.getDefault(); - + //try { TimeZone thedefault = TimeZone.getTimeZone("GMT"); TimeZone.setDefault(thedefault); // java.util.Locale.setDefault(new java.util.Locale("ar", "", "")); - + // check to be sure... its GMT all right TimeZone testdefault = TimeZone.getDefault(); String testtimezone = testdefault.getID(); @@ -1958,7 +1964,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { logln("Test timezone = " + testtimezone); else errln("Test timezone should be GMT, not " + testtimezone); - + // now try to use the default GMT time zone GregorianCalendar greenwichcalendar = new GregorianCalendar(1997, 3, 4, 23, 0); //*****************************greenwichcalendar.setTimeZone(TimeZone.getDefault()); @@ -1969,17 +1975,17 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { Date greenwichdate = greenwichcalendar.getTime(); // format every way String DATA[] = { - "simple format: ", "04/04/97 23:00 GMT", - "MM/dd/yy HH:mm zzz", "full format: ", - "Friday, April 4, 1997 11:00:00 o'clock PM GMT", - "EEEE, MMMM d, yyyy h:mm:ss 'o''clock' a zzz", - "long format: ", "April 4, 1997 11:00:00 PM GMT", - "MMMM d, yyyy h:mm:ss a z", "default format: ", - "04-Apr-97 11:00:00 PM", "dd-MMM-yy h:mm:ss a", - "short format: ", "4/4/97 11:00 PM", - "M/d/yy h:mm a"}; + "simple format: ", "04/04/97 23:00 GMT", + "MM/dd/yy HH:mm zzz", "full format: ", + "Friday, April 4, 1997 11:00:00 o'clock PM GMT", + "EEEE, MMMM d, yyyy h:mm:ss 'o''clock' a zzz", + "long format: ", "April 4, 1997 11:00:00 PM GMT", + "MMMM d, yyyy h:mm:ss a z", "default format: ", + "04-Apr-97 11:00:00 PM", "dd-MMM-yy h:mm:ss a", + "short format: ", "4/4/97 11:00 PM", + "M/d/yy h:mm a"}; int DATA_length = DATA.length; - + for (int i = 0; i < DATA_length; i += 3) { DateFormat fmt = new SimpleDateFormat(DATA[i + 2], Locale.ENGLISH); fmt.setCalendar(greenwichcalendar); @@ -1992,9 +1998,9 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { //finally { TimeZone.setDefault(saveDefault); //} - + } - + /** * Test the formatting of dates in different locales. */ @@ -2133,7 +2139,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { pos.getIndex()); return; } - + /* Check result */ when = cal.getTime(); str = full.format(when); @@ -2250,9 +2256,9 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { catch(Exception e) { warnln("Expected IllegalArgumentException, got: " + e); } - + try { - DateFormat df = new SimpleDateFormat("aabbccc"); + DateFormat df = new SimpleDateFormat("aaNNccc"); df.format(new Date()); errln("Expected exception for format with bad pattern"); } @@ -2262,7 +2268,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { catch(Exception e) { warnln("Expected IllegalArgumentException, got: " + e); } - + { SimpleDateFormat fmt = new SimpleDateFormat("dd/MM/yy"); // opposite of text fmt.set2DigitYearStart(getDate(2003, Calendar.DECEMBER, 25)); @@ -2285,7 +2291,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { // chinese date format patterns Calendar chineseCalendar = new ChineseCalendar(); chineseCalendar.setTimeInMillis((new Date()).getTime()); - SimpleDateFormat longChineseDateFormat = + SimpleDateFormat longChineseDateFormat = (SimpleDateFormat)chineseCalendar.getDateTimeFormat(DateFormat.LONG, DateFormat.LONG, Locale.CHINA ); DateFormatSymbols dfs = new ChineseDateFormatSymbols( chineseCalendar, Locale.CHINA ); longChineseDateFormat.setDateFormatSymbols( dfs ); @@ -2305,7 +2311,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { logln("time: " + f.format(now)); int hash = f.hashCode(); // sigh, everyone overrides this - + f = DateFormat.getInstance(cal); if(hash == f.hashCode()){ errln("FAIL: hashCode equal for inequal objects"); @@ -2327,12 +2333,12 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { if (!sym.equals(sym2)) { errln("fail, date format symbols not equal"); } - + Locale foo = new Locale("fu", "FU", "BAR"); rb = null; sym = new DateFormatSymbols(GregorianCalendar.class, foo); sym.equals(null); - + sym = new ChineseDateFormatSymbols(); sym = new ChineseDateFormatSymbols(new Locale("en_US")); try{ @@ -2350,10 +2356,10 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { // cover new ChineseDateFormatSymbols(Calendar, ULocale) ChineseCalendar ccal = new ChineseCalendar(); sym = new ChineseDateFormatSymbols(ccal, ULocale.CHINA); //gclsh1 add - + StringBuffer buf = new StringBuffer(); FieldPosition pos = new FieldPosition(0); - + f.format((Object)cal, buf, pos); f.format((Object)now, buf, pos); f.format((Object)new Long(now.getTime()), buf, pos); @@ -2365,14 +2371,14 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { NumberFormat nf = f.getNumberFormat(); f.setNumberFormat(nf); - + boolean lenient = f.isLenient(); f.setLenient(lenient); - + ULocale uloc = f.getLocale(ULocale.ACTUAL_LOCALE); - + DateFormat sdfmt = new SimpleDateFormat(); - + if (f.hashCode() != f.hashCode()) { errln("hashCode is not stable"); } @@ -2385,7 +2391,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { if (f.equals(sdfmt)) { errln("A time instance shouldn't equal a default date format"); } - + Date d; { ChineseDateFormat fmt = new ChineseDateFormat("yymm", Locale.US); @@ -2423,7 +2429,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { try { fmt.parse(xbuf.toString()); logln("ok"); - + xbuf.setLength(0); xcal.set(Calendar.HOUR_OF_DAY, 25); fmt.format(xcal, xbuf, fpos); @@ -2434,7 +2440,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { errln("whoops"); } } - + { // cover gmt+hh:mm DateFormat fmt = new SimpleDateFormat("MM/dd/yy z"); @@ -2445,7 +2451,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { catch (ParseException e) { errln("Parse of 07/10/53 GMT+10:00 for pattern MM/dd/yy z"); } - + // cover invalid separator after GMT { ParsePosition pp = new ParsePosition(0); @@ -2456,7 +2462,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { } logln("Parsing of the text stopped at pos: " + pp.getIndex() + " as expected and length is "+text.length()); } - + // cover bad text after GMT+. try { fmt.parse("07/10/53 GMT+blecch"); @@ -2465,7 +2471,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { catch (ParseException e) { errln("whoops GMT+blecch"); } - + // cover bad text after GMT+hh:. try { fmt.parse("07/10/53 GMT+07:blecch"); @@ -2474,7 +2480,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { catch (ParseException e) { errln("whoops GMT+xx:blecch"); } - + // cover no ':' GMT+#, # < 24 (hh) try { d = fmt.parse("07/10/53 GMT+07"); @@ -2483,7 +2489,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { catch (ParseException e) { errln("Parse of 07/10/53 GMT+07 for pattern MM/dd/yy z"); } - + // cover no ':' GMT+#, # > 24 (hhmm) try { d = fmt.parse("07/10/53 GMT+0730"); @@ -2492,7 +2498,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { catch (ParseException e) { errln("Parse of 07/10/53 GMT+0730 for pattern MM/dd/yy z"); } - + // cover GMT+#, # with second field try { d = fmt.parse("07/10/53 GMT+07:30:15"); @@ -2519,8 +2525,8 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { catch (ParseException e) { errln("Parse of 07/10/53 GMT+07300 for pattern MM/dd/yy z"); } - - // cover raw digits with no leading sign (bad RFC822) + + // cover raw digits with no leading sign (bad RFC822) try { d = fmt.parse("07/10/53 07"); errln("Parse of 07/10/53 07 for pattern MM/dd/yy z passed!"); @@ -2528,8 +2534,8 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { catch (ParseException e) { logln("ok"); } - - // cover raw digits (RFC822) + + // cover raw digits (RFC822) try { d = fmt.parse("07/10/53 +07"); logln("ok"); @@ -2537,8 +2543,8 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { catch (ParseException e) { errln("Parse of 07/10/53 +07 for pattern MM/dd/yy z failed"); } - - // cover raw digits (RFC822) + + // cover raw digits (RFC822) try { d = fmt.parse("07/10/53 -0730"); logln("ok"); @@ -2546,7 +2552,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { catch (ParseException e) { errln("Parse of 07/10/53 -00730 for pattern MM/dd/yy z failed"); } - + // cover raw digits (RFC822) in DST try { fmt.setTimeZone(TimeZone.getTimeZone("PDT")); @@ -2557,7 +2563,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { errln("Parse of 07/10/53 -0730 for pattern MM/dd/yy z failed"); } } - + // TODO: revisit toLocalizedPattern if (false) { SimpleDateFormat fmt = new SimpleDateFormat("aabbcc"); @@ -2591,9 +2597,9 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { logln("time zone ex ok"); } } - + { - // force fallback to default timezone when fmt timezone + // force fallback to default timezone when fmt timezone // is not named SimpleDateFormat fmt = new SimpleDateFormat("MM/dd/yy z"); // force fallback to default time zone, still fails @@ -2606,7 +2612,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { catch (ParseException e) { logln("time zone ex2 ok"); } - + // force success on fallback text = "08/15/58 " + TimeZone.getDefault().getDisplayName(true, TimeZone.SHORT); try { @@ -2617,9 +2623,9 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { errln("whoops, got parse exception"); } } - + { - // force fallback to symbols list of timezones when neither + // force fallback to symbols list of timezones when neither // fmt and default timezone is named SimpleDateFormat fmt = new SimpleDateFormat("MM/dd/yy z"); TimeZone oldtz = TimeZone.getDefault(); @@ -2651,18 +2657,18 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { if (count==0) { errln(" got a empty list for getAvailableULocales"); }else{ - logln("" + count + " available ulocales"); + logln("" + count + " available ulocales"); } } - + { //cover DateFormatSymbols.getDateFormatBundle cal = new GregorianCalendar(); Locale loc = Locale.getDefault(); DateFormatSymbols mysym = new DateFormatSymbols(cal, loc); - if (mysym == null) + if (mysym == null) errln("FAIL: constructs DateFormatSymbols with calendar and locale failed"); - + uloc = ULocale.getDefault(); // These APIs are obsolete and return null ResourceBundle resb = DateFormatSymbols.getDateFormatBundle(cal, loc); @@ -2706,7 +2712,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { errln("FAIL: Got a empty list for DateFormatSymbols.getAvailableLocales"); } else { logln("PASS: " + allLocales.length + - " available locales returned by DateFormatSymbols.getAvailableLocales"); + " available locales returned by DateFormatSymbols.getAvailableLocales"); } ULocale[] allULocales = DateFormatSymbols.getAvailableULocales(); @@ -2714,7 +2720,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { errln("FAIL: Got a empty list for DateFormatSymbols.getAvailableLocales"); } else { logln("PASS: " + allULocales.length + - " available locales returned by DateFormatSymbols.getAvailableULocales"); + " available locales returned by DateFormatSymbols.getAvailableULocales"); } } } @@ -2728,7 +2734,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { "yyyy LLL dd H:mm:ss", "fp", "2004 03 10 16:36:31", "2004 Mar 10 16:36:31", "2004 03 10 16:36:31", "yyyy LLLL dd H:mm:ss", "F", "2004 03 10 16:36:31", "2004 March 10 16:36:31", "yyyy LLL dd H:mm:ss", "pf", "2004 Mar 10 16:36:31", "2004 03 10 16:36:31", "2004 Mar 10 16:36:31", - + "LLLL", "fp", "1970 01 01 0:00:00", "January", "1970 01 01 0:00:00", "LLLL", "fp", "1970 02 01 0:00:00", "February", "1970 02 01 0:00:00", "LLLL", "fp", "1970 03 01 0:00:00", "March", "1970 03 01 0:00:00", @@ -2741,7 +2747,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { "LLLL", "fp", "1970 10 01 0:00:00", "October", "1970 10 01 0:00:00", "LLLL", "fp", "1970 11 01 0:00:00", "November", "1970 11 01 0:00:00", "LLLL", "fp", "1970 12 01 0:00:00", "December", "1970 12 01 0:00:00", - + "LLL", "fp", "1970 01 01 0:00:00", "Jan", "1970 01 01 0:00:00", "LLL", "fp", "1970 02 01 0:00:00", "Feb", "1970 02 01 0:00:00", "LLL", "fp", "1970 03 01 0:00:00", "Mar", "1970 03 01 0:00:00", @@ -2755,7 +2761,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { "LLL", "fp", "1970 11 01 0:00:00", "Nov", "1970 11 01 0:00:00", "LLL", "fp", "1970 12 01 0:00:00", "Dec", "1970 12 01 0:00:00", }; - + String CS_DATA[] = { "yyyy MM dd HH:mm:ss", @@ -2766,7 +2772,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { "yyyy MMMM dd H:mm:ss", "F", "2004 04 10 16:36:31", "2004 dubna 10 16:36:31", "yyyy LLLL dd H:mm:ss", "pf", "2004 duben 10 16:36:31", "2004 04 10 16:36:31", "2004 duben 10 16:36:31", "yyyy MMMM dd H:mm:ss", "pf", "2004 dubna 10 16:36:31", "2004 04 10 16:36:31", "2004 dubna 10 16:36:31", - + "LLLL", "fp", "1970 01 01 0:00:00", "leden", "1970 01 01 0:00:00", "LLLL", "fp", "1970 02 01 0:00:00", "\u00FAnor", "1970 02 01 0:00:00", "LLLL", "fp", "1970 03 01 0:00:00", "b\u0159ezen", "1970 03 01 0:00:00", @@ -2793,11 +2799,11 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { "LLL", "fp", "1970 11 01 0:00:00", "lis", "1970 11 01 0:00:00", "LLL", "fp", "1970 12 01 0:00:00", "pro", "1970 12 01 0:00:00", }; - + expect(EN_DATA, new Locale("en", "", "")); expect(CS_DATA, new Locale("cs", "", "")); } - + public void TestStandAloneDays() { String EN_DATA[] = { @@ -2810,7 +2816,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { "cccc", "fp", "1970 01 01 0:00:00", "Thursday", "1970 01 01 0:00:00", "cccc", "fp", "1970 01 02 0:00:00", "Friday", "1970 01 02 0:00:00", "cccc", "fp", "1970 01 03 0:00:00", "Saturday", "1970 01 03 0:00:00", - + "ccc", "fp", "1970 01 04 0:00:00", "Sun", "1970 01 04 0:00:00", "ccc", "fp", "1970 01 05 0:00:00", "Mon", "1970 01 05 0:00:00", "ccc", "fp", "1970 01 06 0:00:00", "Tue", "1970 01 06 0:00:00", @@ -2819,7 +2825,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { "ccc", "fp", "1970 01 02 0:00:00", "Fri", "1970 01 02 0:00:00", "ccc", "fp", "1970 01 03 0:00:00", "Sat", "1970 01 03 0:00:00", }; - + String CS_DATA[] = { "yyyy MM dd HH:mm:ss", @@ -2830,7 +2836,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { "cccc", "fp", "1970 01 01 0:00:00", "\u010Dtvrtek", "1970 01 01 0:00:00", "cccc", "fp", "1970 01 02 0:00:00", "p\u00E1tek", "1970 01 02 0:00:00", "cccc", "fp", "1970 01 03 0:00:00", "sobota", "1970 01 03 0:00:00", - + "ccc", "fp", "1970 01 04 0:00:00", "ne", "1970 01 04 0:00:00", "ccc", "fp", "1970 01 05 0:00:00", "po", "1970 01 05 0:00:00", "ccc", "fp", "1970 01 06 0:00:00", "\u00FAt", "1970 01 06 0:00:00", @@ -2839,11 +2845,11 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { "ccc", "fp", "1970 01 02 0:00:00", "p\u00E1", "1970 01 02 0:00:00", "ccc", "fp", "1970 01 03 0:00:00", "so", "1970 01 03 0:00:00", }; - + expect(EN_DATA, new Locale("en", "", "")); expect(CS_DATA, new Locale("cs", "", "")); } - + public void TestShortDays() { String EN_DATA[] = { @@ -2855,7 +2861,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { "cccccc d", "fp", "1970 01 17 0:00:00", "Sa 17", "1970 01 17 0:00:00", "cccccc", "fp", "1970 01 03 0:00:00", "Sa", "1970 01 03 0:00:00", }; - + String SV_DATA[] = { "yyyy MM dd HH:mm:ss", @@ -2865,11 +2871,11 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { "cccccc d", "fp", "1970 01 17 0:00:00", "l\u00F6 17", "1970 01 17 0:00:00", "cccccc", "fp", "1970 01 03 0:00:00", "l\u00F6", "1970 01 03 0:00:00", }; - + expect(EN_DATA, new Locale("en", "", "")); expect(SV_DATA, new Locale("sv", "", "")); } - + public void TestNarrowNames() { String EN_DATA[] = { @@ -2877,7 +2883,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { "yyyy MMMMM dd H:mm:ss", "2004 03 10 16:36:31", "2004 M 10 16:36:31", "yyyy LLLLL dd H:mm:ss", "2004 03 10 16:36:31", "2004 M 10 16:36:31", - + "MMMMM", "1970 01 01 0:00:00", "J", "MMMMM", "1970 02 01 0:00:00", "F", "MMMMM", "1970 03 01 0:00:00", "M", @@ -2890,7 +2896,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { "MMMMM", "1970 10 01 0:00:00", "O", "MMMMM", "1970 11 01 0:00:00", "N", "MMMMM", "1970 12 01 0:00:00", "D", - + "LLLLL", "1970 01 01 0:00:00", "J", "LLLLL", "1970 02 01 0:00:00", "F", "LLLLL", "1970 03 01 0:00:00", "M", @@ -2911,7 +2917,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { "EEEEE", "1970 01 01 0:00:00", "T", "EEEEE", "1970 01 02 0:00:00", "F", "EEEEE", "1970 01 03 0:00:00", "S", - + "ccccc", "1970 01 04 0:00:00", "S", "ccccc", "1970 01 05 0:00:00", "M", "ccccc", "1970 01 06 0:00:00", "T", @@ -2919,19 +2925,19 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { "ccccc", "1970 01 01 0:00:00", "T", "ccccc", "1970 01 02 0:00:00", "F", "ccccc", "1970 01 03 0:00:00", "S", - + "h:mm a", "2015 01 01 10:00:00", "10:00 AM", "h:mm a", "2015 01 01 22:00:00", "10:00 PM", "h:mm aaaaa", "2015 01 01 10:00:00", "10:00 a", "h:mm aaaaa", "2015 01 01 22:00:00", "10:00 p", }; - + String CS_DATA[] = { "yyyy MM dd HH:mm:ss", "yyyy LLLLL dd H:mm:ss", "2004 04 10 16:36:31", "2004 4 10 16:36:31", "yyyy MMMMM dd H:mm:ss", "2004 04 10 16:36:31", "2004 4 10 16:36:31", - + "MMMMM", "1970 01 01 0:00:00", "1", "MMMMM", "1970 02 01 0:00:00", "2", "MMMMM", "1970 03 01 0:00:00", "3", @@ -2944,7 +2950,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { "MMMMM", "1970 10 01 0:00:00", "10", "MMMMM", "1970 11 01 0:00:00", "11", "MMMMM", "1970 12 01 0:00:00", "12", - + "LLLLL", "1970 01 01 0:00:00", "1", "LLLLL", "1970 02 01 0:00:00", "2", "LLLLL", "1970 03 01 0:00:00", "3", @@ -2973,13 +2979,13 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { "ccccc", "1970 01 01 0:00:00", "\u010C", "ccccc", "1970 01 02 0:00:00", "P", "ccccc", "1970 01 03 0:00:00", "S", - + "h:mm a", "2015 01 01 10:00:00", "10:00 dop.", "h:mm a", "2015 01 01 22:00:00", "10:00 odp.", "h:mm aaaaa", "2015 01 01 10:00:00", "10:00 dop.", "h:mm aaaaa", "2015 01 01 22:00:00", "10:00 odp.", }; - + String CA_DATA[] = { "yyyy MM dd HH:mm:ss", @@ -2993,7 +2999,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { expectFormat(CS_DATA, new Locale("cs", "", "")); expectFormat(CA_DATA, new Locale("ca", "", "")); } - + public void TestEras() { String EN_DATA[] = { @@ -3009,7 +3015,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { "MMMM dd yyyy GGG", "fp", "-438 07 17", "July 17 0439 BC", "-438 07 17", "MMMM dd yyyy GGGG", "fp", "-438 07 17", "July 17 0439 Before Christ", "-438 07 17", }; - + expect(EN_DATA, new Locale("en", "", "")); } @@ -3031,7 +3037,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { "Qyy", "fp", "2015 04 01", "215", "2015 04 01", "QQyy", "fp", "2015 07 01", "0315", "2015 07 01", }; - + expect(EN_DATA, new Locale("en", "", "")); } @@ -3119,15 +3125,15 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { if (got == exp || (got != null && got.equals(exp))) { logln("Ok: " + input + " x " + - currentPat + " => " + gotstr); + currentPat + " => " + gotstr); } else { errln("FAIL: " + input + " x " + currentPat + " => " + gotstr + ", expected " + expstr); } - } + } } - + /** * Test formatting. Input is an array of String that starts * with a single 'header' element @@ -3165,14 +3171,14 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { String datestr = data[i++]; String string = data[i++]; Date date = null; - + try { date = ref.parse(datestr); } catch (ParseException e) { errln("FAIL: Internal test error; can't parse " + datestr); continue; } - + assertEquals("\"" + currentPat + "\".format(" + datestr + ")", string, fmt.format(date)); @@ -3591,7 +3597,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { // SimpleDateFormat constructor formatters[3] = new SimpleDateFormat(testPattern, testLocales[i]); - + // SimpleDateFormat with DateFormatSymbols DateFormatSymbols dfs = new DateFormatSymbols(testLocales[i]); formatters[4] = new SimpleDateFormat(testPattern, dfs, testLocales[i]); @@ -3815,7 +3821,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { errln("FAIL: Parse failure"); } } - + /* * Tests the constructor public SimpleDateFormat(String pattern, String override, ULocale loc) */ @@ -3843,7 +3849,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { } } } - + /* Tests the method public final static DateFormat getPatternInstance */ public void TestGetPatternInstance(){ //public final static DateFormat getPatternInstance(String pattern) @@ -3904,26 +3910,26 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { public void TestISOEra() { - String data[] = { - // input, output - "BC 4004-10-23T07:00:00Z", "BC 4004-10-23T07:00:00Z", - "AD 4004-10-23T07:00:00Z", "AD 4004-10-23T07:00:00Z", - "-4004-10-23T07:00:00Z" , "BC 4005-10-23T07:00:00Z", - "4004-10-23T07:00:00Z" , "AD 4004-10-23T07:00:00Z", + String data[] = { + // input, output + "BC 4004-10-23T07:00:00Z", "BC 4004-10-23T07:00:00Z", + "AD 4004-10-23T07:00:00Z", "AD 4004-10-23T07:00:00Z", + "-4004-10-23T07:00:00Z" , "BC 4005-10-23T07:00:00Z", + "4004-10-23T07:00:00Z" , "AD 4004-10-23T07:00:00Z", }; int numData = 8; - // create formatter + // create formatter SimpleDateFormat fmt1 = new SimpleDateFormat("GGG yyyy-MM-dd'T'HH:mm:ss'Z"); for (int i = 0; i < numData; i += 2) { - // create input string + // create input string String in = data[i]; - // parse string to date + // parse string to date Date dt1; try { @@ -3934,11 +3940,11 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { errln("DateFormat.parse is not suppose to return an exception."); break; } - // format date back to string + // format date back to string String out; out = fmt1.format(dt1); - // check that roundtrip worked as expected + // check that roundtrip worked as expected String expected = data[i + 1]; if (!out.equals(expected)) { @@ -3947,12 +3953,12 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { } } - public void TestFormalChineseDate() { - + public void TestFormalChineseDate() { + String pattern = "y\u5e74M\u6708d\u65e5"; String override = "y=hanidec;M=hans;d=hans"; - - // create formatter + + // create formatter SimpleDateFormat sdf = new SimpleDateFormat(pattern,override,ULocale.CHINA); Calendar cal = Calendar.getInstance(ULocale.ENGLISH); @@ -3962,19 +3968,19 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { StringBuffer result = new StringBuffer(); sdf.format(cal,result,pos); String res1 = result.toString(); - String expected = "\u4e8c\u3007\u3007\u4e5d\u5e74\u4e03\u6708\u4e8c\u5341\u516b\u65e5"; - if (! res1.equals(expected)) { - errln((String)"FAIL: -> " + result.toString() + " expected -> " + expected); - } + String expected = "\u4e8c\u3007\u3007\u4e5d\u5e74\u4e03\u6708\u4e8c\u5341\u516b\u65e5"; + if (! res1.equals(expected)) { + errln((String)"FAIL: -> " + result.toString() + " expected -> " + expected); + } ParsePosition pp = new ParsePosition(0); Date parsedate = sdf.parse(expected, pp); long time1 = parsedate.getTime(); long time2 = cal.getTimeInMillis(); - if ( time1 != time2 ) { - errln("FAIL: parsed -> " + parsedate.toString() + " expected -> " + cal.toString()); + if ( time1 != time2 ) { + errln("FAIL: parsed -> " + parsedate.toString() + " expected -> " + cal.toString()); } - } - + } + public void TestOverrideNumberForamt() { SimpleDateFormat fmt = new SimpleDateFormat("MM/dd/yy z"); @@ -3992,24 +3998,24 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { fmt.setNumberFormat(reused_nf); // test the same override NF will not crash // DATA[i][0] is to tell which field to set, DATA[i][1] is the expected result - String[][] DATA = { - { "", "\u521D\u516D \u5341\u4E94" }, + String[][] DATA = { + { "", "\u521D\u516D \u5341\u4E94" }, { "M", "\u521D\u516D 15" }, - { "Mo", "\u521D\u516D \u5341\u4E94" }, - { "Md", "\u521D\u516D \u5341\u4E94" }, - { "MdMMd", "\u521D\u516D \u5341\u4E94" }, - { "mixed", "\u521D\u516D \u5341\u4E94" }, + { "Mo", "\u521D\u516D \u5341\u4E94" }, + { "Md", "\u521D\u516D \u5341\u4E94" }, + { "MdMMd", "\u521D\u516D \u5341\u4E94" }, + { "mixed", "\u521D\u516D \u5341\u4E94" }, }; NumberFormat override = NumberFormat.getInstance(new ULocale("en@numbers=hanidays")); Calendar cal = Calendar.getInstance(); cal.set(1997, Calendar.JUNE, 15); Date test_date = cal.getTime(); - + for (int i = 0; i < DATA.length; i++) { fmt = new SimpleDateFormat("MM d", new ULocale("en_US")); String field = DATA[i][0]; - + if (field == "") { // use the one w/o field fmt.setNumberFormat(override); } else if (field == "mixed") { // set 1 field at first but then full override, both(M & d) should be override @@ -4217,7 +4223,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { } } } - + public void TestNonGregoFmtParse() { class CalAndFmtTestItem { public int era; @@ -4334,17 +4340,17 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { public void TestTwoDigitWOY() { // See ICU Ticket #8514 String dateText = new String("98MON01"); - + SimpleDateFormat simpleDateFormat = new SimpleDateFormat("YYEEEww"); simpleDateFormat.set2DigitYearStart(new GregorianCalendar(1999,0,1).getTime()); - + Calendar cal = new GregorianCalendar(); cal.clear(); cal.setFirstDayOfWeek(Calendar.SUNDAY); cal.setMinimalDaysInFirstWeek(4); - + ParsePosition pp = new ParsePosition(0); - + simpleDateFormat.parse(dateText, cal, pp); if (pp.getErrorIndex() >= 0) { @@ -4423,7 +4429,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { errln("FAIL: for locale " + item.locale + ", capitalizationContext " + item.capitalizationContext + ", sdfmt.clone() != sdfmt (for SimpleDateFormat)"); } - + StringBuffer result2 = new StringBuffer(); FieldPosition fpos2 = new FieldPosition(0); sdfmt.format(cal, result2, fpos2); @@ -4522,7 +4528,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { } } } - + static Date TEST_DATE = new Date(2012-1900, 1-1, 15); // January 15, 2012 public void TestDotAndAtLeniency() { @@ -4559,7 +4565,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { } } - + private boolean showParse(DateFormat format, String formattedString) { ParsePosition parsePosition = new ParsePosition(0); parsePosition.setIndex(0); @@ -4575,7 +4581,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { public void TestDateFormatLeniency() { // For details see http://bugs.icu-project.org/trac/ticket/10261 - + class TestDateFormatLeniencyItem { public ULocale locale; public boolean leniency; @@ -4677,10 +4683,10 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { } } } - + public void TestParseMultiPatternMatch() { // For details see http://bugs.icu-project.org/trac/ticket/10336 - + class TestMultiPatternMatchItem { public boolean leniency; public String parseString; @@ -4729,7 +4735,7 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { StringBuffer result = new StringBuffer(); Date d = new Date(); - GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT"), Locale.US); + GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT"), Locale.US); SimpleDateFormat sdfmt = new SimpleDateFormat(); ParsePosition p = new ParsePosition(0); for (TestMultiPatternMatchItem item: items) { @@ -4757,10 +4763,10 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { if(!result.toString().equalsIgnoreCase(item.expectedResult)) { errln("error: unexpected format result. expected - " + item.expectedResult + " but result was - " + result); } else { - logln("formatted results match! - " + result.toString()); + logln("formatted results match! - " + result.toString()); } } - + } public void TestParseLeniencyAPIs() { @@ -4809,4 +4815,406 @@ public class DateFormatTest extends com.ibm.icu.dev.test.TestFmwk { assertTrue("ALLOW_NUMERIC after setLenient(TRUE)", fmt.getBooleanAttribute(BooleanAttribute.PARSE_ALLOW_NUMERIC)); } + + public void TestAmPmMidnightNoon() { + // Some times on 2015-11-13. + long k000000 = 1447372800000L; + long k000030 = 1447372830000L; + long k003000 = 1447374600000L; + long k060000 = 1447394400000L; + long k120000 = 1447416000000L; + long k180000 = 1447437600000L; + + // Short. + SimpleDateFormat sdf = new SimpleDateFormat("hh:mm:ss bbb"); + sdf.setTimeZone(TimeZone.GMT_ZONE); + + assertEquals("hh:mm:ss bbbb | 00:00:00", "12:00:00 midnight", sdf.format(k000000)); + assertEquals("hh:mm:ss bbbb | 00:00:30", "12:00:30 AM", sdf.format(k000030)); + assertEquals("hh:mm:ss bbbb | 00:30:00", "12:30:00 AM", sdf.format(k003000)); + assertEquals("hh:mm:ss bbbb | 06:00:00", "06:00:00 AM", sdf.format(k060000)); + assertEquals("hh:mm:ss bbbb | 12:00:00", "12:00:00 noon", sdf.format(k120000)); + assertEquals("hh:mm:ss bbbb | 18:00:00", "06:00:00 PM", sdf.format(k180000)); + + sdf.applyPattern("hh:mm bbb"); + + assertEquals("hh:mm bbb | 00:00:00", "12:00 midnight", sdf.format(k000000)); + assertEquals("hh:mm bbb | 00:00:30", "12:00 midnight", sdf.format(k000030)); + assertEquals("hh:mm bbb | 00:30:00", "12:30 AM", sdf.format(k003000)); + + sdf.applyPattern("hh bbb"); + + assertEquals("hh bbb | 00:00:00", "12 midnight", sdf.format(k000000)); + assertEquals("hh bbb | 00:00:30", "12 midnight", sdf.format(k000030)); + assertEquals("hh bbb | 00:30:00", "12 midnight", sdf.format(k003000)); + + // Wide. + sdf.applyPattern("hh:mm:ss bbbb"); + + assertEquals("hh:mm:ss bbbb | 00:00:00", "12:00:00 midnight", sdf.format(k000000)); + assertEquals("hh:mm:ss bbbb | 00:00:30", "12:00:30 AM", sdf.format(k000030)); + assertEquals("hh:mm:ss bbbb | 00:30:00", "12:30:00 AM", sdf.format(k003000)); + assertEquals("hh:mm:ss bbbb | 06:00:00", "06:00:00 AM", sdf.format(k060000)); + assertEquals("hh:mm:ss bbbb | 12:00:00", "12:00:00 noon", sdf.format(k120000)); + assertEquals("hh:mm:ss bbbb | 18:00:00", "06:00:00 PM", sdf.format(k180000)); + + sdf.applyPattern("hh:mm bbbb"); + + assertEquals("hh:mm bbbb | 00:00:00", "12:00 midnight", sdf.format(k000000)); + assertEquals("hh:mm bbbb | 00:00:30", "12:00 midnight", sdf.format(k000030)); + assertEquals("hh:mm bbbb | 00:30:00", "12:30 AM", sdf.format(k003000)); + + sdf.applyPattern("hh bbbb"); + assertEquals("hh bbbb | 00:00:00", "12 midnight", sdf.format(k000000)); + assertEquals("hh bbbb | 00:00:30", "12 midnight", sdf.format(k000030)); + assertEquals("hh bbbb | 00:30:00", "12 midnight", sdf.format(k003000)); + + // Narrow. + sdf.applyPattern("hh:mm:ss bbbbb"); + + assertEquals("hh:mm:ss bbbbb | 00:00:00", "12:00:00 mi", sdf.format(k000000)); + assertEquals("hh:mm:ss bbbbb | 00:00:30", "12:00:30 a", sdf.format(k000030)); + assertEquals("hh:mm:ss bbbbb | 00:30:00", "12:30:00 a", sdf.format(k003000)); + assertEquals("hh:mm:ss bbbbb | 06:00:00", "06:00:00 a", sdf.format(k060000)); + assertEquals("hh:mm:ss bbbbb | 12:00:00", "12:00:00 n", sdf.format(k120000)); + assertEquals("hh:mm:ss bbbbb | 18:00:00", "06:00:00 p", sdf.format(k180000)); + + sdf.applyPattern("hh:mm bbbbb"); + + assertEquals("hh:mm bbbbb | 00:00:00", "12:00 mi", sdf.format(k000000)); + assertEquals("hh:mm bbbbb | 00:00:30", "12:00 mi", sdf.format(k000030)); + assertEquals("hh:mm bbbbb | 00:30:00", "12:30 a", sdf.format(k003000)); + + sdf.applyPattern("hh bbbbb"); + + assertEquals("hh bbbbb | 00:00:00", "12 mi", sdf.format(k000000)); + assertEquals("hh bbbbb | 00:00:30", "12 mi", sdf.format(k000030)); + assertEquals("hh bbbbb | 00:30:00", "12 mi", sdf.format(k003000)); + } + + public void TestFlexibleDayPeriod() { + // Some times on 2015-11-13. + long k000000 = 1447372800000L; + long k000030 = 1447372830000L; + long k003000 = 1447374600000L; + long k060000 = 1447394400000L; + long k120000 = 1447416000000L; + long k180000 = 1447437600000L; + + // Short. + SimpleDateFormat sdf = new SimpleDateFormat("hh:mm:ss BBB"); + sdf.setTimeZone(TimeZone.GMT_ZONE); + + assertEquals("hh:mm:ss BBB | 00:00:00", "12:00:00 midnight", sdf.format(k000000)); + assertEquals("hh:mm:ss BBB | 00:00:30", "12:00:30 at night", sdf.format(k000030)); + assertEquals("hh:mm:ss BBB | 00:30:00", "12:30:00 at night", sdf.format(k003000)); + assertEquals("hh:mm:ss BBB | 06:00:00", "06:00:00 in the morning", sdf.format(k060000)); + assertEquals("hh:mm:ss BBB | 12:00:00", "12:00:00 noon", sdf.format(k120000)); + assertEquals("hh:mm:ss BBB | 18:00:00", "06:00:00 in the evening", sdf.format(k180000)); + + sdf.applyPattern("hh:mm BBB"); + + assertEquals("hh:mm BBB | 00:00:00", "12:00 midnight", sdf.format(k000000)); + assertEquals("hh:mm BBB | 00:00:30", "12:00 midnight", sdf.format(k000030)); + assertEquals("hh:mm BBB | 00:30:00", "12:30 at night", sdf.format(k003000)); + + sdf.applyPattern("hh BBB"); + + assertEquals("hh BBB | 00:00:00", "12 midnight", sdf.format(k000000)); + assertEquals("hh BBB | 00:00:30", "12 midnight", sdf.format(k000030)); + assertEquals("hh BBB | 00:30:00", "12 midnight", sdf.format(k003000)); + + // Wide + sdf.applyPattern("hh:mm:ss BBBB"); + + assertEquals("hh:mm:ss BBBB | 00:00:00", "12:00:00 midnight", sdf.format(k000000)); + assertEquals("hh:mm:ss BBBB | 00:00:30", "12:00:30 at night", sdf.format(k000030)); + assertEquals("hh:mm:ss BBBB | 00:30:00", "12:30:00 at night", sdf.format(k003000)); + assertEquals("hh:mm:ss BBBB | 06:00:00", "06:00:00 in the morning", sdf.format(k060000)); + assertEquals("hh:mm:ss BBBB | 12:00:00", "12:00:00 noon", sdf.format(k120000)); + assertEquals("hh:mm:ss BBBB | 18:00:00", "06:00:00 in the evening", sdf.format(k180000)); + + sdf.applyPattern("hh:mm BBBB"); + + assertEquals("hh:mm BBBB | 00:00:00", "12:00 midnight", sdf.format(k000000)); + assertEquals("hh:mm BBBB | 00:00:30", "12:00 midnight", sdf.format(k000030)); + assertEquals("hh:mm BBBB | 00:30:00", "12:30 at night", sdf.format(k003000)); + + sdf.applyPattern("hh BBBB"); + + assertEquals("hh BBBB | 00:00:00", "12 midnight", sdf.format(k000000)); + assertEquals("hh BBBB | 00:00:30", "12 midnight", sdf.format(k000030)); + assertEquals("hh BBBB | 00:30:00", "12 midnight", sdf.format(k003000)); + + // Narrow + sdf.applyPattern("hh:mm:ss BBBBB"); + + assertEquals("hh:mm:ss BBBBB | 00:00:00", "12:00:00 mi", sdf.format(k000000)); + assertEquals("hh:mm:ss BBBBB | 00:00:30", "12:00:30 at night", sdf.format(k000030)); + assertEquals("hh:mm:ss BBBBB | 00:30:00", "12:30:00 at night", sdf.format(k003000)); + assertEquals("hh:mm:ss BBBBB | 06:00:00", "06:00:00 in the morning", sdf.format(k060000)); + assertEquals("hh:mm:ss BBBBB | 12:00:00", "12:00:00 n", sdf.format(k120000)); + assertEquals("hh:mm:ss BBBBB | 18:00:00", "06:00:00 in the evening", sdf.format(k180000)); + + sdf.applyPattern("hh:mm BBBBB"); + + assertEquals("hh:mm BBBBB | 00:00:00", "12:00 mi", sdf.format(k000000)); + assertEquals("hh:mm BBBBB | 00:00:30", "12:00 mi", sdf.format(k000030)); + assertEquals("hh:mm BBBBB | 00:30:00", "12:30 at night", sdf.format(k003000)); + + sdf.applyPattern("hh BBBBB"); + + assertEquals("hh BBBBB | 00:00:00", "12 mi", sdf.format(k000000)); + assertEquals("hh BBBBB | 00:00:30", "12 mi", sdf.format(k000030)); + assertEquals("hh BBBBB | 00:30:00", "12 mi", sdf.format(k003000)); + } + + public void TestDayPeriodWithLocales() { + // Some times on 2015-11-13 (UTC+0). + long k000000 = 1447372800000L; + long k010000 = 1447376400000L; + long k120000 = 1447416000000L; + long k220000 = 1447452000000L; + + // Locale de has a word for midnight, but not noon. + SimpleDateFormat sdf = new SimpleDateFormat("", ULocale.GERMANY); + sdf.setTimeZone(TimeZone.GMT_ZONE); + + sdf.applyPattern("hh:mm:ss bbbb"); + + assertEquals("hh:mm:ss bbbb | 00:00:00 | de", "12:00:00 Mitternacht", sdf.format(k000000)); + assertEquals("hh:mm:ss bbbb | 12:00:00 | de", "12:00:00 nachm.", sdf.format(k120000)); + + // Locale ee has a rule that wraps around midnight (21h - 4h). + sdf = new SimpleDateFormat("", new ULocale("ee")); + sdf.setTimeZone(TimeZone.GMT_ZONE); + + sdf.applyPattern("hh:mm:ss BBBB"); + + assertEquals("hh:mm:ss BBBB | 22:00:00 | ee", "10:00:00 zã", sdf.format(k220000)); + assertEquals("hh:mm:ss BBBB | 00:00:00 | ee", "12:00:00 zã", sdf.format(k000000)); + assertEquals("hh:mm:ss BBBB | 01:00:00 | ee", "01:00:00 zã", sdf.format(k010000)); + + // Locale root has rules for AM/PM only. + sdf = new SimpleDateFormat("", new ULocale("root")); + sdf.setTimeZone(TimeZone.GMT_ZONE); + + sdf.applyPattern("hh:mm:ss BBBB"); + + assertEquals("hh:mm:ss BBBB | 00:00:00 | root", "12:00:00 AM", sdf.format(k000000)); + assertEquals("hh:mm:ss BBBB | 12:00:00 | root", "12:00:00 PM", sdf.format(k120000)); + + // Empty string should behave exactly as root. + sdf = new SimpleDateFormat("", new ULocale("")); + sdf.setTimeZone(TimeZone.GMT_ZONE); + + sdf.applyPattern("hh:mm:ss BBBB"); + + assertEquals("hh:mm:ss BBBB | 00:00:00 | \"\" (root)", "12:00:00 AM", sdf.format(k000000)); + assertEquals("hh:mm:ss BBBB | 12:00:00 | \"\" (root)", "12:00:00 PM", sdf.format(k120000)); + + // Locale en_US should fall back to en. + sdf = new SimpleDateFormat("", new ULocale("en_US")); + sdf.setTimeZone(TimeZone.GMT_ZONE); + + sdf.applyPattern("hh:mm:ss BBBB"); + + assertEquals("hh:mm:ss BBBB | 00:00:00 | en_US", "12:00:00 midnight", sdf.format(k000000)); + assertEquals("hh:mm:ss BBBB | 01:00:00 | en_US", "01:00:00 at night", sdf.format(k010000)); + assertEquals("hh:mm:ss BBBB | 12:00:00 | en_US", "12:00:00 noon", sdf.format(k120000)); + + // Locale es_CO should not fall back to es and should have a + // different string for 1 in the morning. + // (es_CO: "de la mañana" vs. es: "de la madrugada") + sdf = new SimpleDateFormat("", new ULocale("es_CO")); + sdf.setTimeZone(TimeZone.GMT_ZONE); + + sdf.applyPattern("hh:mm:ss BBBB"); + assertEquals("hh:mm:ss BBBB | 01:00:00 | es_CO", "01:00:00 de la mañana", sdf.format(k010000)); + + sdf = new SimpleDateFormat("", new ULocale("es")); + sdf.setTimeZone(TimeZone.GMT_ZONE); + + sdf.applyPattern("hh:mm:ss BBBB"); + assertEquals("hh:mm:ss BBBB | 01:00:00 | es", "01:00:00 de la madrugada", sdf.format(k010000)); + } + + public void TestMinuteSecondFieldsInOddPlaces() { + // Some times on 2015-11-13 (UTC+0). + long k000000 = 1447372800000L; + long k000030 = 1447372830000L; + long k003000 = 1447374600000L; + long k060030 = 1447394430000L; + long k063000 = 1447396200000L; + + // Apply pattern through constructor to make sure parsePattern() is called during initialization. + SimpleDateFormat sdf = new SimpleDateFormat("hh:mm 'ss' bbbb"); + sdf.setTimeZone(TimeZone.GMT_ZONE); + + // Seconds field is not present. + assertEquals("hh:mm 'ss' bbbb | 00:00:30", "12:00 ss midnight", sdf.format(k000030)); + assertEquals("hh:mm 'ss' bbbb | 06:00:30", "06:00 ss AM", sdf.format(k060030)); + + sdf.applyPattern("hh:mm 'ss' BBBB"); + + assertEquals("hh:mm 'ss' BBBB | 00:00:30", "12:00 ss midnight", sdf.format(k000030)); + assertEquals("hh:mm 'ss' BBBB | 06:00:30", "06:00 ss in the morning", sdf.format(k060030)); + + // Minutes field is not present. + sdf.applyPattern("hh 'mm ss' bbbb"); + + assertEquals("hh 'mm ss' bbbb | 00:30:00", "12 mm ss midnight", sdf.format(k003000)); + assertEquals("hh 'mm ss' bbbb | 06:30:00", "06 mm ss AM", sdf.format(k063000)); + + sdf.applyPattern("hh 'mm ss' BBBB"); + + assertEquals("hh 'mm ss' BBBB | 00:30:00", "12 mm ss midnight", sdf.format(k003000)); + assertEquals("hh 'mm ss' BBBB | 06:30:00", "06 mm ss in the morning", sdf.format(k063000)); + + // Minutes and seconds fields appear after day periods. + sdf.applyPattern("bbbb hh:mm:ss"); + + assertEquals("bbbb hh:mm:ss | 00:00:00", "midnight 12:00:00", sdf.format(k000000)); + assertEquals("bbbb hh:mm:ss | 00:00:30", "AM 12:00:30", sdf.format(k000030)); + assertEquals("bbbb hh:mm:ss | 00:30:00", "AM 12:30:00", sdf.format(k003000)); + + sdf.applyPattern("BBBB hh:mm:ss"); + + assertEquals("BBBB hh:mm:ss | 00:00:00", "midnight 12:00:00", sdf.format(k000000)); + assertEquals("BBBB hh:mm:ss | 00:00:30", "at night 12:00:30", sdf.format(k000030)); + assertEquals("BBBB hh:mm:ss | 00:30:00", "at night 12:30:00", sdf.format(k003000)); + + // Confirm applyPattern() reparses the pattern string. + sdf.applyPattern("BBBB hh"); + assertEquals("BBBB hh | 00:00:30", "midnight 12", sdf.format(k000030)); + + sdf.applyPattern("BBBB hh:mm:'ss'"); + assertEquals("BBBB hh:mm:'ss' | 00:00:30", "midnight 12:00:ss", sdf.format(k000030)); + + sdf.applyPattern("BBBB hh:mm:ss"); + assertEquals("BBBB hh:mm:ss | 00:00:30", "at night 12:00:30", sdf.format(k000030)); + } + + public void TestDayPeriodParsing() throws ParseException { + // Some times on 2015-11-13 (UTC+0). + Date k000000 = new Date(1447372800000L); + Date k003700 = new Date(1447375020000L); + Date k010000 = new Date(1447376400000L); + Date k013000 = new Date(1447378200000L); + Date k030000 = new Date(1447383600000L); + Date k090000 = new Date(1447405200000L); + Date k120000 = new Date(1447416000000L); + Date k130000 = new Date(1447419600000L); + Date k133700 = new Date(1447421820000L); + Date k150000 = new Date(1447426800000L); + Date k190000 = new Date(1447441200000L); + Date k193000 = new Date(1447443000000L); + Date k200000 = new Date(1447444800000L); + Date k210000 = new Date(1447448400000L); + + SimpleDateFormat sdf = new SimpleDateFormat(""); + sdf.setTimeZone(TimeZone.GMT_ZONE); + + // 'B' -- flexible day periods + // A day period on its own parses to the center of that period. + sdf.applyPattern("yyyy-MM-dd B"); + assertEquals("yyyy-MM-dd B | 2015-11-13 midnight", k000000, sdf.parse("2015-11-13 midnight")); + assertEquals("yyyy-MM-dd B | 2015-11-13 noon", k120000, sdf.parse("2015-11-13 noon")); + assertEquals("yyyy-MM-dd B | 2015-11-13 in the afternoon", k150000, sdf.parse("2015-11-13 in the afternoon")); + assertEquals("yyyy-MM-dd B | 2015-11-13 in the evening", k193000, sdf.parse("2015-11-13 in the evening")); + assertEquals("yyyy-MM-dd B | 2015-11-13 at night", k013000, sdf.parse("2015-11-13 at night")); + + // If time and day period are consistent with each other then time is parsed accordingly. + sdf.applyPattern("yyyy-MM-dd hh:mm B"); + assertEquals("yyyy-MM-dd hh:mm B | 2015-11-13 12:00 midnight", k000000, sdf.parse("2015-11-13 12:00 midnight")); + assertEquals("yyyy-MM-dd hh:mm B | 2015-11-13 12:00 noon", k120000, sdf.parse("2015-11-13 12:00 noon")); + assertEquals("yyyy-MM-dd hh:mm B | 2015-11-13 01:00 at night", k010000, sdf.parse("2015-11-13 01:00 at night")); + assertEquals("yyyy-MM-dd hh:mm B | 2015-11-13 01:00 in the afternoon", k130000, sdf.parse("2015-11-13 01:00 in the afternoon")); + assertEquals("yyyy-MM-dd hh:mm B | 2015-11-13 09:00 in the morning", k090000, sdf.parse("2015-11-13 09:00 in the morning")); + assertEquals("yyyy-MM-dd hh:mm B | 2015-11-13 09:00 at night", k210000, sdf.parse("2015-11-13 09:00 at night")); + + // If the hour is 13 thru 23 then day period has no effect on time (since time is assumed + // to be in 24-hour format). + // TODO: failing! + sdf.applyPattern("yyyy-MM-dd HH:mm B"); + assertEquals("yyyy-MM-dd HH:mm B | 2015-11-13 13:37 midnight", k133700, sdf.parse("2015-11-13 13:37 midnight")); + assertEquals("yyyy-MM-dd HH:mm B | 2015-11-13 13:37 noon", k133700, sdf.parse("2015-11-13 13:37 noon")); + assertEquals("yyyy-MM-dd HH:mm B | 2015-11-13 13:37 at night", k133700, sdf.parse("2015-11-13 13:37 at night")); + assertEquals("yyyy-MM-dd HH:mm B | 2015-11-13 13:37 in the afternoon", k133700, sdf.parse("2015-11-13 13:37 in the afternoon")); + assertEquals("yyyy-MM-dd HH:mm B | 2015-11-13 13:37 in the morning", k133700, sdf.parse("2015-11-13 13:37 in the morning")); + assertEquals("yyyy-MM-dd HH:mm B | 2015-11-13 13:37 at night", k133700, sdf.parse("2015-11-13 13:37 at night")); + + // Hour 0 is synonymous with hour 12 when parsed with 'h'. + // This unfortunately means we have to tolerate "0 noon" as it's synonymous with "12 noon". + sdf.applyPattern("yyyy-MM-dd hh:mm B"); + assertEquals("yyyy-MM-dd hh:mm B | 2015-11-13 00:00 midnight", k000000, sdf.parse("2015-11-13 00:00 midnight")); + assertEquals("yyyy-MM-dd hh:mm B | 2015-11-13 00:00 noon", k120000, sdf.parse("2015-11-13 00:00 noon")); + + // But when parsed with 'H', 0 indicates a 24-hour time, therefore we disregard the day period. + sdf.applyPattern("yyyy-MM-dd HH:mm B"); + assertEquals("yyyy-MM-dd HH:mm B | 2015-11-13 00:37 midnight", k003700, sdf.parse("2015-11-13 00:37 midnight")); + assertEquals("yyyy-MM-dd HH:mm B | 2015-11-13 00:37 noon", k003700, sdf.parse("2015-11-13 00:37 noon")); + assertEquals("yyyy-MM-dd HH:mm B | 2015-11-13 00:37 at night", k003700, sdf.parse("2015-11-13 00:37 at night")); + assertEquals("yyyy-MM-dd HH:mm B | 2015-11-13 00:37 in the afternoon", k003700, sdf.parse("2015-11-13 00:37 in the afternoon")); + assertEquals("yyyy-MM-dd HH:mm B | 2015-11-13 00:37 in the morning", k003700, sdf.parse("2015-11-13 00:37 in the morning")); + assertEquals("yyyy-MM-dd HH:mm B | 2015-11-13 00:37 at night", k003700, sdf.parse("2015-11-13 00:37 at night")); + + // Even when parsed with 'H', hours 1 thru 12 are considered 12-hour time and takes + // day period into account in parsing. + sdf.applyPattern("yyyy-MM-dd HH:mm B"); + assertEquals("yyyy-MM-dd HH:mm B | 2015-11-13 12:00 midnight", k000000, sdf.parse("2015-11-13 12:00 midnight")); + assertEquals("yyyy-MM-dd HH:mm B | 2015-11-13 12:00 noon", k120000, sdf.parse("2015-11-13 12:00 noon")); + assertEquals("yyyy-MM-dd HH:mm B | 2015-11-13 01:00 at night", k010000, sdf.parse("2015-11-13 01:00 at night")); + assertEquals("yyyy-MM-dd HH:mm B | 2015-11-13 01:00 in the afternoon", k130000, sdf.parse("2015-11-13 01:00 in the afternoon")); + assertEquals("yyyy-MM-dd HH:mm B | 2015-11-13 09:00 in the morning", k090000, sdf.parse("2015-11-13 09:00 in the morning")); + assertEquals("yyyy-MM-dd HH:mm B | 2015-11-13 09:00 at night", k210000, sdf.parse("2015-11-13 09:00 at night")); + + // If a 12-hour time and the day period don't agree with each other, time is parsed as close + // to the given day period as possible. + sdf.applyPattern("yyyy-MM-dd hh:mm B"); + + // AFTERNOON1 is [12, 18), but "7 in the afternoon" parses to 19:00. + assertEquals("yyyy-MM-dd hh:mm B | 2015-11-13 07:00 in the afternoon", k190000, sdf.parse("2015-11-13 07:00 in the afternoon")); + // NIGHT1 is [21, 6), but "8 at night" parses to 20:00. + assertEquals("yyyy-MM-dd hh:mm B | 2015-11-13 08:00 at night", k200000, sdf.parse("2015-11-13 08:00 at night")); + + // 'b' -- fixed day periods (AM, PM, midnight, noon) + // On their own, "midnight" parses to 00:00 and "noon" parses to 12:00. + // AM and PM are handled by the 'a' parser (which doesn't handle this case well). + sdf.applyPattern("yyyy-MM-dd b"); + assertEquals("yyyy-MM-dd b | 2015-11-13 midnight", k000000, sdf.parse("2015-11-13 midnight")); + assertEquals("yyyy-MM-dd b | 2015-11-13 noon", k120000, sdf.parse("2015-11-13 noon")); + + // For 12-hour times, AM and PM should be parsed as if with pattern character 'a'. + sdf.applyPattern("yyyy-MM-dd hh:mm b"); + assertEquals("yyyy-MM-dd hh:mm b | 2015-11-13 01:00 AM", k010000, sdf.parse("2015-11-13 01:00 AM")); + assertEquals("yyyy-MM-dd hh:mm b | 2015-11-13 01:00 PM", k130000, sdf.parse("2015-11-13 01:00 PM")); + + // 12 midnight parses to 00:00, and 12 noon parses to 12:00. + assertEquals("yyyy-MM-dd hh:mm b | 2015-11-13 12:00 midnight", k000000, sdf.parse("2015-11-13 12:00 midnight")); + assertEquals("yyyy-MM-dd hh:mm b | 2015-11-13 12:00 noon", k120000, sdf.parse("2015-11-13 12:00 noon")); + + // Hours 13-23 indicate 24-hour time so we disregard "midnight" or "noon". + // Again, AM and PM are handled by the 'a' parser which doesn't handle this case well. + sdf.applyPattern("yyyy-MM-dd HH:mm b"); + assertEquals("yyyy-MM-dd HH:mm b | 2015-11-13 13:37 midnight", k133700, sdf.parse("2015-11-13 13:37 midnight")); + assertEquals("yyyy-MM-dd HH:mm b | 2015-11-13 13:37 noon", k133700, sdf.parse("2015-11-13 13:37 noon")); + + // Hour 0 is synonymous with hour 12 when parsed with 'h'. + // Again, this means we have to tolerate "0 noon" as it's synonymous with "12 noon". + sdf.applyPattern("yyyy-MM-dd hh:mm b"); + assertEquals("yyyy-MM-dd hh:mm b | 2015-11-13 00:00 midnight", k000000, sdf.parse("2015-11-13 00:00 midnight")); + assertEquals("yyyy-MM-dd hh:mm b | 2015-11-13 00:00 noon", k120000, sdf.parse("2015-11-13 00:00 noon")); + + // With 'H' though 0 indicates a 24-hour time, therefore we disregard the day period. + sdf.applyPattern("yyyy-MM-dd HH:mm b"); + assertEquals("yyyy-MM-dd HH:mm b | 2015-11-13 00:37 midnight", k003700, sdf.parse("2015-11-13 00:37 midnight")); + assertEquals("yyyy-MM-dd HH:mm b | 2015-11-13 00:37 noon", k003700, sdf.parse("2015-11-13 00:37 noon")); + + // If "midnight" or "noon" is parsed with a 12-hour time other than 12:00, choose + // the version that's closer to the period given. + sdf.applyPattern("yyyy-MM-dd hh:mm b"); + assertEquals("yyyy-MM-dd hh:mm b | 2015-11-13 03:00 midnight", k030000, sdf.parse("2015-11-13 03:00 midnight")); + assertEquals("yyyy-MM-dd hh:mm b | 2015-11-13 03:00 noon", k150000, sdf.parse("2015-11-13 03:00 noon")); + } } -- 2.40.0