12d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert// © 2016 and later: Unicode, Inc. and others.
22d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert// License & terms of use: http://www.unicode.org/copyright.html#License
37935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert/*
47935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *******************************************************************************
52d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert * Copyright (C) 2007-2016, International Business Machines Corporation and
62d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert * others. All Rights Reserved.
77935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *******************************************************************************
87935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert */
97935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertpackage com.ibm.icu.impl;
107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.text.FieldPosition;
127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.text.ParsePosition;
132d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubertimport java.util.ArrayList;
147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.Date;
152d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubertimport java.util.List;
167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.MissingResourceException;
177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.lang.UCharacter;
197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.text.BreakIterator;
207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.text.DateFormat;
217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.text.DisplayContext;
227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.text.MessageFormat;
237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.text.SimpleDateFormat;
247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.util.Calendar;
257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.util.TimeZone;
267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.util.ULocale;
277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.util.UResourceBundle;
287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert/**
307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * @author srl
317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert */
327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertpublic class RelativeDateFormat extends DateFormat {
337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @author srl
367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public static class URelativeString {
397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        URelativeString(int offset, String string) {
407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            this.offset = offset;
417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            this.string = string;
427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        URelativeString(String offset, String string) {
447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            this.offset = Integer.parseInt(offset);
457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            this.string = string;
467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public int    offset;
487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public String string;
497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // copy c'tor?
522d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert
537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param timeStyle The time style for the date and time.
557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param dateStyle The date style for the date and time.
567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param locale The locale for the date.
577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param cal The calendar to be used
587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public RelativeDateFormat(int timeStyle, int dateStyle, ULocale locale, Calendar cal) {
607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        calendar = cal;
617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        fLocale = locale;
637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        fTimeStyle = timeStyle;
647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        fDateStyle = dateStyle;
657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (fDateStyle != DateFormat.NONE) {
677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int newStyle = fDateStyle & ~DateFormat.RELATIVE;
687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            DateFormat df = DateFormat.getDateInstance(newStyle, locale);
697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (df instanceof SimpleDateFormat) {
707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                fDateTimeFormat = (SimpleDateFormat)df;
717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } else {
727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                throw new IllegalArgumentException("Can't create SimpleDateFormat for date style");
737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            fDatePattern = fDateTimeFormat.toPattern();
757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (fTimeStyle != DateFormat.NONE) {
767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                newStyle = fTimeStyle & ~DateFormat.RELATIVE;
777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                df = DateFormat.getTimeInstance(newStyle, locale);
787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (df instanceof SimpleDateFormat) {
797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    fTimePattern = ((SimpleDateFormat)df).toPattern();
807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        } else {
837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // does not matter whether timeStyle is UDAT_NONE, we need something for fDateTimeFormat
847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int newStyle = fTimeStyle & ~DateFormat.RELATIVE;
857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            DateFormat df = DateFormat.getTimeInstance(newStyle, locale);
867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (df instanceof SimpleDateFormat) {
877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                fDateTimeFormat = (SimpleDateFormat)df;
887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } else {
897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                throw new IllegalArgumentException("Can't create SimpleDateFormat for time style");
907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            fTimePattern = fDateTimeFormat.toPattern();
927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        initializeCalendar(null, fLocale);
957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        loadDates();
967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        initializeCombinedFormat(calendar, fLocale);
977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
1007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * serial version (generated)
1017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
1027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static final long serialVersionUID = 1131984966440549435L;
1037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /* (non-Javadoc)
1057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @see com.ibm.icu.text.DateFormat#format(com.ibm.icu.util.Calendar, java.lang.StringBuffer, java.text.FieldPosition)
1067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
1072d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert    @Override
1087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public StringBuffer format(Calendar cal, StringBuffer toAppendTo,
1097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            FieldPosition fieldPosition) {
1107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String relativeDayString = null;
1127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        DisplayContext capitalizationContext = getContext(DisplayContext.Type.CAPITALIZATION);
1137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (fDateStyle != DateFormat.NONE) {
1157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // calculate the difference, in days, between 'cal' and now.
1167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int dayDiff = dayDifference(cal);
1177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // look up string
1197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            relativeDayString = getStringForDay(dayDiff);
1207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
1217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (fDateTimeFormat != null) {
1237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (relativeDayString != null && fDatePattern != null &&
1247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    (fTimePattern == null || fCombinedFormat == null || combinedFormatHasDateAtStart) ) {
1257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // capitalize relativeDayString according to context for relative, set formatter no context
1267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if ( relativeDayString.length() > 0 && UCharacter.isLowerCase(relativeDayString.codePointAt(0)) &&
1277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                     (capitalizationContext == DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE ||
1287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        (capitalizationContext == DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU && capitalizationOfRelativeUnitsForListOrMenu) ||
1297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        (capitalizationContext == DisplayContext.CAPITALIZATION_FOR_STANDALONE && capitalizationOfRelativeUnitsForStandAlone) )) {
1307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    if (capitalizationBrkIter == null) {
1317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        // should only happen when deserializing, etc.
1327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        capitalizationBrkIter = BreakIterator.getSentenceInstance(fLocale);
1337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    }
1347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    relativeDayString = UCharacter.toTitleCase(fLocale, relativeDayString, capitalizationBrkIter,
1357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                                    UCharacter.TITLECASE_NO_LOWERCASE | UCharacter.TITLECASE_NO_BREAK_ADJUSTMENT);
1367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
1377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                fDateTimeFormat.setContext(DisplayContext.CAPITALIZATION_NONE);
1387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } else {
1397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // set our context for the formatter
1407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                fDateTimeFormat.setContext(capitalizationContext);
1417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
1427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
1437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (fDateTimeFormat != null && (fDatePattern != null || fTimePattern != null)) {
1457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // The new way
1467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (fDatePattern == null) {
1477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // must have fTimePattern
1487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                fDateTimeFormat.applyPattern(fTimePattern);
1497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                fDateTimeFormat.format(cal, toAppendTo, fieldPosition);
1507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } else if (fTimePattern == null) {
1517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // must have fDatePattern
1527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (relativeDayString != null) {
1537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    toAppendTo.append(relativeDayString);
1547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                } else {
1557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    fDateTimeFormat.applyPattern(fDatePattern);
1567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    fDateTimeFormat.format(cal, toAppendTo, fieldPosition);
1577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
1587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } else {
1597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                String datePattern = fDatePattern; // default;
1607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (relativeDayString != null) {
1617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    // Need to quote the relativeDayString to make it a legal date pattern
1627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    datePattern = "'" + relativeDayString.replace("'", "''") + "'";
1637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
1647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                StringBuffer combinedPattern = new StringBuffer("");
1657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                fCombinedFormat.format(new Object[] {fTimePattern, datePattern}, combinedPattern, new FieldPosition(0));
1667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                fDateTimeFormat.applyPattern(combinedPattern.toString());
1677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                fDateTimeFormat.format(cal, toAppendTo, fieldPosition);
1687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
1697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        } else if (fDateFormat != null) {
1707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // A subset of the old way, for serialization compatibility
1717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // (just do the date part)
1727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (relativeDayString != null) {
1737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                toAppendTo.append(relativeDayString);
1747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } else {
1757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                fDateFormat.format(cal, toAppendTo, fieldPosition);
1767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
1777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
1787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return toAppendTo;
1807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
1817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /* (non-Javadoc)
1837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @see com.ibm.icu.text.DateFormat#parse(java.lang.String, com.ibm.icu.util.Calendar, java.text.ParsePosition)
1847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
1852d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert    @Override
1867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public void parse(String text, Calendar cal, ParsePosition pos) {
1877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        throw new UnsupportedOperationException("Relative Date parse is not implemented yet");
1887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
1897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /* (non-Javadoc)
1917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @see com.ibm.icu.text.DateFormat#setContext(com.ibm.icu.text.DisplayContext)
1927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Here we override the DateFormat implementation in order to
1932d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert     * lazily initialize relevant items
1947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
1952d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert    @Override
1967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public void setContext(DisplayContext context) {
1977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        super.setContext(context);
1987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (!capitalizationInfoIsSet &&
1997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert              (context==DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU || context==DisplayContext.CAPITALIZATION_FOR_STANDALONE)) {
2007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            initCapitalizationContextInfo(fLocale);
2017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            capitalizationInfoIsSet = true;
2027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (capitalizationBrkIter == null && (context==DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE ||
2047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert              (context==DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU && capitalizationOfRelativeUnitsForListOrMenu) ||
2057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert              (context==DisplayContext.CAPITALIZATION_FOR_STANDALONE && capitalizationOfRelativeUnitsForStandAlone) )) {
2067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            capitalizationBrkIter = BreakIterator.getSentenceInstance(fLocale);
2077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
2097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private DateFormat fDateFormat; // keep for serialization compatibility
2117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    @SuppressWarnings("unused")
2127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private DateFormat fTimeFormat; // now unused, keep for serialization compatibility
2132d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert    private MessageFormat fCombinedFormat; //  the {0} {1} format.
2147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private SimpleDateFormat fDateTimeFormat = null; // the held date/time formatter
2157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private String fDatePattern = null;
2167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private String fTimePattern = null;
2177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    int fDateStyle;
2197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    int fTimeStyle;
2207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    ULocale  fLocale;
2212d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert
2222d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert    private transient List<URelativeString> fDates = null;
2232d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert
2247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private boolean combinedFormatHasDateAtStart = false;
2257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private boolean capitalizationInfoIsSet = false;
2267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private boolean capitalizationOfRelativeUnitsForListOrMenu = false;
2277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private boolean capitalizationOfRelativeUnitsForStandAlone = false;
2287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private transient BreakIterator capitalizationBrkIter = null;
2292d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert
2307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
2317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Get the string at a specific offset.
2322d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert     * @param day day offset ( -1, 0, 1, etc.. ). Does not require sorting by offset.
2337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return the string, or NULL if none at that location.
2347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
2357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private String getStringForDay(int day) {
2367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if(fDates == null) {
2377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            loadDates();
2387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2392d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert        for(URelativeString dayItem : fDates) {
2402d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert            if(dayItem.offset == day) {
2412d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert                return dayItem.string;
2427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
2437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return null;
2457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
2462d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert
2472d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert    // Sink to get "fields/day/relative".
2482d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert    private final class RelDateFmtDataSink extends UResource.Sink {
2492d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert
2502d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert        @Override
2512d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert        public void put(UResource.Key key, UResource.Value value, boolean noFallback) {
2522d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert            if (value.getType() == ICUResourceBundle.ALIAS) {
2532d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert                return;
2542d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert            }
2552d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert
2562d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert            UResource.Table table = value.getTable();
2572d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert            for (int i = 0; table.getKeyAndValue(i, key, value); ++i) {
2582d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert
2592d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert                int keyOffset;
2602d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert                try {
2612d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert                    keyOffset = Integer.parseInt(key.toString());
2622d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert                }
2632d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert                catch (NumberFormatException nfe) {
2642d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert                    // Flag the error?
2652d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert                    return;
2662d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert                }
2672d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert                // Check if already set.
2682d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert                if (getStringForDay(keyOffset) == null) {
2692d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert                    URelativeString newDayInfo = new URelativeString(keyOffset, value.getString());
2702d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert                    fDates.add(newDayInfo);
2717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
2727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
2737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
2752d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert
2762d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert    /**
2772d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert     * Load the Date string array
2782d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert     */
2792d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert    private synchronized void loadDates() {
2802d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert        ICUResourceBundle rb = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, fLocale);
2812d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert
2822d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert        // Use sink mechanism to traverse data structure.
2832d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert        fDates = new ArrayList<URelativeString>();
2842d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert        RelDateFmtDataSink sink = new RelDateFmtDataSink();
2852d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert        rb.getAllItemsWithFallback("fields/day/relative", sink);
2862d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert    }
2872d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert
2887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
2892d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert     * Set capitalizationOfRelativeUnitsForListOrMenu, capitalizationOfRelativeUnitsForStandAlone
2907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
2917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private void initCapitalizationContextInfo(ULocale locale) {
2922d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert        ICUResourceBundle rb = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, locale);
2937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        try {
2947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            ICUResourceBundle rdb = rb.getWithFallback("contextTransforms/relative");
2957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int[] intVector = rdb.getIntVector();
2967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (intVector.length >= 2) {
2977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                capitalizationOfRelativeUnitsForListOrMenu = (intVector[0] != 0);
2987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                capitalizationOfRelativeUnitsForStandAlone = (intVector[1] != 0);
2997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
3007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        } catch (MissingResourceException e) {
3017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // use default
3027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
3037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
3047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
3067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return the number of days in "until-now"
3077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
3087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static int dayDifference(Calendar until) {
3097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        Calendar nowCal = (Calendar)until.clone();
3107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        Date nowDate = new Date(System.currentTimeMillis());
3117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        nowCal.clear();
3127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        nowCal.setTime(nowDate);
3137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int dayDiff = until.get(Calendar.JULIAN_DAY) - nowCal.get(Calendar.JULIAN_DAY);
3147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return dayDiff;
3157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
3162d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert
3177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
3187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * initializes fCalendar from parameters.  Returns fCalendar as a convenience.
3197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param zone  Zone to be adopted, or NULL for TimeZone::createDefault().
3207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param locale Locale of the calendar
3217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param status Error code
3227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return the newly constructed fCalendar
3237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
3247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private Calendar initializeCalendar(TimeZone zone, ULocale locale) {
3257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (calendar == null) {
3267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if(zone == null) {
3277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                calendar = Calendar.getInstance(locale);
3287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } else {
3297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                calendar = Calendar.getInstance(zone, locale);
3307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
3317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
3327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return calendar;
3337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
3347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private MessageFormat initializeCombinedFormat(Calendar cal, ULocale locale) {
3362d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert        String pattern;
3372d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert        ICUResourceBundle rb = (ICUResourceBundle) UResourceBundle.getBundleInstance(
3382d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert            ICUData.ICU_BASE_NAME, locale);
3392d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert        String resourcePath = "calendar/" + cal.getType() + "/DateTimePatterns";
3402d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert        ICUResourceBundle patternsRb= rb.findWithFallback(resourcePath);
3412d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert        if (patternsRb == null && !cal.getType().equals("gregorian")) {
3422d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert            // Try again with gregorian, if not already attempted.
3432d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert            patternsRb = rb.findWithFallback("calendar/gregorian/DateTimePatterns");
3442d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert        }
3452d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert
3462d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert        if (patternsRb == null || patternsRb.getSize() < 9) {
3472d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert            // Undefined or too few elements.
3482d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert            pattern = "{1} {0}";
3492d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert        } else {
3502d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert            int glueIndex = 8;
3512d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert            if (patternsRb.getSize() >= 13) {
3522d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert              if (fDateStyle >= DateFormat.FULL && fDateStyle <= DateFormat.SHORT) {
3532d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert                  glueIndex += fDateStyle + 1;
3542d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert              } else
3552d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert                  if (fDateStyle >= DateFormat.RELATIVE_FULL &&
3562d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert                      fDateStyle <= DateFormat.RELATIVE_SHORT) {
3572d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert                      glueIndex += fDateStyle + 1 - DateFormat.RELATIVE;
3582d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert                  }
3592d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert            }
3602d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert            int elementType = patternsRb.get(glueIndex).getType();
3612d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert            if (elementType == UResourceBundle.ARRAY) {
3622d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert                pattern = patternsRb.get(glueIndex).getString(0);
3632d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert            } else {
3642d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert                pattern = patternsRb.getString(glueIndex);
3657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
3667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
3677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        combinedFormatHasDateAtStart = pattern.startsWith("{1}");
3687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        fCombinedFormat = new MessageFormat(pattern, locale);
3697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return fCombinedFormat;
3707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
3717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert}
372