DateParser.java Source code

Java tutorial

Introduction

Here is the source code for DateParser.java

Source

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