/**
 
*
 
*/
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;

/**
 
* <p>Formats and parses dates in the six defined W3C date time formats.
  
These formats are described in
 
* "Date and Time Formats",
 
*<a href="http://www.w3.org/TR/NOTE-datetime">http://www.w3.org/TR/NOTE-datetime</a>.</p>
 
*
 
* <p>The formats are:
 
*
 
* <ol>
 
* <li>YEAR: YYYY (eg 1997)
 
* <li>MONTH: YYYY-MM (eg 1997-07)
 
* <li>DAY: YYYY-MM-DD (eg 1997-07-16)
 
* <li>MINUTE: YYYY-MM-DDThh:mmTZD (eg 1997-07-16T19:20+01:00)
 
* <li>SECOND: YYYY-MM-DDThh:mm:ssTZD (eg 1997-07-16T19:20:30+01:00)
 
* <li>MILLISECOND: YYYY-MM-DDThh:mm:ss.sTZD (eg 1997-07-16T19:20:30.45+01:00)
 
* </ol>
 
*
 
* 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".</p>
 
*
 
* <p>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:
 
*
 
* <ol><li>If the date has fractional milliseconds (e.g. 2009-06-06T19:49:04.45Z) we'll use the MILLISECOND pattern
 
* <li>Otherwise, if the date has non-zero seconds (e.g. 2009-06-06T19:49:04Z) we'll use the SECOND pattern
 
* <li>Otherwise, if the date is not at exactly midnight (e.g. 2009-06-06T19:49Z) we'll use the MINUTE pattern
 
* <li>Otherwise, we'll use the DAY pattern.
  
If you want to format using the MONTH or YEAR pattern, you must declare it explicitly.
 
* </ol>
 
*
 
* Finally note that, like all classes that inherit from DateFormat, <b>this class is not thread-safe</b>.
  
Also note that you
 
* can explicitly specify the timezone to use for formatting using the {@link #setTimeZone(TimeZone)} method.
 
*
 
* @author Dan Fabulich
 
* @see<a href="http://www.w3.org/TR/NOTE-datetime">Date and Time Formats</a>
 
*/

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;
	
}
	

}