Mega Code Archive

 
Categories / Java / Data Type
 

Date parser for the ISO 8601 format

//package com.adobe.epubcheck.util; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import java.util.StringTokenizer; import java.util.TimeZone; /**  * Date parser for the ISO 8601 format.  *   * Initial code taken from the jigsaw project (W3C license [1]) and modified consistently to  * apply further checks that were missing, for example the initial code reported   * <code>2011-</code> as valid date.   * See also:  * http://www.w3.org/TR/1998/NOTE-datetime-19980827  *   * @author mircea@oxygenxml.com Initial version and fixes.  * @author mihaela@sync.ro Initial version and fixes.  *   * @author george@oxygenxml.com Additional fixes.  */   /**   ***** [1] W3C license (jigsaw license) *****  *   * Jigsaw Copying Conditions  *   * W3C IPR SOFTWARE NOTICE  *   * Copyright Â© 1995-1998 World Wide Web Consortium, (Massachusetts Institute of  * Technology, Institut National de Recherche en Informatique et en  * Automatique, Keio University). All Rights Reserved.  * http://www.w3.org/Consortium/Legal/  *   * This W3C work (including software, documents, or other related items) is  * being provided by the copyright holders under the following license. By  * obtaining, using and/or copying this work, you (the licensee) agree that you  * have read, understood, and will comply with the following terms and  * conditions:  *   * Permission to use, copy, and modify this software and its documentation,  * with or without modification,  for any purpose and without fee or royalty is  * hereby granted, provided that you include the following on ALL copies of the  * software and documentation or portions thereof, including modifications,  * that you make:  *   *   1. The full text of this NOTICE in a location viewable to users of the  *      redistributed or derivative work.  *   2. Any pre-existing intellectual property disclaimers, notices, or terms  *      and conditions. If none exist, a short notice of the following form  *      (hypertext is preferred, text is permitted) should be used within the  *      body of any redistributed or derivative code: "Copyright Â© World Wide  *      Web Consortium, (Massachusetts Institute of Technology, Institut  *      National de Recherche en Informatique et en Automatique, Keio  *      University). All Rights Reserved. http://www.w3.org/Consortium/Legal/"  *   3. Notice of any changes or modifications to the W3C files, including the  *      date changes were made. (We recommend you provide URIs to the location  *      from which the code is derived).  *   * In addition, creators of derivitive works must include the full text of this  * NOTICE in a location viewable to users of the derivitive work.  *   * THIS SOFTWARE AND DOCUMENTATION IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS  * MAKE NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT  * LIMITED TO, WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR  * PURPOSE OR THAT THE USE OF THE SOFTWARE OR DOCUMENTATION WILL NOT INFRINGE  * ANY THIRD PARTY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS.  *   * COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL OR  * CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE SOFTWARE OR  * DOCUMENTATION.  *   * The name and trademarks of copyright holders may NOT be used in advertising  * or publicity pertaining to the software without specific, written prior  * permission. Title to copyright in this software and any associated  * documentation will at all times remain with copyright holders.  *   * ____________________________________  *   * This formulation of W3C's notice and license became active on August 14  * 1998. See the older formulation for the policy prior to this date. Please  * see our Copyright FAQ for common questions about using materials from our  * site, including specific terms and conditions for packages like libwww,  * Amaya, and Jigsaw. Other questions about this notice can be directed to  * site-policy@w3.org .  *   *   *   *   * webmaster  * (last updated 14-Aug-1998)  ***** end W3C license (jigsaw license) *****  */ public class DateParser {   /**    * Check if the next token, if exists, has a given value and that the     * provided string tokenizer has more tokens after that. It consumes     * the token checked against the expected value from the string tokenizer.    *     * @param st            The StringTokenizer to check.     * @param token         The value expected for the next token.    * @return     *   <code>true</code> if the token matches the value and there are more tokens.    *   <code>false</code> if there are no more tokens and we do not have a token to check.    * @throws InvalidDateException If the token does not match the value or if there are no     * more tokens after the token that matches the expected value.    */   private boolean checkValueAndNext(StringTokenizer st, String token) throws InvalidDateException {     if (!st.hasMoreTokens()) {       return false;     }     String t = st.nextToken();     if (!t.equals(token)) {       throw new InvalidDateException("Unexpected: " + t);     }     if (!st.hasMoreTokens()) {       throw new InvalidDateException("Incomplete date.");     }     return true;   }   /**    * Check if a given date is an iso8601 date.    *     * @param iso8601Date The date to be checked.    * @return <code>true</code> if the date is an iso8601 date.    * @throws InvalidDateException     */   private Calendar getCalendar(String iso8601Date) throws InvalidDateException {     // YYYY-MM-DDThh:mm:ss.sTZD     StringTokenizer st = new StringTokenizer(iso8601Date, "-T:.+Z", true);     if (!st.hasMoreTokens()) {       throw new InvalidDateException("Empty Date");     }     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 (checkValueAndNext(st, "-")) {         int month = Integer.parseInt(st.nextToken()) -1;         calendar.set(Calendar.MONTH, month);       } else {         return calendar;       }       // Day       if (checkValueAndNext(st, "-")) {         int day = Integer.parseInt(st.nextToken());         calendar.set(Calendar.DAY_OF_MONTH, day);       } else {         return calendar;       }       // Hour       if (checkValueAndNext(st, "T")) {         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 (checkValueAndNext(st, ":")) {         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;       }       if (! st.hasMoreTokens()) {         return calendar;       }       // Not mandatory now       // Seconds       String tok = st.nextToken();       if (tok.equals(":")) { // seconds         if (st.hasMoreTokens()) {           int secondes = Integer.parseInt(st.nextToken());           calendar.set(Calendar.SECOND, secondes);           if (! st.hasMoreTokens()) {             return calendar;           }           // decimal fraction of a second           tok = st.nextToken();           if (tok.equals(".")) {             String nt = st.nextToken();             while(nt.length() < 3) {               nt += "0";             }             if (nt.length() > 3) {               // check the other part from the decimal fraction to be formed only from digits               for (int i=3; i<nt.length(); i++) {                 if (!Character.isDigit(nt.charAt(i))) {                   throw new InvalidDateException("Invalid digit in the decimal fraction of a second: " + nt.charAt(i));                 }               }             }             nt = nt.substring( 0, 3 ); //Cut trailing chars..             int millisec = Integer.parseInt(nt);             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);       }       // Time zone       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 (checkValueAndNext(st, ":")) {           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);         }       } else {         if (st.hasMoreTokens()) {           throw new InvalidDateException("Unexpected field at the end of the date field: " + st.nextToken());         }       }     } catch (NumberFormatException ex) {       throw new InvalidDateException("[" + ex.getMessage() + "] is not an integer");     }     return calendar;   }     /**    *     * @param iso8601DateAsString The date parameter as a String.    * @return The corresponding Date object representing the result of parsing the date parameter.    * @throws InvalidDateException In case of an invalid date.    */   public Date parse(String iso8601DateAsString) throws InvalidDateException  {       Calendar calendar = getCalendar(iso8601DateAsString);       try {         calendar.setLenient(false);         return calendar.getTime();       } catch (Exception e) {         throw new InvalidDateException(iso8601DateAsString + " " + e.getClass().toString() + " " + e.getMessage());       }   } } class InvalidDateException extends Exception {   /**    * Creates an exception to signal an invalid date.    * @param message    */   public InvalidDateException(String message) {     super(message);   } }   /**    * Test the ISO8601 date.     * Date grammar:     *   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)    *     * @throws Exception    */ /* package com.adobe.epubcheck.util; public class DateParserTest {   public void testisISO8601Date() throws Exception {     DateParser p = new DateParser();     p.parse(  "2011"            );     p.parse(  "2011-02"          );     p.parse(  "2011-02-12"        );     p.parse(  "2011-03-01T13"        );     p.parse(  "2011-02-01T13:00"      );     p.parse(  "2011-02-01T13:00:00"    );     p.parse(  "2011-02-01T13:00:00Z"    );     p.parse(  "2011-02-01T13:00:00+01:00"  );     p.parse(  "2011-02-01T13:00:00-03:00"  );     try {         p.parse(  ""              );       throw new Exception("Invaid date passed!");     } catch (InvalidDateException e) {}     try {         p.parse(  "2011-"            );       throw new Exception("Invaid date passed!");     } catch (InvalidDateException e) {}     try {         p.parse(  "2011-02-"          );       throw new Exception("Invaid date passed!");     } catch (InvalidDateException e) {}          try {         p.parse(  "2011-02-01T"        );       throw new Exception("Invaid date passed!");     } catch (InvalidDateException e) {}     try {         p.parse(  "2011-02-01T13:"      );       throw new Exception("Invaid date passed!");     } catch (InvalidDateException e) {}     try {         p.parse(  "2011-02-01T13:00:"      );       throw new Exception("Invaid date passed!");     } catch (InvalidDateException e) {}     try {         p.parse(  "2011-02-01T13:00:00T"    );       throw new Exception("Invaid date passed!");     } catch (InvalidDateException e) {}     try {         p.parse(  "2011-02-01T13:00:00+01"  );       throw new Exception("Invaid date passed!");     } catch (InvalidDateException e) {}     try {         p.parse(  "2011-02-01T13:00:00+01:"  );       throw new Exception("Invaid date passed!");     } catch (InvalidDateException e) {}     try {         p.parse(  "2011-02-01T13:00:00-03"  );       throw new Exception("Invaid date passed!");     } catch (InvalidDateException e) {}     try {         p.parse(  "2011-02-01T13:00:00-03:"  );       throw new Exception("Invaid date passed!");     } catch (InvalidDateException e) {}          try {         p.parse(  "2011-02-01T13:00:00-03:AA"  );       throw new Exception("Invaid date passed!");     } catch (InvalidDateException e) {}          try {         p.parse(  "20a1"  );       throw new Exception("Invaid date passed!");     } catch (InvalidDateException e) {}          try {         p.parse(  " 2"  );       throw new Exception("Invaid date passed!");     } catch (InvalidDateException e) {}     try {         p.parse(  "2011-02-29"  );       throw new Exception("Invaid date passed!");     } catch (InvalidDateException e) {}          try {         p.parse(  "2011-02-01T13:00:00.123aqb"  );       throw new Exception("Invaid date passed!");     } catch (InvalidDateException e) {}          try {         p.parse(  "1994-11-05T13:15:30Zab"  );       throw new Exception("Invaid date passed!");     } catch (InvalidDateException e) {}             }   public static void main(String[] args) {     try {       new DateParserTest().testisISO8601Date();       System.out.println("Passed all tests!");     } catch (Exception e) {       System.out.println("Fail:");       e.printStackTrace();     }   } } */