/*
* Copyright (C) 2009 eXo Platform SAS.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.exoplatform.commons.utils;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
/**
* Created by The eXo Platform SAS Author : Peter Nedonosko
* peter.nedonosko@exoplatform.com.ua 05.07.2007 ISO 8601 standard Year: YYYY
* (eg 1997) Year and month: YYYY-MM (eg 1997-07) Complete date: YYYY-MM-DD (eg
* 1997-07-16) Complete date plus hours and minutes: YYYY-MM-DDThh:mmTZD (eg
* 1997-07-16T19:20+01:00) Complete date plus hours, minutes and seconds:
* YYYY-MM-DDThh:mm:ssTZD (eg 1997-07-16T19:20:30+01:00) Complete date plus
* hours, minutes, seconds and a decimal fraction of a second
* YYYY-MM-DDThh:mm:ss.sTZD (eg 1997-07-16T19:20:30.45+01:00) where: YYYY =
* four-digit year MM = two-digit month (01=January, etc.) DD = two-digit day of
* month (01 through 31) hh = two digits of hour (00 through 23) (am/pm NOT
* allowed) mm = two digits of minute (00 through 59) ss = two digits of second
* (00 through 59) s = one or more digits representing a decimal fraction of a
* second TZD = time zone designator (Z or +hh:mm or -hh:mm) a RFC 822 time zone
* is also accepted: For formatting, the RFC 822 4-digit time zone format is
* used: RFC822TimeZone: Sign TwoDigitHours Minutes TwoDigitHours: Digit Digit
* like -8000
*
* @author <a href="mailto:peter.nedonosko@exoplatform.com.ua">Peter
* Nedonosko</a>
* @version $Id: ISO8601.java 34394 2009-07-23 09:23:31Z dkatayev $
*/
public class ISO8601
{
/**
* ISO 8601 time zone designator
*/
protected static final String TZD = "TZD";
/**
* Year: YYYY (eg 1997)
*/
public static final String YEAR_FORMAT = "yyyy";
/**
* Year and month: YYYY-MM (eg 1997-07)
*/
public static final String YEARMONTH_FORMAT = "yyyy-MM";
/**
* Complete date: YYYY-MM-DD (eg 1997-07-16)
*/
public static final String COMPLETE_DATE_FORMAT = "yyyy-MM-dd";
/**
* NON ISO STANDARD. Simple date plus hours and minutes, without time zone:
* YYYY-MM-DDThh:mm (eg 1997-07-16T19:20)
*/
public static final String SIMPLE_DATEHOURSMINUTES_FORMAT = "yyyy-MM-dd'T'HH:mm";
/**
* NON ISO STANDARD. Complete date plus hours and minutes, with time zone by
* RFC822: YYYY-MM-DDThh:mmZ (eg 1997-07-16T19:20+0100)
*/
public static final String COMPLETE_DATEHOURSMINUTESZRFC822_FORMAT = "yyyy-MM-dd'T'HH:mmZ";
/**
* Complete date plus hours and minutes: YYYY-MM-DDThh:mmTZD (eg
* 1997-07-16T19:20+01:00)
*/
public static final String COMPLETE_DATEHOURSMINUTESZ_FORMAT = "yyyy-MM-dd'T'HH:mm" + TZD;
/**
* NON ISO STANDARD. Simple date plus hours, minutes and seconds, without
* time zone: YYYY-MM-DDThh:mm:ss (eg 1997-07-16T19:20:30)
*/
public static final String SIMPLE_DATETIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss";
/**
* NON ISO STANDARD. Complete date plus hours, minutes and seconds, with
* time zone by RFC822: YYYY-MM-DDThh:mm:ssZ (eg 1997-07-16T19:20:30+0100)
*/
public static final String COMPLETE_DATETIMEZRFC822_FORMAT = "yyyy-MM-dd'T'HH:mm:ssZ";
/**
* Complete date plus hours, minutes and seconds: YYYY-MM-DDThh:mm:ssTZD (eg
* 1997-07-16T19:20:30+01:00)
*/
public static final String COMPLETE_DATETIMEZ_FORMAT = "yyyy-MM-dd'T'HH:mm:ss" + TZD;
/**
* NON ISO STANDARD. Simple date plus hours, minutes, seconds and a decimal
* fraction of a second, without time zone YYYY-MM-DDThh:mm:ss.s (eg
* 1997-07-16T19:20:30.45)
*/
public static final String SIMPLE_DATETIMEMS_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS";
/**
* Complete date plus hours, minutes, seconds and a decimal fraction of a
* second, with time zone by RFC822 YYYY-MM-DDThh:mm:ss.sZ (eg
* 1997-07-16T19:20:30.45+0100)
*/
public static final String COMPLETE_DATETIMEMSZRFC822_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
/**
* Complete date plus hours, minutes, seconds and a decimal fraction of a
* second YYYY-MM-DDThh:mm:ss.sTZD (eg 1997-07-16T19:20:30.45+01:00)
*/
public static final String COMPLETE_DATETIMEMSZ_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS" + TZD;
/**
* Possible formats list. ISO 8601, RFC822 + simple formats in order of
* priority of parse
*/
public static final String[] FORMATS =
{COMPLETE_DATETIMEMSZ_FORMAT, COMPLETE_DATETIMEMSZRFC822_FORMAT, SIMPLE_DATETIMEMS_FORMAT,
COMPLETE_DATETIMEZ_FORMAT, COMPLETE_DATETIMEZRFC822_FORMAT, SIMPLE_DATETIME_FORMAT,
COMPLETE_DATEHOURSMINUTESZ_FORMAT, COMPLETE_DATEHOURSMINUTESZRFC822_FORMAT, SIMPLE_DATEHOURSMINUTES_FORMAT,
COMPLETE_DATE_FORMAT, YEARMONTH_FORMAT, YEAR_FORMAT};
protected static class ISODateFormat
{
private final SimpleDateFormat formater;
private final String format;
private final boolean isoTZ;
ISODateFormat(String format)
{
this.isoTZ = format.endsWith(TZD);
this.format = this.isoTZ ? format.substring(0, format.length() - TZD.length()) + "Z" : format;
this.formater = new SimpleDateFormat(this.format, Locale.US);
}
public Calendar parse(String dateString) throws ParseException, NumberFormatException
{
Date isoDate = null;
TimeZone timeZone = null;
if (dateString.length() >= 16)
{
if (isoTZ)
{
// need fix TZ from ISO 8601 (+01:00) to RFC822 (+0100)
if (dateString.endsWith("Z"))
{
dateString = dateString.substring(0, dateString.length() - 1) + "+0000";
}
else
{
int tzsindex = dateString.length() - 6;
char tzsign = dateString.charAt(tzsindex); // sixth char from the end
if (tzsign == '+' || tzsign == '-')
{
dateString = dateString.substring(0, tzsindex) + dateString.substring(tzsindex).replaceAll(":", "");
}
}
}
int index = dateString.lastIndexOf('-');
if (index >= 16 || (index = dateString.lastIndexOf('+')) >= 16)
{
String timeZoneStr = dateString.substring(index);
timeZone = TimeZone.getTimeZone("GMT" + timeZoneStr);
formater.setTimeZone(timeZone);
}
}
isoDate = formater.parse(dateString);
Calendar isoCalendar = Calendar.getInstance();
isoCalendar.setTime(isoDate);
if (timeZone != null)
isoCalendar.setTimeZone(timeZone);
return isoCalendar;
}
public String format(Calendar source)
{
if (isoTZ)
{
formater.setTimeZone(source.getTimeZone());
String formatedDate = formater.format(source.getTime());
if (formatedDate.endsWith("0000"))
{
return formatedDate.substring(0, formatedDate.length() - 5) + "Z"; // GMT
// (
// UTC
// )
}
else
{
int dindex = formatedDate.length() - 2;
return formatedDate.substring(0, dindex) + ":" + formatedDate.substring(dindex); // GMT
// offset
}
}
else
return formater.format(source);
}
}
/**
* Format date using format: complete date plus hours, minutes, seconds and a
* decimal fraction of a second.
*
* @param date
* @return
*/
public static String format(Calendar date)
{
return new ISODateFormat(COMPLETE_DATETIMEMSZ_FORMAT).format(date);
}
/**
* Parse string using possible formats list.
*
* @param dateString - date string
* @return - calendar or null if dateString is inparseable text
*/
public static Calendar parse(String dateString)
{
try
{
return parse(dateString, FORMATS);
}
catch (ParseException e)
{
return null;
}
}
/**
* Parse string using possible formats list.
*
* @param dateString - date string
* @return - calendar
* @throws ParseException
* @throws NumberFormatException
*/
public static Calendar parseEx(String dateString) throws ParseException, NumberFormatException
{
return parse(dateString, FORMATS);
}
/**
* Parse string using given formats list.
*
* @param dateString
* @param formats
* @return
* @throws ParseException
* @throws NumberFormatException
*/
public static Calendar parse(String dateString, String[] formats) throws ParseException
{
StringBuilder problems = new StringBuilder();
int errOffset = 0;
for (String format : formats)
{
try
{
Calendar isoDate = new ISODateFormat(format).parse(dateString);
return isoDate; // done
}
catch (ParseException e)
{
if (errOffset == 0)
errOffset = e.getErrorOffset();
problems.append(format);
problems.append(" - ");
problems.append(e.getMessage());
problems.append(", error offset ");
problems.append(e.getErrorOffset());
problems.append(" \n");
}
catch (NumberFormatException e)
{
errOffset = 0;
problems.append(format);
problems.append(" - ");
problems.append(e.getMessage());
problems.append(" \n");
}
}
throw new ParseException("Can not parse " + dateString + " as Date. " + problems.toString(), errOffset);
}
}