EMMA Coverage Report (generated Sun Mar 01 22:06:14 CET 2015)
[all classes][org.h2.util]

COVERAGE SUMMARY FOR SOURCE FILE [ToChar.java]

nameclass, %method, %block, %line, %
ToChar.java100% (3/3)89%  (16/18)99%  (2483/2509)99%  (400.7/405)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class ToChar$Capitalization100% (1/1)83%  (5/6)84%  (104/124)88%  (15.8/18)
valueOf (String): ToChar$Capitalization 0%   (0/1)0%   (0/5)0%   (0/1)
apply (String): String 100% (1/1)71%  (36/51)71%  (5/7)
<static initializer> 100% (1/1)100% (34/34)100% (4/4)
ToChar$Capitalization (String, int): void 100% (1/1)100% (5/5)100% (1/1)
toCapitalization (Boolean, Boolean): ToChar$Capitalization 100% (1/1)100% (25/25)100% (7/7)
values (): ToChar$Capitalization [] 100% (1/1)100% (4/4)100% (1/1)
     
class ToChar$1100% (1/1)100% (1/1)88%  (23/26)88%  (0.9/1)
<static initializer> 100% (1/1)88%  (23/26)88%  (0.9/1)
     
class ToChar100% (1/1)91%  (10/11)100% (2356/2359)99%  (385/387)
ToChar (): void 0%   (0/1)0%   (0/3)0%   (0/2)
<static initializer> 100% (1/1)100% (29/29)100% (7/7)
addSign (StringBuilder, int, boolean, boolean, boolean, boolean, boolean): void 100% (1/1)100% (65/65)100% (20/20)
calculateScale (String, int): int 100% (1/1)100% (23/23)100% (6/6)
containsAt (String, int, String []): ToChar$Capitalization 100% (1/1)100% (82/82)100% (19/19)
findDecimalSeparator (String): int 100% (1/1)100% (26/26)100% (8/8)
getYear (Calendar): int 100% (1/1)100% (11/11)100% (4/4)
toChar (BigDecimal, String, String): String 100% (1/1)100% (655/655)100% (128/128)
toChar (Timestamp, String, String): String 100% (1/1)100% (1227/1227)100% (164/164)
toHex (BigDecimal, String): String 100% (1/1)100% (93/93)100% (19/19)
toRomanNumeral (int): String 100% (1/1)100% (145/145)100% (10/10)

