001/**
002 *
003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
004 *
005 * This library is free software; you can redistribute it and/or
006 * modify it under the terms of the GNU Lesser General Public
007 * License as published by the Free Software Foundation; either 
008 * version 2.1 of the License, or (at your option) any later version.
009 * 
010 * This library is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013 * Lesser General Public License for more details.
014 * 
015 * You should have received a copy of the GNU Lesser General Public 
016 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
017 * 
018 **/
019// TODO Time constructor muss auch noch entfernt werden und durch DateUtil methode ersetzen
020package lucee.runtime.op.date;
021
022import java.text.DateFormat;
023import java.text.ParsePosition;
024import java.util.Calendar;
025import java.util.Date;
026import java.util.Locale;
027import java.util.TimeZone;
028
029import lucee.commons.date.DateTimeUtil;
030import lucee.commons.date.JREDateTimeUtil;
031import lucee.commons.date.TimeZoneConstants;
032import lucee.commons.i18n.FormatUtil;
033import lucee.commons.lang.StringUtil;
034import lucee.runtime.Component;
035import lucee.runtime.engine.ThreadLocalPageContext;
036import lucee.runtime.exp.ExpressionException;
037import lucee.runtime.exp.PageException;
038import lucee.runtime.i18n.LocaleFactory;
039import lucee.runtime.op.Castable;
040import lucee.runtime.op.Caster;
041import lucee.runtime.op.Decision;
042import lucee.runtime.type.ObjectWrap;
043import lucee.runtime.type.dt.DateTime;
044import lucee.runtime.type.dt.DateTimeImpl;
045import lucee.runtime.type.dt.Time;
046import lucee.runtime.type.dt.TimeImpl;
047
048/**
049 * Class to cast Strings to Date Objects
050 */
051public final class DateCaster {
052
053        public static final short CONVERTING_TYPE_NONE=0;
054        public static final short CONVERTING_TYPE_YEAR=1;
055        public static final short CONVERTING_TYPE_OFFSET=2;
056        
057        private static final Object NULL = new Object();
058
059        //private static short MODE_DAY_STR=1;
060        //private static short MODE_MONTH_STR=2;
061        //private static short MODE_NONE=4;
062        private static long DEFAULT_VALUE=Long.MIN_VALUE;
063        
064        private static DateTimeUtil util=DateTimeUtil.getInstance();
065        public static boolean classicStyle=false;
066        
067        /**     
068         * converts a Object to a DateTime Object (Advanced but slower)
069         * @param o Object to Convert
070         * @param timezone
071         * @return Date Time Object
072         * @throws PageException
073         */
074        public static DateTime toDateAdvanced(Object o,TimeZone timezone) throws PageException {
075                if(o instanceof Date)           {
076                        if(o instanceof DateTime) return (DateTime)o;
077                        return new DateTimeImpl((Date)o);
078                }
079                else if(o instanceof Castable)  return ((Castable)o).castToDateTime();
080                else if(o instanceof String)    {
081                    DateTime dt=toDateAdvanced((String)o,timezone,null);
082                    if(dt==null)
083                                throw new ExpressionException("can't cast ["+o+"] to date value");
084                    return dt;
085                }
086                else if(o instanceof Number)            return util.toDateTime(((Number)o).doubleValue());
087                else if(o instanceof ObjectWrap) return toDateAdvanced(((ObjectWrap)o).getEmbededObject(),timezone);
088                else if(o instanceof Calendar){
089                        
090                        return new DateTimeImpl((Calendar)o);
091                }
092                throw new ExpressionException("can't cast ["+Caster.toClassName(o)+"] to date value");
093        }
094        
095        /**
096         * converts a Object to a DateTime Object (Advanced but slower)
097         * @param str String to Convert
098         * @param timezone
099         * @return Date Time Object
100         * @throws PageException
101         */
102        public static DateTime toDateAdvanced(String str,TimeZone timezone) throws PageException {
103                DateTime dt=toDateAdvanced(str,timezone,null);
104            if(dt==null)
105                        throw new ExpressionException("can't cast ["+str+"] to date value");
106            return dt;
107        }
108
109        /**
110         * converts a Object to a DateTime Object (Advanced but slower), returns null if invalid string
111         * @param o Object to Convert
112         * @param timeZone
113         * @param defaultValue 
114         * @return Date Time Object
115         */
116        public static DateTime toDateAdvanced(Object o,TimeZone timeZone, DateTime defaultValue) {
117                if(o instanceof DateTime)               return (DateTime)o;
118                else if(o instanceof Date)              return new DateTimeImpl((Date)o);
119                else if(o instanceof Castable)  {
120                    return ((Castable)o).castToDateTime(defaultValue);
121                }
122                else if(o instanceof String)    return toDateAdvanced(o.toString(),timeZone,defaultValue);
123                else if(o instanceof Number)    return util.toDateTime(((Number)o).doubleValue());
124                else if(o instanceof Calendar){
125                        return new DateTimeImpl((Calendar)o);
126                }
127                else if(o instanceof ObjectWrap) return toDateAdvanced(((ObjectWrap)o).getEmbededObject(defaultValue),timeZone,defaultValue);
128                return defaultValue;
129        }
130
131        /**
132         * converts a String to a DateTime Object (Advanced but slower), returns null if invalid string
133         * @param str String to convert
134         * @param convertingType one of the following values:
135         * - CONVERTING_TYPE_NONE: number are not converted at all
136         * - CONVERTING_TYPE_YEAR: integers are handled as years
137         * - CONVERTING_TYPE_OFFSET: numbers are handled as offset from 1899-12-30 00:00:00 UTC
138         * @param timeZone
139         * @param defaultValue 
140         * @return Date Time Object
141         */
142        public static DateTime toDateAdvanced(String str,short convertingType,TimeZone timeZone, DateTime defaultValue) {
143                str=str.trim();
144                if(StringUtil.isEmpty(str)) return defaultValue;
145                timeZone=ThreadLocalPageContext.getTimeZone(timeZone);
146                DateTime dt=toDateSimple(str,convertingType,true,timeZone,defaultValue);
147                if(dt==null) {  
148                DateFormat[] formats = FormatUtil.getCFMLFormats(timeZone, true);
149                    synchronized(formats){
150                        Date d;
151                        ParsePosition pp=new ParsePosition(0);
152                        for(int i=0;i<formats.length;i++) {
153                                        //try {
154                                                pp.setErrorIndex(-1);
155                                                pp.setIndex(0);
156                                                d = formats[i].parse(str,pp);
157                                                if (pp.getIndex() == 0 || d==null || pp.getIndex()<str.length()) continue;   
158                                                dt= new DateTimeImpl(d.getTime(),false);
159                                                return dt;
160                                        //}catch (ParseException e) {}
161                                }
162                }
163                    dt=toDateTime(Locale.US, str, timeZone,defaultValue, false);
164            }
165            return dt;
166        }
167        
168
169    
170    /**
171     * parse a string to a Datetime Object
172     * @param locale 
173     * @param str String representation of a locale Date
174     * @param tz
175     * @return DateTime Object
176     * @throws PageException 
177     */
178    public static DateTime toDateTime(Locale locale,String str, TimeZone tz,boolean useCommomDateParserAsWell) throws PageException {
179        
180        DateTime dt=toDateTime(locale, str, tz,null,useCommomDateParserAsWell);
181        if(dt==null){
182                /* FUTURE 4.1
183                String prefix=locale.getLanguage()+"-"+locale.getCountry()+"-";
184                throw new ExpressionException("can't cast ["+str+"] to date value",
185                                "to add custom formats for "+LocaleFactory.toString(locale)+
186                                ", create/extend on of the following files ["+prefix+"datetime.df (for date time formats), "+prefix+"date.df (for date formats) or "+prefix+"time.df (for time formats)] in the following directory [<context>/lucee/locales]."+
187                                "");
188                */
189                throw new ExpressionException("can't cast ["+str+"] to date value");
190        }
191        return dt;
192    }
193        
194    /**
195     * parse a string to a Datetime Object, returns null if can't convert
196     * @param locale 
197     * @param str String representation of a locale Date
198     * @param tz
199     * @param defaultValue 
200     * @return datetime object
201     */
202    public synchronized static DateTime toDateTime(Locale locale,String str, TimeZone tz, DateTime defaultValue,boolean useCommomDateParserAsWell) {
203        str=str.trim();
204        tz=ThreadLocalPageContext.getTimeZone(tz);
205        DateFormat[] df;
206
207        // get Calendar
208        Calendar c=JREDateTimeUtil.getThreadCalendar(locale,tz);
209        //synchronized(c){
210                
211                // datetime
212                ParsePosition pp=new ParsePosition(0);
213                df=FormatUtil.getDateTimeFormats(locale,tz,false);//dfc[FORMATS_DATE_TIME];
214                Date d;
215                //print.e(locale.getDisplayName(Locale.ENGLISH));
216                for(int i=0;i<df.length;i++) {
217                        //print.e(df[i].format(new Date()));
218                        pp.setErrorIndex(-1);
219                                pp.setIndex(0);
220                                //try {
221                df[i].setTimeZone(tz);
222                d = df[i].parse(str,pp);
223                if (pp.getIndex() == 0 || d==null || pp.getIndex()<str.length()) continue;   
224                                
225                synchronized(c) {
226                        optimzeDate(c,tz,d);
227                        return new DateTimeImpl(c.getTime());
228                }
229                    //}catch (ParseException e) {}
230                }
231                // date
232                df=FormatUtil.getDateFormats(locale,tz,false);//dfc[FORMATS_DATE];
233                //print.e(locale.getDisplayName(Locale.ENGLISH));
234                for(int i=0;i<df.length;i++) {
235                        //print.e(df[i].format(new Date()));
236                        pp.setErrorIndex(-1);
237                                pp.setIndex(0);
238                                //try {
239                df[i].setTimeZone(tz);
240                                d=df[i].parse(str,pp);
241                if (pp.getIndex() == 0 || d==null || pp.getIndex()<str.length()) continue;   
242                
243                                synchronized(c) {
244                        optimzeDate(c,tz,d);
245                        return new DateTimeImpl(c.getTime());
246                }
247                                //}catch (ParseException e) {}
248                }
249                
250                // time
251                df=FormatUtil.getTimeFormats(locale,tz,false);//dfc[FORMATS_TIME];
252                //print.e(locale.getDisplayName(Locale.ENGLISH));
253                for(int i=0;i<df.length;i++) {
254                        //print.e(df[i].format(new Date()));
255                        pp.setErrorIndex(-1);
256                                pp.setIndex(0);
257                                //try {
258                df[i].setTimeZone(tz);
259                d=df[i].parse(str,pp);
260                if (pp.getIndex() == 0 || d==null || pp.getIndex()<str.length()) continue;   
261                synchronized(c) {
262                        c.setTimeZone(tz);
263                        c.setTime(d);
264                        c.set(Calendar.YEAR,1899);
265                        c.set(Calendar.MONTH,11);
266                        c.set(Calendar.DAY_OF_MONTH,30);
267                        c.setTimeZone(tz);
268                }
269                return new DateTimeImpl(c.getTime());
270                    //}catch (ParseException e) {}
271                }
272        //}
273        if(useCommomDateParserAsWell)return DateCaster.toDateSimple(str, CONVERTING_TYPE_NONE,true, tz, defaultValue);
274        return defaultValue;
275    }
276    
277    private static void optimzeDate(Calendar c, TimeZone tz, Date d) {
278        c.setTimeZone(tz);
279        c.setTime(d);
280        int year=c.get(Calendar.YEAR);
281        if(year<40) c.set(Calendar.YEAR,2000+year);
282        else if(year<100) c.set(Calendar.YEAR,1900+year);
283    }
284        
285        
286        
287        public static DateTime toDateAdvanced(String str, TimeZone timeZone, DateTime defaultValue) {
288            return toDateAdvanced(str, CONVERTING_TYPE_OFFSET, timeZone,defaultValue);
289        }
290        
291        /**
292         * converts a boolean to a DateTime Object
293         * @param b boolean to Convert
294         * @param timeZone 
295         * @return coverted Date Time Object
296         */
297        public static DateTime toDateSimple(boolean b, TimeZone timeZone) {
298                return toDateSimple(b?1L:0L, timeZone);
299        }
300
301        /**
302         * converts a char to a DateTime Object
303         * @param c char to Convert
304         * @param timeZone
305         * @return coverted Date Time Object
306         */
307        public static DateTime toDateSimple(char c, TimeZone timeZone) {
308                return toDateSimple((long)c, timeZone);
309        }
310
311        /**
312         * converts a double to a DateTime Object
313         * @param d double to Convert
314         * @param timeZone
315         * @return coverted Date Time Object
316         */
317        public static DateTime toDateSimple(double d, TimeZone timeZone) {
318                return toDateSimple((long)d, timeZone);
319        }
320        
321        /* *
322         * converts a double to a DateTime Object
323         * @param d double to Convert
324         * @param timeZone
325         * @return coverted Date Time Object
326         * /
327        public static DateTime toDateSimple(long l, TimeZone timezone) {
328                return new DateTimeImpl(l,false);
329        }*/
330
331        /**
332         * converts a Object to a DateTime Object, returns null if invalid string
333         * @param o Object to Convert
334         * @param convertingType one of the following values:
335         * - CONVERTING_TYPE_NONE: number are not converted at all
336         * - CONVERTING_TYPE_YEAR: integers are handled as years
337         * - CONVERTING_TYPE_OFFSET: numbers are handled as offset from 1899-12-30 00:00:00 UTC
338         * @param timeZone
339         * @return coverted Date Time Object
340         * @throws PageException
341         */
342        public static DateTime toDateSimple(Object o, short convertingType,boolean alsoMonthString, TimeZone timeZone) throws PageException {
343                if(o instanceof DateTime)               return (DateTime)o;
344                else if(o instanceof Date)              return new DateTimeImpl((Date)o);
345                else if(o instanceof Castable)  return ((Castable)o).castToDateTime();
346                else if(o instanceof String)    return toDateSimple(o.toString(),convertingType,alsoMonthString, timeZone);
347                else if(o instanceof Number)            return util.toDateTime(((Number)o).doubleValue());
348                else if(o instanceof Calendar)          return new DateTimeImpl((Calendar)o);
349                else if(o instanceof ObjectWrap) return toDateSimple(((ObjectWrap)o).getEmbededObject(),convertingType,alsoMonthString,timeZone);
350                else if(o instanceof Calendar){
351                        return new DateTimeImpl((Calendar)o);
352                }
353                
354                if(o instanceof Component)
355                        throw new ExpressionException("can't cast component ["+((Component)o).getAbsName()+"] to date value");
356                
357                throw new ExpressionException("can't cast ["+Caster.toTypeName(o)+"] to date value");
358        }
359        /**
360         * 
361         * @param o
362         * @param convertingType one of the following values:
363         * - CONVERTING_TYPE_NONE: number are not converted at all
364         * - CONVERTING_TYPE_YEAR: integers are handled as years
365         * - CONVERTING_TYPE_OFFSET: numbers are handled as offset from 1899-12-30 00:00:00 UTC
366         * @param alsoMonthString
367         * @param timeZone
368         * @param defaultValue
369         * @return
370         */
371        public static DateTime toDateSimple(Object o, short convertingType,boolean alsoMonthString, TimeZone timeZone, DateTime defaultValue) {
372                if(o instanceof DateTime)               return (DateTime)o;
373                else if(o instanceof Date)              return new DateTimeImpl((Date)o);
374                else if(o instanceof Castable)  return ((Castable)o).castToDateTime(defaultValue);
375                else if(o instanceof String)    return toDateSimple(o.toString(),convertingType,alsoMonthString, timeZone,defaultValue);
376                else if(o instanceof Number)            return util.toDateTime(((Number)o).doubleValue());
377                else if(o instanceof Calendar)          return new DateTimeImpl((Calendar)o);
378                else if(o instanceof ObjectWrap) {
379                        Object eo = ((ObjectWrap)o).getEmbededObject(NULL);
380                        if(eo==NULL) return defaultValue;
381                        return toDateSimple(eo,convertingType,alsoMonthString,timeZone,defaultValue);
382                }
383                else if(o instanceof Calendar){
384                        return new DateTimeImpl((Calendar)o);
385                }
386                return defaultValue;
387        }
388        
389        /**
390         * converts a Object to a DateTime Object, returns null if invalid string
391         * @param str String to Convert
392         * @param timeZone
393         * @return coverted Date Time Object
394         * @throws PageException
395         */
396        public static DateTime toDateSimple(String str, TimeZone timeZone) throws PageException {
397                 DateTime dt=toDateSimple(str,CONVERTING_TYPE_OFFSET,true, timeZone,null);
398                 if(dt==null)
399                         throw new ExpressionException("can't cast ["+str+"] to date value");
400                 return dt;
401        }
402
403        /**
404         * converts a Object to a Time Object, returns null if invalid string
405         * @param o Object to Convert
406         * @return coverted Date Time Object
407         * @throws PageException
408         */
409        public static Time toTime(TimeZone timeZone,Object o) throws PageException {
410            if(o instanceof Time)               return (Time)o;
411            else if(o instanceof Date)          return new TimeImpl((Date)o);
412                else if(o instanceof Castable)  return new TimeImpl(((Castable)o).castToDateTime());
413                else if(o instanceof String)    {
414                    Time dt=toTime(timeZone,o.toString(),null);
415                    if(dt==null)
416                                throw new ExpressionException("can't cast ["+o+"] to time value");
417                    return dt;
418                }
419                else if(o instanceof ObjectWrap) return toTime(timeZone,((ObjectWrap)o).getEmbededObject());
420                else if(o instanceof Calendar){
421                        // TODO check timezone offset
422                        return new TimeImpl(((Calendar)o).getTimeInMillis(),false);
423                }
424                throw new ExpressionException("can't cast ["+Caster.toClassName(o)+"] to time value");
425        }
426        
427        /**
428         * 
429         * @param o
430         * @param convertingType one of the following values:
431         * - CONVERTING_TYPE_NONE: number are not converted at all
432         * - CONVERTING_TYPE_YEAR: integers are handled as years
433         * - CONVERTING_TYPE_OFFSET: numbers are handled as offset from 1899-12-30 00:00:00 UTC
434         * @param timeZone
435         * @param defaultValue
436         * @return
437         */
438        public static DateTime toDateAdvanced(Object o,short convertingType, TimeZone timeZone, DateTime defaultValue) {
439        return _toDateAdvanced(o, convertingType, timeZone, defaultValue, true);
440         }
441         
442         private static DateTime _toDateAdvanced(Object o,short convertingType, TimeZone timeZone, DateTime defaultValue, boolean advanced) {
443        if(o instanceof DateTime)       return (DateTime)o;
444        else if(o instanceof Date)      return new DateTimeImpl((Date)o);
445        else if(o instanceof Castable)  {
446            return ((Castable)o).castToDateTime(defaultValue);
447        }
448        else if(o instanceof String)    {
449                if(advanced)return toDateAdvanced(o.toString(),convertingType, timeZone,defaultValue);
450                return toDateSimple(o.toString(),convertingType,true, timeZone,defaultValue);
451        }
452        else if(o instanceof Number){
453                return numberToDate(timeZone, ((Number)o).doubleValue(), convertingType, defaultValue);
454        }
455        else if(o instanceof ObjectWrap) {
456                return _toDateAdvanced(((ObjectWrap)o).getEmbededObject(defaultValue),convertingType,timeZone,defaultValue,advanced);
457        }
458        else if(o instanceof Calendar){
459                        return new DateTimeImpl((Calendar)o);
460                }
461                return defaultValue;
462    }
463            
464    
465
466    /**
467     * converts a Object to a DateTime Object, returns null if invalid string
468     * @param str Stringt to Convert
469     * @param convertingType one of the following values:
470         * - CONVERTING_TYPE_NONE: number are not converted at all
471         * - CONVERTING_TYPE_YEAR: integers are handled as years
472         * - CONVERTING_TYPE_OFFSET: numbers are handled as offset from 1899-12-30 00:00:00 UTC
473         * @param timeZone
474     * @return coverted Date Time Object
475     * @throws PageException  
476     */
477    public static DateTime toDateSimple(String str,short convertingType,boolean alsoMonthString, TimeZone timeZone) throws PageException {
478        DateTime dt = toDateSimple(str,convertingType,alsoMonthString,timeZone,null);
479        if(dt==null) throw new ExpressionException("can't cast value to a Date Object");
480        return dt;
481    }
482    
483    public static DateTime toDateAdvanced(Object o,short convertingType, TimeZone timeZone) throws PageException {
484        DateTime dt = toDateAdvanced(o,convertingType,timeZone,null);
485        if(dt==null) throw new ExpressionException("can't cast value to a Date Object");
486        return dt;
487    }
488    
489    
490        
491        /**
492         * converts a String to a Time Object, returns null if invalid string
493         * @param str String to convert
494         * @param defaultValue 
495         * @return Time Object
496         * @throws  
497         */
498        public static Time toTime(TimeZone timeZone,String str, Time defaultValue)  {
499                
500                if(str==null || str.length()<3) {
501                    return defaultValue;
502                }
503                DateString ds=new DateString(str);
504        // Timestamp
505                if(ds.isCurrent('{') && ds.isLast('}')) {
506                    
507                        // Time
508                        // "^\\{t '([0-9]{1,2}):([0-9]{1,2}):([0-9]{2})'\\}$"
509                        if(ds.fwIfNext('t')) {
510                                    
511                                    // Time
512                                                if(!(ds.fwIfNext(' ') && ds.fwIfNext('\'')))return defaultValue;
513                                                ds.next();
514                                        // hour
515                                                int hour=ds.readDigits();
516                                                if(hour==-1) return defaultValue;
517                                                
518                                                if(!ds.fwIfCurrent(':'))return defaultValue;
519                                        
520                                        // minute
521                                                int minute=ds.readDigits();
522                                                if(minute==-1) return defaultValue;
523                                                
524                                                if(!ds.fwIfCurrent(':'))return defaultValue;
525                                        
526                                        // second
527                                                int second=ds.readDigits();
528                                                if(second==-1) return defaultValue;
529                                                
530                                                if(!(ds.fwIfCurrent('\'') && ds.fwIfCurrent('}')))return defaultValue;
531                                                
532                                                if(ds.isAfterLast()){
533                                                        long time=util.toTime(timeZone,1899,12,30,hour,minute,second,0,DEFAULT_VALUE);
534                                                        if(time==DEFAULT_VALUE)return defaultValue;
535                                                        return new TimeImpl(time,false);
536                                                }
537                                                return defaultValue;
538                                
539                                                
540                        }
541                        return defaultValue;
542                }
543        // Time start with int
544                /*else if(ds.isDigit()) {
545                    char sec=ds.charAt(1);
546                    char third=ds.charAt(2);
547                    // 16.10.2004 (02:15)?
548                        if(sec==':' || third==':') {
549                                // hour
550                                int hour=ds.readDigits();
551                                if(hour==-1) return defaultValue;
552                                
553                                if(!ds.fwIfCurrent(':'))return defaultValue;
554                                
555                                // minutes
556                                int minutes=ds.readDigits();
557                                if(minutes==-1) return defaultValue;
558                                
559                                if(ds.isAfterLast()) {
560                                        long time=util.toTime(timeZone,1899,12,30,hour,minutes,0,0,DEFAULT_VALUE);
561                                        if(time==DEFAULT_VALUE) return defaultValue;
562                                        
563                                        return new TimeImpl(time,false);
564                                }
565                                //else if(!ds.fwIfCurrent(':'))return null;
566                                else if(!ds.fwIfCurrent(':')) {
567                                    if(!ds.fwIfCurrent(' '))return defaultValue;
568                                    
569                                    if(ds.fwIfCurrent('a') || ds.fwIfCurrent('A'))      {
570                                        if(ds.fwIfCurrent('m') || ds.fwIfCurrent('M')) {
571                                            if(ds.isAfterLast()) {
572                                                long time=util.toTime(timeZone,1899,12,30,hour,minutes,0,0,DEFAULT_VALUE);
573                                                                if(time==DEFAULT_VALUE) return defaultValue;
574                                                return new TimeImpl(time,false);
575                                            }
576                                        }
577                                        return defaultValue;
578                                    }
579                                    else if(ds.fwIfCurrent('p') || ds.fwIfCurrent('P')) {
580                                        if(ds.fwIfCurrent('m') || ds.fwIfCurrent('M')) {
581                                            if(ds.isAfterLast()) {
582                                                long time=util.toTime(timeZone,1899,12,30,hour<13?hour+12:hour,minutes,0,0,DEFAULT_VALUE);
583                                                                if(time==DEFAULT_VALUE) return defaultValue;
584                                                                return new TimeImpl(time,false);
585                                            }
586                                        }
587                                        return defaultValue;
588                                    }
589                                    return defaultValue;
590                                }
591                                
592                                
593                                // seconds
594                                int seconds=ds.readDigits();
595                                if(seconds==-1) return defaultValue;
596                                
597                                if(ds.isAfterLast()) {
598                                        long time=util.toTime(timeZone,1899,12,30,hour,minutes,seconds,0,DEFAULT_VALUE);
599                                        if(time==DEFAULT_VALUE) return defaultValue;
600                                        return new TimeImpl(time,false);
601                                }
602                                                                
603                    }
604                }*/
605                
606                // TODO bessere impl
607                ds.reset();
608                DateTime rtn = parseTime(timeZone, new int[]{1899,12,30}, ds, defaultValue,-1);
609                if(rtn==defaultValue) return defaultValue;
610                return new TimeImpl(rtn);
611                
612                
613                
614                //return defaultValue;
615        }
616
617        
618        
619        /**
620         * converts a String to a DateTime Object, returns null if invalid string
621         * @param str String to convert
622         * @param convertingType one of the following values:
623         * - CONVERTING_TYPE_NONE: number are not converted at all
624         * - CONVERTING_TYPE_YEAR: integers are handled as years
625         * - CONVERTING_TYPE_OFFSET: numbers are handled as offset from 1899-12-30 00:00:00 UTC
626         * @param alsoMonthString allow that the month is a english name
627         * @param timeZone
628         * @param defaultValue 
629         * @return Date Time Object
630         */     
631        private static DateTime parseDateTime(String str,DateString ds,short convertingType,boolean alsoMonthString,TimeZone timeZone, DateTime defaultValue) {
632                int month=0;
633                int first=ds.readDigits();
634                // first
635                if(first==-1) {
636                        if(!alsoMonthString) return defaultValue;
637                        first=ds.readMonthString();
638                        if(first==-1)return defaultValue;
639                        month=1;
640                }
641                
642                if(ds.isAfterLast()) return month==1?defaultValue:numberToDate(timeZone,Caster.toDoubleValue(str,Double.NaN),convertingType,defaultValue);
643                
644                char del=ds.current();
645                if(del!='.' && del!='/' && del!='-' && del!=' ' && del!='\t') {
646                        if(ds.fwIfCurrent(':')){
647                                return parseTime(timeZone, new int[]{1899,12,30}, ds, defaultValue,first);
648                        }
649                        return defaultValue;
650                }
651                ds.next();
652                ds.removeWhitespace();
653                
654                // second
655                int second=ds.readDigits();
656                if(second==-1){
657                        if(!alsoMonthString) return defaultValue;
658                        second=ds.readMonthString();
659                        if(second==-1)return defaultValue;
660                        month=2;
661                }
662                
663                if(ds.isAfterLast()) {
664                        return toDate(month,timeZone,first,second,defaultValue);
665                }
666                
667                char del2=ds.current();
668                if(del!=del2) {
669                        ds.fwIfCurrent(' ');
670                        ds.fwIfCurrent('T');
671                        ds.fwIfCurrent(' ');
672                        return parseTime(timeZone,_toDate(timeZone,month, first, second),ds,defaultValue,-1);
673                }
674                ds.next();
675                ds.removeWhitespace();
676                
677                
678                
679                int third=ds.readDigits();
680                if(third==-1){
681                        return defaultValue;
682                }
683                
684                if(ds.isAfterLast()) {
685                        if(classicStyle() && del=='.')return toDate(month,timeZone,second,first,third,defaultValue);
686                        return toDate(month,timeZone,first,second,third,defaultValue);
687                }
688                ds.fwIfCurrent(' ');
689                ds.fwIfCurrent('T');
690                ds.fwIfCurrent(' ');
691                if(classicStyle() && del=='.')return parseTime(timeZone,_toDate(month, second,first,third),ds,defaultValue,-1);
692                return parseTime(timeZone,_toDate(month, first, second,third),ds,defaultValue,-1);
693                
694                
695        }
696        
697        private static boolean classicStyle() {
698                return classicStyle;
699        }
700
701        private static DateTime parseTime(TimeZone timeZone,int[] date, DateString ds,DateTime defaultValue, int hours) {
702                if(date==null)return defaultValue;
703                
704
705                ds.removeWhitespace();
706                
707                // hour
708                boolean next=false;
709                if(hours==-1){
710                        ds.removeWhitespace();
711                        hours=ds.readDigits();
712                        ds.removeWhitespace();
713                        if(hours==-1) {
714                                return parseOffset(ds,timeZone,date,0,0,0,0,true,defaultValue);
715                        }
716                }
717                else next=true;
718                
719                int minutes=0;
720                if(next || ds.fwIfCurrent(':')){
721                        ds.removeWhitespace();
722                        minutes=ds.readDigits();
723                        ds.removeWhitespace();
724                        if(minutes==-1) return defaultValue;
725                }
726                
727
728                int seconds=0;
729                if(ds.fwIfCurrent(':')){
730                        ds.removeWhitespace();
731                        seconds=ds.readDigits();
732                        ds.removeWhitespace();
733                        if(seconds==-1) return defaultValue;
734                }
735                
736                int msSeconds=0;
737                if(ds.fwIfCurrent('.')){
738                        ds.removeWhitespace();
739                        msSeconds=ds.readDigits();
740                        ds.removeWhitespace();
741                        if(msSeconds==-1) return defaultValue;
742                }
743                
744                if(ds.isAfterLast()){
745                        return DateTimeUtil.getInstance().toDateTime(timeZone, date[0], date[1], date[2], hours, minutes, seconds, msSeconds, defaultValue);
746                }
747                ds.fwIfCurrent(' ');
748                
749                if(ds.fwIfCurrent('a') || ds.fwIfCurrent('A'))  {
750                        if(!ds.fwIfCurrent('m'))ds.fwIfCurrent('M');
751                if(ds.isAfterLast()) 
752                    return DateTimeUtil.getInstance().toDateTime(timeZone, date[0], date[1], date[2], 
753                                hours<12?hours:hours-12, minutes, seconds, msSeconds, defaultValue);
754                return defaultValue;
755            }
756            else if(ds.fwIfCurrent('p') || ds.fwIfCurrent('P')) {
757                if(!ds.fwIfCurrent('m'))ds.fwIfCurrent('M');
758                if(hours>24) return defaultValue;
759                if(ds.isAfterLast()) 
760                        return DateTimeUtil.getInstance().toDateTime(timeZone, date[0], date[1], date[2], 
761                                        hours<12?hours+12:hours, minutes, seconds, msSeconds,defaultValue);
762                return defaultValue;
763            }
764                
765                ds.fwIfCurrent(' ');
766                return parseOffset(ds,timeZone,date,hours,minutes,seconds,msSeconds,true,defaultValue);
767            
768        }
769
770        private static DateTime parseOffset(DateString ds, TimeZone timeZone, int[] date,int hours, int minutes, int seconds, int msSeconds, boolean checkAfterLast,
771                        DateTime defaultValue) {
772                if(ds.isLast() && (ds.fwIfCurrent('Z') ||  ds.fwIfCurrent('z'))) {
773                        return util.toDateTime(TimeZoneConstants.UTC, date[0], date[1], date[2], hours, minutes, seconds, msSeconds,defaultValue);
774                }
775                else if(ds.fwIfCurrent('+')){
776                DateTime rtn = util.toDateTime(timeZone, date[0], date[1], date[2], hours, minutes, seconds, msSeconds,defaultValue);
777                if(rtn==defaultValue) return rtn;
778                return readOffset(true,timeZone,rtn,date[0], date[1], date[2], hours, minutes, seconds, msSeconds,ds,checkAfterLast,defaultValue);
779            }
780            else if(ds.fwIfCurrent('-')){
781                DateTime rtn = util.toDateTime(timeZone, date[0], date[1], date[2], hours, minutes, seconds, msSeconds, defaultValue);
782                if(rtn==defaultValue) return rtn;
783                return readOffset(false,timeZone,rtn,date[0], date[1], date[2], hours, minutes, seconds, msSeconds,ds,checkAfterLast,defaultValue);
784            }
785                return defaultValue;
786        }
787
788        private static DateTime toDate(int month, TimeZone timeZone,int first, int second, DateTime defaultValue) {
789                int[] d = _toDate(timeZone,month, first, second);
790                if(d==null)return defaultValue;
791                return util.toDateTime(timeZone, d[0], d[1], d[2],0,0,0,0,defaultValue);
792        }
793        
794        private static int[] _toDate(TimeZone tz,int month, int first, int second) {
795                int YEAR=year(tz);
796                if(first<=12 && month<2){
797                        if(util.daysInMonth(YEAR, first)>=second)
798                                return new int[]{YEAR, first, second};
799                        return new int[]{util.toYear(second), first, 1};
800                }
801                // first>12
802                if(second<=12){
803                        if(util.daysInMonth(YEAR, second)>=first)
804                                return new int[]{YEAR, second, first};
805                        return new int[]{util.toYear(first), second, 1};
806                }
807                return null;
808        }
809        private static int year(TimeZone tz) {
810                return util.getYear(ThreadLocalPageContext.getTimeZone(tz),new DateTimeImpl());
811        }
812
813        private static DateTime toDate(int month, TimeZone timeZone,int first, int second, int third, DateTime defaultValue) {
814                int[] d = _toDate(month, first, second, third);
815                if(d==null) return defaultValue;
816                return util.toDateTime(timeZone, d[0], d[1], d[2], 0, 0, 0,0,defaultValue);
817        }
818                
819        private static int[] _toDate(int month, int first, int second, int third) {
820                if(first<=12){
821                        if(month==2)
822                                return new int[]{util.toYear(third), second, first};
823                        if(util.daysInMonth(util.toYear(third), first)>=second)
824                                return new int[]{util.toYear(third), first, second};
825                        return null;
826                }
827                if(second>12)return null;
828                if(month==2){
829                        int tmp=first;
830                        first=third;
831                        third=tmp;
832                }
833                if(util.daysInMonth(util.toYear(first), second)<third) {
834                        if(util.daysInMonth(util.toYear(third), second)>=first)
835                                return new int[]{util.toYear(third), second, first};
836                        return null;
837                }
838                return new int[]{util.toYear(first), second, third};
839        }
840        
841        /**
842         * converts the given string to a date following simple and fast parsing rules (no international formats)
843         * @param str
844         * @param convertingType one of the following values:
845         * - CONVERTING_TYPE_NONE: number are not converted at all
846         * - CONVERTING_TYPE_YEAR: integers are handled as years
847         * - CONVERTING_TYPE_OFFSET: numbers are handled as offset from 1899-12-30 00:00:00 UTC
848         * @param alsoMonthString allow that the month is defined as english word (jan,janauary ...)
849         * @param timeZone
850         * @param defaultValue
851         * @return
852         */
853        public static DateTime toDateSimple(String str,short convertingType,boolean alsoMonthString, TimeZone timeZone, DateTime defaultValue) {
854                str=StringUtil.trim(str,"");
855                DateString ds=new DateString(str);
856                
857        // Timestamp
858                if(ds.isCurrent('{') && ds.isLast('}')) {
859                        return _toDateSimpleTS(ds,timeZone,defaultValue);
860                }
861                DateTime res = parseDateTime(str,ds,convertingType,alsoMonthString,timeZone,defaultValue);
862                if(res==defaultValue && Decision.isNumeric(str)) {
863                        return numberToDate(timeZone,Caster.toDoubleValue(str,Double.NaN),convertingType,defaultValue);
864            }
865                return res;
866        }
867        
868        /**
869         * 
870         * @param timeZone
871         * @param d
872         * @param convertingType one of the following values:
873         * - CONVERTING_TYPE_NONE: number are not converted at all
874         * - CONVERTING_TYPE_YEAR: integers are handled as years
875         * - CONVERTING_TYPE_OFFSET: numbers are handled as offset from 1899-12-30 00:00:00 UTC
876         * @return
877         */
878        private static DateTime numberToDate(TimeZone timeZone, double d, short convertingType, DateTime defaultValue) {
879                if(!Decision.isValid(d)) return defaultValue;
880                
881                if(convertingType==CONVERTING_TYPE_YEAR) {
882                        int i=(int) d;
883                        if(i==d) {
884                                timeZone=ThreadLocalPageContext.getTimeZone(timeZone);
885                                Calendar c = Calendar.getInstance(timeZone);
886                                c.set(Calendar.MILLISECOND, 0);
887                                c.set(i, 0, 1, 0, 0, 0);
888                                return new DateTimeImpl(c);
889                        }
890                } 
891                if(convertingType==CONVERTING_TYPE_OFFSET)return util.toDateTime(d);
892                
893                return defaultValue;
894        }
895
896        private static DateTime _toDateSimpleTS(DateString ds, TimeZone timeZone, DateTime defaultValue) {
897                // Date
898                // "^\\{d '([0-9]{2,4})-([0-9]{1,2})-([0-9]{1,2})'\\}$"
899                if(ds.fwIfNext('d')) {
900                        if(!(ds.fwIfNext(' ') && ds.fwIfNext('\'')))return defaultValue;
901                        ds.next();
902                // year
903                        int year=ds.readDigits();
904                        if(year==-1) return defaultValue;
905                        
906                        if(!ds.fwIfCurrent('-'))return defaultValue;
907                
908                // month
909                        int month=ds.readDigits();
910                        if(month==-1) return defaultValue;
911                        
912                        if(!ds.fwIfCurrent('-'))return defaultValue;
913                
914                // day
915                        int day=ds.readDigits();
916                        if(day==-1) return defaultValue;
917                        
918                        if(!(ds.fwIfCurrent('\'') && ds.fwIfCurrent('}')))return defaultValue;
919                        
920                        if(ds.isAfterLast()) return util.toDateTime(timeZone,year, month, day, 0, 0, 0, 0, defaultValue);//new DateTimeImpl(year,month,day);
921                        
922                        return defaultValue;
923                        
924                }
925                
926                // DateTime
927                // "^\\{ts '([0-9]{1,4})-([0-9]{1,2})-([0-9]{1,2}) ([0-9]{2}):([0-9]{1,2}):([0-9]{2})'\\}$"
928                else if(ds.fwIfNext('t')) {
929                        if(!(ds.fwIfNext('s') && ds.fwIfNext(' ') && ds.fwIfNext('\''))) {
930                                
931                            // Time
932                                        if(!(ds.fwIfNext(' ') && ds.fwIfNext('\'')))return defaultValue;
933                                        ds.next();
934                                // hour
935                                        int hour=ds.readDigits();
936                                        if(hour==-1) return defaultValue;
937                                        
938                                        if(!ds.fwIfCurrent(':'))return defaultValue;
939                                
940                                // minute
941                                        int minute=ds.readDigits();
942                                        if(minute==-1) return defaultValue;
943                                        
944                                        if(!ds.fwIfCurrent(':'))return defaultValue;
945                                
946                                // second
947                                        int second=ds.readDigits();
948                                        if(second==-1) return defaultValue;
949                                
950                   // Milli Second
951                        int millis=0;
952                        
953                        if(ds.fwIfCurrent('.')){
954                            millis=ds.readDigits();
955                        }
956                                        
957                        int before=ds.getPos();
958                        DateTime tmp = parseOffset(ds, timeZone, new int[]{1899,12,30}, hour, minute, second, millis,false, defaultValue);
959                                        if(tmp==null && before!=ds.getPos()) return defaultValue;
960                                        
961                                        if(!(ds.fwIfCurrent('\'') && ds.fwIfCurrent('}')))return defaultValue;
962                                        
963                                        if(ds.isAfterLast()){
964                                                if(tmp!=null) {
965                                                        return new TimeImpl(tmp.getTime(),false);
966                                                }
967                                                long time=util.toTime(timeZone,1899,12,30,hour,minute,second,millis,DEFAULT_VALUE);
968                                                if(time==DEFAULT_VALUE)return defaultValue;
969                                                return new TimeImpl(time,false);
970                                        }
971                                        return defaultValue;
972                        }
973                        ds.next();
974                // year
975                        int year=ds.readDigits();
976                        if(year==-1) return defaultValue;
977                        
978                        if(!ds.fwIfCurrent('-'))return defaultValue;
979                
980                // month
981                        int month=ds.readDigits();
982                        if(month==-1) return defaultValue;
983                        
984                        if(!ds.fwIfCurrent('-'))return defaultValue;
985                
986                // day
987                        int day=ds.readDigits();
988                        if(day==-1) return defaultValue;
989                        
990                        if(!ds.fwIfCurrent(' '))return defaultValue;
991                
992                // hour
993                        int hour=ds.readDigits();
994                        if(hour==-1) return defaultValue;
995                        
996                        if(!ds.fwIfCurrent(':'))return defaultValue;
997                
998                // minute
999                        int minute=ds.readDigits();
1000                        if(minute==-1) return defaultValue;
1001                        
1002                        if(!ds.fwIfCurrent(':'))return defaultValue;
1003                
1004                // second
1005                        int second=ds.readDigits();
1006                        if(second==-1) return defaultValue;
1007                        
1008
1009
1010       // Milli Second
1011            int millis=0;
1012            
1013            if(ds.fwIfCurrent('.')){
1014                millis=ds.readDigits();
1015            }
1016            
1017            int before=ds.getPos();
1018            DateTime tmp = parseOffset(ds, timeZone, new int[]{year,month,day}, hour, minute, second, millis,false, defaultValue);
1019                        if(tmp==null && before!=ds.getPos()) return defaultValue;
1020            
1021            
1022            if(!(ds.fwIfCurrent('\'') && ds.fwIfCurrent('}')))return defaultValue;
1023            
1024            if(ds.isAfterLast()){
1025                                if(tmp!=null) return tmp;
1026                                return util.toDateTime(timeZone,year, month, day,hour,minute,second,millis,defaultValue);
1027                        }
1028            
1029            
1030                        return defaultValue;
1031                        
1032                }
1033                else return defaultValue;
1034        }
1035
1036
1037
1038    /**
1039         * reads a offset definition at the end of a date string
1040     * @param timeZone
1041         * @param dt previous parsed date Object
1042         * @param ds DateString to parse
1043     * @param defaultValue 
1044         * @return date Object with offset
1045     */
1046    private static DateTime readOffset(boolean isPlus,TimeZone timeZone,DateTime dt,int years, int months, int days, int hours, int minutes, int seconds, int milliSeconds, DateString ds, boolean checkAfterLast,DateTime defaultValue) {
1047    //timeZone=ThreadLocalPageContext.getTimeZone(timeZone);
1048    if(timeZone==null) return defaultValue;
1049        // HOUR
1050        int hourLength=ds.getPos();
1051        int hour=ds.readDigits();
1052        hourLength=ds.getPos()-hourLength;
1053        if(hour==-1) return defaultValue;
1054        
1055        // MINUTE
1056        int minute=0;
1057        if(!ds.isAfterLast()) {
1058                if(!(ds.fwIfCurrent(':') || ds.fwIfCurrent('.')))return defaultValue;           
1059                minute=ds.readDigits();
1060                if(minute==-1) return defaultValue;
1061                
1062        }
1063        else if(hourLength>2){
1064                int h=hour/100;
1065                minute=hour-(h*100);
1066                hour=h;
1067        }
1068
1069        if(hour>12) return defaultValue;
1070        if(minute>59) return defaultValue;
1071        if(hour==12 && minute>0) return defaultValue;
1072        
1073        long offset = hour*60L*60L*1000L;
1074        offset+=minute*60*1000;
1075        
1076        if(!checkAfterLast || ds.isAfterLast()) {
1077                long time= util.toTime(TimeZoneConstants.UTC, years, months, days, hours, minutes, seconds, milliSeconds, 0);
1078        
1079                if(isPlus)time-=offset;
1080                else time+=offset;
1081                return new DateTimeImpl(time,false);
1082        }
1083        return defaultValue;
1084    }
1085    
1086    public static String toUSDate(Object o, TimeZone timeZone) throws PageException {
1087        if(Decision.isUSDate(o)) return Caster.toString(o);
1088        DateTime date = DateCaster.toDateAdvanced(o, timeZone);
1089        return new lucee.runtime.format.DateFormat(Locale.US).format(date,"mm/dd/yyyy");
1090    }
1091    
1092    public static String toEuroDate(Object o, TimeZone timeZone) throws PageException {
1093        if(Decision.isEuroDate(o)) return Caster.toString(o);
1094        DateTime date = DateCaster.toDateAdvanced(o, timeZone);
1095        return new lucee.runtime.format.DateFormat(Locale.US).format(date,"dd.mm.yyyy");
1096    }
1097
1098        public static String toShortTime(long time) {
1099                return Long.toString(time/1000,36);
1100        }
1101        
1102        public static long fromShortTime(String str) {
1103                return Long.parseLong(str,36)*1000;
1104        }
1105}