/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.commons.lang3.time;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.text.DateFormat;
import java.text.DateFormatSymbols;
import java.text.FieldPosition;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import org.apache.commons.lang3.exception.ExceptionUtils;

FastDatePrinter is a fast and thread-safe version of SimpleDateFormat.

To obtain a FastDatePrinter, use FastDateFormat.getInstance(String, TimeZone, Locale) or another variation of the factory methods of FastDateFormat.

Since FastDatePrinter is thread safe, you can use a static member instance:

private static final DatePrinter DATE_PRINTER = FastDateFormat.getInstance("yyyy-MM-dd");

This class can be used as a direct replacement to SimpleDateFormat in most formatting situations. This class is especially useful in multi-threaded server environments. SimpleDateFormat is not thread-safe in any JDK version, nor will it be as Sun have closed the bug/RFE.

Only formatting is supported by this class, but all patterns are compatible with SimpleDateFormat (except time zones and some year patterns - see below).

Java 1.4 introduced a new pattern letter, 'Z', to represent time zones in RFC822 format (eg. +0800 or -1100). This pattern letter can be used here (on all JDK versions).

In addition, the pattern 'ZZ' has been made to represent ISO 8601 extended format time zones (eg. +08:00 or -11:00). This introduces a minor incompatibility with Java 1.4, but at a gain of useful functionality.

Starting with JDK7, ISO 8601 support was added using the pattern 'X'. To maintain compatibility, 'ZZ' will continue to be supported, but using one of the 'X' formats is recommended.

Javadoc cites for the year pattern: For formatting, if the number of pattern letters is 2, the year is truncated to 2 digits; otherwise it is interpreted as a number. Starting with Java 1.7 a pattern of 'Y' or 'YYY' will be formatted as '2003', while it was '03' in former Java versions. FastDatePrinter implements the behavior of Java 7.

