/*****************************************************************************
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
package org.apache.xmpbox;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.SimpleTimeZone;
/**
* This class is used to convert dates to strings and back using the PDF date standards. Date are described in
* PDFReference1.4 section 3.8.2
*
* @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
* @author <a href="mailto:chris@oezbek.net">Christopher Oezbek</a>
*
* @version $Revision: 1.3 $
*/
public class DateConverter
{
// The Date format is supposed to be the PDF_DATE_FORMAT, but not all PDF
// documents
// will use that date, so I have added a couple other potential formats
// to try if the original one does not work.
private static final SimpleDateFormat[] POTENTIAL_FORMATS = new SimpleDateFormat[] {
new SimpleDateFormat("EEEE, dd MMM yyyy hh:mm:ss a"),
new SimpleDateFormat("EEEE, MMM dd, yyyy hh:mm:ss a"), new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"),
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssz") };
/**
* According to check-style, Utility classes should not have a public or default constructor.
*/
protected DateConverter()
{
};
/**
* This will convert a string to a calendar.
*
* @param date
* The string representation of the calendar.
*
* @return The calendar that this string represents.
*
* @throws IOException
* If the date string is not in the correct format.
*/
public static Calendar toCalendar(String date) throws IOException
{
Calendar retval = null;
if ((date != null) && (date.trim().length() > 0))
{
// these are the default values
int year = 0;
int month = 1;
int day = 1;
int hour = 0;
int minute = 0;
int second = 0;
// first string off the prefix if it exists
try
{
SimpleTimeZone zone = null;
if (date.startsWith("D:"))
{
date = date.substring(2, date.length());
}
date = date.replaceAll("[-:T]", "");
if (date.length() < 4)
{
throw new IOException("Error: Invalid date format '" + date + "'");
}
year = Integer.parseInt(date.substring(0, 4));
if (date.length() >= 6)
{
month = Integer.parseInt(date.substring(4, 6));
}
if (date.length() >= 8)
{
day = Integer.parseInt(date.substring(6, 8));
}
if (date.length() >= 10)
{
hour = Integer.parseInt(date.substring(8, 10));
}
if (date.length() >= 12)
{
minute = Integer.parseInt(date.substring(10, 12));
}
int timeZonePos = 12;
if (date.length() - 12 > 5 || (date.length() - 12 == 3 && date.endsWith("Z")))
{
if (date.length() >= 14)
{
second = Integer.parseInt(date.substring(12, 14));
}
timeZonePos = 14;
}
else
{
second = 0;
}
if (date.length() >= (timeZonePos + 1))
{
char sign = date.charAt(timeZonePos);
if (sign == 'Z')
{
zone = new SimpleTimeZone(0, "Unknown");
}
else
{
int hours = 0;
int minutes = 0;
if (date.length() >= (timeZonePos + 3))
{
if (sign == '+')
{
// parseInt cannot handle the + sign
hours = Integer.parseInt(date.substring((timeZonePos + 1), (timeZonePos + 3)));
}
else
{
hours = -Integer.parseInt(date.substring(timeZonePos, (timeZonePos + 2)));
}
}
if (sign == '+')
{
if (date.length() >= (timeZonePos + 5))
{
minutes = Integer.parseInt(date.substring((timeZonePos + 3), (timeZonePos + 5)));
}
}
else
{
if (date.length() >= (timeZonePos + 4))
{
minutes = Integer.parseInt(date.substring((timeZonePos + 2), (timeZonePos + 4)));
}
}
zone = new SimpleTimeZone(hours * 60 * 60 * 1000 + minutes * 60 * 1000, "Unknown");
}
}
if (zone == null)
{
retval = new GregorianCalendar();
}
else
{
retval = new GregorianCalendar(zone);
}
retval.clear();
retval.set(year, month - 1, day, hour, minute, second);
}
catch (NumberFormatException e)
{
// remove the arbitrary : in the timezone. SimpleDateFormat
// can't handle it
if (date.substring(date.length() - 3, date.length() - 2).equals(":")
&& (date.substring(date.length() - 6, date.length() - 5).equals("+") || date.substring(
date.length() - 6, date.length() - 5).equals("-")))
{
// thats a timezone string, remove the :
date = date.substring(0, date.length() - 3) + date.substring(date.length() - 2);
}
for (int i = 0; (retval == null) && (i < POTENTIAL_FORMATS.length); i++)
{
try
{
Date utilDate = POTENTIAL_FORMATS[i].parse(date);
retval = new GregorianCalendar();
retval.setTime(utilDate);
}
catch (ParseException pe)
{
// ignore and move to next potential format
}
}
if (retval == null)
{
// we didn't find a valid date format so throw an exception
IOException ioe = new IOException("Error converting date:" + date);
ioe.initCause(e);
throw ioe;
}
}
}
return retval;
}
/**
* Append Zero to String Buffer before number < 10 ('1' become '01')
*
* @param out
* The String buffer
* @param number
* The concerned number
*/
private static void zeroAppend(StringBuffer out, int number)
{
if (number < 10)
{
out.append("0");
}
out.append(number);
}
/**
* Convert the date to iso 8601 string format.
*
* @param cal
* The date to convert.
* @return The date represented as an ISO 8601 string.
*/
public static String toISO8601(Calendar cal)
{
StringBuffer retval = new StringBuffer();
retval.append(cal.get(Calendar.YEAR));
retval.append("-");
zeroAppend(retval, cal.get(Calendar.MONTH) + 1);
retval.append("-");
zeroAppend(retval, cal.get(Calendar.DAY_OF_MONTH));
retval.append("T");
zeroAppend(retval, cal.get(Calendar.HOUR_OF_DAY));
retval.append(":");
zeroAppend(retval, cal.get(Calendar.MINUTE));
retval.append(":");
zeroAppend(retval, cal.get(Calendar.SECOND));
int timeZone = cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET);
if (timeZone < 0)
{
retval.append("-");
}
else
{
retval.append("+");
}
timeZone = Math.abs(timeZone);
// milliseconds/1000 = seconds = seconds / 60 = minutes = minutes/60 =
// hours
int hours = timeZone / 1000 / 60 / 60;
int minutes = (timeZone - (hours * 1000 * 60 * 60)) / 1000 / 1000;
if (hours < 10)
{
retval.append("0");
}
retval.append(Integer.toString(hours));
retval.append(":");
if (minutes < 10)
{
retval.append("0");
}
retval.append(Integer.toString(minutes));
return retval.toString();
}
}