/* * 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); } }