Home » commons-lang-2.4-src » org.apache.commons » lang » time » [javadoc | source]
    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.commons.lang.time;
   18   
   19   import java.io.IOException;
   20   import java.io.ObjectInputStream;
   21   import java.text.DateFormat;
   22   import java.text.DateFormatSymbols;
   23   import java.text.FieldPosition;
   24   import java.text.Format;
   25   import java.text.ParsePosition;
   26   import java.text.SimpleDateFormat;
   27   import java.util.ArrayList;
   28   import java.util.Calendar;
   29   import java.util.Date;
   30   import java.util.GregorianCalendar;
   31   import java.util.HashMap;
   32   import java.util.List;
   33   import java.util.Locale;
   34   import java.util.Map;
   35   import java.util.TimeZone;
   36   
   37   import org.apache.commons.lang.Validate;
   38   
   39   /**
   40    * <p>FastDateFormat is a fast and thread-safe version of
   41    * {@link java.text.SimpleDateFormat}.</p>
   42    * 
   43    * <p>This class can be used as a direct replacement to
   44    * <code>SimpleDateFormat</code> in most formatting situations.
   45    * This class is especially useful in multi-threaded server environments.
   46    * <code>SimpleDateFormat</code> is not thread-safe in any JDK version,
   47    * nor will it be as Sun have closed the bug/RFE.
   48    * </p>
   49    *
   50    * <p>Only formatting is supported, but all patterns are compatible with
   51    * SimpleDateFormat (except time zones - see below).</p>
   52    *
   53    * <p>Java 1.4 introduced a new pattern letter, <code>'Z'</code>, to represent
   54    * time zones in RFC822 format (eg. <code>+0800</code> or <code>-1100</code>).
   55    * This pattern letter can be used here (on all JDK versions).</p>
   56    *
   57    * <p>In addition, the pattern <code>'ZZ'</code> has been made to represent
   58    * ISO8601 full format time zones (eg. <code>+08:00</code> or <code>-11:00</code>).
   59    * This introduces a minor incompatibility with Java 1.4, but at a gain of
   60    * useful functionality.</p>
   61    *
   62    * @author TeaTrove project
   63    * @author Brian S O'Neill
   64    * @author Sean Schofield
   65    * @author Gary Gregory
   66    * @author Stephen Colebourne
   67    * @author Nikolay Metchev
   68    * @since 2.0
   69    * @version $Id: FastDateFormat.java 590552 2007-10-31 04:04:32Z bayard $
   70    */
   71   public class FastDateFormat extends Format {
   72       // A lot of the speed in this class comes from caching, but some comes
   73       // from the special int to StringBuffer conversion.
   74       //
   75       // The following produces a padded 2 digit number:
   76       //   buffer.append((char)(value / 10 + '0'));
   77       //   buffer.append((char)(value % 10 + '0'));
   78       //
   79       // Note that the fastest append to StringBuffer is a single char (used here).
   80       // Note that Integer.toString() is not called, the conversion is simply
   81       // taking the value and adding (mathematically) the ASCII value for '0'.
   82       // So, don't change this code! It works and is very fast.
   83       
   84       /**
   85        * Required for serialization support.
   86        * 
   87        * @see java.io.Serializable
   88        */
   89       private static final long serialVersionUID = 1L;
   90   
   91       /**
   92        * FULL locale dependent date or time style.
   93        */
   94       public static final int FULL = DateFormat.FULL;
   95       /**
   96        * LONG locale dependent date or time style.
   97        */
   98       public static final int LONG = DateFormat.LONG;
   99       /**
  100        * MEDIUM locale dependent date or time style.
  101        */
  102       public static final int MEDIUM = DateFormat.MEDIUM;
  103       /**
  104        * SHORT locale dependent date or time style.
  105        */
  106       public static final int SHORT = DateFormat.SHORT;
  107       
  108       private static String cDefaultPattern;
  109   
  110       private static final Map cInstanceCache = new HashMap(7);
  111       private static final Map cDateInstanceCache = new HashMap(7);
  112       private static final Map cTimeInstanceCache = new HashMap(7);
  113       private static final Map cDateTimeInstanceCache = new HashMap(7);
  114       private static final Map cTimeZoneDisplayCache = new HashMap(7);
  115   
  116       /**
  117        * The pattern.
  118        */
  119       private final String mPattern;
  120       /**
  121        * The time zone.
  122        */
  123       private final TimeZone mTimeZone;
  124       /**
  125        * Whether the time zone overrides any on Calendars.
  126        */
  127       private final boolean mTimeZoneForced;
  128       /**
  129        * The locale.
  130        */
  131       private final Locale mLocale;
  132       /**
  133        * Whether the locale overrides the default.
  134        */
  135       private final boolean mLocaleForced;
  136       /**
  137        * The parsed rules.
  138        */
  139       private transient Rule[] mRules;
  140       /**
  141        * The estimated maximum length.
  142        */
  143       private transient int mMaxLengthEstimate;
  144   
  145       //-----------------------------------------------------------------------
  146       /**
  147        * <p>Gets a formatter instance using the default pattern in the
  148        * default locale.</p>
  149        * 
  150        * @return a date/time formatter
  151        */
  152       public static FastDateFormat getInstance() {
  153           return getInstance(getDefaultPattern(), null, null);
  154       }
  155   
  156       /**
  157        * <p>Gets a formatter instance using the specified pattern in the
  158        * default locale.</p>
  159        * 
  160        * @param pattern  {@link java.text.SimpleDateFormat} compatible
  161        *  pattern
  162        * @return a pattern based date/time formatter
  163        * @throws IllegalArgumentException if pattern is invalid
  164        */
  165       public static FastDateFormat getInstance(String pattern) {
  166           return getInstance(pattern, null, null);
  167       }
  168   
  169       /**
  170        * <p>Gets a formatter instance using the specified pattern and
  171        * time zone.</p>
  172        * 
  173        * @param pattern  {@link java.text.SimpleDateFormat} compatible
  174        *  pattern
  175        * @param timeZone  optional time zone, overrides time zone of
  176        *  formatted date
  177        * @return a pattern based date/time formatter
  178        * @throws IllegalArgumentException if pattern is invalid
  179        */
  180       public static FastDateFormat getInstance(String pattern, TimeZone timeZone) {
  181           return getInstance(pattern, timeZone, null);
  182       }
  183   
  184       /**
  185        * <p>Gets a formatter instance using the specified pattern and
  186        * locale.</p>
  187        * 
  188        * @param pattern  {@link java.text.SimpleDateFormat} compatible
  189        *  pattern
  190        * @param locale  optional locale, overrides system locale
  191        * @return a pattern based date/time formatter
  192        * @throws IllegalArgumentException if pattern is invalid
  193        */
  194       public static FastDateFormat getInstance(String pattern, Locale locale) {
  195           return getInstance(pattern, null, locale);
  196       }
  197   
  198       /**
  199        * <p>Gets a formatter instance using the specified pattern, time zone
  200        * and locale.</p>
  201        * 
  202        * @param pattern  {@link java.text.SimpleDateFormat} compatible
  203        *  pattern
  204        * @param timeZone  optional time zone, overrides time zone of
  205        *  formatted date
  206        * @param locale  optional locale, overrides system locale
  207        * @return a pattern based date/time formatter
  208        * @throws IllegalArgumentException if pattern is invalid
  209        *  or <code>null</code>
  210        */
  211       public static synchronized FastDateFormat getInstance(String pattern, TimeZone timeZone, Locale locale) {
  212           FastDateFormat emptyFormat = new FastDateFormat(pattern, timeZone, locale);
  213           FastDateFormat format = (FastDateFormat) cInstanceCache.get(emptyFormat);
  214           if (format == null) {
  215               format = emptyFormat;
  216               format.init();  // convert shell format into usable one
  217               cInstanceCache.put(format, format);  // this is OK!
  218           }
  219           return format;
  220       }
  221   
  222       //-----------------------------------------------------------------------
  223       /**
  224        * <p>Gets a date formatter instance using the specified style in the
  225        * default time zone and locale.</p>
  226        * 
  227        * @param style  date style: FULL, LONG, MEDIUM, or SHORT
  228        * @return a localized standard date formatter
  229        * @throws IllegalArgumentException if the Locale has no date
  230        *  pattern defined
  231        * @since 2.1
  232        */
  233       public static FastDateFormat getDateInstance(int style) {
  234           return getDateInstance(style, null, null);
  235       }
  236   
  237       /**
  238        * <p>Gets a date formatter instance using the specified style and
  239        * locale in the default time zone.</p>
  240        * 
  241        * @param style  date style: FULL, LONG, MEDIUM, or SHORT
  242        * @param locale  optional locale, overrides system locale
  243        * @return a localized standard date formatter
  244        * @throws IllegalArgumentException if the Locale has no date
  245        *  pattern defined
  246        * @since 2.1
  247        */
  248       public static FastDateFormat getDateInstance(int style, Locale locale) {
  249           return getDateInstance(style, null, locale);
  250       }
  251   
  252       /**
  253        * <p>Gets a date formatter instance using the specified style and
  254        * time zone in the default locale.</p>
  255        * 
  256        * @param style  date style: FULL, LONG, MEDIUM, or SHORT
  257        * @param timeZone  optional time zone, overrides time zone of
  258        *  formatted date
  259        * @return a localized standard date formatter
  260        * @throws IllegalArgumentException if the Locale has no date
  261        *  pattern defined
  262        * @since 2.1
  263        */
  264       public static FastDateFormat getDateInstance(int style, TimeZone timeZone) {
  265           return getDateInstance(style, timeZone, null);
  266       }
  267       /**
  268        * <p>Gets a date formatter instance using the specified style, time
  269        * zone and locale.</p>
  270        * 
  271        * @param style  date style: FULL, LONG, MEDIUM, or SHORT
  272        * @param timeZone  optional time zone, overrides time zone of
  273        *  formatted date
  274        * @param locale  optional locale, overrides system locale
  275        * @return a localized standard date formatter
  276        * @throws IllegalArgumentException if the Locale has no date
  277        *  pattern defined
  278        */
  279       public static synchronized FastDateFormat getDateInstance(int style, TimeZone timeZone, Locale locale) {
  280           Object key = new Integer(style);
  281           if (timeZone != null) {
  282               key = new Pair(key, timeZone);
  283           }
  284   
  285           if (locale == null) {
  286               locale = Locale.getDefault();
  287           }
  288   
  289           key = new Pair(key, locale);
  290   
  291           FastDateFormat format = (FastDateFormat) cDateInstanceCache.get(key);
  292           if (format == null) {
  293               try {
  294                   SimpleDateFormat formatter = (SimpleDateFormat) DateFormat.getDateInstance(style, locale);
  295                   String pattern = formatter.toPattern();
  296                   format = getInstance(pattern, timeZone, locale);
  297                   cDateInstanceCache.put(key, format);
  298                   
  299               } catch (ClassCastException ex) {
  300                   throw new IllegalArgumentException("No date pattern for locale: " + locale);
  301               }
  302           }
  303           return format;
  304       }
  305   
  306       //-----------------------------------------------------------------------
  307       /**
  308        * <p>Gets a time formatter instance using the specified style in the
  309        * default time zone and locale.</p>
  310        * 
  311        * @param style  time style: FULL, LONG, MEDIUM, or SHORT
  312        * @return a localized standard time formatter
  313        * @throws IllegalArgumentException if the Locale has no time
  314        *  pattern defined
  315        * @since 2.1
  316        */
  317       public static FastDateFormat getTimeInstance(int style) {
  318           return getTimeInstance(style, null, null);
  319       }
  320   
  321       /**
  322        * <p>Gets a time formatter instance using the specified style and
  323        * locale in the default time zone.</p>
  324        * 
  325        * @param style  time style: FULL, LONG, MEDIUM, or SHORT
  326        * @param locale  optional locale, overrides system locale
  327        * @return a localized standard time formatter
  328        * @throws IllegalArgumentException if the Locale has no time
  329        *  pattern defined
  330        * @since 2.1
  331        */
  332       public static FastDateFormat getTimeInstance(int style, Locale locale) {
  333           return getTimeInstance(style, null, locale);
  334       }
  335       
  336       /**
  337        * <p>Gets a time formatter instance using the specified style and
  338        * time zone in the default locale.</p>
  339        * 
  340        * @param style  time style: FULL, LONG, MEDIUM, or SHORT
  341        * @param timeZone  optional time zone, overrides time zone of
  342        *  formatted time
  343        * @return a localized standard time formatter
  344        * @throws IllegalArgumentException if the Locale has no time
  345        *  pattern defined
  346        * @since 2.1
  347        */
  348       public static FastDateFormat getTimeInstance(int style, TimeZone timeZone) {
  349           return getTimeInstance(style, timeZone, null);
  350       }
  351       
  352       /**
  353        * <p>Gets a time formatter instance using the specified style, time
  354        * zone and locale.</p>
  355        * 
  356        * @param style  time style: FULL, LONG, MEDIUM, or SHORT
  357        * @param timeZone  optional time zone, overrides time zone of
  358        *  formatted time
  359        * @param locale  optional locale, overrides system locale
  360        * @return a localized standard time formatter
  361        * @throws IllegalArgumentException if the Locale has no time
  362        *  pattern defined
  363        */
  364       public static synchronized FastDateFormat getTimeInstance(int style, TimeZone timeZone, Locale locale) {
  365           Object key = new Integer(style);
  366           if (timeZone != null) {
  367               key = new Pair(key, timeZone);
  368           }
  369           if (locale != null) {
  370               key = new Pair(key, locale);
  371           }
  372   
  373           FastDateFormat format = (FastDateFormat) cTimeInstanceCache.get(key);
  374           if (format == null) {
  375               if (locale == null) {
  376                   locale = Locale.getDefault();
  377               }
  378   
  379               try {
  380                   SimpleDateFormat formatter = (SimpleDateFormat) DateFormat.getTimeInstance(style, locale);
  381                   String pattern = formatter.toPattern();
  382                   format = getInstance(pattern, timeZone, locale);
  383                   cTimeInstanceCache.put(key, format);
  384               
  385               } catch (ClassCastException ex) {
  386                   throw new IllegalArgumentException("No date pattern for locale: " + locale);
  387               }
  388           }
  389           return format;
  390       }
  391   
  392       //-----------------------------------------------------------------------
  393       /**
  394        * <p>Gets a date/time formatter instance using the specified style
  395        * in the default time zone and locale.</p>
  396        * 
  397        * @param dateStyle  date style: FULL, LONG, MEDIUM, or SHORT
  398        * @param timeStyle  time style: FULL, LONG, MEDIUM, or SHORT
  399        * @return a localized standard date/time formatter
  400        * @throws IllegalArgumentException if the Locale has no date/time
  401        *  pattern defined
  402        * @since 2.1
  403        */
  404       public static FastDateFormat getDateTimeInstance(
  405               int dateStyle, int timeStyle) {
  406           return getDateTimeInstance(dateStyle, timeStyle, null, null);
  407       }
  408       
  409       /**
  410        * <p>Gets a date/time formatter instance using the specified style and
  411        * locale in the default time zone.</p>
  412        * 
  413        * @param dateStyle  date style: FULL, LONG, MEDIUM, or SHORT
  414        * @param timeStyle  time style: FULL, LONG, MEDIUM, or SHORT
  415        * @param locale  optional locale, overrides system locale
  416        * @return a localized standard date/time formatter
  417        * @throws IllegalArgumentException if the Locale has no date/time
  418        *  pattern defined
  419        * @since 2.1
  420        */
  421       public static FastDateFormat getDateTimeInstance(
  422               int dateStyle, int timeStyle, Locale locale) {
  423           return getDateTimeInstance(dateStyle, timeStyle, null, locale);
  424       }
  425       
  426       /**
  427        * <p>Gets a date/time formatter instance using the specified style and
  428        * time zone in the default locale.</p>
  429        * 
  430        * @param dateStyle  date style: FULL, LONG, MEDIUM, or SHORT
  431        * @param timeStyle  time style: FULL, LONG, MEDIUM, or SHORT
  432        * @param timeZone  optional time zone, overrides time zone of
  433        *  formatted date
  434        * @return a localized standard date/time formatter
  435        * @throws IllegalArgumentException if the Locale has no date/time
  436        *  pattern defined
  437        * @since 2.1
  438        */
  439       public static FastDateFormat getDateTimeInstance(
  440               int dateStyle, int timeStyle, TimeZone timeZone) {
  441           return getDateTimeInstance(dateStyle, timeStyle, timeZone, null);
  442       }    
  443       /**
  444        * <p>Gets a date/time formatter instance using the specified style,
  445        * time zone and locale.</p>
  446        * 
  447        * @param dateStyle  date style: FULL, LONG, MEDIUM, or SHORT
  448        * @param timeStyle  time style: FULL, LONG, MEDIUM, or SHORT
  449        * @param timeZone  optional time zone, overrides time zone of
  450        *  formatted date
  451        * @param locale  optional locale, overrides system locale
  452        * @return a localized standard date/time formatter
  453        * @throws IllegalArgumentException if the Locale has no date/time
  454        *  pattern defined
  455        */
  456       public static synchronized FastDateFormat getDateTimeInstance(int dateStyle, int timeStyle, TimeZone timeZone,
  457               Locale locale) {
  458   
  459           Object key = new Pair(new Integer(dateStyle), new Integer(timeStyle));
  460           if (timeZone != null) {
  461               key = new Pair(key, timeZone);
  462           }
  463           if (locale == null) {
  464               locale = Locale.getDefault();
  465           }
  466           key = new Pair(key, locale);
  467   
  468           FastDateFormat format = (FastDateFormat) cDateTimeInstanceCache.get(key);
  469           if (format == null) {
  470               try {
  471                   SimpleDateFormat formatter = (SimpleDateFormat) DateFormat.getDateTimeInstance(dateStyle, timeStyle,
  472                           locale);
  473                   String pattern = formatter.toPattern();
  474                   format = getInstance(pattern, timeZone, locale);
  475                   cDateTimeInstanceCache.put(key, format);
  476   
  477               } catch (ClassCastException ex) {
  478                   throw new IllegalArgumentException("No date time pattern for locale: " + locale);
  479               }
  480           }
  481           return format;
  482       }
  483   
  484       //-----------------------------------------------------------------------
  485       /**
  486        * <p>Gets the time zone display name, using a cache for performance.</p>
  487        * 
  488        * @param tz  the zone to query
  489        * @param daylight  true if daylight savings
  490        * @param style  the style to use <code>TimeZone.LONG</code>
  491        *  or <code>TimeZone.SHORT</code>
  492        * @param locale  the locale to use
  493        * @return the textual name of the time zone
  494        */
  495       static synchronized String getTimeZoneDisplay(TimeZone tz, boolean daylight, int style, Locale locale) {
  496           Object key = new TimeZoneDisplayKey(tz, daylight, style, locale);
  497           String value = (String) cTimeZoneDisplayCache.get(key);
  498           if (value == null) {
  499               // This is a very slow call, so cache the results.
  500               value = tz.getDisplayName(daylight, style, locale);
  501               cTimeZoneDisplayCache.put(key, value);
  502           }
  503           return value;
  504       }
  505   
  506       /**
  507        * <p>Gets the default pattern.</p>
  508        * 
  509        * @return the default pattern
  510        */
  511       private static synchronized String getDefaultPattern() {
  512           if (cDefaultPattern == null) {
  513               cDefaultPattern = new SimpleDateFormat().toPattern();
  514           }
  515           return cDefaultPattern;
  516       }
  517   
  518       // Constructor
  519       //-----------------------------------------------------------------------
  520       /**
  521        * <p>Constructs a new FastDateFormat.</p>
  522        * 
  523        * @param pattern  {@link java.text.SimpleDateFormat} compatible
  524        *  pattern
  525        * @param timeZone  time zone to use, <code>null</code> means use
  526        *  default for <code>Date</code> and value within for
  527        *  <code>Calendar</code>
  528        * @param locale  locale, <code>null</code> means use system
  529        *  default
  530        * @throws IllegalArgumentException if pattern is invalid or
  531        *  <code>null</code>
  532        */
  533       protected FastDateFormat(String pattern, TimeZone timeZone, Locale locale) {
  534           super();
  535           if (pattern == null) {
  536               throw new IllegalArgumentException("The pattern must not be null");
  537           }
  538           mPattern = pattern;
  539           
  540           mTimeZoneForced = (timeZone != null);
  541           if (timeZone == null) {
  542               timeZone = TimeZone.getDefault();
  543           }
  544           mTimeZone = timeZone;
  545           
  546           mLocaleForced = (locale != null);
  547           if (locale == null) {
  548               locale = Locale.getDefault();
  549           }
  550           mLocale = locale;
  551       }
  552   
  553       /**
  554        * <p>Initializes the instance for first use.</p>
  555        */
  556       protected void init() {
  557           List rulesList = parsePattern();
  558           mRules = (Rule[]) rulesList.toArray(new Rule[rulesList.size()]);
  559   
  560           int len = 0;
  561           for (int i=mRules.length; --i >= 0; ) {
  562               len += mRules[i].estimateLength();
  563           }
  564   
  565           mMaxLengthEstimate = len;
  566       }
  567   
  568       // Parse the pattern
  569       //-----------------------------------------------------------------------
  570       /**
  571        * <p>Returns a list of Rules given a pattern.</p>
  572        * 
  573        * @return a <code>List</code> of Rule objects
  574        * @throws IllegalArgumentException if pattern is invalid
  575        */
  576       protected List parsePattern() {
  577           DateFormatSymbols symbols = new DateFormatSymbols(mLocale);
  578           List rules = new ArrayList();
  579   
  580           String[] ERAs = symbols.getEras();
  581           String[] months = symbols.getMonths();
  582           String[] shortMonths = symbols.getShortMonths();
  583           String[] weekdays = symbols.getWeekdays();
  584           String[] shortWeekdays = symbols.getShortWeekdays();
  585           String[] AmPmStrings = symbols.getAmPmStrings();
  586   
  587           int length = mPattern.length();
  588           int[] indexRef = new int[1];
  589   
  590           for (int i = 0; i < length; i++) {
  591               indexRef[0] = i;
  592               String token = parseToken(mPattern, indexRef);
  593               i = indexRef[0];
  594   
  595               int tokenLen = token.length();
  596               if (tokenLen == 0) {
  597                   break;
  598               }
  599   
  600               Rule rule;
  601               char c = token.charAt(0);
  602   
  603               switch (c) {
  604               case 'G': // era designator (text)
  605                   rule = new TextField(Calendar.ERA, ERAs);
  606                   break;
  607               case 'y': // year (number)
  608                   if (tokenLen >= 4) {
  609                       rule = selectNumberRule(Calendar.YEAR, tokenLen);
  610                   } else {
  611                       rule = TwoDigitYearField.INSTANCE;
  612                   }
  613                   break;
  614               case 'M': // month in year (text and number)
  615                   if (tokenLen >= 4) {
  616                       rule = new TextField(Calendar.MONTH, months);
  617                   } else if (tokenLen == 3) {
  618                       rule = new TextField(Calendar.MONTH, shortMonths);
  619                   } else if (tokenLen == 2) {
  620                       rule = TwoDigitMonthField.INSTANCE;
  621                   } else {
  622                       rule = UnpaddedMonthField.INSTANCE;
  623                   }
  624                   break;
  625               case 'd': // day in month (number)
  626                   rule = selectNumberRule(Calendar.DAY_OF_MONTH, tokenLen);
  627                   break;
  628               case 'h': // hour in am/pm (number, 1..12)
  629                   rule = new TwelveHourField(selectNumberRule(Calendar.HOUR, tokenLen));
  630                   break;
  631               case 'H': // hour in day (number, 0..23)
  632                   rule = selectNumberRule(Calendar.HOUR_OF_DAY, tokenLen);
  633                   break;
  634               case 'm': // minute in hour (number)
  635                   rule = selectNumberRule(Calendar.MINUTE, tokenLen);
  636                   break;
  637               case 's': // second in minute (number)
  638                   rule = selectNumberRule(Calendar.SECOND, tokenLen);
  639                   break;
  640               case 'S': // millisecond (number)
  641                   rule = selectNumberRule(Calendar.MILLISECOND, tokenLen);
  642                   break;
  643               case 'E': // day in week (text)
  644                   rule = new TextField(Calendar.DAY_OF_WEEK, tokenLen < 4 ? shortWeekdays : weekdays);
  645                   break;
  646               case 'D': // day in year (number)
  647                   rule = selectNumberRule(Calendar.DAY_OF_YEAR, tokenLen);
  648                   break;
  649               case 'F': // day of week in month (number)
  650                   rule = selectNumberRule(Calendar.DAY_OF_WEEK_IN_MONTH, tokenLen);
  651                   break;
  652               case 'w': // week in year (number)
  653                   rule = selectNumberRule(Calendar.WEEK_OF_YEAR, tokenLen);
  654                   break;
  655               case 'W': // week in month (number)
  656                   rule = selectNumberRule(Calendar.WEEK_OF_MONTH, tokenLen);
  657                   break;
  658               case 'a': // am/pm marker (text)
  659                   rule = new TextField(Calendar.AM_PM, AmPmStrings);
  660                   break;
  661               case 'k': // hour in day (1..24)
  662                   rule = new TwentyFourHourField(selectNumberRule(Calendar.HOUR_OF_DAY, tokenLen));
  663                   break;
  664               case 'K': // hour in am/pm (0..11)
  665                   rule = selectNumberRule(Calendar.HOUR, tokenLen);
  666                   break;
  667               case 'z': // time zone (text)
  668                   if (tokenLen >= 4) {
  669                       rule = new TimeZoneNameRule(mTimeZone, mTimeZoneForced, mLocale, TimeZone.LONG);
  670                   } else {
  671                       rule = new TimeZoneNameRule(mTimeZone, mTimeZoneForced, mLocale, TimeZone.SHORT);
  672                   }
  673                   break;
  674               case 'Z': // time zone (value)
  675                   if (tokenLen == 1) {
  676                       rule = TimeZoneNumberRule.INSTANCE_NO_COLON;
  677                   } else {
  678                       rule = TimeZoneNumberRule.INSTANCE_COLON;
  679                   }
  680                   break;
  681               case '\'': // literal text
  682                   String sub = token.substring(1);
  683                   if (sub.length() == 1) {
  684                       rule = new CharacterLiteral(sub.charAt(0));
  685                   } else {
  686                       rule = new StringLiteral(sub);
  687                   }
  688                   break;
  689               default:
  690                   throw new IllegalArgumentException("Illegal pattern component: " + token);
  691               }
  692   
  693               rules.add(rule);
  694           }
  695   
  696           return rules;
  697       }
  698   
  699       /**
  700        * <p>Performs the parsing of tokens.</p>
  701        * 
  702        * @param pattern  the pattern
  703        * @param indexRef  index references
  704        * @return parsed token
  705        */
  706       protected String parseToken(String pattern, int[] indexRef) {
  707           StringBuffer buf = new StringBuffer();
  708   
  709           int i = indexRef[0];
  710           int length = pattern.length();
  711   
  712           char c = pattern.charAt(i);
  713           if (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z') {
  714               // Scan a run of the same character, which indicates a time
  715               // pattern.
  716               buf.append(c);
  717   
  718               while (i + 1 < length) {
  719                   char peek = pattern.charAt(i + 1);
  720                   if (peek == c) {
  721                       buf.append(c);
  722                       i++;
  723                   } else {
  724                       break;
  725                   }
  726               }
  727           } else {
  728               // This will identify token as text.
  729               buf.append('\'');
  730   
  731               boolean inLiteral = false;
  732   
  733               for (; i < length; i++) {
  734                   c = pattern.charAt(i);
  735   
  736                   if (c == '\'') {
  737                       if (i + 1 < length && pattern.charAt(i + 1) == '\'') {
  738                           // '' is treated as escaped '
  739                           i++;
  740                           buf.append(c);
  741                       } else {
  742                           inLiteral = !inLiteral;
  743                       }
  744                   } else if (!inLiteral &&
  745                            (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z')) {
  746                       i--;
  747                       break;
  748                   } else {
  749                       buf.append(c);
  750                   }
  751               }
  752           }
  753   
  754           indexRef[0] = i;
  755           return buf.toString();
  756       }
  757   
  758       /**
  759        * <p>Gets an appropriate rule for the padding required.</p>
  760        * 
  761        * @param field  the field to get a rule for
  762        * @param padding  the padding required
  763        * @return a new rule with the correct padding
  764        */
  765       protected NumberRule selectNumberRule(int field, int padding) {
  766           switch (padding) {
  767           case 1:
  768               return new UnpaddedNumberField(field);
  769           case 2:
  770               return new TwoDigitNumberField(field);
  771           default:
  772               return new PaddedNumberField(field, padding);
  773           }
  774       }
  775   
  776       // Format methods
  777       //-----------------------------------------------------------------------
  778       /**
  779        * <p>Formats a <code>Date</code>, <code>Calendar</code> or
  780        * <code>Long</code> (milliseconds) object.</p>
  781        * 
  782        * @param obj  the object to format
  783        * @param toAppendTo  the buffer to append to
  784        * @param pos  the position - ignored
  785        * @return the buffer passed in
  786        */
  787       public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) {
  788           if (obj instanceof Date) {
  789               return format((Date) obj, toAppendTo);
  790           } else if (obj instanceof Calendar) {
  791               return format((Calendar) obj, toAppendTo);
  792           } else if (obj instanceof Long) {
  793               return format(((Long) obj).longValue(), toAppendTo);
  794           } else {
  795               throw new IllegalArgumentException("Unknown class: " +
  796                   (obj == null ? "<null>" : obj.getClass().getName()));
  797           }
  798       }
  799   
  800       /**
  801        * <p>Formats a millisecond <code>long</code> value.</p>
  802        * 
  803        * @param millis  the millisecond value to format
  804        * @return the formatted string
  805        * @since 2.1
  806        */
  807       public String format(long millis) {
  808           return format(new Date(millis));
  809       }
  810   
  811       /**
  812        * <p>Formats a <code>Date</code> object.</p>
  813        * 
  814        * @param date  the date to format
  815        * @return the formatted string
  816        */
  817       public String format(Date date) {
  818           Calendar c = new GregorianCalendar(mTimeZone);
  819           c.setTime(date);
  820           return applyRules(c, new StringBuffer(mMaxLengthEstimate)).toString();
  821       }
  822   
  823       /**
  824        * <p>Formats a <code>Calendar</code> object.</p>
  825        * 
  826        * @param calendar  the calendar to format
  827        * @return the formatted string
  828        */
  829       public String format(Calendar calendar) {
  830           return format(calendar, new StringBuffer(mMaxLengthEstimate)).toString();
  831       }
  832   
  833       /**
  834        * <p>Formats a milliseond <code>long</code> value into the
  835        * supplied <code>StringBuffer</code>.</p>
  836        * 
  837        * @param millis  the millisecond value to format
  838        * @param buf  the buffer to format into
  839        * @return the specified string buffer
  840        * @since 2.1
  841        */
  842       public StringBuffer format(long millis, StringBuffer buf) {
  843           return format(new Date(millis), buf);
  844       }
  845   
  846       /**
  847        * <p>Formats a <code>Date</code> object into the
  848        * supplied <code>StringBuffer</code>.</p>
  849        * 
  850        * @param date  the date to format
  851        * @param buf  the buffer to format into
  852        * @return the specified string buffer
  853        */
  854       public StringBuffer format(Date date, StringBuffer buf) {
  855           Calendar c = new GregorianCalendar(mTimeZone);
  856           c.setTime(date);
  857           return applyRules(c, buf);
  858       }
  859   
  860       /**
  861        * <p>Formats a <code>Calendar</code> object into the
  862        * supplied <code>StringBuffer</code>.</p>
  863        * 
  864        * @param calendar  the calendar to format
  865        * @param buf  the buffer to format into
  866        * @return the specified string buffer
  867        */
  868       public StringBuffer format(Calendar calendar, StringBuffer buf) {
  869           if (mTimeZoneForced) {
  870               calendar = (Calendar) calendar.clone();
  871               calendar.setTimeZone(mTimeZone);
  872           }
  873           return applyRules(calendar, buf);
  874       }
  875   
  876       /**
  877        * <p>Performs the formatting by applying the rules to the
  878        * specified calendar.</p>
  879        * 
  880        * @param calendar  the calendar to format
  881        * @param buf  the buffer to format into
  882        * @return the specified string buffer
  883        */
  884       protected StringBuffer applyRules(Calendar calendar, StringBuffer buf) {
  885           Rule[] rules = mRules;
  886           int len = mRules.length;
  887           for (int i = 0; i < len; i++) {
  888               rules[i].appendTo(buf, calendar);
  889           }
  890           return buf;
  891       }
  892   
  893       // Parsing
  894       //-----------------------------------------------------------------------
  895       /**
  896        * <p>Parsing is not supported.</p>
  897        * 
  898        * @param source  the string to parse
  899        * @param pos  the parsing position
  900        * @return <code>null</code> as not supported
  901        */
  902       public Object parseObject(String source, ParsePosition pos) {
  903           pos.setIndex(0);
  904           pos.setErrorIndex(0);
  905           return null;
  906       }
  907       
  908       // Accessors
  909       //-----------------------------------------------------------------------
  910       /**
  911        * <p>Gets the pattern used by this formatter.</p>
  912        * 
  913        * @return the pattern, {@link java.text.SimpleDateFormat} compatible
  914        */
  915       public String getPattern() {
  916           return mPattern;
  917       }
  918   
  919       /**
  920        * <p>Gets the time zone used by this formatter.</p>
  921        *
  922        * <p>This zone is always used for <code>Date</code> formatting.
  923        * If a <code>Calendar</code> is passed in to be formatted, the
  924        * time zone on that may be used depending on
  925        * {@link #getTimeZoneOverridesCalendar()}.</p>
  926        * 
  927        * @return the time zone
  928        */
  929       public TimeZone getTimeZone() {
  930           return mTimeZone;
  931       }
  932   
  933       /**
  934        * <p>Returns <code>true</code> if the time zone of the
  935        * calendar overrides the time zone of the formatter.</p>
  936        * 
  937        * @return <code>true</code> if time zone of formatter
  938        *  overridden for calendars
  939        */
  940       public boolean getTimeZoneOverridesCalendar() {
  941           return mTimeZoneForced;
  942       }
  943   
  944       /**
  945        * <p>Gets the locale used by this formatter.</p>
  946        * 
  947        * @return the locale
  948        */
  949       public Locale getLocale() {
  950           return mLocale;
  951       }
  952   
  953       /**
  954        * <p>Gets an estimate for the maximum string length that the
  955        * formatter will produce.</p>
  956        *
  957        * <p>The actual formatted length will almost always be less than or
  958        * equal to this amount.</p>
  959        * 
  960        * @return the maximum formatted length
  961        */
  962       public int getMaxLengthEstimate() {
  963           return mMaxLengthEstimate;
  964       }
  965   
  966       // Basics
  967       //-----------------------------------------------------------------------
  968       /**
  969        * <p>Compares two objects for equality.</p>
  970        * 
  971        * @param obj  the object to compare to
  972        * @return <code>true</code> if equal
  973        */
  974       public boolean equals(Object obj) {
  975           if (obj instanceof FastDateFormat == false) {
  976               return false;
  977           }
  978           FastDateFormat other = (FastDateFormat) obj;
  979           if (
  980               (mPattern == other.mPattern || mPattern.equals(other.mPattern)) &&
  981               (mTimeZone == other.mTimeZone || mTimeZone.equals(other.mTimeZone)) &&
  982               (mLocale == other.mLocale || mLocale.equals(other.mLocale)) &&
  983               (mTimeZoneForced == other.mTimeZoneForced) &&
  984               (mLocaleForced == other.mLocaleForced)
  985               ) {
  986               return true;
  987           }
  988           return false;
  989       }
  990   
  991       /**
  992        * <p>Returns a hashcode compatible with equals.</p>
  993        * 
  994        * @return a hashcode compatible with equals
  995        */
  996       public int hashCode() {
  997           int total = 0;
  998           total += mPattern.hashCode();
  999           total += mTimeZone.hashCode();
 1000           total += (mTimeZoneForced ? 1 : 0);
 1001           total += mLocale.hashCode();
 1002           total += (mLocaleForced ? 1 : 0);
 1003           return total;
 1004       }
 1005   
 1006       /**
 1007        * <p>Gets a debugging string version of this formatter.</p>
 1008        * 
 1009        * @return a debugging string
 1010        */
 1011       public String toString() {
 1012           return "FastDateFormat[" + mPattern + "]";
 1013       }
 1014   
 1015       // Serializing
 1016       //-----------------------------------------------------------------------
 1017       /**
 1018        * Create the object after serialization. This implementation reinitializes the 
 1019        * transient properties.
 1020        *
 1021        * @param in ObjectInputStream from which the object is being deserialized.
 1022        * @throws IOException if there is an IO issue.
 1023        * @throws ClassNotFoundException if a class cannot be found.
 1024        */
 1025       private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
 1026           in.defaultReadObject();
 1027           init();
 1028       }
 1029       
 1030       // Rules
 1031       //-----------------------------------------------------------------------
 1032       /**
 1033        * <p>Inner class defining a rule.</p>
 1034        */
 1035       private interface Rule {
 1036           /**
 1037            * Returns the estimated lentgh of the result.
 1038            * 
 1039            * @return the estimated length
 1040            */
 1041           int estimateLength();
 1042           
 1043           /**
 1044            * Appends the value of the specified calendar to the output buffer based on the rule implementation.
 1045            * 
 1046            * @param buffer the output buffer
 1047            * @param calendar calendar to be appended
 1048            */
 1049           void appendTo(StringBuffer buffer, Calendar calendar);
 1050       }
 1051   
 1052       /**
 1053        * <p>Inner class defining a numeric rule.</p>
 1054        */
 1055       private interface NumberRule extends Rule {
 1056           /**
 1057            * Appends the specified value to the output buffer based on the rule implementation.
 1058            * 
 1059            * @param buffer the output buffer
 1060            * @param value the value to be appended
 1061            */
 1062           void appendTo(StringBuffer buffer, int value);
 1063       }
 1064   
 1065       /**
 1066        * <p>Inner class to output a constant single character.</p>
 1067        */
 1068       private static class CharacterLiteral implements Rule {
 1069           private final char mValue;
 1070   
 1071           /**
 1072            * Constructs a new instance of <code>CharacterLiteral</code>
 1073            * to hold the specified value.
 1074            * 
 1075            * @param value the character literal
 1076            */
 1077           CharacterLiteral(char value) {
 1078               mValue = value;
 1079           }
 1080   
 1081           /**
 1082            * {@inheritDoc}
 1083            */
 1084           public int estimateLength() {
 1085               return 1;
 1086           }
 1087   
 1088           /**
 1089            * {@inheritDoc}
 1090            */
 1091           public void appendTo(StringBuffer buffer, Calendar calendar) {
 1092               buffer.append(mValue);
 1093           }
 1094       }
 1095   
 1096       /**
 1097        * <p>Inner class to output a constant string.</p>
 1098        */
 1099       private static class StringLiteral implements Rule {
 1100           private final String mValue;
 1101   
 1102           /**
 1103            * Constructs a new instance of <code>StringLiteral</code>
 1104            * to hold the specified value.
 1105            * 
 1106            * @param value the string literal
 1107            */
 1108           StringLiteral(String value) {
 1109               mValue = value;
 1110           }
 1111   
 1112           /**
 1113            * {@inheritDoc}
 1114            */
 1115           public int estimateLength() {
 1116               return mValue.length();
 1117           }
 1118   
 1119           /**
 1120            * {@inheritDoc}
 1121            */
 1122           public void appendTo(StringBuffer buffer, Calendar calendar) {
 1123               buffer.append(mValue);
 1124           }
 1125       }
 1126   
 1127       /**
 1128        * <p>Inner class to output one of a set of values.</p>
 1129        */
 1130       private static class TextField implements Rule {
 1131           private final int mField;
 1132           private final String[] mValues;
 1133   
 1134           /**
 1135            * Constructs an instance of <code>TextField</code>
 1136            * with the specified field and values.
 1137            * 
 1138            * @param field the field
 1139            * @param values the field values
 1140            */
 1141           TextField(int field, String[] values) {
 1142               mField = field;
 1143               mValues = values;
 1144           }
 1145   
 1146           /**
 1147            * {@inheritDoc}
 1148            */
 1149           public int estimateLength() {
 1150               int max = 0;
 1151               for (int i=mValues.length; --i >= 0; ) {
 1152                   int len = mValues[i].length();
 1153                   if (len > max) {
 1154                       max = len;
 1155                   }
 1156               }
 1157               return max;
 1158           }
 1159   
 1160           /**
 1161            * {@inheritDoc}
 1162            */
 1163           public void appendTo(StringBuffer buffer, Calendar calendar) {
 1164               buffer.append(mValues[calendar.get(mField)]);
 1165           }
 1166       }
 1167   
 1168       /**
 1169        * <p>Inner class to output an unpadded number.</p>
 1170        */
 1171       private static class UnpaddedNumberField implements NumberRule {
 1172           static final UnpaddedNumberField INSTANCE_YEAR = new UnpaddedNumberField(Calendar.YEAR);
 1173           
 1174           private final int mField;
 1175   
 1176           /**
 1177            * Constructs an instance of <code>UnpadedNumberField</code> with the specified field.
 1178            * 
 1179            * @param field the field
 1180            */
 1181           UnpaddedNumberField(int field) {
 1182               mField = field;
 1183           }
 1184   
 1185           /**
 1186            * {@inheritDoc}
 1187            */
 1188           public int estimateLength() {
 1189               return 4;
 1190           }
 1191   
 1192           /**
 1193            * {@inheritDoc}
 1194            */
 1195           public void appendTo(StringBuffer buffer, Calendar calendar) {
 1196               appendTo(buffer, calendar.get(mField));
 1197           }
 1198   
 1199           /**
 1200            * {@inheritDoc}
 1201            */
 1202           public final void appendTo(StringBuffer buffer, int value) {
 1203               if (value < 10) {
 1204                   buffer.append((char)(value + '0'));
 1205               } else if (value < 100) {
 1206                   buffer.append((char)(value / 10 + '0'));
 1207                   buffer.append((char)(value % 10 + '0'));
 1208               } else {
 1209                   buffer.append(Integer.toString(value));
 1210               }
 1211           }
 1212       }
 1213   
 1214       /**
 1215        * <p>Inner class to output an unpadded month.</p>
 1216        */
 1217       private static class UnpaddedMonthField implements NumberRule {
 1218           static final UnpaddedMonthField INSTANCE = new UnpaddedMonthField();
 1219   
 1220           /**
 1221            * Constructs an instance of <code>UnpaddedMonthField</code>.
 1222            *
 1223            */
 1224           UnpaddedMonthField() {
 1225               super();
 1226           }
 1227   
 1228           /**
 1229            * {@inheritDoc}
 1230            */
 1231           public int estimateLength() {
 1232               return 2;
 1233           }
 1234   
 1235           /**
 1236            * {@inheritDoc}
 1237            */
 1238           public void appendTo(StringBuffer buffer, Calendar calendar) {
 1239               appendTo(buffer, calendar.get(Calendar.MONTH) + 1);
 1240           }
 1241   
 1242           /**
 1243            * {@inheritDoc}
 1244            */
 1245           public final void appendTo(StringBuffer buffer, int value) {
 1246               if (value < 10) {
 1247                   buffer.append((char)(value + '0'));
 1248               } else {
 1249                   buffer.append((char)(value / 10 + '0'));
 1250                   buffer.append((char)(value % 10 + '0'));
 1251               }
 1252           }
 1253       }
 1254   
 1255       /**
 1256        * <p>Inner class to output a padded number.</p>
 1257        */
 1258       private static class PaddedNumberField implements NumberRule {
 1259           private final int mField;
 1260           private final int mSize;
 1261   
 1262           /**
 1263            * Constructs an instance of <code>PaddedNumberField</code>.
 1264            * 
 1265            * @param field the field
 1266            * @param size size of the output field
 1267            */
 1268           PaddedNumberField(int field, int size) {
 1269               if (size < 3) {
 1270                   // Should use UnpaddedNumberField or TwoDigitNumberField.
 1271                   throw new IllegalArgumentException();
 1272               }
 1273               mField = field;
 1274               mSize = size;
 1275           }
 1276   
 1277           /**
 1278            * {@inheritDoc}
 1279            */
 1280           public int estimateLength() {
 1281               return 4;
 1282           }
 1283   
 1284           /**
 1285            * {@inheritDoc}
 1286            */
 1287           public void appendTo(StringBuffer buffer, Calendar calendar) {
 1288               appendTo(buffer, calendar.get(mField));
 1289           }
 1290   
 1291           /**
 1292            * {@inheritDoc}
 1293            */
 1294           public final void appendTo(StringBuffer buffer, int value) {
 1295               if (value < 100) {
 1296                   for (int i = mSize; --i >= 2; ) {
 1297                       buffer.append('0');
 1298                   }
 1299                   buffer.append((char)(value / 10 + '0'));
 1300                   buffer.append((char)(value % 10 + '0'));
 1301               } else {
 1302                   int digits;
 1303                   if (value < 1000) {
 1304                       digits = 3;
 1305                   } else {
 1306                       Validate.isTrue(value > -1, "Negative values should not be possible", value);
 1307                       digits = Integer.toString(value).length();
 1308                   }
 1309                   for (int i = mSize; --i >= digits; ) {
 1310                       buffer.append('0');
 1311                   }
 1312                   buffer.append(Integer.toString(value));
 1313               }
 1314           }
 1315       }
 1316   
 1317       /**
 1318        * <p>Inner class to output a two digit number.</p>
 1319        */
 1320       private static class TwoDigitNumberField implements NumberRule {
 1321           private final int mField;
 1322   
 1323           /**
 1324            * Constructs an instance of <code>TwoDigitNumberField</code> with the specified field.
 1325            * 
 1326            * @param field the field
 1327            */
 1328           TwoDigitNumberField(int field) {
 1329               mField = field;
 1330           }
 1331   
 1332           /**
 1333            * {@inheritDoc}
 1334            */
 1335           public int estimateLength() {
 1336               return 2;
 1337           }
 1338   
 1339           /**
 1340            * {@inheritDoc}
 1341            */
 1342           public void appendTo(StringBuffer buffer, Calendar calendar) {
 1343               appendTo(buffer, calendar.get(mField));
 1344           }
 1345   
 1346           /**
 1347            * {@inheritDoc}
 1348            */
 1349           public final void appendTo(StringBuffer buffer, int value) {
 1350               if (value < 100) {
 1351                   buffer.append((char)(value / 10 + '0'));
 1352                   buffer.append((char)(value % 10 + '0'));
 1353               } else {
 1354                   buffer.append(Integer.toString(value));
 1355               }
 1356           }
 1357       }
 1358   
 1359       /**
 1360        * <p>Inner class to output a two digit year.</p>
 1361        */
 1362       private static class TwoDigitYearField implements NumberRule {
 1363           static final TwoDigitYearField INSTANCE = new TwoDigitYearField();
 1364   
 1365           /**
 1366            * Constructs an instance of <code>TwoDigitYearField</code>.
 1367            */
 1368           TwoDigitYearField() {
 1369               super();
 1370           }
 1371   
 1372           /**
 1373            * {@inheritDoc}
 1374            */
 1375           public int estimateLength() {
 1376               return 2;
 1377           }
 1378   
 1379           /**
 1380            * {@inheritDoc}
 1381            */
 1382           public void appendTo(StringBuffer buffer, Calendar calendar) {
 1383               appendTo(buffer, calendar.get(Calendar.YEAR) % 100);
 1384           }
 1385   
 1386           /**
 1387            * {@inheritDoc}
 1388            */
 1389           public final void appendTo(StringBuffer buffer, int value) {
 1390               buffer.append((char)(value / 10 + '0'));
 1391               buffer.append((char)(value % 10 + '0'));
 1392           }
 1393       }
 1394   
 1395       /**
 1396        * <p>Inner class to output a two digit month.</p>
 1397        */
 1398       private static class TwoDigitMonthField implements NumberRule {
 1399           static final TwoDigitMonthField INSTANCE = new TwoDigitMonthField();
 1400   
 1401           /**
 1402            * Constructs an instance of <code>TwoDigitMonthField</code>.
 1403            */
 1404           TwoDigitMonthField() {
 1405               super();
 1406           }
 1407   
 1408           /**
 1409            * {@inheritDoc}
 1410            */
 1411           public int estimateLength() {
 1412               return 2;
 1413           }
 1414   
 1415           /**
 1416            * {@inheritDoc}
 1417            */
 1418           public void appendTo(StringBuffer buffer, Calendar calendar) {
 1419               appendTo(buffer, calendar.get(Calendar.MONTH) + 1);
 1420           }
 1421   
 1422           /**
 1423            * {@inheritDoc}
 1424            */
 1425           public final void appendTo(StringBuffer buffer, int value) {
 1426               buffer.append((char)(value / 10 + '0'));
 1427               buffer.append((char)(value % 10 + '0'));
 1428           }
 1429       }
 1430   
 1431       /**
 1432        * <p>Inner class to output the twelve hour field.</p>
 1433        */
 1434       private static class TwelveHourField implements NumberRule {
 1435           private final NumberRule mRule;
 1436   
 1437           /**
 1438            * Constructs an instance of <code>TwelveHourField</code> with the specified
 1439            * <code>NumberRule</code>.
 1440            * 
 1441            * @param rule the rule
 1442            */
 1443           TwelveHourField(NumberRule rule) {
 1444               mRule = rule;
 1445           }
 1446   
 1447           /**
 1448            * {@inheritDoc}
 1449            */
 1450           public int estimateLength() {
 1451               return mRule.estimateLength();
 1452           }
 1453   
 1454           /**
 1455            * {@inheritDoc}
 1456            */
 1457           public void appendTo(StringBuffer buffer, Calendar calendar) {
 1458               int value = calendar.get(Calendar.HOUR);
 1459               if (value == 0) {
 1460                   value = calendar.getLeastMaximum(Calendar.HOUR) + 1;
 1461               }
 1462               mRule.appendTo(buffer, value);
 1463           }
 1464   
 1465           /**
 1466            * {@inheritDoc}
 1467            */
 1468           public void appendTo(StringBuffer buffer, int value) {
 1469               mRule.appendTo(buffer, value);
 1470           }
 1471       }
 1472   
 1473       /**
 1474        * <p>Inner class to output the twenty four hour field.</p>
 1475        */
 1476       private static class TwentyFourHourField implements NumberRule {
 1477           private final NumberRule mRule;
 1478   
 1479           /**
 1480            * Constructs an instance of <code>TwentyFourHourField</code> with the specified
 1481            * <code>NumberRule</code>.
 1482            * 
 1483            * @param rule the rule
 1484            */
 1485           TwentyFourHourField(NumberRule rule) {
 1486               mRule = rule;
 1487           }
 1488   
 1489           /**
 1490            * {@inheritDoc}
 1491            */
 1492           public int estimateLength() {
 1493               return mRule.estimateLength();
 1494           }
 1495   
 1496           /**
 1497            * {@inheritDoc}
 1498            */
 1499           public void appendTo(StringBuffer buffer, Calendar calendar) {
 1500               int value = calendar.get(Calendar.HOUR_OF_DAY);
 1501               if (value == 0) {
 1502                   value = calendar.getMaximum(Calendar.HOUR_OF_DAY) + 1;
 1503               }
 1504               mRule.appendTo(buffer, value);
 1505           }
 1506   
 1507           /**
 1508            * {@inheritDoc}
 1509            */
 1510           public void appendTo(StringBuffer buffer, int value) {
 1511               mRule.appendTo(buffer, value);
 1512           }
 1513       }
 1514   
 1515       /**
 1516        * <p>Inner class to output a time zone name.</p>
 1517        */
 1518       private static class TimeZoneNameRule implements Rule {
 1519           private final TimeZone mTimeZone;
 1520           private final boolean mTimeZoneForced;
 1521           private final Locale mLocale;
 1522           private final int mStyle;
 1523           private final String mStandard;
 1524           private final String mDaylight;
 1525   
 1526           /**
 1527            * Constructs an instance of <code>TimeZoneNameRule</code> with the specified properties.
 1528            * 
 1529            * @param timeZone the time zone
 1530            * @param timeZoneForced if <code>true</code> the time zone is forced into standard and daylight
 1531            * @param locale the locale
 1532            * @param style the style
 1533            */
 1534           TimeZoneNameRule(TimeZone timeZone, boolean timeZoneForced, Locale locale, int style) {
 1535               mTimeZone = timeZone;
 1536               mTimeZoneForced = timeZoneForced;
 1537               mLocale = locale;
 1538               mStyle = style;
 1539   
 1540               if (timeZoneForced) {
 1541                   mStandard = getTimeZoneDisplay(timeZone, false, style, locale);
 1542                   mDaylight = getTimeZoneDisplay(timeZone, true, style, locale);
 1543               } else {
 1544                   mStandard = null;
 1545                   mDaylight = null;
 1546               }
 1547           }
 1548   
 1549           /**
 1550            * {@inheritDoc}
 1551            */
 1552           public int estimateLength() {
 1553               if (mTimeZoneForced) {
 1554                   return Math.max(mStandard.length(), mDaylight.length());
 1555               } else if (mStyle == TimeZone.SHORT) {
 1556                   return 4;
 1557               } else {
 1558                   return 40;
 1559               }
 1560           }
 1561   
 1562           /**
 1563            * {@inheritDoc}
 1564            */
 1565           public void appendTo(StringBuffer buffer, Calendar calendar) {
 1566               if (mTimeZoneForced) {
 1567                   if (mTimeZone.useDaylightTime() && calendar.get(Calendar.DST_OFFSET) != 0) {
 1568                       buffer.append(mDaylight);
 1569                   } else {
 1570                       buffer.append(mStandard);
 1571                   }
 1572               } else {
 1573                   TimeZone timeZone = calendar.getTimeZone();
 1574                   if (timeZone.useDaylightTime() && calendar.get(Calendar.DST_OFFSET) != 0) {
 1575                       buffer.append(getTimeZoneDisplay(timeZone, true, mStyle, mLocale));
 1576                   } else {
 1577                       buffer.append(getTimeZoneDisplay(timeZone, false, mStyle, mLocale));
 1578                   }
 1579               }
 1580           }
 1581       }
 1582   
 1583       /**
 1584        * <p>Inner class to output a time zone as a number <code>+/-HHMM</code>
 1585        * or <code>+/-HH:MM</code>.</p>
 1586        */
 1587       private static class TimeZoneNumberRule implements Rule {
 1588           static final TimeZoneNumberRule INSTANCE_COLON = new TimeZoneNumberRule(true);
 1589           static final TimeZoneNumberRule INSTANCE_NO_COLON = new TimeZoneNumberRule(false);
 1590           
 1591           final boolean mColon;
 1592           
 1593           /**
 1594            * Constructs an instance of <code>TimeZoneNumberRule</code> with the specified properties.
 1595            * 
 1596            * @param colon add colon between HH and MM in the output if <code>true</code>
 1597            */
 1598           TimeZoneNumberRule(boolean colon) {
 1599               mColon = colon;
 1600           }
 1601   
 1602           /**
 1603            * {@inheritDoc}
 1604            */
 1605           public int estimateLength() {
 1606               return 5;
 1607           }
 1608   
 1609           /**
 1610            * {@inheritDoc}
 1611            */
 1612           public void appendTo(StringBuffer buffer, Calendar calendar) {
 1613               int offset = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET);
 1614               
 1615               if (offset < 0) {
 1616                   buffer.append('-');
 1617                   offset = -offset;
 1618               } else {
 1619                   buffer.append('+');
 1620               }
 1621               
 1622               int hours = offset / (60 * 60 * 1000);
 1623               buffer.append((char)(hours / 10 + '0'));
 1624               buffer.append((char)(hours % 10 + '0'));
 1625               
 1626               if (mColon) {
 1627                   buffer.append(':');
 1628               }
 1629               
 1630               int minutes = offset / (60 * 1000) - 60 * hours;
 1631               buffer.append((char)(minutes / 10 + '0'));
 1632               buffer.append((char)(minutes % 10 + '0'));
 1633           }            
 1634       }
 1635   
 1636       // ----------------------------------------------------------------------
 1637       /**
 1638        * <p>Inner class that acts as a compound key for time zone names.</p>
 1639        */
 1640       private static class TimeZoneDisplayKey {
 1641           private final TimeZone mTimeZone;
 1642           private final int mStyle;
 1643           private final Locale mLocale;
 1644   
 1645           /**
 1646            * Constructs an instance of <code>TimeZoneDisplayKey</code> with the specified properties.
 1647            *  
 1648            * @param timeZone the time zone
 1649            * @param daylight adjust the style for daylight saving time if <code>true</code>
 1650            * @param style the timezone style
 1651            * @param locale the timezone locale
 1652            */
 1653           TimeZoneDisplayKey(TimeZone timeZone,
 1654                              boolean daylight, int style, Locale locale) {
 1655               mTimeZone = timeZone;
 1656               if (daylight) {
 1657                   style |= 0x80000000;
 1658               }
 1659               mStyle = style;
 1660               mLocale = locale;
 1661           }
 1662   
 1663           /**
 1664            * {@inheritDoc}
 1665            */
 1666           public int hashCode() {
 1667               return mStyle * 31 + mLocale.hashCode();
 1668           }
 1669   
 1670           /**
 1671            * {@inheritDoc}
 1672            */
 1673           public boolean equals(Object obj) {
 1674               if (this == obj) {
 1675                   return true;
 1676               }
 1677               if (obj instanceof TimeZoneDisplayKey) {
 1678                   TimeZoneDisplayKey other = (TimeZoneDisplayKey)obj;
 1679                   return
 1680                       mTimeZone.equals(other.mTimeZone) &&
 1681                       mStyle == other.mStyle &&
 1682                       mLocale.equals(other.mLocale);
 1683               }
 1684               return false;
 1685           }
 1686       }
 1687   
 1688       // ----------------------------------------------------------------------
 1689       /**
 1690        * <p>Helper class for creating compound objects.</p>
 1691        *
 1692        * <p>One use for this class is to create a hashtable key
 1693        * out of multiple objects.</p>
 1694        */
 1695       private static class Pair {
 1696           private final Object mObj1;
 1697           private final Object mObj2;
 1698   
 1699           /**
 1700            * Constructs an instance of <code>Pair</code> to hold the specified objects.
 1701            * @param obj1 one object in the pair
 1702            * @param obj2 second object in the pair
 1703            */
 1704           public Pair(Object obj1, Object obj2) {
 1705               mObj1 = obj1;
 1706               mObj2 = obj2;
 1707           }
 1708   
 1709           /**
 1710            * {@inheritDoc}
 1711            */
 1712           public boolean equals(Object obj) {
 1713               if (this == obj) {
 1714                   return true;
 1715               }
 1716   
 1717               if (!(obj instanceof Pair)) {
 1718                   return false;
 1719               }
 1720   
 1721               Pair key = (Pair)obj;
 1722   
 1723               return
 1724                   (mObj1 == null ?
 1725                    key.mObj1 == null : mObj1.equals(key.mObj1)) &&
 1726                   (mObj2 == null ?
 1727                    key.mObj2 == null : mObj2.equals(key.mObj2));
 1728           }
 1729   
 1730           /**
 1731            * {@inheritDoc}
 1732            */
 1733           public int hashCode() {
 1734               return
 1735                   (mObj1 == null ? 0 : mObj1.hashCode()) +
 1736                   (mObj2 == null ? 0 : mObj2.hashCode());
 1737           }
 1738   
 1739           /**
 1740            * {@inheritDoc}
 1741            */
 1742           public String toString() {
 1743               return "[" + mObj1 + ':' + mObj2 + ']';
 1744           }
 1745       }
 1746   
 1747   }

Save This Page
Home » commons-lang-2.4-src » org.apache.commons » lang » time » [javadoc | source]