    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017package org.apache.commons.lang3.time;
    018
    019import java.text.DateFormat;
    020import java.text.FieldPosition;
    021import java.text.Format;
    022import java.text.ParseException;
    023import java.text.ParsePosition;
    024import java.util.Calendar;
    025import java.util.Date;
    026import java.util.Locale;
    027import java.util.TimeZone;
    028
    029/**
    030 * <p>FastDateFormat is a fast and thread-safe version of
    031 * {@link java.text.SimpleDateFormat}.</p>
    032 *
    033 * <p>To obtain an instance of FastDateFormat, use one of the static factory methods:
    034 * {@link #getInstance(String, TimeZone, Locale)}, {@link #getDateInstance(int, TimeZone, Locale)},
    035 * {@link #getTimeInstance(int, TimeZone, Locale)}, or {@link #getDateTimeInstance(int, int, TimeZone, Locale)}
    036 * </p>
    037 *
    038 * <p>Since FastDateFormat is thread safe, you can use a static member instance:</p>
    039 * <code>
    040 *   private static final FastDateFormat DATE_FORMATTER = FastDateFormat.getDateTimeInstance(FastDateFormat.LONG, FastDateFormat.SHORT);
    041 * </code>
    042 *
    043 * <p>This class can be used as a direct replacement to
    044 * {@code SimpleDateFormat} in most formatting and parsing situations.
    045 * This class is especially useful in multi-threaded server environments.
    046 * {@code SimpleDateFormat} is not thread-safe in any JDK version,
    047 * nor will it be as Sun have closed the bug/RFE.
    048 * </p>
    049 *
    050 * <p>All patterns are compatible with
    051 * SimpleDateFormat (except time zones and some year patterns - see below).</p>
    052 *
    053 * <p>Since 3.2, FastDateFormat supports parsing as well as printing.</p>
    054 *
    055 * <p>Java 1.4 introduced a new pattern letter, {@code 'Z'}, to represent
    056 * time zones in RFC822 format (eg. {@code +0800} or {@code -1100}).
    057 * This pattern letter can be used here (on all JDK versions).</p>
    058 *
    059 * <p>In addition, the pattern {@code 'ZZ'} has been made to represent
    060 * ISO 8601 extended format time zones (eg. {@code +08:00} or {@code -11:00}).
    061 * This introduces a minor incompatibility with Java 1.4, but at a gain of
    062 * useful functionality.</p>
    063 *
    064 * <p>Javadoc cites for the year pattern: <i>For formatting, if the number of
    065 * pattern letters is 2, the year is truncated to 2 digits; otherwise it is
    066 * interpreted as a number.</i> Starting with Java 1.7 a pattern of 'Y' or
    067 * 'YYY' will be formatted as '2003', while it was '03' in former Java
    068 * versions. FastDateFormat implements the behavior of Java 7.</p>
    069 *
    070 * @since 2.0
    071 */
    072public class FastDateFormat extends Format implements DateParser, DatePrinter {
    073
    074    /**
    075     * Required for serialization support.
    076     *
    077     * @see java.io.Serializable
    078     */
    079    private static final long serialVersionUID = 2L;
    080
    081    /**
    082     * FULL locale dependent date or time style.
    083     */
    084
    085    public static final int FULL = DateFormat.FULL;
    086
    087    /**
    088     * LONG locale dependent date or time style.
    089     */
    090    public static final int LONG = DateFormat.LONG;
    091
    092    /**
    093     * MEDIUM locale dependent date or time style.
    094     */
    095    public static final int MEDIUM = DateFormat.MEDIUM;
    096
    097    /**
    098     * SHORT locale dependent date or time style.
    099     */
    100    public static final int SHORT = DateFormat.SHORT;
    101
    102    private static final FormatCache<FastDateFormat> cache = new FormatCache<FastDateFormat>() {
    103        @Override
    104        protected FastDateFormat createInstance(final String pattern, final TimeZone timeZone, final Locale locale) {
    105            return new FastDateFormat(pattern, timeZone, locale);
    106        }
    107    };
    108
    109    private final FastDatePrinter printer;
    110    private final FastDateParser parser;
    111
    112    //-----------------------------------------------------------------------
    113    /**
    114     * <p>Gets a formatter instance using the default pattern in the
    115     * default locale.</p>
    116     *
    117     * @return a date/time formatter
    118     */
    119    public static FastDateFormat getInstance() {
    120        return cache.getInstance();
    121    }
    122
    123    /**
    124     * <p>Gets a formatter instance using the specified pattern in the
    125     * default locale.</p>
    126     *
    127     * @param pattern  {@link java.text.SimpleDateFormat} compatible
    128     *  pattern
    129     * @return a pattern based date/time formatter
    130     * @throws IllegalArgumentException if pattern is invalid
    131     */
    132    public static FastDateFormat getInstance(final String pattern) {
    133        return cache.getInstance(pattern, null, null);
    134    }
    135
    136    /**
    137     * <p>Gets a formatter instance using the specified pattern and
    138     * time zone.</p>
    139     *
    140     * @param pattern  {@link java.text.SimpleDateFormat} compatible
    141     *  pattern
    142     * @param timeZone  optional time zone, overrides time zone of
    143     *  formatted date
    144     * @return a pattern based date/time formatter
    145     * @throws IllegalArgumentException if pattern is invalid
    146     */
    147    public static FastDateFormat getInstance(final String pattern, final TimeZone timeZone) {
    148        return cache.getInstance(pattern, timeZone, null);
    149    }
    150
    151    /**
    152     * <p>Gets a formatter instance using the specified pattern and
    153     * locale.</p>
    154     *
    155     * @param pattern  {@link java.text.SimpleDateFormat} compatible
    156     *  pattern
    157     * @param locale  optional locale, overrides system locale
    158     * @return a pattern based date/time formatter
    159     * @throws IllegalArgumentException if pattern is invalid
    160     */
    161    public static FastDateFormat getInstance(final String pattern, final Locale locale) {
    162        return cache.getInstance(pattern, null, locale);
    163    }
    164
    165    /**
    166     * <p>Gets a formatter instance using the specified pattern, time zone
    167     * and locale.</p>
    168     *
    169     * @param pattern  {@link java.text.SimpleDateFormat} compatible
    170     *  pattern
    171     * @param timeZone  optional time zone, overrides time zone of
    172     *  formatted date
    173     * @param locale  optional locale, overrides system locale
    174     * @return a pattern based date/time formatter
    175     * @throws IllegalArgumentException if pattern is invalid
    176     *  or {@code null}
    177     */
    178    public static FastDateFormat getInstance(final String pattern, final TimeZone timeZone, final Locale locale) {
    179        return cache.getInstance(pattern, timeZone, locale);
    180    }
    181
    182    //-----------------------------------------------------------------------
    183    /**
    184     * <p>Gets a date formatter instance using the specified style in the
    185     * default time zone and locale.</p>
    186     *
    187     * @param style  date style: FULL, LONG, MEDIUM, or SHORT
    188     * @return a localized standard date formatter
    189     * @throws IllegalArgumentException if the Locale has no date
    190     *  pattern defined
    191     * @since 2.1
    192     */
    193    public static FastDateFormat getDateInstance(final int style) {
    194        return cache.getDateInstance(style, null, null);
    195    }
    196
    197    /**
    198     * <p>Gets a date formatter instance using the specified style and
    199     * locale in the default time zone.</p>
    200     *
    201     * @param style  date style: FULL, LONG, MEDIUM, or SHORT
    202     * @param locale  optional locale, overrides system locale
    203     * @return a localized standard date formatter
    204     * @throws IllegalArgumentException if the Locale has no date
    205     *  pattern defined
    206     * @since 2.1
    207     */
    208    public static FastDateFormat getDateInstance(final int style, final Locale locale) {
    209        return cache.getDateInstance(style, null, locale);
    210    }
    211
    212    /**
    213     * <p>Gets a date formatter instance using the specified style and
    214     * time zone in the default locale.</p>
    215     *
    216     * @param style  date style: FULL, LONG, MEDIUM, or SHORT
    217     * @param timeZone  optional time zone, overrides time zone of
    218     *  formatted date
    219     * @return a localized standard date formatter
    220     * @throws IllegalArgumentException if the Locale has no date
    221     *  pattern defined
    222     * @since 2.1
    223     */
    224    public static FastDateFormat getDateInstance(final int style, final TimeZone timeZone) {
    225        return cache.getDateInstance(style, timeZone, null);
    226    }
    227
    228    /**
    229     * <p>Gets a date formatter instance using the specified style, time
    230     * zone and locale.</p>
    231     *
    232     * @param style  date style: FULL, LONG, MEDIUM, or SHORT
    233     * @param timeZone  optional time zone, overrides time zone of
    234     *  formatted date
    235     * @param locale  optional locale, overrides system locale
    236     * @return a localized standard date formatter
    237     * @throws IllegalArgumentException if the Locale has no date
    238     *  pattern defined
    239     */
    240    public static FastDateFormat getDateInstance(final int style, final TimeZone timeZone, final Locale locale) {
    241        return cache.getDateInstance(style, timeZone, locale);
    242    }
    243
    244    //-----------------------------------------------------------------------
    245    /**
    246     * <p>Gets a time formatter instance using the specified style in the
    247     * default time zone and locale.</p>
    248     *
    249     * @param style  time style: FULL, LONG, MEDIUM, or SHORT
    250     * @return a localized standard time formatter
    251     * @throws IllegalArgumentException if the Locale has no time
    252     *  pattern defined
    253     * @since 2.1
    254     */
    255    public static FastDateFormat getTimeInstance(final int style) {
    256        return cache.getTimeInstance(style, null, null);
    257    }
    258
    259    /**
    260     * <p>Gets a time formatter instance using the specified style and
    261     * locale in the default time zone.</p>
    262     *
    263     * @param style  time style: FULL, LONG, MEDIUM, or SHORT
    264     * @param locale  optional locale, overrides system locale
    265     * @return a localized standard time formatter
    266     * @throws IllegalArgumentException if the Locale has no time
    267     *  pattern defined
    268     * @since 2.1
    269     */
    270    public static FastDateFormat getTimeInstance(final int style, final Locale locale) {
    271        return cache.getTimeInstance(style, null, locale);
    272    }
    273
    274    /**
    275     * <p>Gets a time formatter instance using the specified style and
    276     * time zone in the default locale.</p>
    277     *
    278     * @param style  time style: FULL, LONG, MEDIUM, or SHORT
    279     * @param timeZone  optional time zone, overrides time zone of
    280     *  formatted time
    281     * @return a localized standard time formatter
    282     * @throws IllegalArgumentException if the Locale has no time
    283     *  pattern defined
    284     * @since 2.1
    285     */
    286    public static FastDateFormat getTimeInstance(final int style, final TimeZone timeZone) {
    287        return cache.getTimeInstance(style, timeZone, null);
    288    }
    289
    290    /**
    291     * <p>Gets a time formatter instance using the specified style, time
    292     * zone and locale.</p>
    293     *
    294     * @param style  time style: FULL, LONG, MEDIUM, or SHORT
    295     * @param timeZone  optional time zone, overrides time zone of
    296     *  formatted time
    297     * @param locale  optional locale, overrides system locale
    298     * @return a localized standard time formatter
    299     * @throws IllegalArgumentException if the Locale has no time
    300     *  pattern defined
    301     */
    302    public static FastDateFormat getTimeInstance(final int style, final TimeZone timeZone, final Locale locale) {
    303        return cache.getTimeInstance(style, timeZone, locale);
    304    }
    305
    306    //-----------------------------------------------------------------------
    307    /**
    308     * <p>Gets a date/time formatter instance using the specified style
    309     * in the default time zone and locale.</p>
    310     *
    311     * @param dateStyle  date style: FULL, LONG, MEDIUM, or SHORT
    312     * @param timeStyle  time style: FULL, LONG, MEDIUM, or SHORT
    313     * @return a localized standard date/time formatter
    314     * @throws IllegalArgumentException if the Locale has no date/time
    315     *  pattern defined
    316     * @since 2.1
    317     */
    318    public static FastDateFormat getDateTimeInstance(final int dateStyle, final int timeStyle) {
    319        return cache.getDateTimeInstance(dateStyle, timeStyle, null, null);
    320    }
    321
    322    /**
    323     * <p>Gets a date/time formatter instance using the specified style and
    324     * locale in the default time zone.</p>
    325     *
    326     * @param dateStyle  date style: FULL, LONG, MEDIUM, or SHORT
    327     * @param timeStyle  time style: FULL, LONG, MEDIUM, or SHORT
    328     * @param locale  optional locale, overrides system locale
    329     * @return a localized standard date/time formatter
    330     * @throws IllegalArgumentException if the Locale has no date/time
    331     *  pattern defined
    332     * @since 2.1
    333     */
    334    public static FastDateFormat getDateTimeInstance(final int dateStyle, final int timeStyle, final Locale locale) {
    335        return cache.getDateTimeInstance(dateStyle, timeStyle, null, locale);
    336    }
    337
    338    /**
    339     * <p>Gets a date/time formatter instance using the specified style and
    340     * time zone in the default locale.</p>
    341     *
    342     * @param dateStyle  date style: FULL, LONG, MEDIUM, or SHORT
    343     * @param timeStyle  time style: FULL, LONG, MEDIUM, or SHORT
    344     * @param timeZone  optional time zone, overrides time zone of
    345     *  formatted date
    346     * @return a localized standard date/time formatter
    347     * @throws IllegalArgumentException if the Locale has no date/time
    348     *  pattern defined
    349     * @since 2.1
    350     */
    351    public static FastDateFormat getDateTimeInstance(final int dateStyle, final int timeStyle, final TimeZone timeZone) {
    352        return getDateTimeInstance(dateStyle, timeStyle, timeZone, null);
    353    }
    354    /**
    355     * <p>Gets a date/time formatter instance using the specified style,
    356     * time zone and locale.</p>
    357     *
    358     * @param dateStyle  date style: FULL, LONG, MEDIUM, or SHORT
    359     * @param timeStyle  time style: FULL, LONG, MEDIUM, or SHORT
    360     * @param timeZone  optional time zone, overrides time zone of
    361     *  formatted date
    362     * @param locale  optional locale, overrides system locale
    363     * @return a localized standard date/time formatter
    364     * @throws IllegalArgumentException if the Locale has no date/time
    365     *  pattern defined
    366     */
    367    public static FastDateFormat getDateTimeInstance(
    368            final int dateStyle, final int timeStyle, final TimeZone timeZone, final Locale locale) {
    369        return cache.getDateTimeInstance(dateStyle, timeStyle, timeZone, locale);
    370    }
    371
    372    // Constructor
    373    //-----------------------------------------------------------------------
    374    /**
    375     * <p>Constructs a new FastDateFormat.</p>
    376     *
    377     * @param pattern  {@link java.text.SimpleDateFormat} compatible pattern
    378     * @param timeZone  non-null time zone to use
    379     * @param locale  non-null locale to use
    380     * @throws NullPointerException if pattern, timeZone, or locale is null.
    381     */
    382    protected FastDateFormat(final String pattern, final TimeZone timeZone, final Locale locale) {
    383        this(pattern, timeZone, locale, null);
    384    }
    385
    386    // Constructor
    387    //-----------------------------------------------------------------------
    388    /**
    389     * <p>Constructs a new FastDateFormat.</p>
    390     *
    391     * @param pattern  {@link java.text.SimpleDateFormat} compatible pattern
    392     * @param timeZone  non-null time zone to use
    393     * @param locale  non-null locale to use
    394     * @param centuryStart The start of the 100 year period to use as the "default century" for 2 digit year parsing.  If centuryStart is null, defaults to now - 80 years
    395     * @throws NullPointerException if pattern, timeZone, or locale is null.
    396     */
    397    protected FastDateFormat(final String pattern, final TimeZone timeZone, final Locale locale, final Date centuryStart) {
    398        printer = new FastDatePrinter(pattern, timeZone, locale);
    399        parser = new FastDateParser(pattern, timeZone, locale, centuryStart);
    400    }
    401
    402    // Format methods
    403    //-----------------------------------------------------------------------
    404    /**
    405     * <p>Formats a {@code Date}, {@code Calendar} or
    406     * {@code Long} (milliseconds) object.</p>
    407     * This method is an implementation of {@link Format#format(Object, StringBuffer, FieldPosition)}
    408     *
    409     * @param obj  the object to format
    410     * @param toAppendTo  the buffer to append to
    411     * @param pos  the position - ignored
    412     * @return the buffer passed in
    413     */
    414    @Override
    415    public StringBuffer format(final Object obj, final StringBuffer toAppendTo, final FieldPosition pos) {
    416        return toAppendTo.append(printer.format(obj));
    417    }
    418
    419    /**
    420     * <p>Formats a millisecond {@code long} value.</p>
    421     *
    422     * @param millis  the millisecond value to format
    423     * @return the formatted string
    424     * @since 2.1
    425     */
    426    @Override
    427    public String format(final long millis) {
    428        return printer.format(millis);
    429    }
    430
    431    /**
    432     * <p>Formats a {@code Date} object using a {@code GregorianCalendar}.</p>
    433     *
    434     * @param date  the date to format
    435     * @return the formatted string
    436     */
    437    @Override
    438    public String format(final Date date) {
    439        return printer.format(date);
    440    }
    441
    442    /**
    443     * <p>Formats a {@code Calendar} object.</p>
    444     *
    445     * @param calendar  the calendar to format
    446     * @return the formatted string
    447     */
    448    @Override
    449    public String format(final Calendar calendar) {
    450        return printer.format(calendar);
    451    }
    452
    453    /**
    454     * <p>Formats a millisecond {@code long} value into the
    455     * supplied {@code StringBuffer}.</p>
    456     *
    457     * @param millis  the millisecond value to format
    458     * @param buf  the buffer to format into
    459     * @return the specified string buffer
    460     * @since 2.1
    461     * @deprecated Use {{@link #format(long, Appendable)}.
    462     */
    463    @Deprecated
    464    @Override
    465    public StringBuffer format(final long millis, final StringBuffer buf) {
    466        return printer.format(millis, buf);
    467    }
    468
    469    /**
    470     * <p>Formats a {@code Date} object into the
    471     * supplied {@code StringBuffer} using a {@code GregorianCalendar}.</p>
    472     *
    473     * @param date  the date to format
    474     * @param buf  the buffer to format into
    475     * @return the specified string buffer
    476     * @deprecated Use {{@link #format(Date, Appendable)}.
    477     */
    478    @Deprecated
    479    @Override
    480    public StringBuffer format(final Date date, final StringBuffer buf) {
    481        return printer.format(date, buf);
    482    }
    483
    484    /**
    485     * <p>Formats a {@code Calendar} object into the
    486     * supplied {@code StringBuffer}.</p>
    487     *
    488     * @param calendar  the calendar to format
    489     * @param buf  the buffer to format into
    490     * @return the specified string buffer
    491     * @deprecated Use {{@link #format(Calendar, Appendable)}.
    492     */
    493    @Deprecated
    494    @Override
    495    public StringBuffer format(final Calendar calendar, final StringBuffer buf) {
    496        return printer.format(calendar, buf);
    497    }
    498
    499    /**
    500     * <p>Formats a millisecond {@code long} value into the
    501     * supplied {@code StringBuffer}.</p>
    502     *
    503     * @param millis  the millisecond value to format
    504     * @param buf  the buffer to format into
    505     * @return the specified string buffer
    506     * @since 3.5
    507     */
    508    @Override
    509    public <B extends Appendable> B format(final long millis, final B buf) {
    510        return printer.format(millis, buf);
    511    }
    512
    513    /**
    514     * <p>Formats a {@code Date} object into the
    515     * supplied {@code StringBuffer} using a {@code GregorianCalendar}.</p>
    516     *
    517     * @param date  the date to format
    518     * @param buf  the buffer to format into
    519     * @return the specified string buffer
    520     * @since 3.5
    521     */
    522    @Override
    523    public <B extends Appendable> B format(final Date date, final B buf) {
    524        return printer.format(date, buf);
    525    }
    526
    527    /**
    528     * <p>Formats a {@code Calendar} object into the
    529     * supplied {@code StringBuffer}.</p>
    530     *
    531     * @param calendar  the calendar to format
    532     * @param buf  the buffer to format into
    533     * @return the specified string buffer
    534     * @since 3.5
    535    */
    536    @Override
    537    public <B extends Appendable> B format(final Calendar calendar, final B buf) {
    538        return printer.format(calendar, buf);
    539    }
    540
    541    // Parsing
    542    //-----------------------------------------------------------------------
    543
    544
    545    /* (non-Javadoc)
    546     * @see DateParser#parse(java.lang.String)
    547     */
    548    @Override
    549    public Date parse(final String source) throws ParseException {
    550        return parser.parse(source);
    551    }
    552
    553    /* (non-Javadoc)
    554     * @see DateParser#parse(java.lang.String, java.text.ParsePosition)
    555     */
    556    @Override
    557    public Date parse(final String source, final ParsePosition pos) {
    558        return parser.parse(source, pos);
    559    }
    560
    561    /*
    562     * (non-Javadoc)
    563     * @see org.apache.commons.lang3.time.DateParser#parse(java.lang.String, java.text.ParsePosition, java.util.Calendar)
    564     */
    565    @Override
    566    public boolean parse(final String source, final ParsePosition pos, final Calendar calendar) {
    567        return parser.parse(source, pos, calendar);
    568    }
    569
    570    /* (non-Javadoc)
    571     * @see java.text.Format#parseObject(java.lang.String, java.text.ParsePosition)
    572     */
    573    @Override
    574    public Object parseObject(final String source, final ParsePosition pos) {
    575        return parser.parseObject(source, pos);
    576    }
    577
    578    // Accessors
    579    //-----------------------------------------------------------------------
    580    /**
    581     * <p>Gets the pattern used by this formatter.</p>
    582     *
    583     * @return the pattern, {@link java.text.SimpleDateFormat} compatible
    584     */
    585    @Override
    586    public String getPattern() {
    587        return printer.getPattern();
    588    }
    589
    590    /**
    591     * <p>Gets the time zone used by this formatter.</p>
    592     *
    593     * <p>This zone is always used for {@code Date} formatting. </p>
    594     *
    595     * @return the time zone
    596     */
    597    @Override
    598    public TimeZone getTimeZone() {
    599        return printer.getTimeZone();
    600    }
    601
    602    /**
    603     * <p>Gets the locale used by this formatter.</p>
    604     *
    605     * @return the locale
    606     */
    607    @Override
    608    public Locale getLocale() {
    609        return printer.getLocale();
    610    }
    611
    612    /**
    613     * <p>Gets an estimate for the maximum string length that the
    614     * formatter will produce.</p>
    615     *
    616     * <p>The actual formatted length will almost always be less than or
    617     * equal to this amount.</p>
    618     *
    619     * @return the maximum formatted length
    620     */
    621    public int getMaxLengthEstimate() {
    622        return printer.getMaxLengthEstimate();
    623    }
    624
    625    // Basics
    626    //-----------------------------------------------------------------------
    627    /**
    628     * <p>Compares two objects for equality.</p>
    629     *
    630     * @param obj  the object to compare to
    631     * @return {@code true} if equal
    632     */
    633    @Override
    634    public boolean equals(final Object obj) {
    635        if (!(obj instanceof FastDateFormat)) {
    636            return false;
    637        }
    638        final FastDateFormat other = (FastDateFormat) obj;
    639        // no need to check parser, as it has same invariants as printer
    640        return printer.equals(other.printer);
    641    }
    642
    643    /**
    644     * <p>Returns a hash code compatible with equals.</p>
    645     *
    646     * @return a hash code compatible with equals
    647     */
    648    @Override
    649    public int hashCode() {
    650        return printer.hashCode();
    651    }
    652
    653    /**
    654     * <p>Gets a debugging string version of this formatter.</p>
    655     *
    656     * @return a debugging string
    657     */
    658    @Override
    659    public String toString() {
    660        return "FastDateFormat[" + printer.getPattern() + "," + printer.getLocale() + "," + printer.getTimeZone().getID() + "]";
    661    }
    662
    663    /**
    664     * <p>Performs the formatting by applying the rules to the
    665     * specified calendar.</p>
    666     *
    667     * @param calendar the calendar to format
    668     * @param buf  the buffer to format into
    669     * @return the specified string buffer
    670     * @deprecated Use {@link #format(Calendar, Appendable)}
    671     */
    672    @Deprecated
    673    protected StringBuffer applyRules(final Calendar calendar, final StringBuffer buf) {
    674        return printer.applyRules(calendar, buf);
    675    }
    676}