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}