All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.joda.time.format.DateTimeFormat Maven / Gradle / Ivy

The newest version!
/*
 *  Copyright 2001-2014 Stephen Colebourne
 *
 *  Licensed 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.joda.time.format;

import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Locale;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReferenceArray;

import org.joda.time.Chronology;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.ReadablePartial;

/**
 * Factory that creates instances of DateTimeFormatter from patterns and styles.
 * 

* Datetime formatting is performed by the {@link DateTimeFormatter} class. * Three classes provide factory methods to create formatters, and this is one. * The others are {@link ISODateTimeFormat} and {@link DateTimeFormatterBuilder}. *

* This class provides two types of factory: *

    *
  • {@link #forPattern(String) Pattern} provides a DateTimeFormatter based on * a pattern string that is mostly compatible with the JDK date patterns. *
  • {@link #forStyle(String) Style} provides a DateTimeFormatter based on a * two character style, representing short, medium, long and full. *
*

* For example, to use a pattern: *

 * DateTime dt = new DateTime();
 * DateTimeFormatter fmt = DateTimeFormat.forPattern("MMMM, yyyy");
 * String str = fmt.print(dt);
 * 
* * The pattern syntax is mostly compatible with java.text.SimpleDateFormat - * time zone names cannot be parsed and a few more symbols are supported. * All ASCII letters are reserved as pattern letters, which are defined as follows: *
*
 * Symbol  Meaning                      Presentation  Examples
 * ------  -------                      ------------  -------
 * G       era                          text          AD
 * C       century of era (>=0)         number        20
 * Y       year of era (>=0)            year          1996
 *
 * x       weekyear                     year          1996
 * w       week of weekyear             number        27
 * e       day of week                  number        2
 * E       day of week                  text          Tuesday; Tue
 *
 * y       year                         year          1996
 * D       day of year                  number        189
 * M       month of year                month         July; Jul; 07
 * d       day of month                 number        10
 *
 * a       halfday of day               text          PM
 * K       hour of halfday (0~11)       number        0
 * h       clockhour of halfday (1~12)  number        12
 *
 * H       hour of day (0~23)           number        0
 * k       clockhour of day (1~24)      number        24
 * m       minute of hour               number        30
 * s       second of minute             number        55
 * S       fraction of second           millis        978
 *
 * z       time zone                    text          Pacific Standard Time; PST
 * Z       time zone offset/id          zone          -0800; -08:00; America/Los_Angeles
 *
 * '       escape for text              delimiter
 * ''      single quote                 literal       '
 * 
*
* The count of pattern letters determine the format. *

* Text: If the number of pattern letters is 4 or more, * the full form is used; otherwise a short or abbreviated form is used if * available. *

* Number: The minimum number of digits. * Shorter numbers are zero-padded to this amount. * When parsing, any number of digits are accepted. *

* Year: Numeric presentation for year and weekyear fields * are handled specially. For example, if the count of 'y' is 2, the year * will be displayed as the zero-based year of the century, which is two * digits. *

* Month: 3 or over, use text, otherwise use number. *

* Millis: The exact number of fractional digits. * If more millisecond digits are available then specified the number will be truncated, * if there are fewer than specified then the number will be zero-padded to the right. * When parsing, only the exact number of digits are accepted. *

* Zone: 'Z' outputs offset without a colon, 'ZZ' outputs * the offset with a colon, 'ZZZ' or more outputs the zone id. *

* Zone names: Time zone names ('z') cannot be parsed. *

* Any characters in the pattern that are not in the ranges of ['a'..'z'] * and ['A'..'Z'] will be treated as quoted text. For instance, characters * like ':', '.', ' ', '#' and '?' will appear in the resulting time text * even they are not embraced within single quotes. *

* DateTimeFormat is thread-safe and immutable, and the formatters it returns * are as well. * * @author Brian S O'Neill * @author Maxim Zhao * @since 1.0 * @see ISODateTimeFormat * @see DateTimeFormatterBuilder */ public class DateTimeFormat { /** Style constant for FULL. */ static final int FULL = 0; // DateFormat.FULL /** Style constant for LONG. */ static final int LONG = 1; // DateFormat.LONG /** Style constant for MEDIUM. */ static final int MEDIUM = 2; // DateFormat.MEDIUM /** Style constant for SHORT. */ static final int SHORT = 3; // DateFormat.SHORT /** Style constant for NONE. */ static final int NONE = 4; /** Type constant for DATE only. */ static final int DATE = 0; /** Type constant for TIME only. */ static final int TIME = 1; /** Type constant for DATETIME. */ static final int DATETIME = 2; /** Maximum size of the pattern cache. */ private static final int PATTERN_CACHE_SIZE = 500; /** Maps patterns to formatters, patterns don't vary by locale. Size capped at PATTERN_CACHE_SIZE*/ private static final ConcurrentHashMap cPatternCache = new ConcurrentHashMap(); /** Maps patterns to formatters, patterns don't vary by locale. */ private static final AtomicReferenceArray cStyleCache = new AtomicReferenceArray(25); //----------------------------------------------------------------------- /** * Factory to create a formatter from a pattern string. * The pattern string is described above in the class level javadoc. * It is very similar to SimpleDateFormat patterns. *

* The format may contain locale specific output, and this will change as * you change the locale of the formatter. * Call {@link DateTimeFormatter#withLocale(Locale)} to switch the locale. * For example: *

     * DateTimeFormat.forPattern(pattern).withLocale(Locale.FRANCE).print(dt);
     * 
* * @param pattern pattern specification * @return the formatter * @throws IllegalArgumentException if the pattern is invalid */ public static DateTimeFormatter forPattern(String pattern) { return createFormatterForPattern(pattern); } /** * Factory to create a format from a two character style pattern. *

* The first character is the date style, and the second character is the * time style. Specify a character of 'S' for short style, 'M' for medium, * 'L' for long, and 'F' for full. * A date or time may be omitted by specifying a style character '-'. *

* The returned formatter will dynamically adjust to the locale that * the print/parse takes place in. Thus you just call * {@link DateTimeFormatter#withLocale(Locale)} and the Short/Medium/Long/Full * style for that locale will be output. For example: *

     * DateTimeFormat.forStyle(style).withLocale(Locale.FRANCE).print(dt);
     * 
* * @param style two characters from the set {"S", "M", "L", "F", "-"} * @return the formatter * @throws IllegalArgumentException if the style is invalid */ public static DateTimeFormatter forStyle(String style) { return createFormatterForStyle(style); } /** * Returns the pattern used by a particular style and locale. *

* The first character is the date style, and the second character is the * time style. Specify a character of 'S' for short style, 'M' for medium, * 'L' for long, and 'F' for full. * A date or time may be omitted by specifying a style character '-'. * * @param style two characters from the set {"S", "M", "L", "F", "-"} * @param locale locale to use, null means default * @return the formatter * @throws IllegalArgumentException if the style is invalid * @since 1.3 */ public static String patternForStyle(String style, Locale locale) { DateTimeFormatter formatter = createFormatterForStyle(style); if (locale == null) { locale = Locale.getDefault(); } // Not pretty, but it works. return ((StyleFormatter) formatter.getPrinter0()).getPattern(locale); } //----------------------------------------------------------------------- /** * Creates a format that outputs a short date format. *

* The format will change as you change the locale of the formatter. * Call {@link DateTimeFormatter#withLocale(Locale)} to switch the locale. * * @return the formatter */ public static DateTimeFormatter shortDate() { return createFormatterForStyleIndex(SHORT, NONE); } /** * Creates a format that outputs a short time format. *

* The format will change as you change the locale of the formatter. * Call {@link DateTimeFormatter#withLocale(Locale)} to switch the locale. * * @return the formatter */ public static DateTimeFormatter shortTime() { return createFormatterForStyleIndex(NONE, SHORT); } /** * Creates a format that outputs a short datetime format. *

* The format will change as you change the locale of the formatter. * Call {@link DateTimeFormatter#withLocale(Locale)} to switch the locale. * * @return the formatter */ public static DateTimeFormatter shortDateTime() { return createFormatterForStyleIndex(SHORT, SHORT); } //----------------------------------------------------------------------- /** * Creates a format that outputs a medium date format. *

* The format will change as you change the locale of the formatter. * Call {@link DateTimeFormatter#withLocale(Locale)} to switch the locale. * * @return the formatter */ public static DateTimeFormatter mediumDate() { return createFormatterForStyleIndex(MEDIUM, NONE); } /** * Creates a format that outputs a medium time format. *

* The format will change as you change the locale of the formatter. * Call {@link DateTimeFormatter#withLocale(Locale)} to switch the locale. * * @return the formatter */ public static DateTimeFormatter mediumTime() { return createFormatterForStyleIndex(NONE, MEDIUM); } /** * Creates a format that outputs a medium datetime format. *

* The format will change as you change the locale of the formatter. * Call {@link DateTimeFormatter#withLocale(Locale)} to switch the locale. * * @return the formatter */ public static DateTimeFormatter mediumDateTime() { return createFormatterForStyleIndex(MEDIUM, MEDIUM); } //----------------------------------------------------------------------- /** * Creates a format that outputs a long date format. *

* The format will change as you change the locale of the formatter. * Call {@link DateTimeFormatter#withLocale(Locale)} to switch the locale. * * @return the formatter */ public static DateTimeFormatter longDate() { return createFormatterForStyleIndex(LONG, NONE); } /** * Creates a format that outputs a long time format. *

* The format will change as you change the locale of the formatter. * Call {@link DateTimeFormatter#withLocale(Locale)} to switch the locale. * * @return the formatter */ public static DateTimeFormatter longTime() { return createFormatterForStyleIndex(NONE, LONG); } /** * Creates a format that outputs a long datetime format. *

* The format will change as you change the locale of the formatter. * Call {@link DateTimeFormatter#withLocale(Locale)} to switch the locale. * * @return the formatter */ public static DateTimeFormatter longDateTime() { return createFormatterForStyleIndex(LONG, LONG); } //----------------------------------------------------------------------- /** * Creates a format that outputs a full date format. *

* The format will change as you change the locale of the formatter. * Call {@link DateTimeFormatter#withLocale(Locale)} to switch the locale. * * @return the formatter */ public static DateTimeFormatter fullDate() { return createFormatterForStyleIndex(FULL, NONE); } /** * Creates a format that outputs a full time format. *

* The format will change as you change the locale of the formatter. * Call {@link DateTimeFormatter#withLocale(Locale)} to switch the locale. * * @return the formatter */ public static DateTimeFormatter fullTime() { return createFormatterForStyleIndex(NONE, FULL); } /** * Creates a format that outputs a full datetime format. *

* The format will change as you change the locale of the formatter. * Call {@link DateTimeFormatter#withLocale(Locale)} to switch the locale. * * @return the formatter */ public static DateTimeFormatter fullDateTime() { return createFormatterForStyleIndex(FULL, FULL); } //----------------------------------------------------------------------- /** * Parses the given pattern and appends the rules to the given * DateTimeFormatterBuilder. * * @param pattern pattern specification * @throws IllegalArgumentException if the pattern is invalid */ static void appendPatternTo(DateTimeFormatterBuilder builder, String pattern) { parsePatternTo(builder, pattern); } //----------------------------------------------------------------------- /** * Constructor. * * @since 1.1 (previously private) */ protected DateTimeFormat() { super(); } //----------------------------------------------------------------------- /** * Parses the given pattern and appends the rules to the given * DateTimeFormatterBuilder. * * @param pattern pattern specification * @throws IllegalArgumentException if the pattern is invalid * @see #forPattern */ private static void parsePatternTo(DateTimeFormatterBuilder builder, String pattern) { int length = pattern.length(); int[] indexRef = new int[1]; for (int i=0; i= 3) { if (tokenLen >= 4) { builder.appendMonthOfYearText(); } else { builder.appendMonthOfYearShortText(); } } else { builder.appendMonthOfYear(tokenLen); } break; case 'd': // day of month (number) builder.appendDayOfMonth(tokenLen); break; case 'a': // am/pm marker (text) builder.appendHalfdayOfDayText(); break; case 'h': // clockhour of halfday (number, 1..12) builder.appendClockhourOfHalfday(tokenLen); break; case 'H': // hour of day (number, 0..23) builder.appendHourOfDay(tokenLen); break; case 'k': // clockhour of day (1..24) builder.appendClockhourOfDay(tokenLen); break; case 'K': // hour of halfday (0..11) builder.appendHourOfHalfday(tokenLen); break; case 'm': // minute of hour (number) builder.appendMinuteOfHour(tokenLen); break; case 's': // second of minute (number) builder.appendSecondOfMinute(tokenLen); break; case 'S': // fraction of second (number) builder.appendFractionOfSecond(tokenLen, tokenLen); break; case 'e': // day of week (number) builder.appendDayOfWeek(tokenLen); break; case 'E': // dayOfWeek (text) if (tokenLen >= 4) { builder.appendDayOfWeekText(); } else { builder.appendDayOfWeekShortText(); } break; case 'D': // day of year (number) builder.appendDayOfYear(tokenLen); break; case 'w': // week of weekyear (number) builder.appendWeekOfWeekyear(tokenLen); break; case 'z': // time zone (text) if (tokenLen >= 4) { builder.appendTimeZoneName(); } else { builder.appendTimeZoneShortName(null); } break; case 'Z': // time zone offset if (tokenLen == 1) { builder.appendTimeZoneOffset(null, "Z", false, 2, 2); } else if (tokenLen == 2) { builder.appendTimeZoneOffset(null, "Z", true, 2, 2); } else { builder.appendTimeZoneId(); } break; case '\'': // literal text String sub = token.substring(1); if (sub.length() == 1) { builder.appendLiteral(sub.charAt(0)); } else { // Create copy of sub since otherwise the temporary quoted // string would still be referenced internally. builder.appendLiteral(new String(sub)); } break; default: throw new IllegalArgumentException ("Illegal pattern component: " + token); } } } /** * Parses an individual token. * * @param pattern the pattern string * @param indexRef a single element array, where the input is the start * location and the output is the location after parsing the token * @return the parsed token */ private static String parseToken(String pattern, int[] indexRef) { StringBuilder buf = new StringBuilder(); int i = indexRef[0]; 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) { 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(); } /** * Returns true if token should be parsed as a numeric field. * * @param token the token to parse * @return true if numeric field */ private static boolean isNumericToken(String token) { int tokenLen = token.length(); if (tokenLen > 0) { char c = token.charAt(0); switch (c) { case 'c': // century (number) case 'C': // century of era (number) case 'x': // weekyear (number) case 'y': // year (number) case 'Y': // year of era (number) case 'd': // day of month (number) case 'h': // hour of day (number, 1..12) case 'H': // hour of day (number, 0..23) case 'm': // minute of hour (number) case 's': // second of minute (number) case 'S': // fraction of second (number) case 'e': // day of week (number) case 'D': // day of year (number) case 'F': // day of week in month (number) case 'w': // week of year (number) case 'W': // week of month (number) case 'k': // hour of day (1..24) case 'K': // hour of day (0..11) return true; case 'M': // month of year (text and number) if (tokenLen <= 2) { return true; } } } return false; } //----------------------------------------------------------------------- /** * Select a format from a custom pattern. * * @param pattern pattern specification * @throws IllegalArgumentException if the pattern is invalid * @see #appendPatternTo */ private static DateTimeFormatter createFormatterForPattern(String pattern) { if (pattern == null || pattern.length() == 0) { throw new IllegalArgumentException("Invalid pattern specification"); } DateTimeFormatter formatter = cPatternCache.get(pattern); if (formatter == null) { DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder(); parsePatternTo(builder, pattern); formatter = builder.toFormatter(); if (cPatternCache.size() < PATTERN_CACHE_SIZE) { // the size check is not locked against concurrent access, // but is accepted to be slightly off in contention scenarios. DateTimeFormatter oldFormatter = cPatternCache.putIfAbsent(pattern, formatter); if (oldFormatter != null) { formatter = oldFormatter; } } } return formatter; } /** * Select a format from a two character style pattern. The first character * is the date style, and the second character is the time style. Specify a * character of 'S' for short style, 'M' for medium, 'L' for long, and 'F' * for full. A date or time may be omitted by specifying a style character '-'. * * @param style two characters from the set {"S", "M", "L", "F", "-"} * @throws IllegalArgumentException if the style is invalid */ private static DateTimeFormatter createFormatterForStyle(String style) { if (style == null || style.length() != 2) { throw new IllegalArgumentException("Invalid style specification: " + style); } int dateStyle = selectStyle(style.charAt(0)); int timeStyle = selectStyle(style.charAt(1)); if (dateStyle == NONE && timeStyle == NONE) { throw new IllegalArgumentException("Style '--' is invalid"); } return createFormatterForStyleIndex(dateStyle, timeStyle); } /** * Gets the formatter for the specified style. * * @param dateStyle the date style * @param timeStyle the time style * @return the formatter */ private static DateTimeFormatter createFormatterForStyleIndex(int dateStyle, int timeStyle) { int index = ((dateStyle << 2) + dateStyle) + timeStyle; // (dateStyle * 5 + timeStyle); // Should never happen but do a double check... if (index >= cStyleCache.length()) { return createDateTimeFormatter(dateStyle, timeStyle); } DateTimeFormatter f = cStyleCache.get(index); if (f == null) { f = createDateTimeFormatter(dateStyle, timeStyle); if (cStyleCache.compareAndSet(index, null, f) == false) { f = cStyleCache.get(index); } } return f; } /** * Creates a formatter for the specified style. * * @param dateStyle the date style * @param timeStyle the time style * @return the formatter */ private static DateTimeFormatter createDateTimeFormatter(int dateStyle, int timeStyle){ int type = DATETIME; if (dateStyle == NONE) { type = TIME; } else if (timeStyle == NONE) { type = DATE; } StyleFormatter llf = new StyleFormatter(dateStyle, timeStyle, type); return new DateTimeFormatter(llf, llf); } /** * Gets the JDK style code from the Joda code. * * @param ch the Joda style code * @return the JDK style code */ private static int selectStyle(char ch) { switch (ch) { case 'S': return SHORT; case 'M': return MEDIUM; case 'L': return LONG; case 'F': return FULL; case '-': return NONE; default: throw new IllegalArgumentException("Invalid style character: " + ch); } } //----------------------------------------------------------------------- static class StyleFormatter implements InternalPrinter, InternalParser { private static final ConcurrentHashMap cCache = new ConcurrentHashMap(); private final int iDateStyle; private final int iTimeStyle; private final int iType; StyleFormatter(int dateStyle, int timeStyle, int type) { super(); iDateStyle = dateStyle; iTimeStyle = timeStyle; iType = type; } public int estimatePrintedLength() { return 40; // guess } public void printTo( Appendable appenadble, long instant, Chronology chrono, int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException { InternalPrinter p = getFormatter(locale).getPrinter0(); p.printTo(appenadble, instant, chrono, displayOffset, displayZone, locale); } public void printTo(Appendable appendable, ReadablePartial partial, Locale locale) throws IOException { InternalPrinter p = getFormatter(locale).getPrinter0(); p.printTo(appendable, partial, locale); } public int estimateParsedLength() { return 40; // guess } public int parseInto(DateTimeParserBucket bucket, CharSequence text, int position) { InternalParser p = getFormatter(bucket.getLocale()).getParser0(); return p.parseInto(bucket, text, position); } private DateTimeFormatter getFormatter(Locale locale) { locale = (locale == null ? Locale.getDefault() : locale); StyleFormatterCacheKey key = new StyleFormatterCacheKey(iType, iDateStyle, iTimeStyle, locale); DateTimeFormatter f = cCache.get(key); if (f == null) { f = DateTimeFormat.forPattern(getPattern(locale)); DateTimeFormatter oldFormatter = cCache.putIfAbsent(key, f); if (oldFormatter != null) { f = oldFormatter; } } return f; } String getPattern(Locale locale) { DateFormat f = null; switch (iType) { case DATE: f = DateFormat.getDateInstance(iDateStyle, locale); break; case TIME: f = DateFormat.getTimeInstance(iTimeStyle, locale); break; case DATETIME: f = DateFormat.getDateTimeInstance(iDateStyle, iTimeStyle, locale); break; } if (f instanceof SimpleDateFormat == false) { throw new IllegalArgumentException("No datetime pattern for locale: " + locale); } return ((SimpleDateFormat) f).toPattern(); } } static class StyleFormatterCacheKey { private final int combinedTypeAndStyle; private final Locale locale; public StyleFormatterCacheKey(int iType, int iDateStyle, int iTimeStyle, Locale locale) { this.locale = locale; // keeping old key generation logic of shifting type and style this.combinedTypeAndStyle = iType + (iDateStyle << 4) + (iTimeStyle << 8); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + combinedTypeAndStyle; result = prime * result + ((locale == null) ? 0 : locale.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (!(obj instanceof StyleFormatterCacheKey)) { return false; } StyleFormatterCacheKey other = (StyleFormatterCacheKey) obj; if (combinedTypeAndStyle != other.combinedTypeAndStyle) { return false; } if (locale == null) { if (other.locale != null) { return false; } } else if (!locale.equals(other.locale)) { return false; } return true; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy