001/*
002 * Copyright 1999,2004 The Apache Software Foundation.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.nuxeo.box.api.utils;
017
018import org.apache.commons.lang.StringUtils;
019
020import java.text.DateFormat;
021import java.text.ParseException;
022import java.text.SimpleDateFormat;
023import java.util.Date;
024import java.util.TimeZone;
025
026/**
027 * ISO 8601 date parsing utility. Designed for parsing the ISO subset used in Dublin Core, RSS 1.0, and Atom.
028 *
029 * @author <a href="mailto:burton@apache.org">Kevin A. Burton (burtonator)</a>
030 * @version $Id: ISO8601DateParser.java,v 1.2 2005/06/03 20:25:29 snoopdave Exp $
031 */
032public class ISO8601DateParser {
033
034    // Use ThreadLocal because SimpleDateFormat is not thread safe. See
035    // http://www.javacodegeeks.com/2010/07/java-best-practices-dateformat-in
036    // .html.
037    private final static ThreadLocal<DateFormat> mThreadLocalSimpleDateFormat = new ThreadLocal<DateFormat>() {
038
039        @Override
040        protected DateFormat initialValue() {
041            return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssz");
042        }
043
044    };
045
046    // 2004-06-14T19:GMT20:30Z
047    // 2004-06-20T06:GMT22:01Z
048
049    // http://www.cl.cam.ac.uk/~mgk25/iso-time.html
050    //
051    // http://www.intertwingly.net/wiki/pie/DateTime
052    //
053    // http://www.w3.org/TR/NOTE-datetime
054    //
055    // Different standards may need different levels of granularity in the
056    // date and
057    // time, so this profile defines six levels. Standards that reference this
058    // profile should specify one or more of these granularities. If a given
059    // standard allows more than one granularity, it should specify the
060    // meaning of
061    // the dates and times with reduced precision, for example, the result of
062    // comparing two dates with different precisions.
063
064    // The formats are as follows. Exactly the components shown here must be
065    // present, with exactly this punctuation. Note that the "T" appears
066    // literally
067    // in the string, to indicate the beginning of the time element,
068    // as specified in
069    // ISO 8601.
070
071    // Year:
072    // YYYY (eg 1997)
073    // Year and month:
074    // YYYY-MM (eg 1997-07)
075    // Complete date:
076    // YYYY-MM-DD (eg 1997-07-16)
077    // Complete date plus hours and minutes:
078    // YYYY-MM-DDThh:mmTZD (eg 1997-07-16T19:20+01:00)
079    // Complete date plus hours, minutes and seconds:
080    // YYYY-MM-DDThh:mm:ssTZD (eg 1997-07-16T19:20:30+01:00)
081    // Complete date plus hours, minutes, seconds and a decimal fraction of a
082    // second
083    // YYYY-MM-DDThh:mm:ss.sTZD (eg 1997-07-16T19:20:30.45+01:00)
084
085    // where:
086
087    // YYYY = four-digit year
088    // MM = two-digit month (01=January, etc.)
089    // DD = two-digit day of month (01 through 31)
090    // hh = two digits of hour (00 through 23) (am/pm NOT allowed)
091    // mm = two digits of minute (00 through 59)
092    // ss = two digits of second (00 through 59)
093    // s = one or more digits representing a decimal fraction of a second
094    // TZD = time zone designator (Z or +hh:mm or -hh:mm)
095    public static Date parse(String input) throws ParseException {
096
097        // NOTE: SimpleDateFormat uses GMT[-+]hh:mm for the TZ which breaks
098        // things a bit. Before we go on we have to repair this.
099        DateFormat df = mThreadLocalSimpleDateFormat.get();
100
101        // this is zero time so we need to add that TZ indicator for
102        if (input.endsWith("Z")) {
103            input = input.substring(0, input.length() - 1) + "GMT-00:00";
104        } else {
105            int inset = 6;
106
107            String s0 = input.substring(0, input.length() - inset);
108            String s1 = input.substring(input.length() - inset, input.length());
109
110            input = s0 + "GMT" + s1;
111        }
112
113        return df.parse(input);
114
115    }
116
117    /**
118     * Same as parse method but does not throws. In case input date string cannot be parsed, null is returned.
119     */
120    public static Date parseSilently(String input) {
121        try {
122            Date date = StringUtils.isEmpty(input) ? null : parse(input);
123            return date;
124        } catch (ParseException e) {
125            return null;
126        }
127    }
128
129    public static String toString(Date date) {
130
131        DateFormat df = mThreadLocalSimpleDateFormat.get();
132
133        TimeZone tz = TimeZone.getTimeZone("UTC");
134
135        df.setTimeZone(tz);
136
137        String output = df.format(date);
138
139        String result = output.replaceAll("UTC", "+00:00");
140
141        return result;
142
143    }
144
145}