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}