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 **/
019package lucee.commons.date;
020
021import java.text.SimpleDateFormat;
022import java.util.Date;
023import java.util.Locale;
024import java.util.TimeZone;
025
026import lucee.commons.lang.StringUtil;
027import lucee.commons.lang.SystemOut;
028import lucee.runtime.engine.ThreadLocalPageContext;
029import lucee.runtime.exp.ExpressionException;
030import lucee.runtime.type.dt.DateTime;
031import lucee.runtime.type.dt.DateTimeImpl;
032
033public abstract class DateTimeUtil {
034        
035        private final static SimpleDateFormat HTTP_TIME_STRING_FORMAT_OLD;
036        private final static SimpleDateFormat HTTP_TIME_STRING_FORMAT;
037        static {
038                HTTP_TIME_STRING_FORMAT_OLD = new SimpleDateFormat("EE, dd MMM yyyy HH:mm:ss zz",Locale.ENGLISH);
039                HTTP_TIME_STRING_FORMAT_OLD.setTimeZone(TimeZone.getTimeZone("GMT"));
040                
041                HTTP_TIME_STRING_FORMAT = new SimpleDateFormat("EE, dd-MMM-yyyy HH:mm:ss zz",Locale.ENGLISH);
042                HTTP_TIME_STRING_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT"));
043        }
044        
045    private static final double DAY_MILLIS = 86400000D;
046        private static final long CF_UNIX_OFFSET = 2209161600000L;
047
048        public static final int SECOND = 0;
049        public static final int MINUTE = 1;
050        public static final int HOUR = 2;
051        public static final int DAY = 3;
052        public static final int YEAR = 10;
053        public static final int MONTH = 11;
054        public static final int WEEK = 12;
055        public static final int QUARTER = 20;
056        public static final int MILLISECOND =30;
057        
058        
059        
060        private static DateTimeUtil instance;
061
062        public static DateTimeUtil getInstance(){
063                if(instance==null)      {
064                        instance=new JREDateTimeUtil();
065                        SystemOut.printDate("using JRE Date Library");
066                }
067                return instance;
068        }
069
070        public DateTime toDateTime(TimeZone tz,int year, int month, int day, int hour, int minute, int second, int milliSecond) throws DateTimeException {
071                return new DateTimeImpl(toTime(tz,year, month, day, hour, minute, second,milliSecond),false);
072        }
073        
074        public DateTime toDateTime(TimeZone tz,int year, int month, int day, int hour, int minute, int second, int milliSecond, DateTime defaultValue) {
075                long time = toTime(tz,year, month, day, hour, minute, second,milliSecond,Long.MIN_VALUE);
076                if(time==Long.MIN_VALUE) return defaultValue;
077                return new DateTimeImpl(time,false);
078        }       
079        
080        /**
081     * returns a date time instance by a number, the conversion from the double to 
082     * date is o the base of the CFML rules.
083     * @param days double value to convert to a number
084     * @return DateTime Instance
085     */
086    public DateTime toDateTime(double days) {
087        long utc=Math.round(days*DAY_MILLIS);
088        utc-=CF_UNIX_OFFSET;
089        utc-=getLocalTimeZoneOffset(utc);
090        return new DateTimeImpl(utc,false);
091    }
092        
093        
094        public long toTime(TimeZone tz,int year,int month,int day,int hour,int minute,int second,int milliSecond,long defaultValue){
095                tz=ThreadLocalPageContext.getTimeZone(tz);
096                year=toYear(year);
097        
098        if(month<1)          return defaultValue;
099        if(month>12) return defaultValue;
100        if(day<1)            return defaultValue;
101        if(hour<0)           return defaultValue;
102        if(minute<0)         return defaultValue;
103        if(second<0)         return defaultValue;
104        if(milliSecond<0)    return defaultValue;
105        if(hour>24)  return defaultValue;
106        if(minute>59)        return defaultValue;
107        if(second>59)        return defaultValue;
108        
109        if(daysInMonth(year, month)<day) return defaultValue;
110        
111        return _toTime(tz, year, month, day, hour, minute, second, milliSecond);
112        }
113
114        public long toTime(TimeZone tz,int year,int month,int day,int hour,int minute,int second,int milliSecond) throws DateTimeException{
115                tz=ThreadLocalPageContext.getTimeZone(tz);
116                year=toYear(year);
117        
118        if(month<1)          throw new DateTimeException("month number ["+month+"] must be at least 1");
119        if(month>12) throw new DateTimeException("month number ["+month+"] can not be greater than 12");
120        if(day<1)            throw new DateTimeException("day number ["+day+"] must be at least 1");
121        if(hour<0)           throw new DateTimeException("hour number ["+hour+"] must be at least 0");
122        if(minute<0)         throw new DateTimeException("minute number ["+minute+"] must be at least 0");
123        if(second<0)         throw new DateTimeException("second number ["+second+"] must be at least 0");
124        if(milliSecond<0)throw new DateTimeException("milli second number ["+milliSecond+"] must be at least 0");
125        
126        if(hour>24)  throw new DateTimeException("hour number ["+hour+"] can not be greater than 24");
127        if(minute>59)        throw new DateTimeException("minute number ["+minute+"] can not be greater than 59");
128        if(second>59)        throw new DateTimeException("second number ["+second+"] can not be greater than 59");
129        
130        if(daysInMonth(year, month)<day) 
131                throw new DateTimeException("day number ["+day+"] can not be greater than "+daysInMonth(year, month)+" when month is "+month+" and year "+year);
132        
133        return _toTime(tz, year, month, day, hour, minute, second, milliSecond);
134        }
135        
136        /**
137         * return how much days given month in given year has
138         * @param year
139         * @param month
140         * @return
141         */
142        public int daysInMonth(int year,int month){
143                switch(month) {
144                case 1:
145                case 3:
146                case 5:
147                case 7:
148                case 8:
149                case 10:
150                case 12:
151                        return 31;
152                case 4:
153                case 6:
154                case 9:
155                    case 11:
156                        return 30;
157                case 2:
158                        return isLeapYear(year)?29:28;
159        }
160                return -1;
161        }
162        
163        /**
164         * translate 2 digit numbers to a year; for example 10 to 2010 or 50 to 1950
165         * @param year
166         * @return year matching number
167         */
168        public int toYear(int year) {
169                if(year<100) {
170            if(year<30)year=year+=2000;
171            else year=year+=1900;
172        }
173                return year;
174        }
175        
176        /**
177         * return if given is is a leap year or not
178         * @param year
179         * @return is leap year
180         */
181        public boolean isLeapYear(int year) {
182                return ((year%4 == 0) && ((year%100 != 0) || (year%400 == 0)));
183    }
184        
185        /**
186     * cast boolean value
187     * @param dateTime 
188     * @return boolean value
189     * @throws ExpressionException
190     */
191    public boolean toBooleanValue(DateTime dateTime) throws DateTimeException {
192        throw new DateTimeException("can't cast Date ["+DateTimeUtil.toHTTPTimeString(dateTime,false)+"] to boolean value");
193    }
194    
195    public double toDoubleValue(DateTime dateTime) {
196        return toDoubleValue(dateTime.getTime()); 
197    }
198    
199    public double toDoubleValue(long time) {
200        time+=getLocalTimeZoneOffset    (time);
201        time+=CF_UNIX_OFFSET;
202        return time/DAY_MILLIS;
203    }
204    
205    private static long getLocalTimeZoneOffset(long time){
206        return ThreadLocalPageContext.getTimeZone().getOffset(time);
207    }
208    
209
210        public long getMilliSecondsAdMidnight(TimeZone timeZone, long time) {
211                return time-getMilliSecondsInDay(timeZone, time);
212        }
213    
214    abstract long _toTime(TimeZone tz,int year,int month,int day,int hour,int minute,int second,int milliSecond);
215        
216        public abstract int getYear(TimeZone tz,lucee.runtime.type.dt.DateTime dt);
217        
218        public abstract int getMonth(TimeZone tz,DateTime dt);
219        
220        public abstract int getDay(TimeZone tz,DateTime dt);
221
222        public abstract int getHour(TimeZone tz,DateTime dt);
223        
224        public abstract int getMinute(TimeZone tz,DateTime dt);
225        
226        public abstract int getSecond(TimeZone tz,DateTime dt);
227        
228        public abstract int getMilliSecond(TimeZone tz,DateTime dt);
229
230        public abstract long getMilliSecondsInDay(TimeZone tz, long time);
231        
232        public abstract int getDaysInMonth(TimeZone tz,DateTime dt);
233
234        public abstract int getDayOfYear(Locale locale,TimeZone tz, DateTime dt);
235
236        public abstract int getDayOfWeek(Locale locale,TimeZone tz, DateTime dt);
237
238        public abstract int getWeekOfYear(Locale locale,TimeZone tz,DateTime dt);
239        public abstract int getFirstDayOfMonth(TimeZone tz, DateTime dt);
240
241        public abstract String toString(DateTime dt, TimeZone tz);
242
243        public static String toHTTPTimeString(long time, boolean oldFormat) {
244                return toHTTPTimeString(new Date(time),oldFormat);
245        }
246        
247        /**
248         * converts a date to a http time String 
249         * @param date date to convert
250         * @param oldFormat "old" in that context means the format support the existing functionality in CFML like the function getHTTPTimeString, in that format the date parts are separated by a space (like "EE, dd MMM yyyy HH:mm:ss zz"),
251         * in the "new" format, the date part is separated by "-" (like "EE, dd-MMM-yyyy HH:mm:ss zz")
252         * @return
253         */
254        public static String toHTTPTimeString(Date date, boolean oldFormat) {
255                if(oldFormat) {
256                        synchronized(HTTP_TIME_STRING_FORMAT_OLD){
257                                return StringUtil.replace(HTTP_TIME_STRING_FORMAT_OLD.format(date),"+00:00","",true);
258                        }
259                }
260                synchronized(HTTP_TIME_STRING_FORMAT){
261                        return StringUtil.replace(HTTP_TIME_STRING_FORMAT.format(date),"+00:00","",true);
262                }
263        }
264
265
266}