]> granicus.if.org Git - icu/commitdiff
ICU-11872 new time formatting pattern chars b/B
authorkazède king <kazede@google.com>
Thu, 25 Feb 2016 19:53:49 +0000 (19:53 +0000)
committerkazède king <kazede@google.com>
Thu, 25 Feb 2016 19:53:49 +0000 (19:53 +0000)
Merging from the branch.

X-SVN-Rev: 38371

icu4j/main/classes/core/src/com/ibm/icu/impl/DayPeriodRules.java [new file with mode: 0644]
icu4j/main/classes/core/src/com/ibm/icu/text/DateFormat.java
icu4j/main/classes/core/src/com/ibm/icu/text/DateFormatSymbols.java
icu4j/main/classes/core/src/com/ibm/icu/text/SimpleDateFormat.java
icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/DateFormatTest.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 (file)
index 0000000..a9244e0
--- /dev/null
@@ -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<String, Integer> localesToRuleSetNumMap = new HashMap<String, Integer>();
+        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.
+    }
+}
index 7ebbe3d5a1d5c19721547874ce7aa8f7ea97fa8d..adb9c48f80078a6cbf1405526fdae8df69e8d7b4 100644 (file)
@@ -31,13 +31,13 @@ import com.ibm.icu.util.ULocale.Category;
 
 /**
  * {@icuenhanced java.text.DateFormat}.{@icu _usage_}
- * 
+ *
  * <p>
  * 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 <code>Date</code>
  * object or as the milliseconds since January 1, 1970, 00:00:00 GMT.
- * 
+ *
  * <p>
  * 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".
  * </ol>
- * 
+ *
  * <p>
  * To format a date for the current Locale, use one of the static factory methods:
- * 
+ *
  * <pre>
  * 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;
  * <p>
  * 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.
- * 
+ *
  * <pre>
  * DateFormat df = DateFormat.getDateInstance();
  * for (int i = 0; i &lt; a.length; ++i) {
@@ -72,13 +72,13 @@ import com.ibm.icu.util.ULocale.Category;
  * </pre>
  * <p>
  * To format a date for a different Locale, specify it in the call to getDateInstance().
- * 
+ *
  * <pre>
  * DateFormat df = DateFormat.getDateInstance(DateFormat.LONG, Locale.FRANCE);
  * </pre>
  * <p>
  * You can use a DateFormat to parse also.
- * 
+ *
  * <pre>
  * myDate = df.parse(myString);
  * </pre>
@@ -93,7 +93,7 @@ import com.ibm.icu.util.ULocale.Category;
  * <li>LONG is longer, such as January 12, 1952 or 3:30:32pm
  * <li>FULL is pretty completely specified, such as Tuesday, April 12, 1952 AD or 3:30:42pm PST.
  * </ul>
- * 
+ *
  * <p>
  * 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.
  * </ol>
  * </ol>
- * 
+ *
  * <p>
  * 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.
- * 
+ *
  * <p>
  * You can also use forms of the parse and format methods with ParsePosition and FieldPosition to allow you to
  * <ul>
  * <li>progressively parse through pieces of a string.
  * <li>align any particular field, or find out where it is for selection on the screen.
  * </ul>
- * 
+ *
  * <h4>Synchronization</h4>
- * 
+ *
  * 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<BooleanAttribute> booleanAttributes = EnumSet.allOf(BooleanAttribute.class); 
+    private EnumSet<BooleanAttribute> 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 <i>generic non-location format</i>, 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 <i>generic non-location format</i>, 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 <i>specific non-location format</i>, 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 <i>specific non-location format</i>, 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 <i>localized GMT/UTC format</i>, 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.
-     * <br/><br/> 
-     * <b>Note:</b> 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 
+     * <br/><br/>
+     * <b>Note:</b> 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
index 243b91a51cb76de4e6fd35c71ae4a2a243ca40d1..1f4b6dad2ed285a6b8aa3122e2b417bcaa9a5c38 100644 (file)
@@ -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 <code>Calendar.SUNDAY</code>,
      * <code>Calendar.MONDAY</code>, 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 <code>Calendar.SUNDAY</code>,
      * <code>Calendar.MONDAY</code>, 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<CapitalizationContextUsage,boolean[]>();
         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
      */
