com.redfin.sitemapgenerator.W3CDateFormat Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of sitemapgen4j Show documentation
Show all versions of sitemapgen4j Show documentation
SitemapGen4j is an XML sitemap generator written in Java.
/**
*
*/
package com.redfin.sitemapgenerator;
import static java.util.Calendar.HOUR_OF_DAY;
import static java.util.Calendar.MILLISECOND;
import static java.util.Calendar.MINUTE;
import static java.util.Calendar.SECOND;
import java.text.DateFormat;
import java.text.FieldPosition;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;
/**
* Formats and parses dates in the six defined W3C date time formats. These formats are described in
* "Date and Time Formats",
* http://www.w3.org/TR/NOTE-datetime.
*
* The formats are:
*
*
* - YEAR: YYYY (eg 1997)
*
- MONTH: YYYY-MM (eg 1997-07)
*
- DAY: YYYY-MM-DD (eg 1997-07-16)
*
- MINUTE: YYYY-MM-DDThh:mmTZD (eg 1997-07-16T19:20+01:00)
*
- SECOND: YYYY-MM-DDThh:mm:ssTZD (eg 1997-07-16T19:20:30+01:00)
*
- MILLISECOND: YYYY-MM-DDThh:mm:ss.sTZD (eg 1997-07-16T19:20:30.45+01:00)
*
*
* Note that W3C timezone designators (TZD) are either the letter "Z" (for GMT) or a pattern like "+00:30" or "-08:00". This is unlike
* RFC 822 timezones generated by SimpleDateFormat, which omit the ":" like this: "+0030" or "-0800".
*
* This class allows you to either specify which format pattern to use, or (by default) to
* automatically guess which pattern to use (AUTO mode). When parsing in AUTO mode, we'll try parsing using each pattern
* until we find one that works. When formatting in AUTO mode, we'll use this algorithm:
*
*
- If the date has fractional milliseconds (e.g. 2009-06-06T19:49:04.45Z) we'll use the MILLISECOND pattern
*
- Otherwise, if the date has non-zero seconds (e.g. 2009-06-06T19:49:04Z) we'll use the SECOND pattern
*
- Otherwise, if the date is not at exactly midnight (e.g. 2009-06-06T19:49Z) we'll use the MINUTE pattern
*
- Otherwise, we'll use the DAY pattern. If you want to format using the MONTH or YEAR pattern, you must declare it explicitly.
*
*
* Finally note that, like all classes that inherit from DateFormat, this class is not thread-safe. Also note that you
* can explicitly specify the timezone to use for formatting using the {@link #setTimeZone(TimeZone)} method.
*
* @author Dan Fabulich
* @see Date and Time Formats
*/
public class W3CDateFormat extends SimpleDateFormat {
private static final long serialVersionUID = -5733368073260485802L;
/** The six patterns defined by W3C, plus {@link #AUTO} configuration */
public enum Pattern {
/** "yyyy-MM-dd'T'HH:mm:ss.SSSZ" */
MILLISECOND("yyyy-MM-dd'T'HH:mm:ss.SSSZ", true),
/** "yyyy-MM-dd'T'HH:mm:ssZ" */
SECOND("yyyy-MM-dd'T'HH:mm:ssZ", true),
/** "yyyy-MM-dd'T'HH:mmZ" */
MINUTE("yyyy-MM-dd'T'HH:mmZ", true),
/** "yyyy-MM-dd" */
DAY("yyyy-MM-dd", false),
/** "yyyy-MM" */
MONTH("yyyy-MM", false),
/** "yyyy" */
YEAR("yyyy", false),
/** Automatically compute the right pattern to use */
AUTO("", true);
private final String pattern;
private final boolean includeTimeZone;
Pattern(String pattern, boolean includeTimeZone) {
this.pattern = pattern;
this.includeTimeZone = includeTimeZone;
}
}
private final Pattern pattern;
/** The GMT ("zulu") time zone, for your convenience */
public static final TimeZone ZULU = TimeZone.getTimeZone("GMT");
/** Build a formatter in AUTO mode */
public W3CDateFormat() {
this(Pattern.AUTO);
}
/** Build a formatter using the specified Pattern, or AUTO mode */
public W3CDateFormat(Pattern pattern) {
super(pattern.pattern);
this.pattern = pattern;
}
/** This is what you override when you extend DateFormat; use {@link DateFormat#format(Date)} instead */
@Override
public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition pos) {
boolean includeTimeZone = pattern.includeTimeZone;
if (pattern == Pattern.AUTO) {
includeTimeZone = autoFormat(date);
}
super.format(date, toAppendTo, pos);
if (includeTimeZone) convertRfc822TimeZoneToW3c(toAppendTo);
return toAppendTo;
}
private boolean applyPattern(Pattern pattern) {
applyPattern(pattern.pattern);
return pattern.includeTimeZone;
}
private boolean autoFormat(Date date) {
if (calendar == null) calendar = new GregorianCalendar();
calendar.setTime(date);
boolean hasMillis = calendar.get(MILLISECOND) > 0;
if (hasMillis) {
return applyPattern(Pattern.MILLISECOND);
}
boolean hasSeconds = calendar.get(SECOND) > 0;
if (hasSeconds) {
return applyPattern(Pattern.SECOND);
}
boolean hasTime = (calendar.get(HOUR_OF_DAY) + calendar.get(MINUTE)) > 0;
if (hasTime) {
return applyPattern(Pattern.MINUTE);
}
return applyPattern(Pattern.DAY);
}
/** This is what you override when you extend DateFormat; use {@link DateFormat#parse(String)} instead */
@Override
public Date parse(String text, ParsePosition pos) {
text = convertW3cTimeZoneToRfc822(text);
if (pattern == Pattern.AUTO) {
return autoParse(text, pos);
}
return super.parse(text, pos);
}
private Date autoParse(String text, ParsePosition pos) {
for (Pattern pattern : Pattern.values()) {
if (pattern == Pattern.AUTO) continue;
applyPattern(pattern);
Date out = super.parse(text, pos);
if (out != null) return out;
}
return null; // this will force a ParseException
}
private void convertRfc822TimeZoneToW3c(StringBuffer toAppendTo) {
int length = toAppendTo.length();
if (ZULU.equals(calendar.getTimeZone())) {
toAppendTo.replace(length - 5, length, "Z");
} else {
toAppendTo.insert(length - 2, ':');
}
}
private String convertW3cTimeZoneToRfc822(String source) {
int length = source.length();
if (source.endsWith("Z")) {
return source.substring(0, length-1) + "+0000";
}
if (source.charAt(length-3) == ':') {
return source.substring(0, length-3) + source.substring(length - 2);
}
return source;
}
}