001    /*
002     * Copyright 1999,2006 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     */
016    
017    package org.apache.commons.feedparser.tools;
018    
019    import java.text.SimpleDateFormat;
020    import java.util.Date;
021    import java.util.Locale;
022    import java.util.TimeZone;
023    
024    /**
025     * ISO 8601 date parsing utility.  Designed for parsing the ISO subset used in
026     * 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 373572 2006-01-30 19:28:41Z mvdb $
030     */
031    public class ISO8601DateParser {
032    
033        // 2004-06-14T19:GMT20:30Z
034        // 2004-06-20T06:GMT22:01Z
035    
036        private static SimpleDateFormat df
037            = new SimpleDateFormat( "yyyy-MM-dd'T'hh:mm:ssz", Locale.ENGLISH );
038    
039        // http://www.cl.cam.ac.uk/~mgk25/iso-time.html
040        //    
041        // http://www.intertwingly.net/wiki/pie/DateTime
042        //
043        // http://www.w3.org/TR/NOTE-datetime
044        //
045        // Different standards may need different levels of granularity in the date and
046        // time, so this profile defines six levels. Standards that reference this
047        // profile should specify one or more of these granularities. If a given
048        // standard allows more than one granularity, it should specify the meaning of
049        // the dates and times with reduced precision, for example, the result of
050        // comparing two dates with different precisions.
051    
052        // The formats are as follows. Exactly the components shown here must be
053        // present, with exactly this punctuation. Note that the "T" appears literally
054        // in the string, to indicate the beginning of the time element, as specified in
055        // ISO 8601.
056    
057        //    Year:
058        //       YYYY (eg 1997)
059        //    Year and month:
060        //       YYYY-MM (eg 1997-07)
061        //    Complete date:
062        //       YYYY-MM-DD (eg 1997-07-16)
063        //    Complete date plus hours and minutes:
064        //       YYYY-MM-DDThh:mmTZD (eg 1997-07-16T19:20+01:00)
065        //    Complete date plus hours, minutes and seconds:
066        //       YYYY-MM-DDThh:mm:ssTZD (eg 1997-07-16T19:20:30+01:00)
067        //    Complete date plus hours, minutes, seconds and a decimal fraction of a
068        // second
069        //       YYYY-MM-DDThh:mm:ss.sTZD (eg 1997-07-16T19:20:30.45+01:00)
070    
071        // where:
072    
073        //      YYYY = four-digit year
074        //      MM   = two-digit month (01=January, etc.)
075        //      DD   = two-digit day of month (01 through 31)
076        //      hh   = two digits of hour (00 through 23) (am/pm NOT allowed)
077        //      mm   = two digits of minute (00 through 59)
078        //      ss   = two digits of second (00 through 59)
079        //      s    = one or more digits representing a decimal fraction of a second
080        //      TZD  = time zone designator (Z or +hh:mm or -hh:mm)
081        public static Date parse( String input ) throws java.text.ParseException {
082    
083            //NOTE: SimpleDateFormat uses GMT[-+]hh:mm for the TZ which breaks
084            //things a bit.  Before we go on we have to repair this.
085    
086            //this is zero time so we need to add that TZ indicator for 
087            if ( input.endsWith( "Z" ) ) {
088                input = input.substring( 0, input.length() - 1) + "GMT-00:00";
089            } else {
090                int inset = 6;
091            
092                String s0 = input.substring( 0, input.length() - inset );
093                String s1 = input.substring( input.length() - inset, input.length() );
094    
095                input = s0 + "GMT" + s1;
096            }
097            
098            return df.parse( input );
099            
100        }
101    
102        public static String toString( Date date ) {
103    
104            TimeZone tz = TimeZone.getTimeZone( "UTC" );
105            
106            df.setTimeZone( tz );
107    
108            String output = df.format( date );
109    
110            int inset0 = 9;
111            int inset1 = 6;
112            
113            String s0 = output.substring( 0, output.length() - inset0 );
114            String s1 = output.substring( output.length() - inset1, output.length() );
115    
116            String result = s0 + s1;
117    
118            result = result.replaceAll( "UTC", "+00:00" );
119            
120            return result;
121            
122        }
123    
124        public static void main( String[] args ) throws Exception {
125    
126            System.out.println( parse( "2004-05-31T09:19:31-06:00" ) );
127            System.out.println( parse( "2004-06-23T17:25:31-00:00" ) );
128            System.out.println( parse( "2004-06-23T17:25:31Z" ) );
129    
130            //2002-10-02T10:00:00-05:00
131            System.out.println( "v: " + toString( new Date( System.currentTimeMillis() ) ) );
132    
133        }
134    
135    }
136