/* * 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.naryx.tagfusion.expression.function; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import java.util.TimeZone; import org.apache.oro.text.regex.MalformedPatternException; import org.apache.oro.text.regex.MatchResult; import org.apache.oro.text.regex.Pattern; import org.apache.oro.text.regex.PatternCompiler; import org.apache.oro.text.regex.PatternMatcher; import org.apache.oro.text.regex.PatternMatcherInput; import org.apache.oro.text.regex.Perl5Compiler; import org.apache.oro.text.regex.Perl5Matcher; import com.nary.util.date.dateTimeTokenizer; import com.nary.util.date.monthConverter; import com.naryx.tagfusion.cfm.engine.cfArgStructData; import com.naryx.tagfusion.cfm.engine.cfCatchData; import com.naryx.tagfusion.cfm.engine.cfData; import com.naryx.tagfusion.cfm.engine.cfDateData; import com.naryx.tagfusion.cfm.engine.cfSession; import com.naryx.tagfusion.cfm.engine.cfmRunTimeException; public class ParseDateTime extends functionBase { private static final long serialVersionUID = 1L; public ParseDateTime() { min = 1; max = 2; setNamedParams( new String[] { "date", "format" } ); } public String[] getParamInfo() { return new String[] { "date1", "pop format; 'standard' or 'pop'" }; } public java.util.Map getInfo() { return makeInfo( "date", "Formats a date string to a given output supports standard formats include SQL, Unix date string and HTTP timestamps.", ReturnType.DATE ); } public cfData execute( cfSession _session, cfArgStructData argStruct ) throws cfmRunTimeException { // TODO: add support for additional date-time formats supported by cfmx // e.g '24-01-2005 01:31:24', '2-13-2005 01:31:24', '2015-10-06T12:56:55Z' boolean convertTimeZone = false; String popconversion = getNamedStringParam( argStruct, "format", "standard" ); if ( popconversion.equalsIgnoreCase( "pop" ) ) { convertTimeZone = true; } else if ( !popconversion.equalsIgnoreCase( "standard" ) ) { throwException( _session, "Invalid value for pop-conversion parameter. Must be \"POP\" or \"STANDARD\"." ); } String dateTimeStr = getNamedStringParam( argStruct, "date", null ); cfData dateTime = parseDateTimeString( dateTimeStr, convertTimeZone ); // --[ if the format was bad throw exception if ( dateTime == null ) { java.util.Date jdateTime = parseSQLDateTime( dateTimeStr ); if ( jdateTime == null ) jdateTime = parseDateString( dateTimeStr ); if ( jdateTime == null ) jdateTime = parseHttpDateTime( dateTimeStr ); if ( jdateTime == null ) jdateTime = parseISO8601DateTime( dateTimeStr ); if ( jdateTime == null ) jdateTime = parseUnixDateTime( dateTimeStr, convertTimeZone ); if ( jdateTime == null ) throwException( _session, "invalid date/time string: " + dateTimeStr ); dateTime = new cfDateData( jdateTime ); } return dateTime; } // ----------------------- // this converts a date-time string to a date object. This is completely different // to parseDateString. It only accepts a date formatted in one way. // Tues, 27 March 1999 19:19:23 -0500 public static cfDateData parseDateTimeString( String _dateTimeString, boolean _convertTimeZone ) { String[] match; int offset = 0; try { match = regexpMatch( "[a-zA-Z]+,[[:space:]]*([0-9]+)[[:space:]]+([a-zA-Z]+)[[:space:]]+([0-9]+)[[:space:]]+([0-9]{1,2}):([0-9]{1,2})(?::([0-9]{1,2}))?([[:space:]]*(?:[+-][0-9]{2}:?[0-9]{2}(?: \\([a-zA-Z]+\\))?)|[[:space:]]*(?:[a-zA-Z]{3}))?", _dateTimeString ); if ( match.length > 0 ) { java.util.GregorianCalendar cal = (GregorianCalendar) java.util.GregorianCalendar.getInstance(); cal.setLenient( false ); cal.set( java.util.Calendar.YEAR, Integer.parseInt( match[3] ) ); cal.set( java.util.Calendar.MONTH, monthConverter.convertMonthToInt( match[2].toLowerCase().toCharArray() ) ); cal.set( java.util.Calendar.DAY_OF_MONTH, Integer.parseInt( match[1] ) ); cal.set( java.util.Calendar.HOUR_OF_DAY, Integer.parseInt( match[4] ) ); cal.set( java.util.Calendar.MINUTE, Integer.parseInt( match[5] ) ); // if ( match.length == 7 ){ if ( match[6] != null ) { cal.set( java.util.Calendar.SECOND, Integer.parseInt( match[6] ) ); } else { cal.set( java.util.Calendar.SECOND, 0 ); } cal.set( java.util.Calendar.MILLISECOND, 0 ); // if POP and we have a timezone match if ( _convertTimeZone && match.length > 7 && match[7] != null ) { offset = convertOffset( match[7] ); } try { if ( offset != 0 ) { cal.add( Calendar.MILLISECOND, offset ); } return new cfDateData( cal.getTime() ); } catch ( IllegalArgumentException e ) { // thrown if invalid date return null; } } } catch ( MalformedPatternException e ) { return null; // shouldn't happen since the reg exp is hard coded } return null; } public static java.util.Date parseDateString( String _dateString ) { java.util.Date d = dateTimeTokenizer.getUSDate( _dateString ); if ( d == null ) { d = dateTimeTokenizer.getNeutralDate( _dateString ); } if ( d == null ) { d = dateTimeTokenizer.getUKDate( _dateString ); } return d; }// parseDateString() /* * takes a string in the format +0500, -12:00 etc or short-style TimeZone and * return the integer value it corresponds to in ms. */ private static int convertOffset( String _offset ) { String offset = _offset.trim(); int offsetMs = 0; try { // Test if offset is short TZ if ( regexpMatch( "[A-Za-z]{3}", offset ).length == 1 ) { TimeZone tz = TimeZone.getTimeZone( offset ); offsetMs = -tz.getRawOffset(); // Else fallback on to +/- numeric ofsets } else { int spaceIndx = offset.indexOf( ' ' ); if ( spaceIndx != -1 ) { offset = offset.substring( 0, spaceIndx ); } // remove the colon if one exists int colonIndex = offset.indexOf( ":" ); if ( colonIndex != -1 ) { offset = offset.substring( 0, colonIndex ) + offset.substring( colonIndex + 1 ); } if ( offset.charAt( 0 ) == '+' ) { offset = offset.substring( 1 ); } // Convert offset into ms offsetMs = Integer.parseInt( offset ); offsetMs = -1 * ( ( offsetMs / 100 ) * 60 + ( offsetMs % 100 ) ) * 60 * 1000; } return offsetMs; } catch ( Exception e ) { return 0; } }// convertOffset() private static java.util.Date parseSQLDateTime( String _dateTime ) throws cfmRunTimeException { // The reg exp below represents the format "(year)-(month)-(day) (hour):(min):(sec)" // Note that it allows for invalid dates/times like 2004-99-99 99:99:99 // but that's ok as it's format that matters at this point String regexp = "([0-9]{4})-([0-9]{2})-([0-9]{2}) ([0-9]{1,2}):([0-9]{1,2})(:([0-9]{1,2}))?"; try { String[] match = regexpMatch( regexp, _dateTime ); if ( match.length >= 5 ) { java.util.GregorianCalendar cal = (GregorianCalendar) java.util.GregorianCalendar.getInstance(); cal.setLenient( false ); cal.set( java.util.Calendar.YEAR, Integer.parseInt( match[1] ) ); cal.set( java.util.Calendar.MONTH, Integer.parseInt( match[2] ) - 1 ); cal.set( java.util.Calendar.DAY_OF_MONTH, Integer.parseInt( match[3] ) ); cal.set( java.util.Calendar.HOUR_OF_DAY, Integer.parseInt( match[4] ) ); cal.set( java.util.Calendar.MINUTE, Integer.parseInt( match[5] ) ); if ( match.length == 8 && match[7] != null ) { cal.set( java.util.Calendar.SECOND, Integer.parseInt( match[7] ) ); } else { cal.set( java.util.Calendar.SECOND, 0 ); } cal.set( java.util.Calendar.MILLISECOND, 0 ); try { return cal.getTime(); } catch ( IllegalArgumentException e ) { // thrown if invalid date return null; } } else { return null; } } catch ( MalformedPatternException e ) { // shouldn't happen as we control the RegEx // unlikely exception since we've set the regexp cfCatchData catchD = new cfCatchData(); catchD.setType( "Function" ); catchD.setMessage( "parseSQLDateTime - internal error" ); catchD.setDetail( "Invalid regular expression ( " + regexp + " )" ); throw new cfmRunTimeException( catchD ); } } private static java.util.Date parseHttpDateTime( String _dateTime ) throws cfmRunTimeException { // The reg exp below represents the format "dd MMM yyyy HH:mm:ss GMT" - 11 Aug 2010 17:58:48 GMT // Note that it allows for invalid dates/times like 99 Zbr 9999 99:99:99 GMT" // but that's ok as it's format that matters at this point String regexp = "([0-9]{2}) ([A-Za-z]{3}) ([0-9]{4}) ([0-9]{2}):([0-9]{2}):([0-9]{2}) GMT"; try { String[] match = regexpMatch( regexp, _dateTime ); if ( match.length == 7 ) { try { Date dateTime = null; java.util.GregorianCalendar cal = (GregorianCalendar) java.util.GregorianCalendar.getInstance(); cal.set( java.util.Calendar.YEAR, Integer.parseInt( match[3] ) ); cal.set( java.util.Calendar.MONTH, monthConverter.convertMonthToInt( match[2].toLowerCase().toCharArray() ) ); cal.set( java.util.Calendar.DAY_OF_MONTH, Integer.parseInt( match[1] ) ); cal.set( java.util.Calendar.HOUR_OF_DAY, Integer.parseInt( match[4] ) ); cal.set( java.util.Calendar.MINUTE, Integer.parseInt( match[5] ) ); cal.set( java.util.Calendar.SECOND, Integer.parseInt( match[6] ) ); dateTime = cal.getTime(); return dateTime; } catch ( IllegalArgumentException e ) { // thrown if invalid date return null; } } else { return null; } } catch ( MalformedPatternException e ) { // unlikely exception since we've set the regexp cfCatchData catchD = new cfCatchData(); catchD.setType( "Function" ); catchD.setMessage( "ParseDateTime - internal error" ); catchD.setDetail( "Invalid regular expression ( " + regexp + " )" ); throw new cfmRunTimeException( catchD ); } } private static java.util.Date parseUnixDateTime( String _dateTime, boolean _convertTimeZone ) throws cfmRunTimeException { // The reg exp below represents the format "EEE MMM dd HH:mm:ss Z yyyy" // Note that it allows for invalid dates/times like "Zbr Hot 99, 99:99:99 UTC 0100" // but that's ok as it's format that matters at this point String regexp = "([A-Za-z]{3}) ([A-Za-z]{3}) ([0-9]{2}) ([0-9]{2}):([0-9]{2}):([0-9]{2}) ([A-Za-z]{3}|\\+[0-9]{4}|-[0-9]{4}) ([0-9]{4})"; int offset = 0; try { String[] match = regexpMatch( regexp, _dateTime ); if ( match.length == 9 ) { try { Date dateTime = null; java.util.GregorianCalendar cal = (GregorianCalendar) java.util.GregorianCalendar.getInstance(); cal.set( java.util.Calendar.YEAR, Integer.parseInt( match[8] ) ); cal.set( java.util.Calendar.MONTH, monthConverter.convertMonthToInt( match[2].toLowerCase().toCharArray() ) ); cal.set( java.util.Calendar.DAY_OF_MONTH, Integer.parseInt( match[3] ) ); cal.set( java.util.Calendar.HOUR_OF_DAY, Integer.parseInt( match[4] ) ); cal.set( java.util.Calendar.MINUTE, Integer.parseInt( match[5] ) ); cal.set( java.util.Calendar.SECOND, Integer.parseInt( match[6] ) ); // if POP and we have a timezone match if ( _convertTimeZone ) { offset = convertOffset( match[7] ); } if ( offset != 0 ) { cal.add( Calendar.MILLISECOND, offset ); } dateTime = cal.getTime(); return dateTime; } catch ( IllegalArgumentException e ) { // thrown if invalid date return null; } } else { return null; } } catch ( MalformedPatternException e ) { // unlikely exception since we've set the regexp cfCatchData catchD = new cfCatchData(); catchD.setType( "Function" ); catchD.setMessage( "ParseDateTime - internal error" ); catchD.setDetail( "Invalid regular expression ( " + regexp + " )" ); throw new cfmRunTimeException( catchD ); } } private static java.util.Date parseISO8601DateTime( String _dateTime ) throws cfmRunTimeException { // The reg exp below represents the format "(year)-(month)-(day)T(hour):(min):(sec)Z(+|-)(hour):(min)" // Note that it allows for invalid dates/times like "2004-99-99T99:99:99Z99:99+99:99" // but that's ok as it's format that matters at this point String regexp = "([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2})(Z|((\\+|-)([0-9]{1,2}):([0-9]{1,2})))?"; long offset = 0; try { String[] match = regexpMatch( regexp, _dateTime ); if ( match.length >= 6 ) { // Date is at least "2015-01-01T13:15:11" java.util.GregorianCalendar cal = (GregorianCalendar) java.util.GregorianCalendar.getInstance(); cal.setLenient( false ); Integer hour = Integer.parseInt( match[4] ); Integer minute = Integer.parseInt( match[5] ); cal.set( java.util.Calendar.YEAR, Integer.parseInt( match[1] ) ); cal.set( java.util.Calendar.MONTH, Integer.parseInt( match[2] ) - 1 ); cal.set( java.util.Calendar.DAY_OF_MONTH, Integer.parseInt( match[3] ) ); cal.set( java.util.Calendar.HOUR_OF_DAY, hour ); cal.set( java.util.Calendar.MINUTE, minute ); cal.set( java.util.Calendar.SECOND, Integer.parseInt( match[6] ) ); if ( match[10] != null ) { // if there's an offset if ( Integer.parseInt( match[10] ) < 24 && Integer.parseInt( match[11] ) <= 59 ) { // check offset < 23:59 long timeInMills = cal.getTimeInMillis(); offset = convertOffset( match[7] ); // get offset in ms timeInMills += offset; // add offset to datetime cal.setTimeInMillis( timeInMills ); } else { return null; } } try { return cal.getTime(); } catch ( IllegalArgumentException e ) { // thrown if invalid date return null; } } else { return null; } } catch ( MalformedPatternException e ) { // shouldn't happen as we control the RegEx // unlikely exception since we've set the regexp cfCatchData catchD = new cfCatchData(); catchD.setType( "Function" ); catchD.setMessage( "parseSQLDateTime - internal error" ); catchD.setDetail( "Invalid regular expression ( " + regexp + " )" ); throw new cfmRunTimeException( catchD ); } } private static String[] regexpMatch( String _re, String _txt ) throws MalformedPatternException { PatternMatcher matcher; PatternCompiler compiler; Pattern pattern = null; PatternMatcherInput input; MatchResult result; compiler = new Perl5Compiler(); matcher = new Perl5Matcher(); pattern = compiler.compile( _re ); input = new PatternMatcherInput( _txt ); if ( matcher.matches( input, pattern ) ) { result = matcher.getMatch(); int noGroups = result.groups(); String[] matches = new String[noGroups]; for ( int i = 0; i < noGroups; i++ ) { matches[i] = result.group( i ); } return matches; } else { return new String[0]; } } public static void main( String[] _args ) { System.out.println( parseDateString( "22-Mar-2000 11:02" ) ); System.out.println( parseDateString( "22 Mar 2000 11:02" ) ); System.out.println( parseDateString( "2 March 2000 11:02" ) ); System.out.println( parseDateString( "Tue, 27 Mar 2001 12:25:08 -0500" ) ); System.out.println( parseDateString( "01/02/89" ) ); System.out.println( parseDateString( "18:12" ) ); } }