ilscipio/scipio-erp

View on GitHub
framework/base/src/com/redfin/sitemapgenerator/W3CDateFormat.java

Summary

Maintainability
A
0 mins
Test Coverage
/*******************************************************************************
 * 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 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;
    }
    
}