/* * Copyright (C) 2000 - 2008 TagServlet Ltd * * This file is part of Open BlueDragon (OpenBD) CFML Server Engine. * * OpenBD is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * Free Software Foundation,version 3. * * OpenBD 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenBD. If not, see http://www.gnu.org/licenses/ * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining * it with any of the JARS listed in the README.txt (or a modified version of * (that library), containing parts covered by the terms of that JAR, the * licensors of this Program grant you additional permission to convey the * resulting work. * README.txt @ http://www.openbluedragon.org/license/README.txt * * http://www.openbluedragon.org/ */ package com.nary.util.date; import java.io.CharArrayWriter; import java.text.DateFormatSymbols; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import java.util.List; import java.util.Locale; /** * A class for tokenizing a date and/or time string in to the * tokens - number, separator, space, month, time * Usage: Create a new dateTimeTokenizer with a date (and/or time) string. * If wishing to ensure that this will convert correctly, call validateStructure() * which will return true if the string is a correctly convertible dat/time. * Call getDate() to get the date object equivalent to the date/time string. */ public class dateTimeTokenizer{ String dateTimeStr; Object [] tokens; int numberCount = 0; int monthCount = 0; public static byte NEUTRAL=0, US = 1, UK = 2; private byte locale; private Locale realLocale; public dateTimeTokenizer(String _dateTimeString, byte _locale){ locale = _locale; try { if ( _dateTimeString.startsWith( "{ts '" ) ){ dateTimeStr = _dateTimeString.substring( _dateTimeString.indexOf( '\'' ) + 1, _dateTimeString.lastIndexOf( '\'' ) ); locale = NEUTRAL; }else if ( _dateTimeString.startsWith( "{d '" ) ){ dateTimeStr = _dateTimeString.substring( _dateTimeString.indexOf( '\'' ) + 1, _dateTimeString.lastIndexOf( '\'' ) ); locale = NEUTRAL; }else{ dateTimeStr = _dateTimeString; } dateTimeStr = dateTimeStr.trim().replace(',','/').replace('-','/').replace('\t',' ').replace('\n',' ').replace('\r',' ').toLowerCase(); tokens = tokenize(); } catch ( RuntimeException e ) { // String.substring() can throw StringIndexOutOfBoundsException } }// DateTimeTokenizer() public dateTimeTokenizer(String _dateTimeString){ this( _dateTimeString, US ); }// dateTimeTokenizer() public Object[] getTokens(){ return tokens; }// getTokens() public void setLocale(Locale l){ realLocale = l; } public static Date getDate(String _date, Locale l){ dateTimeTokenizer dtt; if(l.getCountry().equals("US")) dtt = new dateTimeTokenizer(_date, US); else dtt = new dateTimeTokenizer(_date, UK); if( dtt.tokens == null ) return null; if ( !dtt.validateStructure() ) { dtt = new dateTimeTokenizer(_date, NEUTRAL); if ( !dtt.validateStructure() ) return null; } dtt.setLocale(l); return dtt.getDate(); } public static java.util.Date getUSDate( String _date ){ return getDate( _date, US ); }// getUSDate() // use for e.g 2001-2-1 public static java.util.Date getNeutralDate( String _date ){ return getDate( _date, NEUTRAL ); } public static java.util.Date getUKDate( String _date ){ return getDate( _date, UK ); } private static java.util.Date getDate( String _date, byte _locale ){ dateTimeTokenizer dtt = new dateTimeTokenizer( _date, _locale ); if ( dtt.tokens != null && dtt.validateStructure() ){ return dtt.getDate(); }else{ return null; } } // returns null if failed to tokenize private Object[] tokenize() { List<token> tokenList = new ArrayList<token>(); char [] strBytes = dateTimeStr.toCharArray(); charArrayParser dateTimeChars = new charArrayParser(strBytes);//dateTimeStr.getBytes()); while (!dateTimeChars.endOfCharArray()){ // note chars will be in lower case char nextChar = dateTimeChars.peakChar(); if ((nextChar >= 'a' && nextChar <= 'z')){ tokenList.add(dateTimeChars.getLabel()); }else if (nextChar >= '0' && nextChar <= '9'){ tokenList.add(dateTimeChars.getNumeric()); }else if (nextChar == ' '){ while (dateTimeChars.peakChar() == ' '){ dateTimeChars.getChar(); } tokenList.add(new token(token.SPACE)); }else if (nextChar == '/' || nextChar == '.' ){ tokenList.add(new token(token.SEPARATOR)); dateTimeChars.getChar(); }else{ return null; } } return tokenList.toArray(); }// tokenize() /** * returns false if the tokenised date/time doesn't fit the rules * given by this method. If this returns false, it is unsafe to use * getDate(). */ public boolean validateStructure(){ if ( ( tokens == null ) || ( tokens.length == 0 ) ) return false; int lastTokenIndex = tokens.length - 1; if (((token)tokens[0]).getType() == token.SEPARATOR || ((token)tokens[lastTokenIndex]).getType() == token.SEPARATOR){ return false; } int timeCount = 0; for (int i = 0; i <= lastTokenIndex; i++){ token currToken = (token)tokens[i]; token nextToken; switch (currToken.getType()){ case token.SEPARATOR: // invariant :- last token is not a separator nor space so safe to look at next token nextToken = (token)tokens[(i+1)]; if (nextToken.getType() == token.SEPARATOR || ( nextToken.getType() == token.SPACE && ( ((token)tokens[(i+2)]).getType() == token.SEPARATOR) ) ){ return false; } break; case token.TIME: timeCount++; if (i == 0 && (i+1) <= lastTokenIndex && ((token)tokens[1]).getType() == token.SEPARATOR){ return false; }else if (i == (lastTokenIndex) && (i-1) > 0 && ((token)tokens[i-1]).getType() == token.SEPARATOR) { return false; } break; case token.MONTH: // if month is immediately followed by a number if (i < lastTokenIndex){ nextToken = (token)tokens[(i+1)]; if (nextToken.getType() == token.NUMBER){ return false; } } monthCount++; break; case token.NUMBER: // if number is immediately followed by a month if (i < lastTokenIndex){ nextToken = (token)tokens[(i+1)]; if (nextToken.getType() == token.MONTH){ return false; } } numberCount++; break; case token.DAY: // day must be at the beginning of the string; // if only the day is specified, then it's not a valid date (bug #2962) if ( ( i != 0 ) || ( tokens.length == 1 ) ) { return false; } break; default: break; } } // check the counts AND check the TIME is at the start or end of the string // you can't have more than one time, more than one month, must have 2 numbers if a month is specified AND 0 or 3 numbers if a month isn't specified // // if a time is specified then it must be at the start or end of the string if (timeCount > 1 || monthCount > 1 || (monthCount == 1 && ( numberCount == 0 || numberCount > 2 )) // valid number counts 1, 2 || (monthCount == 0 && (numberCount == 1 || numberCount > 3 ) ) // valid number counts 0, 2, 3 || (timeCount == 1 && ((token)tokens[0]).getType() != token.TIME && ((token)tokens[tokens.length - 1]).getType() != token.TIME)) { return false; } return true; }// validateStructure() public java.util.Date getDate() { java.util.GregorianCalendar cal = (GregorianCalendar)java.util.GregorianCalendar.getInstance(); cal.setLenient(false); // initialise time of calendar cal.set(java.util.Calendar.HOUR_OF_DAY, 0); cal.set(java.util.Calendar.MINUTE, 0); cal.set(java.util.Calendar.SECOND, 0); cal.set(java.util.Calendar.MILLISECOND, 0); int timeIndex = -1; int lastTokenIndex = tokens.length - 1; // invariant: there is only one time which should be the first or last if (((token)tokens[0]).getType() == token.TIME){ timeIndex = 0; }else if ( (((token)tokens[lastTokenIndex]).getType() == token.TIME)){ timeIndex = tokens.length - 1; } if (timeIndex != -1){ // convert time to if (!processTime(cal, ((token)tokens[timeIndex]).getValue())){ return null; } } // if there is a date if (numberCount > 0){// i.e. numberCount == 2 or 3 // convert the date if (monthCount == 1){ // numberCount == 2 // if there's a month then any of the other 2 are year and day in any order char[] month = null; int part1 = -1; int part2 = -1; for (int i = 0; i < tokens.length; i++){ if (((token)tokens[i]).getType() == token.MONTH){ month = ((token)tokens[i]).getValue(); }else if (((token)tokens[i]).getType() == token.NUMBER){ if (part1 == -1){ part1 = intValue(((token)tokens[i]).getValue()); }else{ part2 = intValue(((token)tokens[i]).getValue()); } } } if ( numberCount == 1 ){ if ( !processDate1Digit( cal, month, part1 ) ){ return null; } }else if (!processDate(cal, month, part1, part2)){ return null; } }else{ // monthCount == 0, numberCount == 3 int part1 = -1; int part2 = -1; int part3 = -1; for (int i = 0; i < tokens.length; i++){ if (((token)tokens[i]).getType() == token.NUMBER){ if (part1 == -1){ part1 = intValue(((token)tokens[i]).getValue()); }else if (part2 == -1){ part2 = intValue(((token)tokens[i]).getValue()); }else{ part3 = intValue(((token)tokens[i]).getValue()); } } } if ( numberCount == 2 ){ if ( !processDate2Digit( cal, part1, part2 ) ){ return null; } }else if (!processDate(cal, part1,part2,part3)){ return null; } } } try { return cal.getTime(); } catch ( IllegalArgumentException exc ) { // With some VMs if the hour is 2am on the day that daylight savings begins then // an IllegalArgumentException will be thrown with a message of "HOUR_OF_DAY". // In this case set lenient to true and call getTime() again so it will work. // NOTE: In this case it returns the hour as 3am instead of 2am. In VMs that behave // properly the hour is returned as 2am. // NOTE: We know this problem exists with the following VMs: 1.5.0_07, 1.6.0 and 1.6.0_01. // But doesn't exist with these VMs: 1.5.0_01 and 1.6.0_12. // NOTE: This is the fix for bug NA#3187. cal.setLenient(true); if ( exc.getMessage().equals("HOUR_OF_DAY") && (cal.get(java.util.Calendar.HOUR_OF_DAY)==3) ) return cal.getTime(); else throw exc; } }// getDate() // attempts to create date using the parameters trying parts as day, year then year, day // returns false if failed to process it private boolean processDate(java.util.GregorianCalendar _calendar, char [] _month, int _part1, int _part2) { int month; if(realLocale == null) month = monthConverter.convertMonthToInt(_month);// returns month byte[] as an int index from 0; else month = monthConverter.convertMonthToInt(_month, new DateFormatSymbols(realLocale)); if ( month == -1 ) return false; // note: calendar.set() takes params year, month, day // whilst prelimDateCheck takes them in the order day, month, year // basically, this works out which comes first, the day or the year but checks // them in the order of priority, hence the number of if statements if (prelimDateCheck(_calendar, month + 1, _part1, _part2)){ // month/day/year return setCalendar(_calendar, _part2, month, _part1); } if (prelimDateCheck(_calendar, _part1, month + 1, _part2)){ // day/month/year return setCalendar(_calendar, _part2, month, _part1); } if (prelimDateCheck(_calendar, _part2, month + 1, _part1)){ // year/month/day return setCalendar(_calendar, _part1, month, _part2); } if (prelimDateCheck(_calendar, month + 1, _part1, _part2)){ // month/year/day return setCalendar(_calendar, _part1, month, _part2); } if (prelimDateCheck(_calendar, _part1, _part2, month + 1)){ // day/year/month return setCalendar(_calendar, _part2, month, _part1); } if (prelimDateCheck(_calendar, _part2, _part1, month + 1)){ // year/day/month return setCalendar(_calendar, _part1, month, _part2); } return false; }// processTime() private boolean setCalendar(java.util.GregorianCalendar _calendar, int _year, int _month, int _day){ _calendar.set( convertYear(_year), _month, _day ); return true; } // returns false if failed to process it private boolean processDate(java.util.GregorianCalendar _calendar, int _part1, int _part2, int _part3) { // note: month index has to be converted if (locale == UK){ if (prelimDateCheck(_calendar, _part1, _part2, _part3)){ return setCalendar(_calendar, _part3, _part2 - 1, _part1); // day/month/year } }else if (locale == US){ if (prelimDateCheck(_calendar, _part2, _part1, _part3)){ return setCalendar(_calendar, _part3, _part1 - 1, _part2); // month/day/year } }else if (locale == NEUTRAL){ if (prelimDateCheck(_calendar, _part3, _part2, _part1)){ return setCalendar(_calendar, _part1, _part2 - 1, _part3); // year/month/day } } return false; }// processDate() // attempts to create date using the parameters trying parts as day, year then year, day // returns false if failed to process it private boolean processDate2Digit(java.util.GregorianCalendar _calendar, int _part1, int _part2) { int defaultYear = _calendar.get( Calendar.YEAR ); if ( prelimDateCheck( _calendar, _part2, _part1, defaultYear ) ){ // month/day return setCalendar(_calendar, defaultYear, _part1 - 1, _part2); }else if ( prelimDateCheck( _calendar, _part1, _part2, defaultYear ) ){ // day/month return setCalendar(_calendar, defaultYear, _part2 - 1, _part1); }else if ( prelimDateCheck(_calendar, 1, _part1, _part2 ) ){ // month/year return setCalendar(_calendar, _part2, _part1 - 1, 1 ); }else if ( prelimDateCheck(_calendar, 1, _part2, _part1 ) ){ // year/month return setCalendar(_calendar, _part1, _part2 - 1, 1 ); } return false; } private boolean processDate1Digit(java.util.GregorianCalendar _calendar, char[] _month, int _part1 ) { // convert the month characters to a numeric int month; if(realLocale == null) month = monthConverter.convertMonthToInt(_month);// returns month byte[] as an int index from 0; else month = monthConverter.convertMonthToInt(_month, new DateFormatSymbols(realLocale)); if ( month == -1 ) return false; int defaultYear = _calendar.get( Calendar.YEAR ); // now we need to figure out whether the numerical part represents the year or the day if (prelimDateCheck(_calendar, month + 1, _part1, defaultYear)){ // month/day return setCalendar(_calendar, defaultYear, month, _part1); }else if (prelimDateCheck(_calendar, 1, month + 1, _part1)){ // month/year return setCalendar(_calendar, _part1, month, 1); } return false; } private boolean processTime(java.util.GregorianCalendar _calendar, char [] _time) { int index = 0; int hours = 0; int mins = 0; int secs = 0; int ms = 0; CharArrayWriter number = new CharArrayWriter(); // get hours while (_time[index] >= '0' && _time[index] <= '9'){ number.write(_time[index]); index++; } hours = intValue(number.toCharArray()); if (_time[index] == 'a' || _time[index] == 'p'){ hours = convertHours(hours, _time[index]); if (hours >= 0 && hours <= 23){ _calendar.set(java.util.Calendar.HOUR_OF_DAY, hours); return true; }else{ return false; } } if ( _time[index] == '.' ) return false; // get minutes number.reset(); index++; // ignore the : while ((index < _time.length) && _time[index] >= '0' && _time[index] <= '9'){ number.write(_time[index]); index++; } mins = intValue(number.toCharArray()); if ( (index < _time.length) && _time[index] == '.' ) return false; if (index == _time.length){ if (prelimTimeCheck(hours, mins, 0)){ _calendar.set(java.util.Calendar.HOUR_OF_DAY, hours); _calendar.set(java.util.Calendar.MINUTE, mins); return true; }else{ return false; } } if (_time[index] == 'a' || _time[index] == 'p'){ hours = convertHours(hours, _time[index]); if (prelimTimeCheck(hours, mins, 0)){ _calendar.set(java.util.Calendar.HOUR_OF_DAY, hours); _calendar.set(java.util.Calendar.MINUTE, mins); return true; }else{ return false; } } index++; // ignore the ':' // get secs number.reset(); while (index < _time.length && _time[index] >= '0' && _time[index] <= '9'){ number.write(_time[index]); index++; } secs = intValue(number.toCharArray()); // milliseconds number.reset(); if ( index < _time.length && _time[index] == '.' ){ index++; while (index < _time.length && _time[index] >= '0' && _time[index] <= '9'){ number.write(_time[index]); index++; } if ( number.size() > 9 ){ return false; }else if ( number.size() > 3 ){ // only include the first 3 numbers since we can't set the millisecond value // to any higher precision char [] numberChars = number.toCharArray(); ms = intValue( new char[]{ numberChars[0], numberChars[1], numberChars[2] }); }else{ ms = intValue( number.toCharArray() ); ms = number.size() == 3 ? ms : (number.size() == 2 ? ms * 10 : ms * 100 ); } } if (index < _time.length && (_time[index] == 'a' || _time[index] == 'p')){ hours = convertHours(hours, _time[index]); } if (!prelimTimeCheck(hours, mins, secs)){ return false; } _calendar.set(java.util.Calendar.HOUR_OF_DAY, hours); _calendar.set(java.util.Calendar.MINUTE, mins); _calendar.set(java.util.Calendar.SECOND, secs); _calendar.set(java.util.Calendar.MILLISECOND, ms); return true; }// processTime() public static int intValue(char [] in){ int power = in.length - 1; int value = 0; // the integer value to return for (int i = 0; i < in.length; i++){ value += ((int) Math.pow(10, power)) * (in[i] - '0'); power--; } return value; }// intValue() private static boolean prelimTimeCheck(int _hours, int _mins, int _secs){ return (_hours >= 0 && _hours <= 23 && _mins >= 0 && _mins <=59 && _secs >= 0 && _secs <=59); }// prelimTimeCheck() private boolean prelimDateCheck(java.util.GregorianCalendar _cal, int _day, int _month, int _year){ if (_day > 0 && _day < 32 && _month > 0 && _month < 13 && _year >= 0 && _year < 10000 ){ _cal.set(convertYear(_year), _month - 1, _day); // note must convert month back to java form try{ _cal.getTime(); }catch(IllegalArgumentException e){ return false; } return true; }else{ return false; } }// prelimDateCheck private static int convertHours(int _hours, char _period){ if (_period == 'a' && _hours == 12){ return 0; }else if (_period == 'p' && _hours < 12){ return _hours + 12; }else{ return _hours; } }// convertHours() private static int convertYear(int _year){ return( _year < 100 ? ( _year < 30 ? _year + 2000 : _year + 1900 ) : _year ); }// convertYear() }// DateTimeTokenizer