1d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller/*
2d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller * Based on the UCB version of strftime.c with the copyright notice appearing below.
3d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller */
4d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller
5d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller/*
6d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller** Copyright (c) 1989 The Regents of the University of California.
7d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller** All rights reserved.
8d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller**
9d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller** Redistribution and use in source and binary forms are permitted
10d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller** provided that the above copyright notice and this paragraph are
11d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller** duplicated in all such forms and that any documentation,
12d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller** advertising materials, and other materials related to such
13d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller** distribution and use acknowledge that the software was developed
14d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller** by the University of California, Berkeley. The name of the
15d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller** University may not be used to endorse or promote products derived
16d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller** from this software without specific prior written permission.
17d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller** THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
18d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller** IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
19d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
20d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller*/
21d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fullerpackage android.text.format;
22d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller
23d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fullerimport android.content.res.Resources;
24d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller
254037d51b132a85dcfe37a95f9d2d91ad23d162fdAurimas Liutikasimport libcore.icu.LocaleData;
264037d51b132a85dcfe37a95f9d2d91ad23d162fdAurimas Liutikasimport libcore.util.ZoneInfo;
274037d51b132a85dcfe37a95f9d2d91ad23d162fdAurimas Liutikas
28788cb18f652fca380acefdadce305415bc0602b0Neil Fullerimport java.nio.CharBuffer;
29d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fullerimport java.util.Formatter;
30d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fullerimport java.util.Locale;
31d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fullerimport java.util.TimeZone;
32d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller
33d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller/**
34788cb18f652fca380acefdadce305415bc0602b0Neil Fuller * Formatting logic for {@link Time}. Contains a port of Bionic's broken strftime_tz to Java.
35d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller *
36d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller * <p>This class is not thread safe.
37d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller */
38d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fullerclass TimeFormatter {
39788cb18f652fca380acefdadce305415bc0602b0Neil Fuller    // An arbitrary value outside the range representable by a char.
40788cb18f652fca380acefdadce305415bc0602b0Neil Fuller    private static final int FORCE_LOWER_CASE = -1;
41d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller
42d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller    private static final int SECSPERMIN = 60;
43d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller    private static final int MINSPERHOUR = 60;
44d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller    private static final int DAYSPERWEEK = 7;
45d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller    private static final int MONSPERYEAR = 12;
46d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller    private static final int HOURSPERDAY = 24;
47d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller    private static final int DAYSPERLYEAR = 366;
48d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller    private static final int DAYSPERNYEAR = 365;
49d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller
50d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller    /**
51d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller     * The Locale for which the cached LocaleData and formats have been loaded.
52d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller     */
53d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller    private static Locale sLocale;
54d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller    private static LocaleData sLocaleData;
55d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller    private static String sTimeOnlyFormat;
56d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller    private static String sDateOnlyFormat;
57d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller    private static String sDateTimeFormat;
58d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller
59d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller    private final LocaleData localeData;
60d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller    private final String dateTimeFormat;
61d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller    private final String timeOnlyFormat;
62d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller    private final String dateOnlyFormat;
63d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller
64d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller    private StringBuilder outputBuilder;
65788cb18f652fca380acefdadce305415bc0602b0Neil Fuller    private Formatter numberFormatter;
66d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller
67d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller    public TimeFormatter() {
68d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller        synchronized (TimeFormatter.class) {
69d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller            Locale locale = Locale.getDefault();
70d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller
71d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller            if (sLocale == null || !(locale.equals(sLocale))) {
72d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                sLocale = locale;
73d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                sLocaleData = LocaleData.get(locale);
74d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller
75d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                Resources r = Resources.getSystem();
76d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                sTimeOnlyFormat = r.getString(com.android.internal.R.string.time_of_day);
77d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                sDateOnlyFormat = r.getString(com.android.internal.R.string.month_day_year);
78d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                sDateTimeFormat = r.getString(com.android.internal.R.string.date_and_time);
79d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller            }
80d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller
81d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller            this.dateTimeFormat = sDateTimeFormat;
82d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller            this.timeOnlyFormat = sTimeOnlyFormat;
83d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller            this.dateOnlyFormat = sDateOnlyFormat;
84d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller            localeData = sLocaleData;
85d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller        }
86d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller    }
87d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller
88d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller    /**
89d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller     * Format the specified {@code wallTime} using {@code pattern}. The output is returned.
90d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller     */
91d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller    public String format(String pattern, ZoneInfo.WallTime wallTime, ZoneInfo zoneInfo) {
92d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller        try {
93d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller            StringBuilder stringBuilder = new StringBuilder();
94d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller
95d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller            outputBuilder = stringBuilder;
96788cb18f652fca380acefdadce305415bc0602b0Neil Fuller            // This uses the US locale because number localization is handled separately (see below)
97788cb18f652fca380acefdadce305415bc0602b0Neil Fuller            // and locale sensitive strings are output directly using outputBuilder.
98788cb18f652fca380acefdadce305415bc0602b0Neil Fuller            numberFormatter = new Formatter(stringBuilder, Locale.US);
99d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller
100d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller            formatInternal(pattern, wallTime, zoneInfo);
101d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller            String result = stringBuilder.toString();
102d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller            // This behavior is the source of a bug since some formats are defined as being
103788cb18f652fca380acefdadce305415bc0602b0Neil Fuller            // in ASCII and not localized.
104d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller            if (localeData.zeroDigit != '0') {
105d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                result = localizeDigits(result);
106d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller            }
107d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller            return result;
108d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller        } finally {
109d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller            outputBuilder = null;
110788cb18f652fca380acefdadce305415bc0602b0Neil Fuller            numberFormatter = null;
111d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller        }
112d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller    }
113d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller
114d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller    private String localizeDigits(String s) {
115d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller        int length = s.length();
116d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller        int offsetToLocalizedDigits = localeData.zeroDigit - '0';
117d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller        StringBuilder result = new StringBuilder(length);
118d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller        for (int i = 0; i < length; ++i) {
119d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller            char ch = s.charAt(i);
120d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller            if (ch >= '0' && ch <= '9') {
121d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                ch += offsetToLocalizedDigits;
122d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller            }
123d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller            result.append(ch);
124d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller        }
125d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller        return result.toString();
126d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller    }
127d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller
128d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller    /**
129d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller     * Format the specified {@code wallTime} using {@code pattern}. The output is written to
130d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller     * {@link #outputBuilder}.
131d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller     */
132d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller    private void formatInternal(String pattern, ZoneInfo.WallTime wallTime, ZoneInfo zoneInfo) {
133788cb18f652fca380acefdadce305415bc0602b0Neil Fuller        CharBuffer formatBuffer = CharBuffer.wrap(pattern);
134d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller        while (formatBuffer.remaining() > 0) {
135788cb18f652fca380acefdadce305415bc0602b0Neil Fuller            boolean outputCurrentChar = true;
136788cb18f652fca380acefdadce305415bc0602b0Neil Fuller            char currentChar = formatBuffer.get(formatBuffer.position());
137788cb18f652fca380acefdadce305415bc0602b0Neil Fuller            if (currentChar == '%') {
138788cb18f652fca380acefdadce305415bc0602b0Neil Fuller                outputCurrentChar = handleToken(formatBuffer, wallTime, zoneInfo);
139d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller            }
140788cb18f652fca380acefdadce305415bc0602b0Neil Fuller            if (outputCurrentChar) {
141788cb18f652fca380acefdadce305415bc0602b0Neil Fuller                outputBuilder.append(formatBuffer.get(formatBuffer.position()));
142d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller            }
143d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller            formatBuffer.position(formatBuffer.position() + 1);
144d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller        }
145d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller    }
146d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller
147788cb18f652fca380acefdadce305415bc0602b0Neil Fuller    private boolean handleToken(CharBuffer formatBuffer, ZoneInfo.WallTime wallTime,
148d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller            ZoneInfo zoneInfo) {
149d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller
150788cb18f652fca380acefdadce305415bc0602b0Neil Fuller        // The char at formatBuffer.position() is expected to be '%' at this point.
151d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller        int modifier = 0;
152d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller        while (formatBuffer.remaining() > 1) {
153788cb18f652fca380acefdadce305415bc0602b0Neil Fuller            // Increment the position then get the new current char.
154d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller            formatBuffer.position(formatBuffer.position() + 1);
155788cb18f652fca380acefdadce305415bc0602b0Neil Fuller            char currentChar = formatBuffer.get(formatBuffer.position());
156788cb18f652fca380acefdadce305415bc0602b0Neil Fuller            switch (currentChar) {
157d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                case 'A':
158d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    modifyAndAppend((wallTime.getWeekDay() < 0
159d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                                    || wallTime.getWeekDay() >= DAYSPERWEEK)
160d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                                    ? "?" : localeData.longWeekdayNames[wallTime.getWeekDay() + 1],
161d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                            modifier);
162d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    return false;
163d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                case 'a':
164d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    modifyAndAppend((wallTime.getWeekDay() < 0
165d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                                    || wallTime.getWeekDay() >= DAYSPERWEEK)
166d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                                    ? "?" : localeData.shortWeekdayNames[wallTime.getWeekDay() + 1],
167d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                            modifier);
168d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    return false;
169d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                case 'B':
170d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    if (modifier == '-') {
171d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                        modifyAndAppend((wallTime.getMonth() < 0
172d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                                        || wallTime.getMonth() >= MONSPERYEAR)
173d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                                        ? "?"
174d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                                        : localeData.longStandAloneMonthNames[wallTime.getMonth()],
175d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                                modifier);
176d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    } else {
177d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                        modifyAndAppend((wallTime.getMonth() < 0
178d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                                        || wallTime.getMonth() >= MONSPERYEAR)
179d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                                        ? "?" : localeData.longMonthNames[wallTime.getMonth()],
180d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                                modifier);
181d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    }
182d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    return false;
183d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                case 'b':
184d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                case 'h':
185d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    modifyAndAppend((wallTime.getMonth() < 0 || wallTime.getMonth() >= MONSPERYEAR)
186d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                                    ? "?" : localeData.shortMonthNames[wallTime.getMonth()],
187d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                            modifier);
188d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    return false;
189d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                case 'C':
190d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    outputYear(wallTime.getYear(), true, false, modifier);
191d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    return false;
192d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                case 'c':
193d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    formatInternal(dateTimeFormat, wallTime, zoneInfo);
194d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    return false;
195d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                case 'D':
196d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    formatInternal("%m/%d/%y", wallTime, zoneInfo);
197d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    return false;
198d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                case 'd':
199788cb18f652fca380acefdadce305415bc0602b0Neil Fuller                    numberFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"),
200d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                            wallTime.getMonthDay());
201d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    return false;
202d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                case 'E':
203d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                case 'O':
204d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    // C99 locale modifiers are not supported.
205d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    continue;
206d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                case '_':
207d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                case '-':
208d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                case '0':
209d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                case '^':
210d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                case '#':
211788cb18f652fca380acefdadce305415bc0602b0Neil Fuller                    modifier = currentChar;
212d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    continue;
213d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                case 'e':
214788cb18f652fca380acefdadce305415bc0602b0Neil Fuller                    numberFormatter.format(getFormat(modifier, "%2d", "%2d", "%d", "%02d"),
215d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                            wallTime.getMonthDay());
216d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    return false;
217d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                case 'F':
218d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    formatInternal("%Y-%m-%d", wallTime, zoneInfo);
219d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    return false;
220d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                case 'H':
221788cb18f652fca380acefdadce305415bc0602b0Neil Fuller                    numberFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"),
222d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                            wallTime.getHour());
223d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    return false;
224d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                case 'I':
225d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    int hour = (wallTime.getHour() % 12 != 0) ? (wallTime.getHour() % 12) : 12;
226788cb18f652fca380acefdadce305415bc0602b0Neil Fuller                    numberFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), hour);
227d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    return false;
228d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                case 'j':
229d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    int yearDay = wallTime.getYearDay() + 1;
230788cb18f652fca380acefdadce305415bc0602b0Neil Fuller                    numberFormatter.format(getFormat(modifier, "%03d", "%3d", "%d", "%03d"),
231d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                            yearDay);
232d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    return false;
233d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                case 'k':
234788cb18f652fca380acefdadce305415bc0602b0Neil Fuller                    numberFormatter.format(getFormat(modifier, "%2d", "%2d", "%d", "%02d"),
235d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                            wallTime.getHour());
236d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    return false;
237d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                case 'l':
238d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    int n2 = (wallTime.getHour() % 12 != 0) ? (wallTime.getHour() % 12) : 12;
239788cb18f652fca380acefdadce305415bc0602b0Neil Fuller                    numberFormatter.format(getFormat(modifier, "%2d", "%2d", "%d", "%02d"), n2);
240d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    return false;
241d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                case 'M':
242788cb18f652fca380acefdadce305415bc0602b0Neil Fuller                    numberFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"),
243d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                            wallTime.getMinute());
244d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    return false;
245d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                case 'm':
246788cb18f652fca380acefdadce305415bc0602b0Neil Fuller                    numberFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"),
247d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                            wallTime.getMonth() + 1);
248d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    return false;
249d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                case 'n':
250788cb18f652fca380acefdadce305415bc0602b0Neil Fuller                    outputBuilder.append('\n');
251d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    return false;
252d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                case 'p':
253d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    modifyAndAppend((wallTime.getHour() >= (HOURSPERDAY / 2)) ? localeData.amPm[1]
254d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                            : localeData.amPm[0], modifier);
255d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    return false;
256d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                case 'P':
257d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    modifyAndAppend((wallTime.getHour() >= (HOURSPERDAY / 2)) ? localeData.amPm[1]
258d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                            : localeData.amPm[0], FORCE_LOWER_CASE);
259d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    return false;
260d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                case 'R':
261d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    formatInternal("%H:%M", wallTime, zoneInfo);
262d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    return false;
263d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                case 'r':
264d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    formatInternal("%I:%M:%S %p", wallTime, zoneInfo);
265d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    return false;
266d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                case 'S':
267788cb18f652fca380acefdadce305415bc0602b0Neil Fuller                    numberFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"),
268d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                            wallTime.getSecond());
269d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    return false;
270d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                case 's':
271d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    int timeInSeconds = wallTime.mktime(zoneInfo);
272788cb18f652fca380acefdadce305415bc0602b0Neil Fuller                    outputBuilder.append(Integer.toString(timeInSeconds));
273d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    return false;
274d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                case 'T':
275d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    formatInternal("%H:%M:%S", wallTime, zoneInfo);
276d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    return false;
277d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                case 't':
278788cb18f652fca380acefdadce305415bc0602b0Neil Fuller                    outputBuilder.append('\t');
279d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    return false;
280d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                case 'U':
281788cb18f652fca380acefdadce305415bc0602b0Neil Fuller                    numberFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"),
282d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                            (wallTime.getYearDay() + DAYSPERWEEK - wallTime.getWeekDay())
283d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                                    / DAYSPERWEEK);
284d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    return false;
285d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                case 'u':
286d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    int day = (wallTime.getWeekDay() == 0) ? DAYSPERWEEK : wallTime.getWeekDay();
287788cb18f652fca380acefdadce305415bc0602b0Neil Fuller                    numberFormatter.format("%d", day);
288d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    return false;
289d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                case 'V':   /* ISO 8601 week number */
290d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                case 'G':   /* ISO 8601 year (four digits) */
291d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                case 'g':   /* ISO 8601 year (two digits) */
292d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                {
293d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    int year = wallTime.getYear();
294d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    int yday = wallTime.getYearDay();
295d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    int wday = wallTime.getWeekDay();
296d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    int w;
297d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    while (true) {
298d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                        int len = isLeap(year) ? DAYSPERLYEAR : DAYSPERNYEAR;
299d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                        // What yday (-3 ... 3) does the ISO year begin on?
300d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                        int bot = ((yday + 11 - wday) % DAYSPERWEEK) - 3;
301d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                        // What yday does the NEXT ISO year begin on?
302d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                        int top = bot - (len % DAYSPERWEEK);
303d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                        if (top < -3) {
304d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                            top += DAYSPERWEEK;
305d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                        }
306d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                        top += len;
307d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                        if (yday >= top) {
308d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                            ++year;
309d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                            w = 1;
310d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                            break;
311d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                        }
312d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                        if (yday >= bot) {
313d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                            w = 1 + ((yday - bot) / DAYSPERWEEK);
314d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                            break;
315d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                        }
316d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                        --year;
317d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                        yday += isLeap(year) ? DAYSPERLYEAR : DAYSPERNYEAR;
318d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    }
319788cb18f652fca380acefdadce305415bc0602b0Neil Fuller                    if (currentChar == 'V') {
320788cb18f652fca380acefdadce305415bc0602b0Neil Fuller                        numberFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), w);
321788cb18f652fca380acefdadce305415bc0602b0Neil Fuller                    } else if (currentChar == 'g') {
322d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                        outputYear(year, false, true, modifier);
323d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    } else {
324d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                        outputYear(year, true, true, modifier);
325d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    }
326d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    return false;
327d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                }
328d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                case 'v':
329d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    formatInternal("%e-%b-%Y", wallTime, zoneInfo);
330d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    return false;
331d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                case 'W':
332d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    int n = (wallTime.getYearDay() + DAYSPERWEEK - (
333d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                                    wallTime.getWeekDay() != 0 ? (wallTime.getWeekDay() - 1)
334d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                                            : (DAYSPERWEEK - 1))) / DAYSPERWEEK;
335788cb18f652fca380acefdadce305415bc0602b0Neil Fuller                    numberFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), n);
336d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    return false;
337d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                case 'w':
338788cb18f652fca380acefdadce305415bc0602b0Neil Fuller                    numberFormatter.format("%d", wallTime.getWeekDay());
339d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    return false;
340d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                case 'X':
341d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    formatInternal(timeOnlyFormat, wallTime, zoneInfo);
342d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    return false;
343d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                case 'x':
344d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    formatInternal(dateOnlyFormat, wallTime, zoneInfo);
345d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    return false;
346d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                case 'y':
347d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    outputYear(wallTime.getYear(), false, true, modifier);
348d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    return false;
349d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                case 'Y':
350d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    outputYear(wallTime.getYear(), true, true, modifier);
351d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    return false;
352d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                case 'Z':
353d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    if (wallTime.getIsDst() < 0) {
354d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                        return false;
355d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    }
356d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    boolean isDst = wallTime.getIsDst() != 0;
357d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    modifyAndAppend(zoneInfo.getDisplayName(isDst, TimeZone.SHORT), modifier);
358d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    return false;
359d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                case 'z': {
360d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    if (wallTime.getIsDst() < 0) {
361d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                        return false;
362d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    }
363d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    int diff = wallTime.getGmtOffset();
364788cb18f652fca380acefdadce305415bc0602b0Neil Fuller                    char sign;
365d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    if (diff < 0) {
366788cb18f652fca380acefdadce305415bc0602b0Neil Fuller                        sign = '-';
367d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                        diff = -diff;
368d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    } else {
369788cb18f652fca380acefdadce305415bc0602b0Neil Fuller                        sign = '+';
370d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    }
371788cb18f652fca380acefdadce305415bc0602b0Neil Fuller                    outputBuilder.append(sign);
372d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    diff /= SECSPERMIN;
373d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    diff = (diff / MINSPERHOUR) * 100 + (diff % MINSPERHOUR);
374788cb18f652fca380acefdadce305415bc0602b0Neil Fuller                    numberFormatter.format(getFormat(modifier, "%04d", "%4d", "%d", "%04d"), diff);
375d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    return false;
376d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                }
377d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                case '+':
378d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    formatInternal("%a %b %e %H:%M:%S %Z %Y", wallTime, zoneInfo);
379d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    return false;
380d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                case '%':
381d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    // If conversion char is undefined, behavior is undefined. Print out the
382d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    // character itself.
383d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                default:
384d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    return true;
385d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller            }
386d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller        }
387d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller        return true;
388d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller    }
389d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller
390d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller    private void modifyAndAppend(CharSequence str, int modifier) {
391d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller        switch (modifier) {
392d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller            case FORCE_LOWER_CASE:
393d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                for (int i = 0; i < str.length(); i++) {
394d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    outputBuilder.append(brokenToLower(str.charAt(i)));
395d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                }
396d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                break;
397d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller            case '^':
398d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                for (int i = 0; i < str.length(); i++) {
399d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    outputBuilder.append(brokenToUpper(str.charAt(i)));
400d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                }
401d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                break;
402d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller            case '#':
403d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                for (int i = 0; i < str.length(); i++) {
404d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    char c = str.charAt(i);
405d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    if (brokenIsUpper(c)) {
406d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                        c = brokenToLower(c);
407d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    } else if (brokenIsLower(c)) {
408d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                        c = brokenToUpper(c);
409d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    }
410d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                    outputBuilder.append(c);
411d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                }
412d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                break;
413d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller            default:
414d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                outputBuilder.append(str);
415d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller        }
416d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller    }
417d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller
418d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller    private void outputYear(int value, boolean outputTop, boolean outputBottom, int modifier) {
419d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller        int lead;
420d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller        int trail;
421d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller
422d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller        final int DIVISOR = 100;
423d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller        trail = value % DIVISOR;
424d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller        lead = value / DIVISOR + trail / DIVISOR;
425d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller        trail %= DIVISOR;
426d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller        if (trail < 0 && lead > 0) {
427d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller            trail += DIVISOR;
428d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller            --lead;
429d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller        } else if (lead < 0 && trail > 0) {
430d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller            trail -= DIVISOR;
431d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller            ++lead;
432d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller        }
433d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller        if (outputTop) {
434d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller            if (lead == 0 && trail < 0) {
435788cb18f652fca380acefdadce305415bc0602b0Neil Fuller                outputBuilder.append("-0");
436d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller            } else {
437788cb18f652fca380acefdadce305415bc0602b0Neil Fuller                numberFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), lead);
438d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller            }
439d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller        }
440d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller        if (outputBottom) {
441d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller            int n = ((trail < 0) ? -trail : trail);
442788cb18f652fca380acefdadce305415bc0602b0Neil Fuller            numberFormatter.format(getFormat(modifier, "%02d", "%2d", "%d", "%02d"), n);
443d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller        }
444d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller    }
445d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller
446d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller    private static String getFormat(int modifier, String normal, String underscore, String dash,
447d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller            String zero) {
448d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller        switch (modifier) {
449d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller            case '_':
450d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                return underscore;
451d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller            case '-':
452d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                return dash;
453d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller            case '0':
454d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller                return zero;
455d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller        }
456d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller        return normal;
457d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller    }
458d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller
459d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller    private static boolean isLeap(int year) {
460d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller        return (((year) % 4) == 0 && (((year) % 100) != 0 || ((year) % 400) == 0));
461d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller    }
462d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller
463d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller    /**
464788cb18f652fca380acefdadce305415bc0602b0Neil Fuller     * A broken implementation of {@link Character#isUpperCase(char)} that assumes ASCII codes in
465788cb18f652fca380acefdadce305415bc0602b0Neil Fuller     * order to be compatible with the old native implementation.
466d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller     */
467d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller    private static boolean brokenIsUpper(char toCheck) {
468d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller        return toCheck >= 'A' && toCheck <= 'Z';
469d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller    }
470d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller
471d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller    /**
472788cb18f652fca380acefdadce305415bc0602b0Neil Fuller     * A broken implementation of {@link Character#isLowerCase(char)} that assumes ASCII codes in
473788cb18f652fca380acefdadce305415bc0602b0Neil Fuller     * order to be compatible with the old native implementation.
474d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller     */
475d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller    private static boolean brokenIsLower(char toCheck) {
476d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller        return toCheck >= 'a' && toCheck <= 'z';
477d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller    }
478d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller
479d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller    /**
480788cb18f652fca380acefdadce305415bc0602b0Neil Fuller     * A broken implementation of {@link Character#toLowerCase(char)} that assumes ASCII codes in
481788cb18f652fca380acefdadce305415bc0602b0Neil Fuller     * order to be compatible with the old native implementation.
482d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller     */
483d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller    private static char brokenToLower(char input) {
484d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller        if (input >= 'A' && input <= 'Z') {
485d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller            return (char) (input - 'A' + 'a');
486d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller        }
487d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller        return input;
488d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller    }
489d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller
490d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller    /**
491788cb18f652fca380acefdadce305415bc0602b0Neil Fuller     * A broken implementation of {@link Character#toUpperCase(char)} that assumes ASCII codes in
492788cb18f652fca380acefdadce305415bc0602b0Neil Fuller     * order to be compatible with the old native implementation.
493d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller     */
494d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller    private static char brokenToUpper(char input) {
495d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller        if (input >= 'a' && input <= 'z') {
496d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller            return (char) (input - 'a' + 'A');
497d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller        }
498d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller        return input;
499d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller    }
500d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller
501d7f0849b8c053ccc6abf0dc7d5bc07da502782a4Neil Fuller}
502