See Also:
Since:3.2
/** * <p>FastDatePrinter is a fast and thread-safe version of * {@link java.text.SimpleDateFormat}.</p> * * <p>To obtain a FastDatePrinter, use {@link FastDateFormat#getInstance(String, TimeZone, Locale)} * or another variation of the factory methods of {@link FastDateFormat}.</p> * * <p>Since FastDatePrinter is thread safe, you can use a static member instance:</p> * <code> * private static final DatePrinter DATE_PRINTER = FastDateFormat.getInstance("yyyy-MM-dd"); * </code> * * <p>This class can be used as a direct replacement to * {@code SimpleDateFormat} in most formatting situations. * This class is especially useful in multi-threaded server environments. * {@code SimpleDateFormat} is not thread-safe in any JDK version, * nor will it be as Sun have closed the bug/RFE. * </p> * * <p>Only formatting is supported by this class, but all patterns are compatible with * SimpleDateFormat (except time zones and some year patterns - see below).</p> * * <p>Java 1.4 introduced a new pattern letter, {@code 'Z'}, to represent * time zones in RFC822 format (eg. {@code +0800} or {@code -1100}). * This pattern letter can be used here (on all JDK versions).</p> * * <p>In addition, the pattern {@code 'ZZ'} has been made to represent * ISO 8601 extended format time zones (eg. {@code +08:00} or {@code -11:00}). * This introduces a minor incompatibility with Java 1.4, but at a gain of * useful functionality.</p> * * <p>Starting with JDK7, ISO 8601 support was added using the pattern {@code 'X'}. * To maintain compatibility, {@code 'ZZ'} will continue to be supported, but using * one of the {@code 'X'} formats is recommended. * * <p>Javadoc cites for the year pattern: <i>For formatting, if the number of * pattern letters is 2, the year is truncated to 2 digits; otherwise it is * interpreted as a number.</i> Starting with Java 1.7 a pattern of 'Y' or * 'YYY' will be formatted as '2003', while it was '03' in former Java * versions. FastDatePrinter implements the behavior of Java 7.</p> * * @since 3.2 * @see FastDateParser */
public class FastDatePrinter implements DatePrinter, Serializable { // A lot of the speed in this class comes from caching, but some comes // from the special int to StringBuffer conversion. // // The following produces a padded 2 digit number: // buffer.append((char)(value / 10 + '0')); // buffer.append((char)(value % 10 + '0')); // // Note that the fastest append to StringBuffer is a single char (used here). // Note that Integer.toString() is not called, the conversion is simply // taking the value and adding (mathematically) the ASCII value for '0'. // So, don't change this code! It works and is very fast.
Required for serialization support.
See Also:
  • Serializable
/** * Required for serialization support. * * @see java.io.Serializable */
private static final long serialVersionUID = 1L;
FULL locale dependent date or time style.
/** * FULL locale dependent date or time style. */
public static final int FULL = DateFormat.FULL;
LONG locale dependent date or time style.
/** * LONG locale dependent date or time style. */
public static final int LONG = DateFormat.LONG;
MEDIUM locale dependent date or time style.
/** * MEDIUM locale dependent date or time style. */
public static final int MEDIUM = DateFormat.MEDIUM;
SHORT locale dependent date or time style.
/** * SHORT locale dependent date or time style. */
public static final int SHORT = DateFormat.SHORT;
The pattern.
/** * The pattern. */
private final String mPattern;
The time zone.
/** * The time zone. */
private final TimeZone mTimeZone;
The locale.
/** * The locale. */
private final Locale mLocale;
The parsed rules.
/** * The parsed rules. */
private transient Rule[] mRules;
The estimated maximum length.
/** * The estimated maximum length. */
private transient int mMaxLengthEstimate; // Constructor //-----------------------------------------------------------------------

Constructs a new FastDatePrinter.

Use FastDateFormat.getInstance(String, TimeZone, Locale) or another variation of the factory methods of FastDateFormat to get a cached FastDatePrinter instance.
Params:
  • pattern – SimpleDateFormat compatible pattern
  • timeZone – non-null time zone to use
  • locale – non-null locale to use
Throws:
/** * <p>Constructs a new FastDatePrinter.</p> * Use {@link FastDateFormat#getInstance(String, TimeZone, Locale)} or another variation of the * factory methods of {@link FastDateFormat} to get a cached FastDatePrinter instance. * * @param pattern {@link java.text.SimpleDateFormat} compatible pattern * @param timeZone non-null time zone to use * @param locale non-null locale to use * @throws NullPointerException if pattern, timeZone, or locale is null. */
protected FastDatePrinter(final String pattern, final TimeZone timeZone, final Locale locale) { mPattern = pattern; mTimeZone = timeZone; mLocale = locale; init(); }

Initializes the instance for first use.

/** * <p>Initializes the instance for first use.</p> */
private void init() { final List<Rule> rulesList = parsePattern(); mRules = rulesList.toArray(new Rule[rulesList.size()]); int len = 0; for (int i=mRules.length; --i >= 0; ) { len += mRules[i].estimateLength(); } mMaxLengthEstimate = len; } // Parse the pattern //-----------------------------------------------------------------------

Returns a list of Rules given a pattern.

Throws:
Returns:a List of Rule objects
/** * <p>Returns a list of Rules given a pattern.</p> * * @return a {@code List} of Rule objects * @throws IllegalArgumentException if pattern is invalid */
protected List<Rule> parsePattern() { final DateFormatSymbols symbols = new DateFormatSymbols(mLocale); final List<Rule> rules = new ArrayList<>(); final String[] ERAs = symbols.getEras(); final String[] months = symbols.getMonths(); final String[] shortMonths = symbols.getShortMonths(); final String[] weekdays = symbols.getWeekdays(); final String[] shortWeekdays = symbols.getShortWeekdays(); final String[] AmPmStrings = symbols.getAmPmStrings(); final int length = mPattern.length(); final int[] indexRef = new int[1]; for (int i = 0; i < length; i++) { indexRef[0] = i; final String token = parseToken(mPattern, indexRef); i = indexRef[0]; final int tokenLen = token.length(); if (tokenLen == 0) { break; } Rule rule; final char c = token.charAt(0); switch (c) { case 'G': // era designator (text) rule = new TextField(Calendar.ERA, ERAs); break; case 'y': // year (number) case 'Y': // week year if (tokenLen == 2) { rule = TwoDigitYearField.INSTANCE; } else { rule = selectNumberRule(Calendar.YEAR, tokenLen < 4 ? 4 : tokenLen); } if (c == 'Y') { rule = new WeekYear((NumberRule) rule); } break; case 'M': // month in year (text and number) if (tokenLen >= 4) { rule = new TextField(Calendar.MONTH, months); } else if (tokenLen == 3) { rule = new TextField(Calendar.MONTH, shortMonths); } else if (tokenLen == 2) { rule = TwoDigitMonthField.INSTANCE; } else { rule = UnpaddedMonthField.INSTANCE; } break; case 'd': // day in month (number) rule = selectNumberRule(Calendar.DAY_OF_MONTH, tokenLen); break; case 'h': // hour in am/pm (number, 1..12) rule = new TwelveHourField(selectNumberRule(Calendar.HOUR, tokenLen)); break; case 'H': // hour in day (number, 0..23) rule = selectNumberRule(Calendar.HOUR_OF_DAY, tokenLen); break; case 'm': // minute in hour (number) rule = selectNumberRule(Calendar.MINUTE, tokenLen); break; case 's': // second in minute (number) rule = selectNumberRule(Calendar.SECOND, tokenLen); break; case 'S': // millisecond (number) rule = selectNumberRule(Calendar.MILLISECOND, tokenLen); break; case 'E': // day in week (text) rule = new TextField(Calendar.DAY_OF_WEEK, tokenLen < 4 ? shortWeekdays : weekdays); break; case 'u': // day in week (number) rule = new DayInWeekField(selectNumberRule(Calendar.DAY_OF_WEEK, tokenLen)); break; case 'D': // day in year (number) rule = selectNumberRule(Calendar.DAY_OF_YEAR, tokenLen); break; case 'F': // day of week in month (number) rule = selectNumberRule(Calendar.DAY_OF_WEEK_IN_MONTH, tokenLen); break; case 'w': // week in year (number) rule = selectNumberRule(Calendar.WEEK_OF_YEAR, tokenLen); break; case 'W': // week in month (number) rule = selectNumberRule(Calendar.WEEK_OF_MONTH, tokenLen); break; case 'a': // am/pm marker (text) rule = new TextField(Calendar.AM_PM, AmPmStrings); break; case 'k': // hour in day (1..24) rule = new TwentyFourHourField(selectNumberRule(Calendar.HOUR_OF_DAY, tokenLen)); break; case 'K': // hour in am/pm (0..11) rule = selectNumberRule(Calendar.HOUR, tokenLen); break; case 'X': // ISO 8601 rule = Iso8601_Rule.getRule(tokenLen); break; case 'z': // time zone (text) if (tokenLen >= 4) { rule = new TimeZoneNameRule(mTimeZone, mLocale, TimeZone.LONG); } else { rule = new TimeZoneNameRule(mTimeZone, mLocale, TimeZone.SHORT); } break; case 'Z': // time zone (value) if (tokenLen == 1) { rule = TimeZoneNumberRule.INSTANCE_NO_COLON; } else if (tokenLen == 2) { rule = Iso8601_Rule.ISO8601_HOURS_COLON_MINUTES; } else { rule = TimeZoneNumberRule.INSTANCE_COLON; } break; case '\'': // literal text final String sub = token.substring(1); if (sub.length() == 1) { rule = new CharacterLiteral(sub.charAt(0)); } else { rule = new StringLiteral(sub); } break; default: throw new IllegalArgumentException("Illegal pattern component: " + token); } rules.add(rule); } return rules; }

Performs the parsing of tokens.

Params:
  • pattern – the pattern
  • indexRef – index references
Returns:parsed token
/** * <p>Performs the parsing of tokens.</p> * * @param pattern the pattern * @param indexRef index references * @return parsed token */
protected String parseToken(final String pattern, final int[] indexRef) { final StringBuilder buf = new StringBuilder(); int i = indexRef[0]; final int length = pattern.length(); char c = pattern.charAt(i); if (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z') { // Scan a run of the same character, which indicates a time // pattern. buf.append(c); while (i + 1 < length) { final char peek = pattern.charAt(i + 1); if (peek == c) { buf.append(c); i++; } else { break; } } } else { // This will identify token as text. buf.append('\''); boolean inLiteral = false; for (; i < length; i++) { c = pattern.charAt(i); if (c == '\'') { if (i + 1 < length && pattern.charAt(i + 1) == '\'') { // '' is treated as escaped ' i++; buf.append(c); } else { inLiteral = !inLiteral; } } else if (!inLiteral && (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z')) { i--; break; } else { buf.append(c); } } } indexRef[0] = i; return buf.toString(); }

Gets an appropriate rule for the padding required.

Params:
  • field – the field to get a rule for
  • padding – the padding required
Returns:a new rule with the correct padding
/** * <p>Gets an appropriate rule for the padding required.</p> * * @param field the field to get a rule for * @param padding the padding required * @return a new rule with the correct padding */
protected NumberRule selectNumberRule(final int field, final int padding) { switch (padding) { case 1: return new UnpaddedNumberField(field); case 2: return new TwoDigitNumberField(field); default: return new PaddedNumberField(field, padding); } } // Format methods //-----------------------------------------------------------------------

Formats a Date, Calendar or Long (milliseconds) object.

Params:
  • obj – the object to format
  • toAppendTo – the buffer to append to
  • pos – the position - ignored
Deprecated:Use {format(Date), {format(Calendar), {format(long), or {format(Object)
Returns:the buffer passed in
/** * <p>Formats a {@code Date}, {@code Calendar} or * {@code Long} (milliseconds) object.</p> * @deprecated Use {{@link #format(Date)}, {{@link #format(Calendar)}, {{@link #format(long)}, or {{@link #format(Object)} * @param obj the object to format * @param toAppendTo the buffer to append to * @param pos the position - ignored * @return the buffer passed in */
@Deprecated @Override public StringBuffer format(final Object obj, final StringBuffer toAppendTo, final FieldPosition pos) { if (obj instanceof Date) { return format((Date) obj, toAppendTo); } else if (obj instanceof Calendar) { return format((Calendar) obj, toAppendTo); } else if (obj instanceof Long) { return format(((Long) obj).longValue(), toAppendTo); } else { throw new IllegalArgumentException("Unknown class: " + (obj == null ? "<null>" : obj.getClass().getName())); } }

Formats a Date, Calendar or Long (milliseconds) object.

Params:
  • obj – the object to format
Since:3.5
Returns:The formatted value.
/** * <p>Formats a {@code Date}, {@code Calendar} or * {@code Long} (milliseconds) object.</p> * @since 3.5 * @param obj the object to format * @return The formatted value. */
String format(final Object obj) { if (obj instanceof Date) { return format((Date) obj); } else if (obj instanceof Calendar) { return format((Calendar) obj); } else if (obj instanceof Long) { return format(((Long) obj).longValue()); } else { throw new IllegalArgumentException("Unknown class: " + (obj == null ? "<null>" : obj.getClass().getName())); } } /* (non-Javadoc) * @see org.apache.commons.lang3.time.DatePrinter#format(long) */ @Override public String format(final long millis) { final Calendar c = newCalendar(); c.setTimeInMillis(millis); return applyRulesToString(c); }
Creates a String representation of the given Calendar by applying the rules of this printer to it.
Params:
  • c – the Calender to apply the rules to.
Returns:a String representation of the given Calendar.
/** * Creates a String representation of the given Calendar by applying the rules of this printer to it. * @param c the Calender to apply the rules to. * @return a String representation of the given Calendar. */
private String applyRulesToString(final Calendar c) { return applyRules(c, new StringBuilder(mMaxLengthEstimate)).toString(); }
Creation method for new calender instances.
Returns:a new Calendar instance.
/** * Creation method for new calender instances. * @return a new Calendar instance. */
private Calendar newCalendar() { return Calendar.getInstance(mTimeZone, mLocale); } /* (non-Javadoc) * @see org.apache.commons.lang3.time.DatePrinter#format(java.util.Date) */ @Override public String format(final Date date) { final Calendar c = newCalendar(); c.setTime(date); return applyRulesToString(c); } /* (non-Javadoc) * @see org.apache.commons.lang3.time.DatePrinter#format(java.util.Calendar) */ @Override public String format(final Calendar calendar) { return format(calendar, new StringBuilder(mMaxLengthEstimate)).toString(); } /* (non-Javadoc) * @see org.apache.commons.lang3.time.DatePrinter#format(long, java.lang.StringBuffer) */ @Override public StringBuffer format(final long millis, final StringBuffer buf) { final Calendar c = newCalendar(); c.setTimeInMillis(millis); return (StringBuffer) applyRules(c, (Appendable)buf); } /* (non-Javadoc) * @see org.apache.commons.lang3.time.DatePrinter#format(java.util.Date, java.lang.StringBuffer) */ @Override public StringBuffer format(final Date date, final StringBuffer buf) { final Calendar c = newCalendar(); c.setTime(date); return (StringBuffer) applyRules(c, (Appendable)buf); } /* (non-Javadoc) * @see org.apache.commons.lang3.time.DatePrinter#format(java.util.Calendar, java.lang.StringBuffer) */ @Override public StringBuffer format(final Calendar calendar, final StringBuffer buf) { // do not pass in calendar directly, this will cause TimeZone of FastDatePrinter to be ignored return format(calendar.getTime(), buf); } /* (non-Javadoc) * @see org.apache.commons.lang3.time.DatePrinter#format(long, java.lang.Appendable) */ @Override public <B extends Appendable> B format(final long millis, final B buf) { final Calendar c = newCalendar(); c.setTimeInMillis(millis); return applyRules(c, buf); } /* (non-Javadoc) * @see org.apache.commons.lang3.time.DatePrinter#format(java.util.Date, java.lang.Appendable) */ @Override public <B extends Appendable> B format(final Date date, final B buf) { final Calendar c = newCalendar(); c.setTime(date); return applyRules(c, buf); } /* (non-Javadoc) * @see org.apache.commons.lang3.time.DatePrinter#format(java.util.Calendar, java.lang.Appendable) */ @Override public <B extends Appendable> B format(Calendar calendar, final B buf) { // do not pass in calendar directly, this will cause TimeZone of FastDatePrinter to be ignored if(!calendar.getTimeZone().equals(mTimeZone)) { calendar = (Calendar)calendar.clone(); calendar.setTimeZone(mTimeZone); } return applyRules(calendar, buf); }
Performs the formatting by applying the rules to the specified calendar.
Params:
  • calendar – the calendar to format
  • buf – the buffer to format into
Returns:the specified string buffer
Deprecated:use format(Calendar) or format(Calendar, Appendable)
/** * Performs the formatting by applying the rules to the * specified calendar. * * @param calendar the calendar to format * @param buf the buffer to format into * @return the specified string buffer * * @deprecated use {@link #format(Calendar)} or {@link #format(Calendar, Appendable)} */
@Deprecated protected StringBuffer applyRules(final Calendar calendar, final StringBuffer buf) { return (StringBuffer) applyRules(calendar, (Appendable)buf); }

Performs the formatting by applying the rules to the specified calendar.

Params:
  • calendar – the calendar to format
  • buf – the buffer to format into
Type parameters:
  • <B> – the Appendable class type, usually StringBuilder or StringBuffer.
Returns:the specified string buffer
/** * <p>Performs the formatting by applying the rules to the * specified calendar.</p> * * @param calendar the calendar to format * @param buf the buffer to format into * @param <B> the Appendable class type, usually StringBuilder or StringBuffer. * @return the specified string buffer */
private <B extends Appendable> B applyRules(final Calendar calendar, final B buf) { try { for (final Rule rule : mRules) { rule.appendTo(buf, calendar); } } catch (final IOException ioe) { ExceptionUtils.rethrow(ioe); } return buf; } // Accessors //----------------------------------------------------------------------- /* (non-Javadoc) * @see org.apache.commons.lang3.time.DatePrinter#getPattern() */ @Override public String getPattern() { return mPattern; } /* (non-Javadoc) * @see org.apache.commons.lang3.time.DatePrinter#getTimeZone() */ @Override public TimeZone getTimeZone() { return mTimeZone; } /* (non-Javadoc) * @see org.apache.commons.lang3.time.DatePrinter#getLocale() */ @Override public Locale getLocale() { return mLocale; }

Gets an estimate for the maximum string length that the formatter will produce.

The actual formatted length will almost always be less than or equal to this amount.

Returns:the maximum formatted length
/** * <p>Gets an estimate for the maximum string length that the * formatter will produce.</p> * * <p>The actual formatted length will almost always be less than or * equal to this amount.</p> * * @return the maximum formatted length */
public int getMaxLengthEstimate() { return mMaxLengthEstimate; } // Basics //-----------------------------------------------------------------------

Compares two objects for equality.

Params:
  • obj – the object to compare to
Returns:true if equal
/** * <p>Compares two objects for equality.</p> * * @param obj the object to compare to * @return {@code true} if equal */
@Override public boolean equals(final Object obj) { if (!(obj instanceof FastDatePrinter)) { return false; } final FastDatePrinter other = (FastDatePrinter) obj; return mPattern.equals(other.mPattern) && mTimeZone.equals(other.mTimeZone) && mLocale.equals(other.mLocale); }

Returns a hash code compatible with equals.

Returns:a hash code compatible with equals
/** * <p>Returns a hash code compatible with equals.</p> * * @return a hash code compatible with equals */
@Override public int hashCode() { return mPattern.hashCode() + 13 * (mTimeZone.hashCode() + 13 * mLocale.hashCode()); }

Gets a debugging string version of this formatter.

Returns:a debugging string
/** * <p>Gets a debugging string version of this formatter.</p> * * @return a debugging string */
@Override public String toString() { return "FastDatePrinter[" + mPattern + "," + mLocale + "," + mTimeZone.getID() + "]"; } // Serializing //-----------------------------------------------------------------------
Create the object after serialization. This implementation reinitializes the transient properties.
Params:
  • in – ObjectInputStream from which the object is being deserialized.
Throws:
/** * Create the object after serialization. This implementation reinitializes the * transient properties. * * @param in ObjectInputStream from which the object is being deserialized. * @throws IOException if there is an IO issue. * @throws ClassNotFoundException if a class cannot be found. */
private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); init(); }
Appends two digits to the given buffer.
Params:
  • buffer – the buffer to append to.
  • value – the value to append digits from.
/** * Appends two digits to the given buffer. * * @param buffer the buffer to append to. * @param value the value to append digits from. */
private static void appendDigits(final Appendable buffer, final int value) throws IOException { buffer.append((char)(value / 10 + '0')); buffer.append((char)(value % 10 + '0')); } private static final int MAX_DIGITS = 10; // log10(Integer.MAX_VALUE) ~= 9.3
Appends all digits to the given buffer.
Params:
  • buffer – the buffer to append to.
  • value – the value to append digits from.
/** * Appends all digits to the given buffer. * * @param buffer the buffer to append to. * @param value the value to append digits from. */
private static void appendFullDigits(final Appendable buffer, int value, int minFieldWidth) throws IOException { // specialized paths for 1 to 4 digits -> avoid the memory allocation from the temporary work array // see LANG-1248 if (value < 10000) { // less memory allocation path works for four digits or less int nDigits = 4; if (value < 1000) { --nDigits; if (value < 100) { --nDigits; if (value < 10) { --nDigits; } } } // left zero pad for (int i = minFieldWidth - nDigits; i > 0; --i) { buffer.append('0'); } switch (nDigits) { case 4: buffer.append((char) (value / 1000 + '0')); value %= 1000; case 3: if (value >= 100) { buffer.append((char) (value / 100 + '0')); value %= 100; } else { buffer.append('0'); } case 2: if (value >= 10) { buffer.append((char) (value / 10 + '0')); value %= 10; } else { buffer.append('0'); } case 1: buffer.append((char) (value + '0')); } } else { // more memory allocation path works for any digits // build up decimal representation in reverse final char[] work = new char[MAX_DIGITS]; int digit = 0; while (value != 0) { work[digit++] = (char) (value % 10 + '0'); value = value / 10; } // pad with zeros while (digit < minFieldWidth) { buffer.append('0'); --minFieldWidth; } // reverse while (--digit >= 0) { buffer.append(work[digit]); } } } // Rules //-----------------------------------------------------------------------

Inner class defining a rule.

/** * <p>Inner class defining a rule.</p> */
private interface Rule {
Returns the estimated length of the result.
Returns:the estimated length
/** * Returns the estimated length of the result. * * @return the estimated length */
int estimateLength();
Appends the value of the specified calendar to the output buffer based on the rule implementation.
Params:
  • buf – the output buffer
  • calendar – calendar to be appended
Throws:
/** * Appends the value of the specified calendar to the output buffer based on the rule implementation. * * @param buf the output buffer * @param calendar calendar to be appended * @throws IOException if an I/O error occurs */
void appendTo(Appendable buf, Calendar calendar) throws IOException; }

Inner class defining a numeric rule.

/** * <p>Inner class defining a numeric rule.</p> */
private interface NumberRule extends Rule {
Appends the specified value to the output buffer based on the rule implementation.
Params:
  • buffer – the output buffer
  • value – the value to be appended
Throws:
/** * Appends the specified value to the output buffer based on the rule implementation. * * @param buffer the output buffer * @param value the value to be appended * @throws IOException if an I/O error occurs */
void appendTo(Appendable buffer, int value) throws IOException; }

Inner class to output a constant single character.

/** * <p>Inner class to output a constant single character.</p> */
private static class CharacterLiteral implements Rule { private final char mValue;
Constructs a new instance of CharacterLiteral to hold the specified value.
Params:
  • value – the character literal
/** * Constructs a new instance of {@code CharacterLiteral} * to hold the specified value. * * @param value the character literal */
CharacterLiteral(final char value) { mValue = value; }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public int estimateLength() { return 1; }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { buffer.append(mValue); } }

Inner class to output a constant string.

/** * <p>Inner class to output a constant string.</p> */
private static class StringLiteral implements Rule { private final String mValue;
Constructs a new instance of StringLiteral to hold the specified value.
Params:
  • value – the string literal
/** * Constructs a new instance of {@code StringLiteral} * to hold the specified value. * * @param value the string literal */
StringLiteral(final String value) { mValue = value; }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public int estimateLength() { return mValue.length(); }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { buffer.append(mValue); } }

Inner class to output one of a set of values.

/** * <p>Inner class to output one of a set of values.</p> */
private static class TextField implements Rule { private final int mField; private final String[] mValues;
Constructs an instance of TextField with the specified field and values.
Params:
  • field – the field
  • values – the field values
/** * Constructs an instance of {@code TextField} * with the specified field and values. * * @param field the field * @param values the field values */
TextField(final int field, final String[] values) { mField = field; mValues = values; }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public int estimateLength() { int max = 0; for (int i=mValues.length; --i >= 0; ) { final int len = mValues[i].length(); if (len > max) { max = len; } } return max; }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { buffer.append(mValues[calendar.get(mField)]); } }

Inner class to output an unpadded number.

/** * <p>Inner class to output an unpadded number.</p> */
private static class UnpaddedNumberField implements NumberRule { private final int mField;
Constructs an instance of UnpadedNumberField with the specified field.
Params:
  • field – the field
/** * Constructs an instance of {@code UnpadedNumberField} with the specified field. * * @param field the field */
UnpaddedNumberField(final int field) { mField = field; }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public int estimateLength() { return 4; }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { appendTo(buffer, calendar.get(mField)); }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public final void appendTo(final Appendable buffer, final int value) throws IOException { if (value < 10) { buffer.append((char)(value + '0')); } else if (value < 100) { appendDigits(buffer, value); } else { appendFullDigits(buffer, value, 1); } } }

Inner class to output an unpadded month.

/** * <p>Inner class to output an unpadded month.</p> */
private static class UnpaddedMonthField implements NumberRule { static final UnpaddedMonthField INSTANCE = new UnpaddedMonthField();
Constructs an instance of UnpaddedMonthField.
/** * Constructs an instance of {@code UnpaddedMonthField}. * */
UnpaddedMonthField() { super(); }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public int estimateLength() { return 2; }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { appendTo(buffer, calendar.get(Calendar.MONTH) + 1); }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public final void appendTo(final Appendable buffer, final int value) throws IOException { if (value < 10) { buffer.append((char)(value + '0')); } else { appendDigits(buffer, value); } } }

Inner class to output a padded number.

/** * <p>Inner class to output a padded number.</p> */
private static class PaddedNumberField implements NumberRule { private final int mField; private final int mSize;
Constructs an instance of PaddedNumberField.
Params:
  • field – the field
  • size – size of the output field
/** * Constructs an instance of {@code PaddedNumberField}. * * @param field the field * @param size size of the output field */
PaddedNumberField(final int field, final int size) { if (size < 3) { // Should use UnpaddedNumberField or TwoDigitNumberField. throw new IllegalArgumentException(); } mField = field; mSize = size; }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public int estimateLength() { return mSize; }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { appendTo(buffer, calendar.get(mField)); }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public final void appendTo(final Appendable buffer, final int value) throws IOException { appendFullDigits(buffer, value, mSize); } }

Inner class to output a two digit number.

/** * <p>Inner class to output a two digit number.</p> */
private static class TwoDigitNumberField implements NumberRule { private final int mField;
Constructs an instance of TwoDigitNumberField with the specified field.
Params:
  • field – the field
/** * Constructs an instance of {@code TwoDigitNumberField} with the specified field. * * @param field the field */
TwoDigitNumberField(final int field) { mField = field; }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public int estimateLength() { return 2; }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { appendTo(buffer, calendar.get(mField)); }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public final void appendTo(final Appendable buffer, final int value) throws IOException { if (value < 100) { appendDigits(buffer, value); } else { appendFullDigits(buffer, value, 2); } } }

Inner class to output a two digit year.

/** * <p>Inner class to output a two digit year.</p> */
private static class TwoDigitYearField implements NumberRule { static final TwoDigitYearField INSTANCE = new TwoDigitYearField();
Constructs an instance of TwoDigitYearField.
/** * Constructs an instance of {@code TwoDigitYearField}. */
TwoDigitYearField() { super(); }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public int estimateLength() { return 2; }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { appendTo(buffer, calendar.get(Calendar.YEAR) % 100); }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public final void appendTo(final Appendable buffer, final int value) throws IOException { appendDigits(buffer, value); } }

Inner class to output a two digit month.

/** * <p>Inner class to output a two digit month.</p> */
private static class TwoDigitMonthField implements NumberRule { static final TwoDigitMonthField INSTANCE = new TwoDigitMonthField();
Constructs an instance of TwoDigitMonthField.
/** * Constructs an instance of {@code TwoDigitMonthField}. */
TwoDigitMonthField() { super(); }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public int estimateLength() { return 2; }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { appendTo(buffer, calendar.get(Calendar.MONTH) + 1); }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public final void appendTo(final Appendable buffer, final int value) throws IOException { appendDigits(buffer, value); } }

Inner class to output the twelve hour field.

/** * <p>Inner class to output the twelve hour field.</p> */
private static class TwelveHourField implements NumberRule { private final NumberRule mRule;
Constructs an instance of TwelveHourField with the specified NumberRule.
Params:
  • rule – the rule
/** * Constructs an instance of {@code TwelveHourField} with the specified * {@code NumberRule}. * * @param rule the rule */
TwelveHourField(final NumberRule rule) { mRule = rule; }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public int estimateLength() { return mRule.estimateLength(); }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { int value = calendar.get(Calendar.HOUR); if (value == 0) { value = calendar.getLeastMaximum(Calendar.HOUR) + 1; } mRule.appendTo(buffer, value); }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public void appendTo(final Appendable buffer, final int value) throws IOException { mRule.appendTo(buffer, value); } }

Inner class to output the twenty four hour field.

/** * <p>Inner class to output the twenty four hour field.</p> */
private static class TwentyFourHourField implements NumberRule { private final NumberRule mRule;
Constructs an instance of TwentyFourHourField with the specified NumberRule.
Params:
  • rule – the rule
/** * Constructs an instance of {@code TwentyFourHourField} with the specified * {@code NumberRule}. * * @param rule the rule */
TwentyFourHourField(final NumberRule rule) { mRule = rule; }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public int estimateLength() { return mRule.estimateLength(); }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { int value = calendar.get(Calendar.HOUR_OF_DAY); if (value == 0) { value = calendar.getMaximum(Calendar.HOUR_OF_DAY) + 1; } mRule.appendTo(buffer, value); }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public void appendTo(final Appendable buffer, final int value) throws IOException { mRule.appendTo(buffer, value); } }

Inner class to output the numeric day in week.

/** * <p>Inner class to output the numeric day in week.</p> */
private static class DayInWeekField implements NumberRule { private final NumberRule mRule; DayInWeekField(final NumberRule rule) { mRule = rule; } @Override public int estimateLength() { return mRule.estimateLength(); } @Override public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { final int value = calendar.get(Calendar.DAY_OF_WEEK); mRule.appendTo(buffer, value != Calendar.SUNDAY ? value - 1 : 7); } @Override public void appendTo(final Appendable buffer, final int value) throws IOException { mRule.appendTo(buffer, value); } }

Inner class to output the numeric day in week.

/** * <p>Inner class to output the numeric day in week.</p> */
private static class WeekYear implements NumberRule { private final NumberRule mRule; WeekYear(final NumberRule rule) { mRule = rule; } @Override public int estimateLength() { return mRule.estimateLength(); } @Override public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { mRule.appendTo(buffer, calendar.getWeekYear()); } @Override public void appendTo(final Appendable buffer, final int value) throws IOException { mRule.appendTo(buffer, value); } } //----------------------------------------------------------------------- private static final ConcurrentMap<TimeZoneDisplayKey, String> cTimeZoneDisplayCache = new ConcurrentHashMap<>(7);

Gets the time zone display name, using a cache for performance.

Params:
  • tz – the zone to query
  • daylight – true if daylight savings
  • style – the style to use TimeZone.LONG or TimeZone.SHORT
  • locale – the locale to use
Returns:the textual name of the time zone
/** * <p>Gets the time zone display name, using a cache for performance.</p> * * @param tz the zone to query * @param daylight true if daylight savings * @param style the style to use {@code TimeZone.LONG} or {@code TimeZone.SHORT} * @param locale the locale to use * @return the textual name of the time zone */
static String getTimeZoneDisplay(final TimeZone tz, final boolean daylight, final int style, final Locale locale) { final TimeZoneDisplayKey key = new TimeZoneDisplayKey(tz, daylight, style, locale); String value = cTimeZoneDisplayCache.get(key); if (value == null) { // This is a very slow call, so cache the results. value = tz.getDisplayName(daylight, style, locale); final String prior = cTimeZoneDisplayCache.putIfAbsent(key, value); if (prior != null) { value= prior; } } return value; }

Inner class to output a time zone name.

/** * <p>Inner class to output a time zone name.</p> */
private static class TimeZoneNameRule implements Rule { private final Locale mLocale; private final int mStyle; private final String mStandard; private final String mDaylight;
Constructs an instance of TimeZoneNameRule with the specified properties.
Params:
  • timeZone – the time zone
  • locale – the locale
  • style – the style
/** * Constructs an instance of {@code TimeZoneNameRule} with the specified properties. * * @param timeZone the time zone * @param locale the locale * @param style the style */
TimeZoneNameRule(final TimeZone timeZone, final Locale locale, final int style) { mLocale = locale; mStyle = style; mStandard = getTimeZoneDisplay(timeZone, false, style, locale); mDaylight = getTimeZoneDisplay(timeZone, true, style, locale); }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public int estimateLength() { // We have no access to the Calendar object that will be passed to // appendTo so base estimate on the TimeZone passed to the // constructor return Math.max(mStandard.length(), mDaylight.length()); }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { final TimeZone zone = calendar.getTimeZone(); if (calendar.get(Calendar.DST_OFFSET) != 0) { buffer.append(getTimeZoneDisplay(zone, true, mStyle, mLocale)); } else { buffer.append(getTimeZoneDisplay(zone, false, mStyle, mLocale)); } } }

Inner class to output a time zone as a number +/-HHMM or +/-HH:MM.

/** * <p>Inner class to output a time zone as a number {@code +/-HHMM} * or {@code +/-HH:MM}.</p> */
private static class TimeZoneNumberRule implements Rule { static final TimeZoneNumberRule INSTANCE_COLON = new TimeZoneNumberRule(true); static final TimeZoneNumberRule INSTANCE_NO_COLON = new TimeZoneNumberRule(false); final boolean mColon;
Constructs an instance of TimeZoneNumberRule with the specified properties.
Params:
  • colon – add colon between HH and MM in the output if true
/** * Constructs an instance of {@code TimeZoneNumberRule} with the specified properties. * * @param colon add colon between HH and MM in the output if {@code true} */
TimeZoneNumberRule(final boolean colon) { mColon = colon; }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public int estimateLength() { return 5; }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { int offset = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET); if (offset < 0) { buffer.append('-'); offset = -offset; } else { buffer.append('+'); } final int hours = offset / (60 * 60 * 1000); appendDigits(buffer, hours); if (mColon) { buffer.append(':'); } final int minutes = offset / (60 * 1000) - 60 * hours; appendDigits(buffer, minutes); } }

Inner class to output a time zone as a number +/-HHMM or +/-HH:MM.

/** * <p>Inner class to output a time zone as a number {@code +/-HHMM} * or {@code +/-HH:MM}.</p> */
private static class Iso8601_Rule implements Rule { // Sign TwoDigitHours or Z static final Iso8601_Rule ISO8601_HOURS = new Iso8601_Rule(3); // Sign TwoDigitHours Minutes or Z static final Iso8601_Rule ISO8601_HOURS_MINUTES = new Iso8601_Rule(5); // Sign TwoDigitHours : Minutes or Z static final Iso8601_Rule ISO8601_HOURS_COLON_MINUTES = new Iso8601_Rule(6);
Factory method for Iso8601_Rules.
Params:
  • tokenLen – a token indicating the length of the TimeZone String to be formatted.
Returns:a Iso8601_Rule that can format TimeZone String of length tokenLen. If no such rule exists, an IllegalArgumentException will be thrown.
/** * Factory method for Iso8601_Rules. * * @param tokenLen a token indicating the length of the TimeZone String to be formatted. * @return a Iso8601_Rule that can format TimeZone String of length {@code tokenLen}. If no such * rule exists, an IllegalArgumentException will be thrown. */
static Iso8601_Rule getRule(final int tokenLen) { switch(tokenLen) { case 1: return Iso8601_Rule.ISO8601_HOURS; case 2: return Iso8601_Rule.ISO8601_HOURS_MINUTES; case 3: return Iso8601_Rule.ISO8601_HOURS_COLON_MINUTES; default: throw new IllegalArgumentException("invalid number of X"); } } final int length;
Constructs an instance of Iso8601_Rule with the specified properties.
Params:
  • length – The number of characters in output (unless Z is output)
/** * Constructs an instance of {@code Iso8601_Rule} with the specified properties. * * @param length The number of characters in output (unless Z is output) */
Iso8601_Rule(final int length) { this.length = length; }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public int estimateLength() { return length; }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { int offset = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET); if (offset == 0) { buffer.append("Z"); return; } if (offset < 0) { buffer.append('-'); offset = -offset; } else { buffer.append('+'); } final int hours = offset / (60 * 60 * 1000); appendDigits(buffer, hours); if (length<5) { return; } if (length==6) { buffer.append(':'); } final int minutes = offset / (60 * 1000) - 60 * hours; appendDigits(buffer, minutes); } } // ----------------------------------------------------------------------

Inner class that acts as a compound key for time zone names.

/** * <p>Inner class that acts as a compound key for time zone names.</p> */
private static class TimeZoneDisplayKey { private final TimeZone mTimeZone; private final int mStyle; private final Locale mLocale;
Constructs an instance of TimeZoneDisplayKey with the specified properties.
Params:
  • timeZone – the time zone
  • daylight – adjust the style for daylight saving time if true
  • style – the timezone style
  • locale – the timezone locale
/** * Constructs an instance of {@code TimeZoneDisplayKey} with the specified properties. * * @param timeZone the time zone * @param daylight adjust the style for daylight saving time if {@code true} * @param style the timezone style * @param locale the timezone locale */
TimeZoneDisplayKey(final TimeZone timeZone, final boolean daylight, final int style, final Locale locale) { mTimeZone = timeZone; if (daylight) { mStyle = style | 0x80000000; } else { mStyle = style; } mLocale = locale; }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public int hashCode() { return (mStyle * 31 + mLocale.hashCode() ) * 31 + mTimeZone.hashCode(); }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public boolean equals(final Object obj) { if (this == obj) { return true; } if (obj instanceof TimeZoneDisplayKey) { final TimeZoneDisplayKey other = (TimeZoneDisplayKey)obj; return mTimeZone.equals(other.mTimeZone) && mStyle == other.mStyle && mLocale.equals(other.mLocale); } return false; } } }