001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements.  See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership.  The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance
008     * with the License.  You may obtain a copy of the License at
009     *
010     *      http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing, software
013     * distributed under the License is distributed on an "AS IS" BASIS,
014     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    package org.apache.oozie.util;
019    
020    import java.sql.Timestamp;
021    import java.text.DateFormat;
022    import java.text.ParseException;
023    import java.text.ParsePosition;
024    import java.text.SimpleDateFormat;
025    import java.util.Calendar;
026    import java.util.Date;
027    import java.util.GregorianCalendar;
028    import java.util.Locale;
029    import java.util.TimeZone;
030    
031    import org.apache.oozie.coord.TimeUnit;
032    
033    public class DateUtils {
034    
035        private static final String[] W3CDATETIME_MASKS = {"yyyy-MM-dd'T'HH:mmz"};
036    
037        /**
038         * Parses a Date out of a String with a date in W3C date-time format.
039         * <p/>
040         * It parsers the following formats:
041         * <ul>
042         * <li>"yyyy-MM-dd'T'HH:mm:ssz"</li>
043         * <li>"yyyy-MM-dd'T'HH:mmz"</li>
044         * <li>"yyyy-MM-dd"</li>
045         * <li>"yyyy-MM"</li>
046         * <li>"yyyy"</li>
047         * </ul>
048         * <p/>
049         * Refer to the java.text.SimpleDateFormat javadocs for details on the
050         * format of each element.
051         * <p/>
052         *
053         * @param sDate string to parse for a date.
054         * @return the Date represented by the given W3C date-time string. It
055         * returns <b>null</b> if it was not possible to parse the given
056         * string into a Date.
057         */
058        /*
059         * public static Date parseW3CDateTime(String sDate) { // if sDate has time
060         * on it, it injects 'GTM' before de TZ displacement to // allow the
061         * SimpleDateFormat parser to parse it properly int tIndex =
062         * sDate.indexOf("T"); if (tIndex > -1) { if (sDate.endsWith("Z")) { sDate =
063         * sDate.substring(0, sDate.length() - 1) + "+00:00"; } int tzdIndex =
064         * sDate.indexOf("+", tIndex); if (tzdIndex == -1) { tzdIndex =
065         * sDate.indexOf("-", tIndex); } if (tzdIndex > -1) { String pre =
066         * sDate.substring(0, tzdIndex); int secFraction = pre.indexOf(","); if
067         * (secFraction > -1) { pre = pre.substring(0, secFraction); } String post =
068         * sDate.substring(tzdIndex); sDate = pre + "GMT" + post; } } else { sDate
069         * += "T00:00GMT"; } return parseUsingMask(W3CDATETIME_MASKS, sDate); }
070         */
071        /**
072         * Parses a Date out of a string using an array of masks. <p/> It uses the masks in order until one of them succedes
073         * or all fail. <p/>
074         *
075         * @param masks array of masks to use for parsing the string
076         * @param sDate string to parse for a date.
077         * @return the Date represented by the given string using one of the given masks. It returns <b>null</b> if it was
078         *         not possible to parse the the string with any of the masks.
079         */
080        private static Date parseUsingMask(String[] masks, String sDate) {
081            sDate = (sDate != null) ? sDate.trim() : null;
082            ParsePosition pp;
083            Date d = null;
084            if (sDate != null) {
085                for (int i = 0; d == null && i < masks.length; i++) {
086                    DateFormat df = new SimpleDateFormat(masks[i], Locale.US);
087                    df.setLenient(true);
088                    pp = new ParsePosition(0);
089                    d = df.parse(sDate, pp);
090                    if (pp.getIndex() != sDate.length()) {
091                        d = null;
092                    }
093                }
094            }
095            return d;
096        }
097    
098        private static final TimeZone UTC = getTimeZone("UTC");
099    
100        private static DateFormat getISO8601DateFormat() {
101            DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'");
102            dateFormat.setTimeZone(UTC);
103            return dateFormat;
104        }
105    
106        private static DateFormat getSpecificDateFormat(String format) {
107            DateFormat dateFormat = new SimpleDateFormat(format);
108            dateFormat.setTimeZone(UTC);
109            return dateFormat;
110        }
111    
112        public static TimeZone getTimeZone(String tzId) {
113            if (tzId == null) {
114                throw new IllegalArgumentException("Invalid TimeZone: " + tzId);
115            }
116            TimeZone tz = TimeZone.getTimeZone(tzId);
117            if (!tz.getID().equals(tzId)) {
118                throw new IllegalArgumentException("Invalid TimeZone: " + tzId);
119            }
120            return tz;
121        }
122    
123        public static Date parseDateUTC(String s) throws ParseException {
124            return getISO8601DateFormat().parse(s);
125        }
126    
127        public static String formatDateUTC(Date d) throws Exception {
128            return (d != null) ? getISO8601DateFormat().format(d) : "NULL";
129        }
130    
131        public static String formatDateCustom(Date d, String format) throws Exception {
132            return (d != null) ? getSpecificDateFormat(format).format(d) : "NULL";
133        }
134    
135        public static String formatDateUTC(Calendar c) throws Exception {
136            return (c != null) ? formatDateUTC(c.getTime()) : "NULL";
137        }
138    
139        /**
140         * This function returns number of hour in a day when given a Calendar with appropriate TZ. It consider DST to find
141         * the number of hours. Generally it is 24. At some tZ, in one day of a year it is 23 and another day it is 25
142         *
143         * @param cal: The date for which the number of hours is requested
144         * @return number of hour in that day.
145         */
146        public static int hoursInDay(Calendar cal) {
147            Calendar localCal = new GregorianCalendar(cal.getTimeZone());
148            localCal.set(Calendar.MILLISECOND, 0);
149            localCal.set(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH), 0, 30, 0);
150            localCal.add(Calendar.HOUR_OF_DAY, 24);
151            switch (localCal.get(Calendar.HOUR_OF_DAY)) {
152                case 1:
153                    return 23;
154                case 23:
155                    return 25;
156                default: // Case 0
157                    return 24;
158            }
159        }
160    
161        /**
162         * Determine whether a specific date is on DST change day
163         *
164         * @param cal: Date to know if it is DST change day. Appropriate TZ is specified
165         * @return true , if it DST change date otherwise false
166         */
167        public static boolean isDSTChangeDay(Calendar cal) {
168            return hoursInDay(cal) != 24;
169        }
170    
171        /**
172         * Move the any date-time to the end of the duration. If endOfFlag == day, move the date to the end of day (24:00 on
173         * the same day or 00:00 on the next day) If endOf Flag = month. move the date to then end of current month
174         * Otherwise do nothing
175         *
176         * @param cal : Date-time needs to be moved to the end
177         * @param endOfFlag : day (for end of day) or month (for end of month) or empty
178         */
179        public static void moveToEnd(Calendar cal, TimeUnit endOfFlag) {
180            // TODO: Both logic needs to be checked
181            if (endOfFlag == TimeUnit.END_OF_DAY) { // 24:00:00
182                cal.add(Calendar.DAY_OF_MONTH, 1);
183                // cal.set(Calendar.HOUR_OF_DAY, cal
184                // .getActualMaximum(Calendar.HOUR_OF_DAY) + 1);// TODO:
185                cal.set(Calendar.HOUR_OF_DAY, 0);
186                cal.set(Calendar.MINUTE, 0);
187                cal.set(Calendar.SECOND, 0);
188            }
189            else {
190                if (endOfFlag == TimeUnit.END_OF_MONTH) {
191                    cal.add(Calendar.MONTH, 1);
192                    cal.set(Calendar.DAY_OF_MONTH, 1);
193                    cal.set(Calendar.HOUR_OF_DAY, 0);
194                    cal.set(Calendar.MINUTE, 0);
195                    cal.set(Calendar.SECOND, 0);
196                }
197            }
198        }
199    
200        /**
201         * Create a Calendar instance using the specified date and Time zone
202         * @param dateString
203         * @param tz : TimeZone
204         * @return appropriate Calendar object
205         * @throws Exception
206         */
207        public static Calendar getCalendar(String dateString, TimeZone tz) throws Exception {
208            Date date = DateUtils.parseDateUTC(dateString);
209            Calendar calDate = Calendar.getInstance();
210            calDate.setTime(date);
211            calDate.setTimeZone(tz);
212            return calDate;
213        }
214    
215        /**
216         * Create a Calendar instance for UTC time zone using the specified date.
217         * @param dateString
218         * @return appropriate Calendar object
219         * @throws Exception
220         */
221        public static Calendar getCalendar(String dateString) throws Exception {
222            return getCalendar(dateString, DateUtils.getTimeZone("UTC"));
223        }
224    
225        /**
226         * Convert java.sql.Timestamp to java.util.Date
227         *
228         * @param timestamp java.sql.Timestamp
229         * @return java.util.Date
230         */
231        public static java.util.Date toDate(java.sql.Timestamp timestamp) {
232            if (timestamp != null) {
233                long milliseconds = timestamp.getTime();
234                return new java.util.Date(milliseconds);
235            }
236            return null;
237        }
238    
239        /**
240         * Convert java.util.Date to java.sql.Timestamp
241         *
242         * @param d java.util.Date
243         * @return java.sql.Timestamp
244         */
245        public static Timestamp convertDateToTimestamp(Date d) {
246            if (d != null) {
247                return new Timestamp(d.getTime());
248            }
249            return null;
250        }
251    
252        /**
253         * Return the UTC date and time in W3C format down to second
254         * (yyyy-MM-ddTHH:mmZ). i.e.: 1997-07-16T19:20:30Z
255         *
256         * @return the formatted time string.
257         */
258        public static String convertDateToString(Date date) {
259            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'");
260            sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
261            return sdf.format(date);
262        }
263    
264        /**
265         * Return the UTC date and time in W3C format down to second
266         * (yyyy-MM-ddTHH:mmZ). i.e.: 1997-07-16T19:20Z The input date is a
267         * long (Unix Time Stamp)
268         *
269         * @return the formatted time string.
270         */
271        public static String convertDateToString(long timeStamp) {
272            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'");
273            sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
274            return sdf.format(new Date(timeStamp));
275        }
276    
277    }