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