1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 package org.apache.jempbox.impl; 18 19 import java.io.IOException; 20 import java.text.ParseException; 21 import java.text.SimpleDateFormat; 22 import java.util.Calendar; 23 import java.util.Date; 24 import java.util.GregorianCalendar; 25 import java.util.SimpleTimeZone; 26 27 /** 28 * This class is used to convert dates to strings and back using the PDF 29 * date standards. Date are described in PDFReference1.4 section 3.8.2 30 * 31 * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a> 32 * @author <a href="mailto:chris@oezbek.net">Christopher Oezbek</a> 33 * 34 * @version $Revision: 1.6 $ 35 */ 36 public class DateConverter 37 { 38 //The Date format is supposed to be the PDF_DATE_FORMAT, but not all PDF documents 39 //will use that date, so I have added a couple other potential formats 40 //to try if the original one does not work. 41 private static final SimpleDateFormat[] POTENTIAL_FORMATS = new SimpleDateFormat[] { 42 new SimpleDateFormat("EEEE, dd MMM yyyy hh:mm:ss a"), 43 new SimpleDateFormat("EEEE, MMM dd, yyyy hh:mm:ss a"), 44 new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"), 45 new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssz"), 46 new SimpleDateFormat("MM/dd/yyyy hh:mm:ss"), 47 new SimpleDateFormat("MM/dd/yyyy"), 48 new SimpleDateFormat("EEEE, MMM dd, yyyy"), // Acrobat Distiller 1.0.2 for Macintosh 49 new SimpleDateFormat("EEEE MMM dd, yyyy HH:mm:ss"), // ECMP5 50 new SimpleDateFormat("EEEE MMM dd HH:mm:ss z yyyy"), // GNU Ghostscript 7.0.7 51 new SimpleDateFormat("EEEE, MMM dd, yyyy 'at' hh:mma") // Acrobat Net Distiller 1.0 for Windows 52 }; 53 54 private DateConverter() 55 { 56 //utility class should not be constructed. 57 } 58 59 /** 60 * This will convert a string to a calendar. 61 * 62 * @param date The string representation of the calendar. 63 * 64 * @return The calendar that this string represents. 65 * 66 * @throws IOException If the date string is not in the correct format. 67 */ 68 public static Calendar toCalendar( String date ) throws IOException 69 { 70 Calendar retval = null; 71 if( date != null && date.trim().length() > 0 ) 72 { 73 //these are the default values 74 int year = 0; 75 int month = 1; 76 int day = 1; 77 int hour = 0; 78 int minute = 0; 79 int second = 0; 80 //first string off the prefix if it exists 81 try 82 { 83 SimpleTimeZone zone = null; 84 if( date.startsWith( "D:" ) ) 85 { 86 date = date.substring( 2, date.length() ); 87 } 88 date = date.replaceAll("[-:T]", ""); 89 90 if( date.length() < 4 ) 91 { 92 throw new IOException( "Error: Invalid date format '" + date + "'" ); 93 } 94 year = Integer.parseInt( date.substring( 0, 4 ) ); 95 if( date.length() >= 6 ) 96 { 97 month = Integer.parseInt( date.substring( 4, 6 ) ); 98 } 99 if( date.length() >= 8 ) 100 { 101 day = Integer.parseInt( date.substring( 6, 8 ) ); 102 } 103 if( date.length() >= 10 ) 104 { 105 hour = Integer.parseInt( date.substring( 8, 10 ) ); 106 } 107 if( date.length() >= 12 ) 108 { 109 minute = Integer.parseInt( date.substring( 10, 12 ) ); 110 } 111 if( date.length() >= 14 ) 112 { 113 second = Integer.parseInt( date.substring( 12, 14 ) ); 114 } 115 if( date.length() >= 15 ) 116 { 117 char sign = date.charAt( 14 ); 118 if( sign == 'Z' ) 119 { 120 zone = new SimpleTimeZone(0,"Unknown"); 121 } 122 else 123 { 124 int hours = 0; 125 int minutes = 0; 126 if( date.length() >= 17 ) 127 { 128 if( sign == '+' ) 129 { 130 //parseInt cannot handle the + sign 131 hours = Integer.parseInt( date.substring( 15, 17 ) ); 132 } 133 else 134 { 135 hours = -Integer.parseInt( date.substring( 14, 16 ) ); 136 } 137 } 138 if( sign=='+' ) 139 { 140 if( date.length() >= 19 ) 141 { 142 minutes = Integer.parseInt( date.substring( 17, 19 ) ); 143 } 144 } 145 else 146 { 147 if( date.length() >= 18 ) 148 { 149 minutes = Integer.parseInt( date.substring( 16, 18 ) ); 150 } 151 } 152 zone = new SimpleTimeZone( hours*60*60*1000 + minutes*60*1000, "Unknown" ); 153 } 154 } 155 if( zone == null ) 156 { 157 retval = new GregorianCalendar(); 158 } 159 else 160 { 161 retval = new GregorianCalendar( zone ); 162 } 163 retval.clear(); 164 retval.set( year, month-1, day, hour, minute, second ); 165 } 166 catch( NumberFormatException e ) 167 { 168 169 // remove the arbitrary : in the timezone. SimpleDateFormat 170 // can't handle it 171 if (date.substring(date.length()-3,date.length()-2).equals(":") && 172 (date.substring(date.length()-6,date.length()-5).equals("+") || 173 date.substring(date.length()-6,date.length()-5).equals("-"))) 174 { 175 //thats a timezone string, remove the : 176 date = date.substring(0,date.length()-3) + 177 date.substring(date.length()-2); 178 } 179 for( int i=0; retval == null && i<POTENTIAL_FORMATS.length; i++ ) 180 { 181 try 182 { 183 Date utilDate = POTENTIAL_FORMATS[i].parse( date ); 184 retval = new GregorianCalendar(); 185 retval.setTime( utilDate ); 186 } 187 catch( ParseException pe ) 188 { 189 //ignore and move to next potential format 190 } 191 } 192 if( retval == null ) 193 { 194 //we didn't find a valid date format so throw an exception 195 throw new IOException( "Error converting date:" + date ); 196 } 197 } 198 } 199 return retval; 200 } 201 202 private static final void zeroAppend( StringBuffer out, int number ) 203 { 204 if( number < 10 ) 205 { 206 out.append( "0" ); 207 } 208 out.append( number ); 209 } 210 211 /** 212 * Convert the date to iso 8601 string format. 213 * 214 * @param cal The date to convert. 215 * @return The date represented as an ISO 8601 string. 216 */ 217 public static String toISO8601( Calendar cal ) 218 { 219 StringBuffer retval = new StringBuffer(); 220 221 retval.append( cal.get( Calendar.YEAR ) ); 222 retval.append( "-" ); 223 zeroAppend( retval, cal.get( Calendar.MONTH )+1 ); 224 retval.append( "-" ); 225 zeroAppend( retval, cal.get( Calendar.DAY_OF_MONTH ) ); 226 retval.append( "T" ); 227 zeroAppend( retval, cal.get( Calendar.HOUR_OF_DAY )); 228 retval.append( ":" ); 229 zeroAppend( retval, cal.get( Calendar.MINUTE )); 230 retval.append( ":" ); 231 zeroAppend( retval, cal.get( Calendar.SECOND )); 232 233 int timeZone = cal.get( Calendar.ZONE_OFFSET ) + cal.get(Calendar.DST_OFFSET ); 234 if( timeZone < 0 ) 235 { 236 retval.append( "-" ); 237 } 238 else 239 { 240 retval.append( "+" ); 241 } 242 timeZone = Math.abs( timeZone ); 243 //milliseconds/1000 = seconds = seconds / 60 = minutes = minutes/60 = hours 244 int hours = timeZone/1000/60/60; 245 int minutes = (timeZone - (hours*1000*60*60))/1000/1000; 246 if( hours < 10 ) 247 { 248 retval.append( "0" ); 249 } 250 retval.append( Integer.toString( hours ) ); 251 retval.append( ":" ); 252 if( minutes < 10 ) 253 { 254 retval.append( "0" ); 255 } 256 retval.append( Integer.toString( minutes ) ); 257 258 return retval.toString(); 259 } 260 }