/*
* $HeadURL$
* $Id$
*
* Copyright (c) 2006-2010 by Public Library of Science
* http://plos.org
* http://ambraproject.org
*
* Licensed 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.ambraproject.util;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.NoSuchElementException;
import java.util.StringTokenizer;
import java.util.TimeZone;
/**
* Date parser for ISO 8601 format http://www.w3.org/TR/1998/NOTE-datetime-19980827
*
* @version $Revision$
* @author Benoît Mahé (bmahe@w3.org)
* @author Yves Lafon (ylafon@w3.org)
*/
public class DateParser {
private static boolean check(StringTokenizer st, String token) throws InvalidDateException {
try {
if (st.nextToken().equals(token)) {
return true;
} else {
throw new InvalidDateException("Missing [" + token + "]");
}
} catch (NoSuchElementException ex) {
return false;
}
}
private static Calendar getCalendar(String isodate) throws InvalidDateException {
// YYYY-MM-DDThh:mm:ss.sTZD
StringTokenizer st = new StringTokenizer(isodate, "-T:.+Z", true);
Calendar calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
calendar.clear();
try {
// Year
if (st.hasMoreTokens()) {
int year = Integer.parseInt(st.nextToken());
calendar.set(Calendar.YEAR, year);
} else {
return calendar;
}
// Month
if (check(st, "-") && (st.hasMoreTokens())) {
int month = Integer.parseInt(st.nextToken()) - 1;
calendar.set(Calendar.MONTH, month);
} else {
return calendar;
}
// Day
if (check(st, "-") && (st.hasMoreTokens())) {
int day = Integer.parseInt(st.nextToken());
calendar.set(Calendar.DAY_OF_MONTH, day);
} else {
return calendar;
}
// Hour
if (check(st, "T") && (st.hasMoreTokens())) {
int hour = Integer.parseInt(st.nextToken());
calendar.set(Calendar.HOUR_OF_DAY, hour);
} else {
calendar.set(Calendar.HOUR_OF_DAY, 0);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
return calendar;
}
// Minutes
if (check(st, ":") && (st.hasMoreTokens())) {
int minutes = Integer.parseInt(st.nextToken());
calendar.set(Calendar.MINUTE, minutes);
} else {
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
return calendar;
}
//
// Not mandatory now
//
// Secondes
if (!st.hasMoreTokens()) {
return calendar;
}
String tok = st.nextToken();
if (tok.equals(":")) { // secondes
if (st.hasMoreTokens()) {
int secondes = Integer.parseInt(st.nextToken());
calendar.set(Calendar.SECOND, secondes);
if (!st.hasMoreTokens()) {
return calendar;
}
// frac sec
tok = st.nextToken();
if (tok.equals(".")) {
// bug fixed, thx to Martin Bottcher
String nt = st.nextToken();
while (nt.length() < 3) {
nt += "0";
}
nt = nt.substring(0, 3); // Cut trailing chars..
int millisec = Integer.parseInt(nt);
// int millisec = Integer.parseInt(st.nextToken()) * 10;
calendar.set(Calendar.MILLISECOND, millisec);
if (!st.hasMoreTokens()) {
return calendar;
}
tok = st.nextToken();
} else {
calendar.set(Calendar.MILLISECOND, 0);
}
} else {
throw new InvalidDateException("No secondes specified");
}
} else {
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
}
// Timezone
if (!tok.equals("Z")) { // UTC
if (!(tok.equals("+") || tok.equals("-"))) {
throw new InvalidDateException("only Z, + or - allowed");
}
boolean plus = tok.equals("+");
if (!st.hasMoreTokens()) {
throw new InvalidDateException("Missing hour field");
}
int tzhour = Integer.parseInt(st.nextToken());
int tzmin = 0;
if (check(st, ":") && (st.hasMoreTokens())) {
tzmin = Integer.parseInt(st.nextToken());
} else {
throw new InvalidDateException("Missing minute field");
}
if (plus) {
calendar.add(Calendar.HOUR, -tzhour);
calendar.add(Calendar.MINUTE, -tzmin);
} else {
calendar.add(Calendar.HOUR, tzhour);
calendar.add(Calendar.MINUTE, tzmin);
}
}
} catch (NumberFormatException ex) {
throw new InvalidDateException("[" + ex.getMessage() + "] is not an integer");
}
return calendar;
}
/**
* Parse the given string in ISO 8601 format and build a Date object.
*
* @param isodate
* the date in ISO 8601 format
* @return a Date instance
* @exception InvalidDateException
* if the date is not valid
*/
public static Date parse(String isodate) throws InvalidDateException {
if (isodate == null) {
return null;
} else {
return getCalendar(isodate).getTime();
}
}
private static String twoDigit(int i) {
if (i >= 0 && i < 10) {
return "0" + String.valueOf(i);
}
return String.valueOf(i);
}
// /**
// * Convert a date passed in as a string to a Date object. Support both string representations of the Date object and
// * iso8601 formatted dates.
// *
// * @param date the string to convert to a Date object
// * @return a date object (or null if date is null)
// * @throws java.text.ParseException if unable to parse date
// */
// public static Calendar parseDateParam(String date) throws ParseException {
// if (date == null)
// return null;
// try {
// Date parsedDate = new Date(date);
// Calendar calendar = Calendar.getInstance();
// calendar.setTime(parsedDate);
// return calendar;
// } catch (IllegalArgumentException iae) {
// if (log.isDebugEnabled())
// log.debug("failed to parse date '" + date + "' use Date - trying iso8601 format", iae);
// return parseDate(date);
// }
// }
//
// /**
// * Parse an xsd date into a java Date object.
// *
// * @param iso8601date is the date string to parse.
// * @return a java Date object.
// * @throws java.text.ParseException if there is a problem parsing the string
// */
// private static Calendar parseDate(String iso8601date) {
// // Obvious formats:
// SimpleDateFormat df = new SimpleDateFormat();
// for (String format : new String[]{
// "yyyy-MM-dd", "y-M-d", "y-M-d'T'H:m:s", "y-M-d'T'H:m:s.S",
// "y-M-d'T'H:m:s.Sz", "y-M-d'T'H:m:sz"}) {
// df.applyPattern(format);
// try {
// Date date = df.parse(iso8601date);
// Calendar calendar = Calendar.getInstance();
// calendar.setTime(date);
// return calendar;
// } catch (ParseException e) {
// //try other formats
// }
// }
// return null;
// }
/**
* Generate a ISO 8601 date
*
* @param date
* a Date instance
* @return a string representing the date in the ISO 8601 format
*/
public static String getIsoDate(Date date) {
Calendar calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
calendar.setTime(date);
StringBuffer buffer = new StringBuffer();
buffer.append(calendar.get(Calendar.YEAR));
buffer.append("-");
buffer.append(twoDigit(calendar.get(Calendar.MONTH) + 1));
buffer.append("-");
buffer.append(twoDigit(calendar.get(Calendar.DAY_OF_MONTH)));
buffer.append("T");
buffer.append(twoDigit(calendar.get(Calendar.HOUR_OF_DAY)));
buffer.append(":");
buffer.append(twoDigit(calendar.get(Calendar.MINUTE)));
buffer.append(":");
buffer.append(twoDigit(calendar.get(Calendar.SECOND)));
buffer.append(".");
buffer.append(twoDigit(calendar.get(Calendar.MILLISECOND) / 10));
buffer.append("Z");
return buffer.toString();
}
/**
* Generate a ISO 8601 date
*
* @param date
* a Date instance
* @return a string representing the date in the ISO 8601 format
*/
public static String getIsoDateNoMillis(Date date) {
Calendar calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
calendar.setTime(date);
StringBuffer buffer = new StringBuffer();
buffer.append(calendar.get(Calendar.YEAR));
buffer.append("-");
buffer.append(twoDigit(calendar.get(Calendar.MONTH) + 1));
buffer.append("-");
buffer.append(twoDigit(calendar.get(Calendar.DAY_OF_MONTH)));
buffer.append("T");
buffer.append(twoDigit(calendar.get(Calendar.HOUR_OF_DAY)));
buffer.append(":");
buffer.append(twoDigit(calendar.get(Calendar.MINUTE)));
buffer.append(":");
buffer.append(twoDigit(calendar.get(Calendar.SECOND)));
buffer.append("Z");
return buffer.toString();
}
}