1/*
2 * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0,
3 * and the EPL 1.0 (http://h2database.com/html/license.html).
4 * Initial Developer: Daniel Gredler
5 */
6package org.h2.util;
7 
8import static java.lang.Math.abs;
9import java.math.BigDecimal;
10import java.sql.Date;
11import java.sql.Timestamp;
12import java.text.DecimalFormat;
13import java.text.DecimalFormatSymbols;
14import java.text.SimpleDateFormat;
15import java.util.Calendar;
16import java.util.Currency;
17import java.util.GregorianCalendar;
18import java.util.Locale;
19import java.util.TimeZone;
20 
21import org.h2.api.ErrorCode;
22import org.h2.message.DbException;
23 
24/**
25 * Emulates Oracle's TO_CHAR function.
26 */
27public class ToChar {
28 
29    /**
30     * The beginning of the Julian calendar.
31     */
32    private static final long JULIAN_EPOCH;
33 
34    static {
35        GregorianCalendar epoch = new GregorianCalendar(Locale.ENGLISH);
36        epoch.setGregorianChange(new Date(Long.MAX_VALUE));
37        epoch.clear();
38        epoch.set(4713, Calendar.JANUARY, 1, 0, 0, 0);
39        epoch.set(Calendar.ERA, GregorianCalendar.BC);
40        JULIAN_EPOCH = epoch.getTimeInMillis();
41    }
42 
43    private ToChar() {
44        // utility class
45    }
46 
47    /**
48     * Emulates Oracle's TO_CHAR(number) function.
49     *
50     * <p><table border="1">
51     * <th><td>Input</td>
52     * <td>Output</td>
53     * <td>Closest {@link DecimalFormat} Equivalent</td></th>
54     * <tr><td>,</td>
55     * <td>Grouping separator.</td>
56     * <td>,</td></tr>
57     * <tr><td>.</td>
58     * <td>Decimal separator.</td>
59     * <td>.</td></tr>
60     * <tr><td>$</td>
61     * <td>Leading dollar sign.</td>
62     * <td>$</td></tr>
63     * <tr><td>0</td>
64     * <td>Leading or trailing zeroes.</td>
65     * <td>0</td></tr>
66     * <tr><td>9</td>
67     * <td>Digit.</td>
68     * <td>#</td></tr>
69     * <tr><td>B</td>
70     * <td>Blanks integer part of a fixed point number less than 1.</td>
71     * <td>#</td></tr>
72     * <tr><td>C</td>
73     * <td>ISO currency symbol.</td>
74     * <td>\u00A4</td></tr>
75     * <tr><td>D</td>
76     * <td>Local decimal separator.</td>
77     * <td>.</td></tr>
78     * <tr><td>EEEE</td>
79     * <td>Returns a value in scientific notation.</td>
80     * <td>E</td></tr>
81     * <tr><td>FM</td>
82     * <td>Returns values with no leading or trailing spaces.</td>
83     * <td>None.</td></tr>
84     * <tr><td>G</td>
85     * <td>Local grouping separator.</td>
86     * <td>,</td></tr>
87     * <tr><td>L</td>
88     * <td>Local currency symbol.</td>
89     * <td>\u00A4</td></tr>
90     * <tr><td>MI</td>
91     * <td>Negative values get trailing minus sign,
92     * positive get trailing space.</td>
93     * <td>-</td></tr>
94     * <tr><td>PR</td>
95     * <td>Negative values get enclosing angle brackets,
96     * positive get spaces.</td>
97     * <td>None.</td></tr>
98     * <tr><td>RN</td>
99     * <td>Returns values in Roman numerals.</td>
100     * <td>None.</td></tr>
101     * <tr><td>S</td>
102     * <td>Returns values with leading/trailing +/- signs.</td>
103     * <td>None.</td></tr>
104     * <tr><td>TM</td>
105     * <td>Returns smallest number of characters possible.</td>
106     * <td>None.</td></tr>
107     * <tr><td>U</td>
108     * <td>Returns the dual currency symbol.</td>
109     * <td>None.</td></tr>
110     * <tr><td>V</td>
111     * <td>Returns a value multiplied by 10^n.</td>
112     * <td>None.</td></tr>
113     * <tr><td>X</td>
114     * <td>Hex value.</td>
115     * <td>None.</td></tr>
116     * </table>
117     * See also TO_CHAR(number) and number format models
118     * in the Oracle documentation.
119     *
120     * @param number the number to format
121     * @param format the format pattern to use (if any)
122     * @param nlsParam the NLS parameter (if any)
123     * @return the formatted number
124     */
125    public static String toChar(BigDecimal number, String format,
126            String nlsParam) {
127 
128        // short-circuit logic for formats that don't follow common logic below
129        String formatUp = format != null ? format.toUpperCase() : null;
130        if (formatUp == null || formatUp.equals("TM") || formatUp.equals("TM9")) {
131            String s = number.toPlainString();
132            return s.startsWith("0.") ? s.substring(1) : s;
133        } else if (formatUp.equals("TME")) {
134            int pow = number.precision() - number.scale() - 1;
135            number = number.movePointLeft(pow);
136            return number.toPlainString() + "E" +
137                    (pow < 0 ? '-' : '+') + (abs(pow) < 10 ? "0" : "") + abs(pow);
138        } else if (formatUp.equals("RN")) {
139            boolean lowercase = format.startsWith("r");
140            String rn = StringUtils.pad(toRomanNumeral(number.intValue()), 15, " ", false);
141            return lowercase ? rn.toLowerCase() : rn;
142        } else if (formatUp.equals("FMRN")) {
143            boolean lowercase = format.charAt(2) == 'r';
144            String rn = toRomanNumeral(number.intValue());
145            return lowercase ? rn.toLowerCase() : rn;
146        } else if (formatUp.endsWith("X")) {
147            return toHex(number, format);
148        }
149 
150        String originalFormat = format;
151        DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance();
152        char localGrouping = symbols.getGroupingSeparator();
153        char localDecimal = symbols.getDecimalSeparator();
154 
155        boolean leadingSign = formatUp.startsWith("S");
156        if (leadingSign) {
157            format = format.substring(1);
158        }
159 
160        boolean trailingSign = formatUp.endsWith("S");
161        if (trailingSign) {
162            format = format.substring(0, format.length() - 1);
163        }
164 
165        boolean trailingMinus = formatUp.endsWith("MI");
166        if (trailingMinus) {
167            format = format.substring(0, format.length() - 2);
168        }
169 
170        boolean angleBrackets = formatUp.endsWith("PR");
171        if (angleBrackets) {
172            format = format.substring(0, format.length() - 2);
173        }
174 
175        int v = formatUp.indexOf("V");
176        if (v >= 0) {
177            int digits = 0;
178            for (int i = v + 1; i < format.length(); i++) {
179                char c = format.charAt(i);
180                if (c == '0' || c == '9') {
181                    digits++;
182                }
183            }
184            number = number.movePointRight(digits);
185            format = format.substring(0, v) + format.substring(v + 1);
186        }
187 
188        Integer power;
189        if (format.endsWith("EEEE")) {
190            power = number.precision() - number.scale() - 1;
191            number = number.movePointLeft(power);
192            format = format.substring(0, format.length() - 4);
193        } else {
194            power = null;
195        }
196 
197        int maxLength = 1;
198        boolean fillMode = !formatUp.startsWith("FM");
199        if (!fillMode) {
200            format = format.substring(2);
201        }
202 
203        // blanks flag doesn't seem to actually do anything
204        format = format.replaceAll("[Bb]", "");
205 
206        // if we need to round the number to fit into the format specified,
207        // go ahead and do that first
208        int separator = findDecimalSeparator(format);
209        int formatScale = calculateScale(format, separator);
210        if (formatScale < number.scale()) {
211            number = number.setScale(formatScale, BigDecimal.ROUND_HALF_UP);
212        }
213 
214        // any 9s to the left of the decimal separator but to the right of a
215        // 0 behave the same as a 0, e.g. "09999.99" -> "00000.99"
216        for (int i = format.indexOf('0'); i >= 0 && i < separator; i++) {
217            if (format.charAt(i) == '9') {
218                format = format.substring(0, i) + "0" + format.substring(i + 1);
219            }
220        }
221 
222        StringBuilder output = new StringBuilder();
223        String unscaled = number.unscaledValue().abs().toString();
224 
225        // start at the decimal point and fill in the numbers to the left,
226        // working our way from right to left
227        int i = separator - 1;
228        int j = unscaled.length() - number.scale() - 1;
229        for (; i >= 0; i--) {
230            char c = format.charAt(i);
231            maxLength++;
232            if (c == '9' || c == '0') {
233                if (j >= 0) {
234                    char digit = unscaled.charAt(j);
235                    output.insert(0, digit);
236                    j--;
237                } else if (c == '0' && power == null) {
238                    output.insert(0, '0');
239                }
240            } else if (c == ',') {
241                // only add the grouping separator if we have more numbers
242                if (j >= 0 || (i > 0 && format.charAt(i - 1) == '0')) {
243                    output.insert(0, c);
244                }
245            } else if (c == 'G' || c == 'g') {
246                // only add the grouping separator if we have more numbers
247                if (j >= 0 || (i > 0 && format.charAt(i - 1) == '0')) {
248                    output.insert(0, localGrouping);
249                }
250            } else if (c == 'C' || c == 'c') {
251                Currency currency = Currency.getInstance(Locale.getDefault());
252                output.insert(0, currency.getCurrencyCode());
253                maxLength += 6;
254            } else if (c == 'L' || c == 'l' || c == 'U' || c == 'u') {
255                Currency currency = Currency.getInstance(Locale.getDefault());
256                output.insert(0, currency.getSymbol());
257                maxLength += 9;
258            } else if (c == '$') {
259                Currency currency = Currency.getInstance(Locale.getDefault());
260                String cs = currency.getSymbol();
261                output.insert(0, cs);
262            } else {
263                throw DbException.get(
264                        ErrorCode.INVALID_TO_CHAR_FORMAT, originalFormat);
265            }
266        }
267 
268        // if the format (to the left of the decimal point) was too small
269        // to hold the number, return a big "######" string
270        if (j >= 0) {
271            return StringUtils.pad("", format.length() + 1, "#", true);
272        }
273 
274        if (separator < format.length()) {
275 
276            // add the decimal point
277            maxLength++;
278            char pt = format.charAt(separator);
279            if (pt == 'd' || pt == 'D') {
280                output.append(localDecimal);
281            } else {
282                output.append(pt);
283            }
284 
285            // start at the decimal point and fill in the numbers to the right,
286            // working our way from left to right
287            i = separator + 1;
288            j = unscaled.length() - number.scale();
289            for (; i < format.length(); i++) {
290                char c = format.charAt(i);
291                maxLength++;
292                if (c == '9' || c == '0') {
293                    if (j < unscaled.length()) {
294                        char digit = unscaled.charAt(j);
295                        output.append(digit);
296                        j++;
297                    } else {
298                        if (c == '0' || fillMode) {
299                            output.append('0');
300                        }
301                    }
302                } else {
303                    throw DbException.get(
304                            ErrorCode.INVALID_TO_CHAR_FORMAT, originalFormat);
305                }
306            }
307        }
308 
309        addSign(output, number.signum(), leadingSign, trailingSign,
310                trailingMinus, angleBrackets, fillMode);
311 
312        if (power != null) {
313            output.append('E');
314            output.append(power < 0 ? '-' : '+');
315            output.append(Math.abs(power) < 10 ? "0" : "");
316            output.append(Math.abs(power));
317        }
318 
319        if (fillMode) {
320            if (power != null) {
321                output.insert(0, ' ');
322            } else {
323                while (output.length() < maxLength) {
324                    output.insert(0, ' ');
325                }
326            }
327        }
328 
329        return output.toString();
330    }
331 
332    private static void addSign(StringBuilder output, int signum,
333            boolean leadingSign, boolean trailingSign, boolean trailingMinus,
334            boolean angleBrackets, boolean fillMode) {
335        if (angleBrackets) {
336            if (signum < 0) {
337                output.insert(0, '<');
338                output.append('>');
339            } else if (fillMode) {
340                output.insert(0, ' ');
341                output.append(' ');
342            }
343        } else {
344            String sign;
345            if (signum == 0) {
346                sign = "";
347            } else if (signum < 0) {
348                sign = "-";
349            } else {
350                if (leadingSign || trailingSign) {
351                    sign = "+";
352                } else if (fillMode) {
353                    sign = " ";
354                } else {
355                    sign = "";
356                }
357            }
358            if (trailingMinus || trailingSign) {
359                output.append(sign);
360            } else {
361                output.insert(0, sign);
362            }
363        }
364    }
365 
366    private static int findDecimalSeparator(String format) {
367        int index = format.indexOf('.');
368        if (index == -1) {
369            index = format.indexOf('D');
370            if (index == -1) {
371                index = format.indexOf('d');
372                if (index == -1) {
373                    index = format.length();
374                }
375            }
376        }
377        return index;
378    }
379 
380    private static int calculateScale(String format, int separator) {
381        int scale = 0;
382        for (int i = separator; i < format.length(); i++) {
383            char c = format.charAt(i);
384            if (c == '0' || c == '9') {
385                scale++;
386            }
387        }
388        return scale;
389    }
390 
391    private static String toRomanNumeral(int number) {
392        int[] values = new int[] { 1000, 900, 500, 400, 100, 90, 50, 40, 10, 9,
393                5, 4, 1 };
394        String[] numerals = new String[] { "M", "CM", "D", "CD", "C", "XC",
395                "L", "XL", "X", "IX", "V", "IV", "I" };
396        StringBuilder result = new StringBuilder();
397        for (int i = 0; i < values.length; i++) {
398            int value = values[i];
399            String numeral = numerals[i];
400            while (number >= value) {
401                result.append(numeral);
402                number -= value;
403            }
404        }
405        return result.toString();
406    }
407 
408    private static String toHex(BigDecimal number, String format) {
409 
410        boolean fillMode = !format.toUpperCase().startsWith("FM");
411        boolean uppercase = !format.contains("x");
412        boolean zeroPadded = format.startsWith("0");
413        int digits = 0;
414        for (int i = 0; i < format.length(); i++) {
415            char c = format.charAt(i);
416            if (c == '0' || c == 'X' || c == 'x') {
417                digits++;
418            }
419        }
420 
421        int i = number.setScale(0, BigDecimal.ROUND_HALF_UP).intValue();
422        String hex = Integer.toHexString(i);
423        if (digits < hex.length()) {
424            hex = StringUtils.pad("", digits + 1, "#", true);
425        } else {
426            if (uppercase) {
427                hex = hex.toUpperCase();
428            }
429            if (zeroPadded) {
430                hex = StringUtils.pad(hex, digits, "0", false);
431            }
432            if (fillMode) {
433                hex = StringUtils.pad(hex, format.length() + 1, " ", false);
434            }
435        }
436 
437        return hex;
438    }
439 
440    /**
441     * Emulates Oracle's TO_CHAR(datetime) function.
442     *
443     * <p><table border="1">
444     * <th><td>Input</td>
445     * <td>Output</td>
446     * <td>Closest {@link SimpleDateFormat} Equivalent</td></th>
447     * <tr><td>- / , . ; : "text"</td>
448     * <td>Reproduced verbatim.</td>
449     * <td>'text'</td></tr>
450     * <tr><td>A.D. AD B.C. BC</td>
451     * <td>Era designator, with or without periods.</td>
452     * <td>G</td></tr>
453     * <tr><td>A.M. AM P.M. PM</td>
454     * <td>AM/PM marker.</td>
455     * <td>a</td></tr>
456     * <tr><td>CC SCC</td>
457     * <td>Century.</td>
458     * <td>None.</td></tr>
459     * <tr><td>D</td>
460     * <td>Day of week.</td>
461     * <td>u</td></tr>
462     * <tr><td>DAY</td>
463     * <td>Name of day.</td>
464     * <td>EEEE</td></tr>
465     * <tr><td>DY</td>
466     * <td>Abbreviated day name.</td>
467     * <td>EEE</td></tr>
468     * <tr><td>DD</td>
469     * <td>Day of month.</td>
470     * <td>d</td></tr>
471     * <tr><td>DDD</td>
472     * <td>Day of year.</td>
473     * <td>D</td></tr>
474     * <tr><td>DL</td>
475     * <td>Long date format.</td>
476     * <td>EEEE, MMMM d, yyyy</td></tr>
477     * <tr><td>DS</td>
478     * <td>Short date format.</td>
479     * <td>MM/dd/yyyy</td></tr>
480     * <tr><td>E</td>
481     * <td>Abbreviated era name (Japanese, Chinese, Thai)</td>
482     * <td>None.</td></tr>
483     * <tr><td>EE</td>
484     * <td>Full era name (Japanese, Chinese, Thai)</td>
485     * <td>None.</td></tr>
486     * <tr><td>FF[1-9]</td>
487     * <td>Fractional seconds.</td>
488     * <td>S</td></tr>
489     * <tr><td>FM</td>
490     * <td>Returns values with no leading or trailing spaces.</td>
491     * <td>None.</td></tr>
492     * <tr><td>FX</td>
493     * <td>Requires exact matches between character data and format model.</td>
494     * <td>None.</td></tr>
495     * <tr><td>HH HH12</td>
496     * <td>Hour in AM/PM (1-12).</td>
497     * <td>hh</td></tr>
498     * <tr><td>HH24</td>
499     * <td>Hour in day (0-23).</td>
500     * <td>HH</td></tr>
501     * <tr><td>IW</td>
502     * <td>Week in year.</td>
503     * <td>w</td></tr>
504     * <tr><td>WW</td>
505     * <td>Week in year.</td>
506     * <td>w</td></tr>
507     * <tr><td>W</td>
508     * <td>Week in month.</td>
509     * <td>W</td></tr>
510     * <tr><td>IYYY IYY IY I</td>
511     * <td>Last 4/3/2/1 digit(s) of ISO year.</td>
512     * <td>yyyy yyy yy y</td></tr>
513     * <tr><td>RRRR RR</td>
514     * <td>Last 4/2 digits of year.</td>
515     * <td>yyyy yy</td></tr>
516     * <tr><td>Y,YYY</td>
517     * <td>Year with comma.</td>
518     * <td>None.</td></tr>
519     * <tr><td>YEAR SYEAR</td>
520     * <td>Year spelled out (S prefixes BC years with minus sign).</td>
521     * <td>None.</td></tr>
522     * <tr><td>YYYY SYYYY</td>
523     * <td>4-digit year (S prefixes BC years with minus sign).</td>
524     * <td>yyyy</td></tr>
525     * <tr><td>YYY YY Y</td>
526     * <td>Last 3/2/1 digit(s) of year.</td>
527     * <td>yyy yy y</td></tr>
528     * <tr><td>J</td>
529     * <td>Julian day (number of days since January 1, 4712 BC).</td>
530     * <td>None.</td></tr>
531     * <tr><td>MI</td>
532     * <td>Minute in hour.</td>
533     * <td>mm</td></tr>
534     * <tr><td>MM</td>
535     * <td>Month in year.</td>
536     * <td>MM</td></tr>
537     * <tr><td>MON</td>
538     * <td>Abbreviated name of month.</td>
539     * <td>MMM</td></tr>
540     * <tr><td>MONTH</td>
541     * <td>Name of month, padded with spaces.</td>
542     * <td>MMMM</td></tr>
543     * <tr><td>RM</td>
544     * <td>Roman numeral month.</td>
545     * <td>None.</td></tr>
546     * <tr><td>Q</td>
547     * <td>Quarter of year.</td>
548     * <td>None.</td></tr>
549     * <tr><td>SS</td>
550     * <td>Seconds in minute.</td>
551     * <td>ss</td></tr>
552     * <tr><td>SSSSS</td>
553     * <td>Seconds in day.</td>
554     * <td>None.</td></tr>
555     * <tr><td>TS</td>
556     * <td>Short time format.</td>
557     * <td>h:mm:ss aa</td></tr>
558     * <tr><td>TZD</td>
559     * <td>Daylight savings time zone abbreviation.</td>
560     * <td>z</td></tr>
561     * <tr><td>TZR</td>
562     * <td>Time zone region information.</td>
563     * <td>zzzz</td></tr>
564     * <tr><td>X</td>
565     * <td>Local radix character.</td>
566     * <td>None.</td></tr>
567     * </table>
568     * <p>
569     * See also TO_CHAR(datetime) and datetime format models
570     * in the Oracle documentation.
571     *
572     * @param ts the timestamp to format
573     * @param format the format pattern to use (if any)
574     * @param nlsParam the NLS parameter (if any)
575     * @return the formatted timestamp
576     */
577    public static String toChar(Timestamp ts, String format, String nlsParam) {
578 
579        if (format == null) {
580            format = "DD-MON-YY HH.MI.SS.FF PM";
581        }
582 
583        GregorianCalendar cal = new GregorianCalendar(Locale.ENGLISH);
584        cal.setTimeInMillis(ts.getTime());
585        StringBuilder output = new StringBuilder();
586        boolean fillMode = true;
587 
588        for (int i = 0; i < format.length();) {
589 
590            Capitalization cap;
591 
592                // AD / BC
593 
594            if ((cap = containsAt(format, i, "A.D.", "B.C.")) != null) {
595                String era = cal.get(Calendar.ERA) == GregorianCalendar.AD ? "A.D." : "B.C.";
596                output.append(cap.apply(era));
597                i += 4;
598            } else if ((cap = containsAt(format, i, "AD", "BC")) != null) {
599                String era = cal.get(Calendar.ERA) == GregorianCalendar.AD ? "AD" : "BC";
600                output.append(cap.apply(era));
601                i += 2;
602 
603                // AM / PM
604 
605            } else if ((cap = containsAt(format, i, "A.M.", "P.M.")) != null) {
606                String am = cal.get(Calendar.AM_PM) == Calendar.AM ? "A.M." : "P.M.";
607                output.append(cap.apply(am));
608                i += 4;
609            } else if ((cap = containsAt(format, i, "AM", "PM")) != null) {
610                String am = cal.get(Calendar.AM_PM) == Calendar.AM ? "AM" : "PM";
611                output.append(cap.apply(am));
612                i += 2;
613 
614                // Long/short date/time format
615 
616            } else if ((cap = containsAt(format, i, "DL")) != null) {
617                output.append(new SimpleDateFormat("EEEE, MMMM d, yyyy").format(ts));
618                i += 2;
619            } else if ((cap = containsAt(format, i, "DS")) != null) {
620                output.append(new SimpleDateFormat("MM/dd/yyyy").format(ts));
621                i += 2;
622            } else if ((cap = containsAt(format, i, "TS")) != null) {
623                output.append(new SimpleDateFormat("h:mm:ss aa").format(ts));
624                i += 2;
625 
626                // Day
627 
628            } else if ((cap = containsAt(format, i, "DDD")) != null) {
629                output.append(cal.get(Calendar.DAY_OF_YEAR));
630                i += 3;
631            } else if ((cap = containsAt(format, i, "DD")) != null) {
632                output.append(String.format("%02d",
633                        cal.get(Calendar.DAY_OF_MONTH)));
634                i += 2;
635            } else if ((cap = containsAt(format, i, "DY")) != null) {
636                String day = new SimpleDateFormat("EEE").format(ts).toUpperCase();
637                output.append(cap.apply(day));
638                i += 2;
639            } else if ((cap = containsAt(format, i, "DAY")) != null) {
640                String day = new SimpleDateFormat("EEEE").format(ts);
641                if (fillMode) {
642                    day = StringUtils.pad(day, "Wednesday".length(), " ", true);
643                }
644                output.append(cap.apply(day));
645                i += 3;
646            } else if ((cap = containsAt(format, i, "D")) != null) {
647                output.append(cal.get(Calendar.DAY_OF_WEEK));
648                i += 1;
649            } else if ((cap = containsAt(format, i, "J")) != null) {
650                long millis = ts.getTime() - JULIAN_EPOCH;
651                long days = (long) Math.floor(millis / (1000 * 60 * 60 * 24));
652                output.append(days);
653                i += 1;
654 
655                // Hours
656 
657            } else if ((cap = containsAt(format, i, "HH24")) != null) {
658                output.append(new DecimalFormat("00").format(cal.get(Calendar.HOUR_OF_DAY)));
659                i += 4;
660            } else if ((cap = containsAt(format, i, "HH12")) != null) {
661                output.append(new DecimalFormat("00").format(cal.get(Calendar.HOUR)));
662                i += 4;
663            } else if ((cap = containsAt(format, i, "HH")) != null) {
664                output.append(new DecimalFormat("00").format(cal.get(Calendar.HOUR)));
665                i += 2;
666 
667                // Minutes
668 
669            } else if ((cap = containsAt(format, i, "MI")) != null) {
670                output.append(new DecimalFormat("00").format(cal.get(Calendar.MINUTE)));
671                i += 2;
672 
673                // Seconds
674 
675            } else if ((cap = containsAt(format, i, "SSSSS")) != null) {
676                int seconds = cal.get(Calendar.HOUR_OF_DAY) * 60 * 60;
677                seconds += cal.get(Calendar.MINUTE) * 60;
678                seconds += cal.get(Calendar.SECOND);
679                output.append(seconds);
680                i += 5;
681            } else if ((cap = containsAt(format, i, "SS")) != null) {
682                output.append(new DecimalFormat("00").format(cal.get(Calendar.SECOND)));
683                i += 2;
684 
685                // Fractional seconds
686 
687            } else if ((cap = containsAt(format, i, "FF1", "FF2",
688                    "FF3", "FF4", "FF5", "FF6", "FF7", "FF8", "FF9")) != null) {
689                int x = Integer.parseInt(format.substring(i + 2, i + 3));
690                int ff = (int) (cal.get(Calendar.MILLISECOND) * Math.pow(10, x - 3));
691                output.append(ff);
692                i += 3;
693            } else if ((cap = containsAt(format, i, "FF")) != null) {
694                output.append(cal.get(Calendar.MILLISECOND) * 1000);
695                i += 2;
696 
697                // Time zone
698 
699            } else if ((cap = containsAt(format, i, "TZR")) != null) {
700                TimeZone tz = TimeZone.getDefault();
701                output.append(tz.getID());
702                i += 3;
703            } else if ((cap = containsAt(format, i, "TZD")) != null) {
704                TimeZone tz = TimeZone.getDefault();
705                boolean daylight = tz.inDaylightTime(new java.util.Date());
706                output.append(tz.getDisplayName(daylight, TimeZone.SHORT));
707                i += 3;
708 
709                // Week
710 
711            } else if ((cap = containsAt(format, i, "IW", "WW")) != null) {
712                output.append(cal.get(Calendar.WEEK_OF_YEAR));
713                i += 2;
714            } else if ((cap = containsAt(format, i, "W")) != null) {
715                int w = (int) (1 + Math.floor(cal.get(Calendar.DAY_OF_MONTH) / 7));
716                output.append(w);
717                i += 1;
718 
719                // Year
720 
721            } else if ((cap = containsAt(format, i, "Y,YYY")) != null) {
722                output.append(new DecimalFormat("#,###").format(getYear(cal)));
723                i += 5;
724            } else if ((cap = containsAt(format, i, "SYYYY")) != null) {
725                if (cal.get(Calendar.ERA) == GregorianCalendar.BC) {
726                    output.append('-');
727                }
728                output.append(new DecimalFormat("0000").format(getYear(cal)));
729                i += 5;
730            } else if ((cap = containsAt(format, i, "YYYY", "IYYY", "RRRR")) != null) {
731                output.append(new DecimalFormat("0000").format(getYear(cal)));
732                i += 4;
733            } else if ((cap = containsAt(format, i, "YYY", "IYY")) != null) {
734                output.append(new DecimalFormat("000").format(getYear(cal) % 1000));
735                i += 3;
736            } else if ((cap = containsAt(format, i, "YY", "IY", "RR")) != null) {
737                output.append(new DecimalFormat("00").format(getYear(cal) % 100));
738                i += 2;
739            } else if ((cap = containsAt(format, i, "I", "Y")) != null) {
740                output.append(getYear(cal) % 10);
741                i += 1;
742 
743                // Month / quarter
744 
745            } else if ((cap = containsAt(format, i, "MONTH")) != null) {
746                String month = new SimpleDateFormat("MMMM").format(ts);
747                if (fillMode) {
748                    month = StringUtils.pad(month, "September".length(), " ", true);
749                }
750                output.append(cap.apply(month));
751                i += 5;
752            } else if ((cap = containsAt(format, i, "MON")) != null) {
753                String month = new SimpleDateFormat("MMM").format(ts);
754                output.append(cap.apply(month));
755                i += 3;
756            } else if ((cap = containsAt(format, i, "MM")) != null) {
757                output.append(String.format("%02d", cal.get(Calendar.MONTH) + 1));
758                i += 2;
759            } else if ((cap = containsAt(format, i, "RM")) != null) {
760                int month = cal.get(Calendar.MONTH) + 1;
761                output.append(cap.apply(toRomanNumeral(month)));
762                i += 2;
763            } else if ((cap = containsAt(format, i, "Q")) != null) {
764                int q = (int) (1 + Math.floor(cal.get(Calendar.MONTH) / 3));
765                output.append(q);
766                i += 1;
767 
768                // Local radix character
769 
770            } else if ((cap = containsAt(format, i, "X")) != null) {
771                char c = DecimalFormatSymbols.getInstance().getDecimalSeparator();
772                output.append(c);
773                i += 1;
774 
775                // Format modifiers
776 
777            } else if ((cap = containsAt(format, i, "FM")) != null) {
778                fillMode = !fillMode;
779                i += 2;
780            } else if ((cap = containsAt(format, i, "FX")) != null) {
781                i += 2;
782 
783                // Literal text
784 
785            } else if ((cap = containsAt(format, i, "\"")) != null) {
786                for (i = i + 1; i < format.length(); i++) {
787                    char c = format.charAt(i);
788                    if (c != '"') {
789                        output.append(c);
790                    } else {
791                        i++;
792                        break;
793                    }
794                }
795            } else if (format.charAt(i) == '-'
796                    || format.charAt(i) == '/'
797                    || format.charAt(i) == ','
798                    || format.charAt(i) == '.'
799                    || format.charAt(i) == ';'
800                    || format.charAt(i) == ':'
801                    || format.charAt(i) == ' ') {
802                output.append(format.charAt(i));
803                i += 1;
804 
805                // Anything else
806 
807            } else {
808                throw DbException.get(ErrorCode.INVALID_TO_CHAR_FORMAT, format);
809            }
810        }
811 
812        return output.toString();
813    }
814 
815    private static int getYear(Calendar cal) {
816        int year = cal.get(Calendar.YEAR);
817        if (cal.get(Calendar.ERA) == GregorianCalendar.BC) {
818            year--;
819        }
820        return year;
821    }
822 
823    /**
824     * Returns a capitalization strategy if the specified string contains any of
825     * the specified substrings at the specified index. The capitalization
826     * strategy indicates the casing of the substring that was found. If none of
827     * the specified substrings are found, this method returns <code>null</code>
828     * .
829     *
830     * @param s the string to check
831     * @param index the index to check at
832     * @param substrings the substrings to check for within the string
833     * @return a capitalization strategy if the specified string contains any of
834     *         the specified substrings at the specified index,
835     *         <code>null</code> otherwise
836     */
837    private static Capitalization containsAt(String s, int index,
838            String... substrings) {
839        for (String substring : substrings) {
840            if (index + substring.length() <= s.length()) {
841                boolean found = true;
842                Boolean up1 = null;
843                Boolean up2 = null;
844                for (int i = 0; i < substring.length(); i++) {
845                    char c1 = s.charAt(index + i);
846                    char c2 = substring.charAt(i);
847                    if (c1 != c2 && Character.toUpperCase(c1) != Character.toUpperCase(c2)) {
848                        found = false;
849                        break;
850                    } else if (Character.isLetter(c1)) {
851                        if (up1 == null) {
852                            up1 = Character.isUpperCase(c1);
853                        } else if (up2 == null) {
854                            up2 = Character.isUpperCase(c1);
855                        }
856                    }
857                }
858                if (found) {
859                    return Capitalization.toCapitalization(up1, up2);
860                }
861            }
862        }
863        return null;
864    }
865 
866    /** Represents a capitalization / casing strategy. */
867    private enum Capitalization {
868 
869        /**
870         * All letters are uppercased.
871         */
872        UPPERCASE,
873 
874        /**
875         * All letters are lowercased.
876         */
877        LOWERCASE,
878 
879        /**
880         * The string is capitalized (first letter uppercased, subsequent
881         * letters lowercased).
882         */
883        CAPITALIZE;
884 
885        /**
886         * Returns the capitalization / casing strategy which should be used
887         * when the first and second letters have the specified casing.
888         *
889         * @param up1 whether or not the first letter is uppercased
890         * @param up2 whether or not the second letter is uppercased
891         * @return the capitalization / casing strategy which should be used
892         *         when the first and second letters have the specified casing
893         */
894        public static Capitalization toCapitalization(Boolean up1, Boolean up2) {
895            if (up1 == null) {
896                return Capitalization.CAPITALIZE;
897            } else if (up2 == null) {
898                return up1 ? Capitalization.UPPERCASE : Capitalization.LOWERCASE;
899            } else if (up1) {
900                return up2 ? Capitalization.UPPERCASE : Capitalization.CAPITALIZE;
901            } else {
902                return Capitalization.LOWERCASE;
903            }
904        }
905 
906        /**
907         * Applies this capitalization strategy to the specified string.
908         *
909         * @param s the string to apply this strategy to
910         * @return the resultant string
911         */
912        public String apply(String s) {
913            if (s == null || s.isEmpty()) {
914                return s;
915            }
916            switch (this) {
917            case UPPERCASE:
918                return s.toUpperCase();
919            case LOWERCASE:
920                return s.toLowerCase();
921            case CAPITALIZE:
922                return Character.toUpperCase(s.charAt(0)) +
923                        (s.length() > 1 ? s.toLowerCase().substring(1) : "");
924            default:
925                throw new IllegalArgumentException(
926                        "Unknown capitalization strategy: " + this);
927            }
928        }
929    }
930}

[all classes][org.h2.util]
EMMA 2.0.5312 (C) Vladimir Roubtsov