index cd0d9ff109da48928ae3f91db9fee12ac51f781e..6b23c532fe4afdcb3606d80a5c72740fb44215f0 100644 (file)
@@ -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;
  *         <td style="text-align: center" rowspan="3">G</td>
  *         <td style="text-align: center">1..3</td>
  *         <td>AD</td>
- *         <td rowspan="3">Era - Replaced with the Era string for the current date. One to three letters for the 
+ *         <td rowspan="3">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.</td>
  *     </tr>
  *     <tr>
@@ -198,7 +199,7 @@ import com.ibm.icu.util.ULocale.Category;
  *         <td rowspan="3" style="text-align: center">Q</td>
  *         <td style="text-align: center">1..2</td>
  *         <td>02</td>
- *         <td rowspan="3">Quarter - Use one or two for the numerical quarter, three for the abbreviation, or four 
+ *         <td rowspan="3">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).</td>
  *     </tr>
  *     <tr>
@@ -213,7 +214,7 @@ import com.ibm.icu.util.ULocale.Category;
  *         <td rowspan="3" style="text-align: center">q</td>
  *         <td style="text-align: center">1..2</td>
  *         <td>02</td>
- *         <td rowspan="3"><b>Stand-Alone</b> Quarter - Use one or two for the numerical quarter, three for the abbreviation, 
+ *         <td rowspan="3"><b>Stand-Alone</b> 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).</td>
  *     </tr>
  *     <tr>
@@ -249,7 +250,7 @@ import com.ibm.icu.util.ULocale.Category;
  *         <td rowspan="4" style="text-align: center">L</td>
  *         <td style="text-align: center">1..2</td>
  *         <td>09</td>
- *         <td rowspan="4"><b>Stand-Alone</b> Month - Use one or two for the numerical month, three for the abbreviation, 
+ *         <td rowspan="4"><b>Stand-Alone</b> 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").</td>
  *     </tr>
@@ -305,7 +306,7 @@ import com.ibm.icu.util.ULocale.Category;
  *         <td>2451334</td>
  *         <td>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.</td>
  *     </tr>
  *     <tr>
@@ -314,7 +315,7 @@ import com.ibm.icu.util.ULocale.Category;
  *         <td rowspan="4" style="text-align: center">E</td>
  *         <td style="text-align: center">1..3</td>
  *         <td>Tue</td>
- *         <td rowspan="4">Day of week - Use one through three letters for the short day, four for the full (wide) name, 
+ *         <td rowspan="4">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.</td>
  *     </tr>
  *     <tr>
@@ -533,7 +534,7 @@ import com.ibm.icu.util.ULocale.Category;
  *         <td>The <i>generic location format</i>.
  *         Where that is unavailable, falls back to the <i>long localized GMT format</i> ("OOOO";
  *         Note: Fallback is only necessary with a GMT-style Time Zone ID, like Etc/GMT-830.)<br>
- *         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.</td>
  *     </tr>
  *     <tr>
@@ -609,7 +610,7 @@ import com.ibm.icu.util.ULocale.Category;
  *         (Note: The seconds field is not supported by the ISO8601 specification.)</td>
  *     </tr>
  * </table>
- * 
+ *
  * </blockquote>
  * <p>
  * 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<DayPeriodRules.DayPeriod> dayPeriod = new Output<DayPeriodRules.DayPeriod>(null);
+
         Output<TimeType> tzTimeType = new Output<TimeType>(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<DayPeriodRules.DayPeriod> 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 <code>cal</code>.  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<TimeType> 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 <code>cal</code>.  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<TimeType> tzTimeType)
+                           MessageFormat numericLeapMonthFormatter, Output<TimeType> tzTimeType,
+                           Output<DayPeriodRules.DayPeriod> 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<String> data = new ArrayList<String>(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<String, NumberFormat>();
@@ -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;
+                }
+            }
+        }
+    }
 }
index 4d156349105c88caad877335dd445024eab2a060..ac26e44c0b412178121706bb7a1a8c5dccf6c937 100644 (file)
@@ -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<String> expectedSkeletons = new ArrayList<String>(DateFormat.DATE_SKELETONS);
         expectedSkeletons.addAll(DateFormat.TIME_SKELETONS);
         expectedSkeletons.addAll(DateFormat.ZONE_SKELETONS);
         boolean combinations = false;
-        
+
         List<String> testedSkeletons = new ArrayList<String>();
-        
+
         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<String>(expectedSkeletons), new HashSet<String>(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"));
+    }
 }