View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.logging.log4j.core.util.datetime;
18  
19  import java.io.IOException;
20  import java.io.ObjectInputStream;
21  import java.io.Serializable;
22  import java.text.DateFormat;
23  import java.text.DateFormatSymbols;
24  import java.text.FieldPosition;
25  import java.util.ArrayList;
26  import java.util.Calendar;
27  import java.util.Date;
28  import java.util.GregorianCalendar;
29  import java.util.List;
30  import java.util.Locale;
31  import java.util.TimeZone;
32  import java.util.concurrent.ConcurrentHashMap;
33  import java.util.concurrent.ConcurrentMap;
34  
35  /**
36   * Copied from Commons Lang 3
37   *
38   */
39  public class FastDatePrinter implements DatePrinter, Serializable {
40      // A lot of the speed in this class comes from caching, but some comes
41      // from the special int to StringBuilder conversion.
42      //
43      // The following produces a padded 2 digit number:
44      //   buffer.append((char)(value / 10 + '0'));
45      //   buffer.append((char)(value % 10 + '0'));
46      //
47      // Note that the fastest append to StringBuilder is a single char (used here).
48      // Note that Integer.toString() is not called, the conversion is simply
49      // taking the value and adding (mathematically) the ASCII value for '0'.
50      // So, don't change this code! It works and is very fast.
51  
52      /**
53       * Required for serialization support.
54       *
55       * @see java.io.Serializable
56       */
57      private static final long serialVersionUID = 1L;
58  
59      /**
60       * FULL locale dependent date or time style.
61       */
62      public static final int FULL = DateFormat.FULL;
63      /**
64       * LONG locale dependent date or time style.
65       */
66      public static final int LONG = DateFormat.LONG;
67      /**
68       * MEDIUM locale dependent date or time style.
69       */
70      public static final int MEDIUM = DateFormat.MEDIUM;
71      /**
72       * SHORT locale dependent date or time style.
73       */
74      public static final int SHORT = DateFormat.SHORT;
75  
76      /**
77       * The pattern.
78       */
79      private final String mPattern;
80      /**
81       * The time zone.
82       */
83      private final TimeZone mTimeZone;
84      /**
85       * The locale.
86       */
87      private final Locale mLocale;
88      /**
89       * The parsed rules.
90       */
91      private transient Rule[] mRules;
92      /**
93       * The estimated maximum length.
94       */
95      private transient int mMaxLengthEstimate;
96  
97      // Constructor
98      //-----------------------------------------------------------------------
99      /**
100      * <p>Constructs a new FastDatePrinter.</p>
101      * Use {@link FastDateFormat#getInstance(String, TimeZone, Locale)}  or another variation of the 
102      * factory methods of {@link FastDateFormat} to get a cached FastDatePrinter instance.
103      *
104      * @param pattern  {@link java.text.SimpleDateFormat} compatible pattern
105      * @param timeZone  non-null time zone to use
106      * @param locale  non-null locale to use
107      * @throws NullPointerException if pattern, timeZone, or locale is null.
108      */
109     protected FastDatePrinter(final String pattern, final TimeZone timeZone, final Locale locale) {
110         mPattern = pattern;
111         mTimeZone = timeZone;
112         mLocale = locale;
113 
114         init();
115     }
116 
117     /**
118      * <p>Initializes the instance for first use.</p>
119      */
120     private void init() {
121         final List<Rule> rulesList = parsePattern();
122         mRules = rulesList.toArray(new Rule[rulesList.size()]);
123 
124         int len = 0;
125         for (int i=mRules.length; --i >= 0; ) {
126             len += mRules[i].estimateLength();
127         }
128 
129         mMaxLengthEstimate = len;
130     }
131 
132     // Parse the pattern
133     //-----------------------------------------------------------------------
134     /**
135      * <p>Returns a list of Rules given a pattern.</p>
136      *
137      * @return a {@code List} of Rule objects
138      * @throws IllegalArgumentException if pattern is invalid
139      */
140     protected List<Rule> parsePattern() {
141         final DateFormatSymbols symbols = new DateFormatSymbols(mLocale);
142         final List<Rule> rules = new ArrayList<Rule>();
143 
144         final String[] ERAs = symbols.getEras();
145         final String[] months = symbols.getMonths();
146         final String[] shortMonths = symbols.getShortMonths();
147         final String[] weekdays = symbols.getWeekdays();
148         final String[] shortWeekdays = symbols.getShortWeekdays();
149         final String[] AmPmStrings = symbols.getAmPmStrings();
150 
151         final int length = mPattern.length();
152         final int[] indexRef = new int[1];
153 
154         for (int i = 0; i < length; i++) {
155             indexRef[0] = i;
156             final String token = parseToken(mPattern, indexRef);
157             i = indexRef[0];
158 
159             final int tokenLen = token.length();
160             if (tokenLen == 0) {
161                 break;
162             }
163 
164             Rule rule;
165             final char c = token.charAt(0);
166 
167             switch (c) {
168             case 'G': // era designator (text)
169                 rule = new TextField(Calendar.ERA, ERAs);
170                 break;
171             case 'y': // year (number)
172                 if (tokenLen == 2) {
173                     rule = TwoDigitYearField.INSTANCE;
174                 } else {
175                     rule = selectNumberRule(Calendar.YEAR, tokenLen < 4 ? 4 : tokenLen);
176                 }
177                 break;
178             case 'M': // month in year (text and number)
179                 if (tokenLen >= 4) {
180                     rule = new TextField(Calendar.MONTH, months);
181                 } else if (tokenLen == 3) {
182                     rule = new TextField(Calendar.MONTH, shortMonths);
183                 } else if (tokenLen == 2) {
184                     rule = TwoDigitMonthField.INSTANCE;
185                 } else {
186                     rule = UnpaddedMonthField.INSTANCE;
187                 }
188                 break;
189             case 'd': // day in month (number)
190                 rule = selectNumberRule(Calendar.DAY_OF_MONTH, tokenLen);
191                 break;
192             case 'h': // hour in am/pm (number, 1..12)
193                 rule = new TwelveHourField(selectNumberRule(Calendar.HOUR, tokenLen));
194                 break;
195             case 'H': // hour in day (number, 0..23)
196                 rule = selectNumberRule(Calendar.HOUR_OF_DAY, tokenLen);
197                 break;
198             case 'm': // minute in hour (number)
199                 rule = selectNumberRule(Calendar.MINUTE, tokenLen);
200                 break;
201             case 's': // second in minute (number)
202                 rule = selectNumberRule(Calendar.SECOND, tokenLen);
203                 break;
204             case 'S': // millisecond (number)
205                 rule = selectNumberRule(Calendar.MILLISECOND, tokenLen);
206                 break;
207             case 'E': // day in week (text)
208                 rule = new TextField(Calendar.DAY_OF_WEEK, tokenLen < 4 ? shortWeekdays : weekdays);
209                 break;
210             case 'D': // day in year (number)
211                 rule = selectNumberRule(Calendar.DAY_OF_YEAR, tokenLen);
212                 break;
213             case 'F': // day of week in month (number)
214                 rule = selectNumberRule(Calendar.DAY_OF_WEEK_IN_MONTH, tokenLen);
215                 break;
216             case 'w': // week in year (number)
217                 rule = selectNumberRule(Calendar.WEEK_OF_YEAR, tokenLen);
218                 break;
219             case 'W': // week in month (number)
220                 rule = selectNumberRule(Calendar.WEEK_OF_MONTH, tokenLen);
221                 break;
222             case 'a': // am/pm marker (text)
223                 rule = new TextField(Calendar.AM_PM, AmPmStrings);
224                 break;
225             case 'k': // hour in day (1..24)
226                 rule = new TwentyFourHourField(selectNumberRule(Calendar.HOUR_OF_DAY, tokenLen));
227                 break;
228             case 'K': // hour in am/pm (0..11)
229                 rule = selectNumberRule(Calendar.HOUR, tokenLen);
230                 break;
231             case 'X': // ISO 8601 
232                 rule = Iso8601_Rule.getRule(tokenLen);
233                 break;    
234             case 'z': // time zone (text)
235                 if (tokenLen >= 4) {
236                     rule = new TimeZoneNameRule(mTimeZone, mLocale, TimeZone.LONG);
237                 } else {
238                     rule = new TimeZoneNameRule(mTimeZone, mLocale, TimeZone.SHORT);
239                 }
240                 break;
241             case 'Z': // time zone (value)
242                 if (tokenLen == 1) {
243                     rule = TimeZoneNumberRule.INSTANCE_NO_COLON;
244                 } else if (tokenLen == 2) {
245                     rule = Iso8601_Rule.ISO8601_HOURS_COLON_MINUTES;
246                 } else {
247                     rule = TimeZoneNumberRule.INSTANCE_COLON;
248                 }
249                 break;
250             case '\'': // literal text
251                 final String sub = token.substring(1);
252                 if (sub.length() == 1) {
253                     rule = new CharacterLiteral(sub.charAt(0));
254                 } else {
255                     rule = new StringLiteral(sub);
256                 }
257                 break;
258             default:
259                 throw new IllegalArgumentException("Illegal pattern component: " + token);
260             }
261 
262             rules.add(rule);
263         }
264 
265         return rules;
266     }
267 
268     /**
269      * <p>Performs the parsing of tokens.</p>
270      *
271      * @param pattern  the pattern
272      * @param indexRef  index references
273      * @return parsed token
274      */
275     protected String parseToken(final String pattern, final int[] indexRef) {
276         final StringBuilder buf = new StringBuilder();
277 
278         int i = indexRef[0];
279         final int length = pattern.length();
280 
281         char c = pattern.charAt(i);
282         if (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z') {
283             // Scan a run of the same character, which indicates a time
284             // pattern.
285             buf.append(c);
286 
287             while (i + 1 < length) {
288                 final char peek = pattern.charAt(i + 1);
289                 if (peek == c) {
290                     buf.append(c);
291                     i++;
292                 } else {
293                     break;
294                 }
295             }
296         } else {
297             // This will identify token as text.
298             buf.append('\'');
299 
300             boolean inLiteral = false;
301 
302             for (; i < length; i++) {
303                 c = pattern.charAt(i);
304 
305                 if (c == '\'') {
306                     if (i + 1 < length && pattern.charAt(i + 1) == '\'') {
307                         // '' is treated as escaped '
308                         i++;
309                         buf.append(c);
310                     } else {
311                         inLiteral = !inLiteral;
312                     }
313                 } else if (!inLiteral &&
314                          (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z')) {
315                     i--;
316                     break;
317                 } else {
318                     buf.append(c);
319                 }
320             }
321         }
322 
323         indexRef[0] = i;
324         return buf.toString();
325     }
326 
327     /**
328      * <p>Gets an appropriate rule for the padding required.</p>
329      *
330      * @param field  the field to get a rule for
331      * @param padding  the padding required
332      * @return a new rule with the correct padding
333      */
334     protected NumberRule selectNumberRule(final int field, final int padding) {
335         switch (padding) {
336         case 1:
337             return new UnpaddedNumberField(field);
338         case 2:
339             return new TwoDigitNumberField(field);
340         default:
341             return new PaddedNumberField(field, padding);
342         }
343     }
344 
345     // Format methods
346     //-----------------------------------------------------------------------
347     /**
348      * <p>Formats a {@code Date}, {@code Calendar} or
349      * {@code Long} (milliseconds) object.</p>
350      *
351      * @param obj  the object to format
352      * @param toAppendTo  the buffer to append to
353      * @param pos  the position - ignored
354      * @return the buffer passed in
355      */
356     @Override
357     public StringBuilder format(final Object obj, final StringBuilder toAppendTo, final FieldPosition pos) {
358         if (obj instanceof Date) {
359             return format((Date) obj, toAppendTo);
360         } else if (obj instanceof Calendar) {
361             return format((Calendar) obj, toAppendTo);
362         } else if (obj instanceof Long) {
363             return format(((Long) obj).longValue(), toAppendTo);
364         } else {
365             throw new IllegalArgumentException("Unknown class: " +
366                 (obj == null ? "<null>" : obj.getClass().getName()));
367         }
368     }
369 
370     /* (non-Javadoc)
371      * @see org.apache.commons.lang3.time.DatePrinter#format(long)
372      */
373     @Override
374     public String format(final long millis) {
375         final Calendar c = newCalendar();  // hard code GregorianCalendar
376         c.setTimeInMillis(millis);
377         return applyRulesToString(c);
378     }
379 
380     /**
381      * Creates a String representation of the given Calendar by applying the rules of this printer to it.
382      * @param c the Calender to apply the rules to.
383      * @return a String representation of the given Calendar.
384      */
385     private String applyRulesToString(final Calendar c) {
386         return applyRules(c, new StringBuilder(mMaxLengthEstimate)).toString();
387     }
388 
389     /**
390      * Creation method for ne calender instances.
391      * @return a new Calendar instance.
392      */
393     private GregorianCalendar newCalendar() {
394         // hard code GregorianCalendar
395         return new GregorianCalendar(mTimeZone, mLocale);
396     }
397 
398     /* (non-Javadoc)
399      * @see org.apache.commons.lang3.time.DatePrinter#format(java.util.Date)
400      */
401     @Override
402     public String format(final Date date) {
403         final Calendar c = newCalendar();  // hard code GregorianCalendar
404         c.setTime(date);
405         return applyRulesToString(c);
406     }
407 
408     /* (non-Javadoc)
409      * @see org.apache.commons.lang3.time.DatePrinter#format(java.util.Calendar)
410      */
411     @Override
412     public String format(final Calendar calendar) {
413         return format(calendar, new StringBuilder(mMaxLengthEstimate)).toString();
414     }
415 
416     /* (non-Javadoc)
417      * @see org.apache.commons.lang3.time.DatePrinter#format(long, java.lang.StringBuilder)
418      */
419     @Override
420     public StringBuilder format(final long millis, final StringBuilder buf) {
421         return format(new Date(millis), buf);
422     }
423 
424     /* (non-Javadoc)
425      * @see org.apache.commons.lang3.time.DatePrinter#format(java.util.Date, java.lang.StringBuilder)
426      */
427     @Override
428     public StringBuilder format(final Date date, final StringBuilder buf) {
429         final Calendar c = newCalendar();  // hard code GregorianCalendar
430         c.setTime(date);
431         return applyRules(c, buf);
432     }
433 
434     /* (non-Javadoc)
435      * @see org.apache.commons.lang3.time.DatePrinter#format(java.util.Calendar, java.lang.StringBuilder)
436      */
437     @Override
438     public StringBuilder format(final Calendar calendar, final StringBuilder buf) {
439         // do not pass in calendar directly, this will cause TimeZone of FastDatePrinter to be ignored
440         return format(calendar.getTime(), buf);
441     }
442 
443     /**
444      * <p>Performs the formatting by applying the rules to the
445      * specified calendar.</p>
446      *
447      * @param calendar  the calendar to format
448      * @param buf  the buffer to format into
449      * @return the specified string buffer
450      */
451     protected StringBuilder applyRules(final Calendar calendar, final StringBuilder buf) {
452         for (final Rule rule : mRules) {
453             rule.appendTo(buf, calendar);
454         }
455         return buf;
456     }
457 
458     // Accessors
459     //-----------------------------------------------------------------------
460     /* (non-Javadoc)
461      * @see org.apache.commons.lang3.time.DatePrinter#getPattern()
462      */
463     @Override
464     public String getPattern() {
465         return mPattern;
466     }
467 
468     /* (non-Javadoc)
469      * @see org.apache.commons.lang3.time.DatePrinter#getTimeZone()
470      */
471     @Override
472     public TimeZone getTimeZone() {
473         return mTimeZone;
474     }
475 
476     /* (non-Javadoc)
477      * @see org.apache.commons.lang3.time.DatePrinter#getLocale()
478      */
479     @Override
480     public Locale getLocale() {
481         return mLocale;
482     }
483 
484     /**
485      * <p>Gets an estimate for the maximum string length that the
486      * formatter will produce.</p>
487      *
488      * <p>The actual formatted length will almost always be less than or
489      * equal to this amount.</p>
490      *
491      * @return the maximum formatted length
492      */
493     public int getMaxLengthEstimate() {
494         return mMaxLengthEstimate;
495     }
496 
497     // Basics
498     //-----------------------------------------------------------------------
499     /**
500      * <p>Compares two objects for equality.</p>
501      *
502      * @param obj  the object to compare to
503      * @return {@code true} if equal
504      */
505     @Override
506     public boolean equals(final Object obj) {
507         if (obj instanceof FastDatePrinter == false) {
508             return false;
509         }
510         final FastDatePrinter other = (FastDatePrinter) obj;
511         return mPattern.equals(other.mPattern)
512             && mTimeZone.equals(other.mTimeZone) 
513             && mLocale.equals(other.mLocale);
514     }
515 
516     /**
517      * <p>Returns a hashcode compatible with equals.</p>
518      *
519      * @return a hashcode compatible with equals
520      */
521     @Override
522     public int hashCode() {
523         return mPattern.hashCode() + 13 * (mTimeZone.hashCode() + 13 * mLocale.hashCode());
524     }
525 
526     /**
527      * <p>Gets a debugging string version of this formatter.</p>
528      *
529      * @return a debugging string
530      */
531     @Override
532     public String toString() {
533         return "FastDatePrinter[" + mPattern + "," + mLocale + "," + mTimeZone.getID() + "]";
534     }
535 
536     // Serializing
537     //-----------------------------------------------------------------------
538     /**
539      * Create the object after serialization. This implementation reinitializes the
540      * transient properties.
541      *
542      * @param in ObjectInputStream from which the object is being deserialized.
543      * @throws IOException if there is an IO issue.
544      * @throws ClassNotFoundException if a class cannot be found.
545      */
546     private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
547         in.defaultReadObject();
548         init();
549     }
550 
551     /**
552      * Appends digits to the given buffer.
553      * 
554      * @param buffer the buffer to append to.
555      * @param value the value to append digits from.
556      */
557     private static void appendDigits(final StringBuilder buffer, final int value) {
558         buffer.append((char)(value / 10 + '0'));
559         buffer.append((char)(value % 10 + '0'));
560     }
561 
562     // Rules
563     //-----------------------------------------------------------------------
564     /**
565      * <p>Inner class defining a rule.</p>
566      */
567     private interface Rule {
568         /**
569          * Returns the estimated length of the result.
570          *
571          * @return the estimated length
572          */
573         int estimateLength();
574 
575         /**
576          * Appends the value of the specified calendar to the output buffer based on the rule implementation.
577          *
578          * @param buffer the output buffer
579          * @param calendar calendar to be appended
580          */
581         void appendTo(StringBuilder buffer, Calendar calendar);
582     }
583 
584     /**
585      * <p>Inner class defining a numeric rule.</p>
586      */
587     private interface NumberRule extends Rule {
588         /**
589          * Appends the specified value to the output buffer based on the rule implementation.
590          *
591          * @param buffer the output buffer
592          * @param value the value to be appended
593          */
594         void appendTo(StringBuilder buffer, int value);
595     }
596 
597     /**
598      * <p>Inner class to output a constant single character.</p>
599      */
600     private static class CharacterLiteral implements Rule {
601         private final char mValue;
602 
603         /**
604          * Constructs a new instance of {@code CharacterLiteral}
605          * to hold the specified value.
606          *
607          * @param value the character literal
608          */
609         CharacterLiteral(final char value) {
610             mValue = value;
611         }
612 
613         /**
614          * {@inheritDoc}
615          */
616         @Override
617         public int estimateLength() {
618             return 1;
619         }
620 
621         /**
622          * {@inheritDoc}
623          */
624         @Override
625         public void appendTo(final StringBuilder buffer, final Calendar calendar) {
626             buffer.append(mValue);
627         }
628     }
629 
630     /**
631      * <p>Inner class to output a constant string.</p>
632      */
633     private static class StringLiteral implements Rule {
634         private final String mValue;
635 
636         /**
637          * Constructs a new instance of {@code StringLiteral}
638          * to hold the specified value.
639          *
640          * @param value the string literal
641          */
642         StringLiteral(final String value) {
643             mValue = value;
644         }
645 
646         /**
647          * {@inheritDoc}
648          */
649         @Override
650         public int estimateLength() {
651             return mValue.length();
652         }
653 
654         /**
655          * {@inheritDoc}
656          */
657         @Override
658         public void appendTo(final StringBuilder buffer, final Calendar calendar) {
659             buffer.append(mValue);
660         }
661     }
662 
663     /**
664      * <p>Inner class to output one of a set of values.</p>
665      */
666     private static class TextField implements Rule {
667         private final int mField;
668         private final String[] mValues;
669 
670         /**
671          * Constructs an instance of {@code TextField}
672          * with the specified field and values.
673          *
674          * @param field the field
675          * @param values the field values
676          */
677         TextField(final int field, final String[] values) {
678             mField = field;
679             mValues = values;
680         }
681 
682         /**
683          * {@inheritDoc}
684          */
685         @Override
686         public int estimateLength() {
687             int max = 0;
688             for (int i=mValues.length; --i >= 0; ) {
689                 final int len = mValues[i].length();
690                 if (len > max) {
691                     max = len;
692                 }
693             }
694             return max;
695         }
696 
697         /**
698          * {@inheritDoc}
699          */
700         @Override
701         public void appendTo(final StringBuilder buffer, final Calendar calendar) {
702             buffer.append(mValues[calendar.get(mField)]);
703         }
704     }
705 
706     /**
707      * <p>Inner class to output an unpadded number.</p>
708      */
709     private static class UnpaddedNumberField implements NumberRule {
710         private final int mField;
711 
712         /**
713          * Constructs an instance of {@code UnpadedNumberField} with the specified field.
714          *
715          * @param field the field
716          */
717         UnpaddedNumberField(final int field) {
718             mField = field;
719         }
720 
721         /**
722          * {@inheritDoc}
723          */
724         @Override
725         public int estimateLength() {
726             return 4;
727         }
728 
729         /**
730          * {@inheritDoc}
731          */
732         @Override
733         public void appendTo(final StringBuilder buffer, final Calendar calendar) {
734             appendTo(buffer, calendar.get(mField));
735         }
736 
737         /**
738          * {@inheritDoc}
739          */
740         @Override
741         public final void appendTo(final StringBuilder buffer, final int value) {
742             if (value < 10) {
743                 buffer.append((char)(value + '0'));
744             } else if (value < 100) {
745                 appendDigits(buffer, value);
746             } else {
747                 buffer.append(value);
748             }
749         }
750     }
751 
752     /**
753      * <p>Inner class to output an unpadded month.</p>
754      */
755     private static class UnpaddedMonthField implements NumberRule {
756         static final UnpaddedMonthField INSTANCE = new UnpaddedMonthField();
757 
758         /**
759          * Constructs an instance of {@code UnpaddedMonthField}.
760          *
761          */
762         UnpaddedMonthField() {
763             super();
764         }
765 
766         /**
767          * {@inheritDoc}
768          */
769         @Override
770         public int estimateLength() {
771             return 2;
772         }
773 
774         /**
775          * {@inheritDoc}
776          */
777         @Override
778         public void appendTo(final StringBuilder buffer, final Calendar calendar) {
779             appendTo(buffer, calendar.get(Calendar.MONTH) + 1);
780         }
781 
782         /**
783          * {@inheritDoc}
784          */
785         @Override
786         public final void appendTo(final StringBuilder buffer, final int value) {
787             if (value < 10) {
788                 buffer.append((char)(value + '0'));
789             } else {
790                 appendDigits(buffer, value);
791             }
792         }
793     }
794 
795     /**
796      * <p>Inner class to output a padded number.</p>
797      */
798     private static class PaddedNumberField implements NumberRule {
799         private final int mField;
800         private final int mSize;
801 
802         /**
803          * Constructs an instance of {@code PaddedNumberField}.
804          *
805          * @param field the field
806          * @param size size of the output field
807          */
808         PaddedNumberField(final int field, final int size) {
809             if (size < 3) {
810                 // Should use UnpaddedNumberField or TwoDigitNumberField.
811                 throw new IllegalArgumentException();
812             }
813             mField = field;
814             mSize = size;
815         }
816 
817         /**
818          * {@inheritDoc}
819          */
820         @Override
821         public int estimateLength() {
822             return mSize;
823         }
824 
825         /**
826          * {@inheritDoc}
827          */
828         @Override
829         public void appendTo(final StringBuilder buffer, final Calendar calendar) {
830             appendTo(buffer, calendar.get(mField));
831         }
832 
833         /**
834          * {@inheritDoc}
835          */
836         @Override
837         public final void appendTo(final StringBuilder buffer, int value) {
838             // pad the buffer with adequate zeros
839             for(int digit = 0; digit<mSize; ++digit) {
840                 buffer.append('0');                
841             }
842             // backfill the buffer with non-zero digits
843             int index = buffer.length();
844             for( ; value>0; value /= 10) {
845                 buffer.setCharAt(--index, (char)('0' + value % 10));
846             }
847         }
848     }
849 
850     /**
851      * <p>Inner class to output a two digit number.</p>
852      */
853     private static class TwoDigitNumberField implements NumberRule {
854         private final int mField;
855 
856         /**
857          * Constructs an instance of {@code TwoDigitNumberField} with the specified field.
858          *
859          * @param field the field
860          */
861         TwoDigitNumberField(final int field) {
862             mField = field;
863         }
864 
865         /**
866          * {@inheritDoc}
867          */
868         @Override
869         public int estimateLength() {
870             return 2;
871         }
872 
873         /**
874          * {@inheritDoc}
875          */
876         @Override
877         public void appendTo(final StringBuilder buffer, final Calendar calendar) {
878             appendTo(buffer, calendar.get(mField));
879         }
880 
881         /**
882          * {@inheritDoc}
883          */
884         @Override
885         public final void appendTo(final StringBuilder buffer, final int value) {
886             if (value < 100) {
887                 appendDigits(buffer, value);
888             } else {
889                 buffer.append(value);
890             }
891         }
892     }
893 
894     /**
895      * <p>Inner class to output a two digit year.</p>
896      */
897     private static class TwoDigitYearField implements NumberRule {
898         static final TwoDigitYearField INSTANCE = new TwoDigitYearField();
899 
900         /**
901          * Constructs an instance of {@code TwoDigitYearField}.
902          */
903         TwoDigitYearField() {
904             super();
905         }
906 
907         /**
908          * {@inheritDoc}
909          */
910         @Override
911         public int estimateLength() {
912             return 2;
913         }
914 
915         /**
916          * {@inheritDoc}
917          */
918         @Override
919         public void appendTo(final StringBuilder buffer, final Calendar calendar) {
920             appendTo(buffer, calendar.get(Calendar.YEAR) % 100);
921         }
922 
923         /**
924          * {@inheritDoc}
925          */
926         @Override
927         public final void appendTo(final StringBuilder buffer, final int value) {
928             appendDigits(buffer, value);
929         }
930     }
931 
932     /**
933      * <p>Inner class to output a two digit month.</p>
934      */
935     private static class TwoDigitMonthField implements NumberRule {
936         static final TwoDigitMonthField INSTANCE = new TwoDigitMonthField();
937 
938         /**
939          * Constructs an instance of {@code TwoDigitMonthField}.
940          */
941         TwoDigitMonthField() {
942             super();
943         }
944 
945         /**
946          * {@inheritDoc}
947          */
948         @Override
949         public int estimateLength() {
950             return 2;
951         }
952 
953         /**
954          * {@inheritDoc}
955          */
956         @Override
957         public void appendTo(final StringBuilder buffer, final Calendar calendar) {
958             appendTo(buffer, calendar.get(Calendar.MONTH) + 1);
959         }
960 
961         /**
962          * {@inheritDoc}
963          */
964         @Override
965         public final void appendTo(final StringBuilder buffer, final int value) {
966             appendDigits(buffer, value);
967         }
968     }
969 
970     /**
971      * <p>Inner class to output the twelve hour field.</p>
972      */
973     private static class TwelveHourField implements NumberRule {
974         private final NumberRule mRule;
975 
976         /**
977          * Constructs an instance of {@code TwelveHourField} with the specified
978          * {@code NumberRule}.
979          *
980          * @param rule the rule
981          */
982         TwelveHourField(final NumberRule rule) {
983             mRule = rule;
984         }
985 
986         /**
987          * {@inheritDoc}
988          */
989         @Override
990         public int estimateLength() {
991             return mRule.estimateLength();
992         }
993 
994         /**
995          * {@inheritDoc}
996          */
997         @Override
998         public void appendTo(final StringBuilder buffer, final Calendar calendar) {
999             int value = calendar.get(Calendar.HOUR);
1000             if (value == 0) {
1001                 value = calendar.getLeastMaximum(Calendar.HOUR) + 1;
1002             }
1003             mRule.appendTo(buffer, value);
1004         }
1005 
1006         /**
1007          * {@inheritDoc}
1008          */
1009         @Override
1010         public void appendTo(final StringBuilder buffer, final int value) {
1011             mRule.appendTo(buffer, value);
1012         }
1013     }
1014 
1015     /**
1016      * <p>Inner class to output the twenty four hour field.</p>
1017      */
1018     private static class TwentyFourHourField implements NumberRule {
1019         private final NumberRule mRule;
1020 
1021         /**
1022          * Constructs an instance of {@code TwentyFourHourField} with the specified
1023          * {@code NumberRule}.
1024          *
1025          * @param rule the rule
1026          */
1027         TwentyFourHourField(final NumberRule rule) {
1028             mRule = rule;
1029         }
1030 
1031         /**
1032          * {@inheritDoc}
1033          */
1034         @Override
1035         public int estimateLength() {
1036             return mRule.estimateLength();
1037         }
1038 
1039         /**
1040          * {@inheritDoc}
1041          */
1042         @Override
1043         public void appendTo(final StringBuilder buffer, final Calendar calendar) {
1044             int value = calendar.get(Calendar.HOUR_OF_DAY);
1045             if (value == 0) {
1046                 value = calendar.getMaximum(Calendar.HOUR_OF_DAY) + 1;
1047             }
1048             mRule.appendTo(buffer, value);
1049         }
1050 
1051         /**
1052          * {@inheritDoc}
1053          */
1054         @Override
1055         public void appendTo(final StringBuilder buffer, final int value) {
1056             mRule.appendTo(buffer, value);
1057         }
1058     }
1059 
1060     //-----------------------------------------------------------------------
1061 
1062     private static final ConcurrentMap<TimeZoneDisplayKey, String> cTimeZoneDisplayCache =
1063         new ConcurrentHashMap<TimeZoneDisplayKey, String>(7);
1064     /**
1065      * <p>Gets the time zone display name, using a cache for performance.</p>
1066      *
1067      * @param tz  the zone to query
1068      * @param daylight  true if daylight savings
1069      * @param style  the style to use {@code TimeZone.LONG} or {@code TimeZone.SHORT}
1070      * @param locale  the locale to use
1071      * @return the textual name of the time zone
1072      */
1073     static String getTimeZoneDisplay(final TimeZone tz, final boolean daylight, final int style, final Locale locale) {
1074         final TimeZoneDisplayKey key = new TimeZoneDisplayKey(tz, daylight, style, locale);
1075         String value = cTimeZoneDisplayCache.get(key);
1076         if (value == null) {
1077             // This is a very slow call, so cache the results.
1078             value = tz.getDisplayName(daylight, style, locale);
1079             final String prior = cTimeZoneDisplayCache.putIfAbsent(key, value);
1080             if (prior != null) {
1081                 value= prior;
1082             }
1083         }
1084         return value;
1085     }
1086 
1087     /**
1088      * <p>Inner class to output a time zone name.</p>
1089      */
1090     private static class TimeZoneNameRule implements Rule {
1091         private final Locale mLocale;
1092         private final int mStyle;
1093         private final String mStandard;
1094         private final String mDaylight;
1095 
1096         /**
1097          * Constructs an instance of {@code TimeZoneNameRule} with the specified properties.
1098          *
1099          * @param timeZone the time zone
1100          * @param locale the locale
1101          * @param style the style
1102          */
1103         TimeZoneNameRule(final TimeZone timeZone, final Locale locale, final int style) {
1104             mLocale = locale;
1105             mStyle = style;
1106             
1107             mStandard = getTimeZoneDisplay(timeZone, false, style, locale);
1108             mDaylight = getTimeZoneDisplay(timeZone, true, style, locale);
1109         }
1110 
1111         /**
1112          * {@inheritDoc}
1113          */
1114         @Override
1115         public int estimateLength() {
1116             // We have no access to the Calendar object that will be passed to
1117             // appendTo so base estimate on the TimeZone passed to the
1118             // constructor
1119             return Math.max(mStandard.length(), mDaylight.length());
1120         }
1121 
1122         /**
1123          * {@inheritDoc}
1124          */
1125         @Override
1126         public void appendTo(final StringBuilder buffer, final Calendar calendar) {
1127             final TimeZone zone = calendar.getTimeZone();
1128             if (calendar.get(Calendar.DST_OFFSET) != 0) {
1129                 buffer.append(getTimeZoneDisplay(zone, true, mStyle, mLocale));
1130             } else {
1131                 buffer.append(getTimeZoneDisplay(zone, false, mStyle, mLocale));
1132             }
1133         }
1134     }
1135 
1136     /**
1137      * <p>Inner class to output a time zone as a number {@code +/-HHMM}
1138      * or {@code +/-HH:MM}.</p>
1139      */
1140     private static class TimeZoneNumberRule implements Rule {
1141         static final TimeZoneNumberRule INSTANCE_COLON = new TimeZoneNumberRule(true);
1142         static final TimeZoneNumberRule INSTANCE_NO_COLON = new TimeZoneNumberRule(false);
1143         
1144         final boolean mColon;
1145 
1146         /**
1147          * Constructs an instance of {@code TimeZoneNumberRule} with the specified properties.
1148          *
1149          * @param colon add colon between HH and MM in the output if {@code true}
1150          */
1151         TimeZoneNumberRule(final boolean colon) {
1152             mColon = colon;
1153         }
1154 
1155         /**
1156          * {@inheritDoc}
1157          */
1158         @Override
1159         public int estimateLength() {
1160             return 5;
1161         }
1162 
1163         /**
1164          * {@inheritDoc}
1165          */
1166         @Override
1167         public void appendTo(final StringBuilder buffer, final Calendar calendar) {
1168             
1169             int offset = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET);
1170 
1171             if (offset < 0) {
1172                 buffer.append('-');
1173                 offset = -offset;
1174             } else {
1175                 buffer.append('+');
1176             }
1177 
1178             final int hours = offset / (60 * 60 * 1000);
1179             appendDigits(buffer, hours);
1180 
1181             if (mColon) {
1182                 buffer.append(':');
1183             }
1184 
1185             final int minutes = offset / (60 * 1000) - 60 * hours;
1186             appendDigits(buffer, minutes);
1187         }
1188     }
1189 
1190     /**
1191      * <p>Inner class to output a time zone as a number {@code +/-HHMM}
1192      * or {@code +/-HH:MM}.</p>
1193      */
1194     private static class Iso8601_Rule implements Rule {
1195         
1196         // Sign TwoDigitHours or Z
1197         static final Iso8601_Rule ISO8601_HOURS = new Iso8601_Rule(3);       
1198         // Sign TwoDigitHours Minutes or Z
1199         static final Iso8601_Rule ISO8601_HOURS_MINUTES = new Iso8601_Rule(5);
1200         // Sign TwoDigitHours : Minutes or Z
1201         static final Iso8601_Rule ISO8601_HOURS_COLON_MINUTES = new Iso8601_Rule(6);
1202 
1203         /**
1204          * Factory method for Iso8601_Rules.
1205          *
1206          * @param tokenLen a token indicating the length of the TimeZone String to be formatted.
1207          * @return a Iso8601_Rule that can format TimeZone String of length {@code tokenLen}. If no such
1208          *          rule exists, an IllegalArgumentException will be thrown.
1209          */
1210         static Iso8601_Rule getRule(final int tokenLen) {
1211             switch(tokenLen) {
1212             case 1:
1213                 return Iso8601_Rule.ISO8601_HOURS;
1214             case 2:
1215                 return Iso8601_Rule.ISO8601_HOURS_MINUTES;
1216             case 3:
1217                 return Iso8601_Rule.ISO8601_HOURS_COLON_MINUTES;
1218             default:
1219                 throw new IllegalArgumentException("invalid number of X");                    
1220             }
1221         }        
1222         
1223         final int length;
1224 
1225         /**
1226          * Constructs an instance of {@code Iso8601_Rule} with the specified properties.
1227          *
1228          * @param length The number of characters in output (unless Z is output)
1229          */
1230         Iso8601_Rule(final int length) {
1231             this.length = length;
1232         }
1233 
1234         /**
1235          * {@inheritDoc}
1236          */
1237         @Override
1238         public int estimateLength() {
1239             return length;
1240         }
1241 
1242         /**
1243          * {@inheritDoc}
1244          */
1245         @Override
1246         public void appendTo(final StringBuilder buffer, final Calendar calendar) {
1247             int offset = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET);
1248             if (offset == 0) {
1249                 buffer.append("Z");
1250                 return;
1251             }
1252             
1253             if (offset < 0) {
1254                 buffer.append('-');
1255                 offset = -offset;
1256             } else {
1257                 buffer.append('+');
1258             }
1259 
1260             final int hours = offset / (60 * 60 * 1000);
1261             appendDigits(buffer, hours);
1262 
1263             if (length<5) {
1264                 return;
1265             }
1266             
1267             if (length==6) {
1268                 buffer.append(':');
1269             }
1270 
1271             final int minutes = offset / (60 * 1000) - 60 * hours;
1272             appendDigits(buffer, minutes);
1273         }
1274     }
1275 
1276     // ----------------------------------------------------------------------
1277     /**
1278      * <p>Inner class that acts as a compound key for time zone names.</p>
1279      */
1280     private static class TimeZoneDisplayKey {
1281         private final TimeZone mTimeZone;
1282         private final int mStyle;
1283         private final Locale mLocale;
1284 
1285         /**
1286          * Constructs an instance of {@code TimeZoneDisplayKey} with the specified properties.
1287          *
1288          * @param timeZone the time zone
1289          * @param daylight adjust the style for daylight saving time if {@code true}
1290          * @param style the timezone style
1291          * @param locale the timezone locale
1292          */
1293         TimeZoneDisplayKey(final TimeZone timeZone,
1294                            final boolean daylight, final int style, final Locale locale) {
1295             mTimeZone = timeZone;
1296             if (daylight) {
1297                 mStyle = style | 0x80000000;
1298             } else {
1299                 mStyle = style;
1300             }
1301             mLocale = locale;
1302         }
1303 
1304         /**
1305          * {@inheritDoc}
1306          */
1307         @Override
1308         public int hashCode() {
1309             return (mStyle * 31 + mLocale.hashCode() ) * 31 + mTimeZone.hashCode();
1310         }
1311 
1312         /**
1313          * {@inheritDoc}
1314          */
1315         @Override
1316         public boolean equals(final Object obj) {
1317             if (this == obj) {
1318                 return true;
1319             }
1320             if (obj instanceof TimeZoneDisplayKey) {
1321                 final TimeZoneDisplayKey other = (TimeZoneDisplayKey)obj;
1322                 return
1323                     mTimeZone.equals(other.mTimeZone) &&
1324                     mStyle == other.mStyle &&
1325                     mLocale.equals(other.mLocale);
1326             }
1327             return false;
1328         }
1329     }
1330 }