This project has retired. For details please refer to its
Attic page.
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.TimeZone;
029 import java.util.regex.Matcher;
030 import java.util.regex.Pattern;
031
032 import org.apache.hadoop.conf.Configuration;
033 import org.apache.oozie.coord.TimeUnit;
034
035 /**
036 * Date utility classes to parse and format datetimes in Oozie expected datetime formats.
037 */
038 public class DateUtils {
039
040 private static final Pattern GMT_OFFSET_COLON_PATTERN = Pattern.compile("^GMT(\\-|\\+)(\\d{2})(\\d{2})$");
041
042 public static final TimeZone UTC = getTimeZone("UTC");
043
044 public static final String ISO8601_UTC_MASK = "yyyy-MM-dd'T'HH:mm'Z'";
045 private static final String ISO8601_TZ_MASK_WITHOUT_OFFSET = "yyyy-MM-dd'T'HH:mm";
046
047 private static String ACTIVE_MASK = ISO8601_UTC_MASK;
048 private static TimeZone ACTIVE_TIMEZONE = UTC;
049
050 public static final String OOZIE_PROCESSING_TIMEZONE_KEY = "oozie.processing.timezone";
051
052 public static final String OOZIE_PROCESSING_TIMEZONE_DEFAULT = "UTC";
053
054 private static boolean OOZIE_IN_UTC = true;
055
056 private static final Pattern VALID_TIMEZONE_PATTERN = Pattern.compile("^UTC$|^GMT(\\+|\\-)\\d{4}$");
057
058 /**
059 * Configures the Datetime parsing with Oozie processing timezone.
060 * <p/>
061 * The {@link #OOZIE_PROCESSING_TIMEZONE_KEY} property is read and set as the Oozie processing timezone.
062 * Valid values for this property are <code>UTC</code> and <code>GMT(+/-)####</code>
063 *
064 * @param conf Oozie server configuration.
065 */
066 public static void setConf(Configuration conf) {
067 String tz = conf.get(OOZIE_PROCESSING_TIMEZONE_KEY, OOZIE_PROCESSING_TIMEZONE_DEFAULT);
068 if (!VALID_TIMEZONE_PATTERN.matcher(tz).matches()) {
069 throw new RuntimeException("Invalid Oozie timezone, it must be 'UTC' or 'GMT(+/-)####");
070 }
071 ACTIVE_TIMEZONE = TimeZone.getTimeZone(tz);
072 OOZIE_IN_UTC = ACTIVE_TIMEZONE.equals(UTC);
073 ACTIVE_MASK = (OOZIE_IN_UTC) ? ISO8601_UTC_MASK : ISO8601_TZ_MASK_WITHOUT_OFFSET + tz.substring(3);
074 }
075
076 /**
077 * Returns Oozie processing timezone.
078 *
079 * @return Oozie processing timezone. The returned timezone is <code>UTC</code> or a <code>GMT(+/-)####</code>
080 * timezone.
081 */
082 public static TimeZone getOozieProcessingTimeZone() {
083 return ACTIVE_TIMEZONE;
084 }
085
086 /**
087 * Returns Oozie processing datetime mask.
088 * <p/>
089 * This mask is an ISO8601 datetime mask for the Oozie processing timezone.
090 *
091 * @return Oozie processing datetime mask.
092 */
093 public static String getOozieTimeMask() {
094 return ACTIVE_MASK;
095 }
096
097 private static DateFormat getISO8601DateFormat(TimeZone tz, String mask) {
098 DateFormat dateFormat = new SimpleDateFormat(mask);
099 // Stricter parsing to prevent dates such as 2011-12-50T01:00Z (December 50th) from matching
100 dateFormat.setLenient(false);
101 dateFormat.setTimeZone(tz);
102 return dateFormat;
103 }
104
105 private static DateFormat getSpecificDateFormat(String format) {
106 DateFormat dateFormat = new SimpleDateFormat(format);
107 dateFormat.setTimeZone(ACTIVE_TIMEZONE);
108 return dateFormat;
109 }
110
111 /**
112 * {@link TimeZone#getTimeZone(java.lang.String)} takes the timezone ID as an argument; for invalid IDs it returns the
113 * <code>GMT</code> TimeZone. A timezone ID formatted like <code>GMT-####</code> is not a valid ID, however, it will actually
114 * map this to the <code>GMT-##:##</code> TimeZone, instead of returning the <code>GMT</code> TimeZone. We check (later)
115 * check that a timezone ID is valid by calling {@link TimeZone#getTimeZone(java.lang.String)} and seeing if the returned
116 * TimeZone ID is equal to the original; because we want to allow <code>GMT-####</code>, while still disallowing actual
117 * invalid IDs, we have to manually replace <code>GMT-####</code> with <code>GMT-##:##</code> first.
118 *
119 * @param tzId The timezone ID
120 * @return If tzId matches <code>GMT-####</code>, then we return <code>GMT-##:##</code>; otherwise, we return tzId unaltered
121 */
122 private static String handleGMTOffsetTZNames(String tzId) {
123 Matcher m = GMT_OFFSET_COLON_PATTERN.matcher(tzId);
124 if (m.matches() && m.groupCount() == 3) {
125 tzId = "GMT" + m.group(1) + m.group(2) + ":" + m.group(3);
126 }
127 return tzId;
128 }
129
130 /**
131 * Returns the {@link TimeZone} for the given timezone ID.
132 *
133 * @param tzId timezone ID.
134 * @return the {@link TimeZone} for the given timezone ID.
135 */
136 public static TimeZone getTimeZone(String tzId) {
137 if (tzId == null) {
138 throw new IllegalArgumentException("Invalid TimeZone: " + tzId);
139 }
140 tzId = handleGMTOffsetTZNames(tzId); // account for GMT-####
141 TimeZone tz = TimeZone.getTimeZone(tzId);
142 // If these are not equal, it means that the tzId is not valid (invalid tzId's return GMT)
143 if (!tz.getID().equals(tzId)) {
144 throw new IllegalArgumentException("Invalid TimeZone: " + tzId);
145 }
146 return tz;
147 }
148
149 /**
150 * Parses a datetime in ISO8601 format in UTC timezone
151 *
152 * @param s string with the datetime to parse.
153 * @return the corresponding {@link Date} instance for the parsed date.
154 * @throws ParseException thrown if the given string was not an ISO8601 UTC value.
155 */
156 public static Date parseDateUTC(String s) throws ParseException {
157 return getISO8601DateFormat(UTC, ISO8601_UTC_MASK).parse(s);
158 }
159
160 /**
161 * Parses a datetime in ISO8601 format in the Oozie processing timezone.
162 *
163 * @param s string with the datetime to parse.
164 * @return the corresponding {@link Date} instance for the parsed date.
165 * @throws ParseException thrown if the given string was not an ISO8601 value for the Oozie processing timezon.
166 */
167 public static Date parseDateOozieTZ(String s) throws ParseException {
168 s = s.trim();
169 ParsePosition pos = new ParsePosition(0);
170 Date d = getISO8601DateFormat(ACTIVE_TIMEZONE, ACTIVE_MASK).parse(s, pos);
171 if (d == null) {
172 throw new ParseException("Could not parse [" + s + "] using [" + ACTIVE_MASK + "] mask",
173 pos.getErrorIndex());
174 }
175 if (d != null && s.length() > pos.getIndex()) {
176 throw new ParseException("Correct datetime string is followed by invalid characters: " + s, pos.getIndex());
177 }
178 return d;
179 }
180
181 /**
182 * Formats a {@link Date} as a string in ISO8601 format using Oozie processing timezone.
183 *
184 * @param d {@link Date} to format.
185 * @return the ISO8601 string for the given date, <code>NULL</code> if the {@link Date} instance was
186 * <code>NULL</code>
187 */
188 public static String formatDateOozieTZ(Date d) {
189 return (d != null) ? getISO8601DateFormat(ACTIVE_TIMEZONE, ACTIVE_MASK).format(d) : "NULL";
190 }
191
192 /**
193 * Formats a {@link Date} as a string using the specified format mask.
194 * <p/>
195 * The format mask must be a {@link SimpleDateFormat} valid format mask.
196 *
197 * @param d {@link Date} to format.
198 * @return the string for the given date using the specified format mask,
199 * <code>NULL</code> if the {@link Date} instance was <code>NULL</code>
200 */
201 public static String formatDateCustom(Date d, String format) {
202 return (d != null) ? getSpecificDateFormat(format).format(d) : "NULL";
203 }
204
205 /**
206 * Formats a {@link Calendar} as a string in ISO8601 format using Oozie processing timezone.
207 *
208 * @param c {@link Calendar} to format.
209 * @return the ISO8601 string for the given date, <code>NULL</code> if the {@link Calendar} instance was
210 * <code>NULL</code>
211 */
212 public static String formatDateOozieTZ(Calendar c) {
213 return (c != null) ? formatDateOozieTZ(c.getTime()) : "NULL";
214 }
215
216 /**
217 * This function returns number of hour in a day when given a Calendar with appropriate TZ. It consider DST to find
218 * 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
219 *
220 * @param cal: The date for which the number of hours is requested
221 * @return number of hour in that day.
222 */
223 public static int hoursInDay(Calendar cal) {
224 Calendar localCal = new GregorianCalendar(cal.getTimeZone());
225 localCal.set(Calendar.MILLISECOND, 0);
226 localCal.set(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH), 0, 30, 0);
227 localCal.add(Calendar.HOUR_OF_DAY, 24);
228 switch (localCal.get(Calendar.HOUR_OF_DAY)) {
229 case 1:
230 return 23;
231 case 23:
232 return 25;
233 default: // Case 0
234 return 24;
235 }
236 }
237
238 /**
239 * Determine whether a specific date is on DST change day
240 *
241 * @param cal: Date to know if it is DST change day. Appropriate TZ is specified
242 * @return true , if it DST change date otherwise false
243 */
244 public static boolean isDSTChangeDay(Calendar cal) {
245 return hoursInDay(cal) != 24;
246 }
247
248 /**
249 * 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
250 * the same day or 00:00 on the next day) If endOf Flag = month. move the date to then end of current month
251 * Otherwise do nothing
252 *
253 * @param cal : Date-time needs to be moved to the end
254 * @param endOfFlag : day (for end of day) or month (for end of month) or empty
255 */
256 public static void moveToEnd(Calendar cal, TimeUnit endOfFlag) {
257 // TODO: Both logic needs to be checked
258 if (endOfFlag == TimeUnit.END_OF_DAY) { // 24:00:00
259 cal.add(Calendar.DAY_OF_MONTH, 1);
260 // cal.set(Calendar.HOUR_OF_DAY, cal
261 // .getActualMaximum(Calendar.HOUR_OF_DAY) + 1);// TODO:
262 cal.set(Calendar.HOUR_OF_DAY, 0);
263 cal.set(Calendar.MINUTE, 0);
264 cal.set(Calendar.SECOND, 0);
265 }
266 else {
267 if (endOfFlag == TimeUnit.END_OF_MONTH) {
268 cal.add(Calendar.MONTH, 1);
269 cal.set(Calendar.DAY_OF_MONTH, 1);
270 cal.set(Calendar.HOUR_OF_DAY, 0);
271 cal.set(Calendar.MINUTE, 0);
272 cal.set(Calendar.SECOND, 0);
273 }
274 }
275 }
276
277 /**
278 * Create a Calendar instance using the specified date and Time zone
279 * @param dateString
280 * @param tz : TimeZone
281 * @return appropriate Calendar object
282 * @throws Exception
283 */
284 public static Calendar getCalendar(String dateString, TimeZone tz) throws Exception {
285 Date date = DateUtils.parseDateOozieTZ(dateString);
286 Calendar calDate = Calendar.getInstance();
287 calDate.setTime(date);
288 calDate.setTimeZone(tz);
289 return calDate;
290 }
291
292 /**
293 * Create a Calendar instance for UTC time zone using the specified date.
294 * @param dateString
295 * @return appropriate Calendar object
296 * @throws Exception
297 */
298 public static Calendar getCalendar(String dateString) throws Exception {
299 return getCalendar(dateString, ACTIVE_TIMEZONE);
300 }
301
302 /**
303 * Convert java.sql.Timestamp to java.util.Date
304 *
305 * @param timestamp java.sql.Timestamp
306 * @return java.util.Date
307 */
308 public static java.util.Date toDate(java.sql.Timestamp timestamp) {
309 if (timestamp != null) {
310 long milliseconds = timestamp.getTime();
311 return new java.util.Date(milliseconds);
312 }
313 return null;
314 }
315
316 /**
317 * Convert java.util.Date to java.sql.Timestamp
318 *
319 * @param d java.util.Date
320 * @return java.sql.Timestamp
321 */
322 public static Timestamp convertDateToTimestamp(Date d) {
323 if (d != null) {
324 return new Timestamp(d.getTime());
325 }
326 return null;
327 }
328
329 }