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