001: /*
002: * $Id: DateTimeUtils.java,v 1.11 2005/12/20 18:32:42 ahimanikya Exp $
003: * =======================================================================
004: * Copyright (c) 2002-2006 Axion Development Team. All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions
008: * are met:
009: *
010: * 1. Redistributions of source code must retain the above
011: * copyright notice, this list of conditions and the following
012: * disclaimer.
013: *
014: * 2. Redistributions in binary form must reproduce the above copyright
015: * notice, this list of conditions and the following disclaimer in
016: * the documentation and/or other materials provided with the
017: * distribution.
018: *
019: * 3. The names "Tigris", "Axion", nor the names of its contributors may
020: * not be used to endorse or promote products derived from this
021: * software without specific prior written permission.
022: *
023: * 4. Products derived from this software may not be called "Axion", nor
024: * may "Tigris" or "Axion" appear in their names without specific prior
025: * written permission.
026: *
027: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
028: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
029: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
030: * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
031: * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
032: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
033: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
034: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
035: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
036: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
037: * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
038: * =======================================================================
039: */
040: package org.axiondb.util;
041:
042: import java.sql.Timestamp;
043: import java.text.DateFormatSymbols;
044: import java.text.ParsePosition;
045: import java.text.SimpleDateFormat;
046: import java.util.Calendar;
047: import java.util.Date;
048: import java.util.Locale;
049: import java.util.TimeZone;
050: import java.util.regex.Matcher;
051: import java.util.regex.Pattern;
052:
053: import org.apache.commons.logging.Log;
054: import org.apache.commons.logging.LogFactory;
055: import org.axiondb.AxionException;
056: import org.axiondb.parser.AxionDateTimeFormatParser;
057: import org.axiondb.types.TimestampType;
058:
059: /**
060: * Methods to support manipulation and conversion of date, time and timestamp
061: * values.
062: *
063: * @version $Revision: 1.11 $ $Date: 2005/12/20 18:32:42 $
064: * @author Jonathan Giron
065: * @author Ahimanikya Satapathy
066: */
067: public final class DateTimeUtils {
068:
069: /** Constant representing millisecond time interval */
070: public static final int MILLISECOND = 1;
071:
072: /** Constant representing second time interval */
073: public static final int SECOND = 2;
074:
075: /** Constant representing minute time interval */
076: public static final int MINUTE = 4;
077:
078: /** Constant representing hour time interval */
079: public static final int HOUR = 8;
080:
081: /** Constant representing day interval */
082: public static final int DAY = 16;
083:
084: /** Constant representing week interval */
085: public static final int WEEK = 32;
086:
087: /** Constant representing month interval */
088: public static final int MONTH = 64;
089:
090: /** Constant representing quarter interval */
091: public static final int QUARTER = 128;
092:
093: /** Constant representing year interval */
094: public static final int YEAR = 256;
095:
096: /**
097: * Creates a String representation of the given Timestamp object, using
098: * the given format string as a template and the current Axion time zone.
099: *
100: * @param timestamp Timestamp object to be converted to a String representation
101: * @param formatStr template describing the desired format for the String
102: * representation of <code>timestamp</code>
103: * @return formatted String representation of <code>timestamp</code>
104: * @throws AxionException if format is invalid
105: * @see #convertToChar(Timestamp, String, java.util.TimeZone)
106: */
107: public static String convertToChar(Timestamp timestamp,
108: String formatStr) throws AxionException {
109: return convertToChar(timestamp, formatStr, TimestampType
110: .getTimeZone(), Locale.getDefault());
111: }
112:
113: /**
114: * Creates a String representation of the given Timestamp object, using
115: * the given format string as a template, the given time zone, and the
116: * current default Locale.
117: *
118: * @param timestamp Timestamp object to be converted to a String representation
119: * @param formatStr template describing the desired format for the String
120: * representation of <code>timestamp</code>
121: * @param timezone TimeZone to use in interpreting the value of
122: * <code>timestamp</code> to the desired String representation
123: * @return formatted String representation of <code>timestamp</code>
124: * @throws AxionException if format is invalid
125: * @see #convertToChar(Timestamp, String)
126: * @see #convertToChar(Timestamp, String, TimeZone, Locale)
127: */
128: public static String convertToChar(Timestamp timestamp,
129: String formatStr, TimeZone timezone) throws AxionException {
130: return convertToChar(timestamp, formatStr, timezone, Locale
131: .getDefault());
132: }
133:
134: /**
135: * Creates a String representation of the given Timestamp object, using
136: * the given format string as a template, the given time zone, and the
137: * given Locale.
138: *
139: * @param timestamp Timestamp object to be converted to a String representation
140: * @param formatStr template describing the desired format for the String
141: * representation of <code>timestamp</code>
142: * @param timezone TimeZone to use in interpreting the value of
143: * <code>timestamp</code> to the desired String representation
144: * @param locale Locale to use in resolving date components
145: * @return formatted String representation of <code>timestamp</code>
146: * @throws AxionException if format is invalid
147: * @see #convertToChar(Timestamp, String)
148: * @see #convertToChar(Timestamp, String, TimeZone)
149: */
150: public static String convertToChar(Timestamp timestamp,
151: String formatStr, TimeZone timezone, Locale locale)
152: throws AxionException {
153: try {
154: String javaFormat = processFormatString(formatStr);
155:
156: SimpleDateFormat sdf = new SimpleDateFormat(javaFormat,
157: locale);
158: sdf.setCalendar(Calendar.getInstance(timezone));
159: sdf.setLenient(false);
160:
161: return sdf.format(timestamp).toUpperCase();
162: } catch (IllegalArgumentException e) {
163: throw new AxionException(e.getLocalizedMessage());
164: }
165: }
166:
167: /**
168: * Creates a String representation of the given Timestamp object, using
169: * the given format string as a template and the current Axion time zone.
170: *
171: * @param dateStr String representation of Timestamp to be returned
172: * @param formatStr template describing the format of <code>dateStr</code>
173: * @return Timestamp containing date represented by <code>dateStr</code>
174: * @throws AxionException if format is invalid
175: * @see #convertToTimestamp(String, String, java.util.TimeZone)
176: * @see #convertToTimestamp(String, String, java.util.TimeZone, java.util.Locale)
177: */
178: public static Timestamp convertToTimestamp(String dateStr,
179: String formatStr) throws AxionException {
180: return convertToTimestamp(dateStr, formatStr, TimestampType
181: .getTimeZone(), Locale.getDefault());
182: }
183:
184: /**
185: * Creates a String representation of the given Timestamp object, using
186: * the given format string as a template and the given time zone.
187: *
188: * @param dateStr String representation of Timestamp to be returned
189: * @param formatStr template describing the format of <code>dateStr</code>
190: * @param timezone TimeZone to use in interpreting the value of <code>dateStr</code>
191: * @return Timestamp containing date represented by <code>dateStr</code>
192: * @throws AxionException if format is invalid
193: * @see #convertToTimestamp(String, String)
194: * @see #convertToTimestamp(String, String, java.util.TimeZone, java.util.Locale)
195: *
196: */
197: public static Timestamp convertToTimestamp(String dateStr,
198: String formatStr, TimeZone timezone) throws AxionException {
199: return convertToTimestamp(dateStr, formatStr, timezone, Locale
200: .getDefault());
201: }
202:
203: /**
204: * Creates a String representation of the given Timestamp object, using
205: * the given format string as a template, the given time zone, and the
206: * given Locale
207: *
208: * @param dateStr String representation of Timestamp to be returned
209: * @param formatStr template describing the format of <code>dateStr</code>
210: * @param timezone TimeZone to use in interpreting the value of <code>dateStr</code>
211: * @param locale Locale to use in resolving date components
212: * @return Timestamp containing date represented by <code>dateStr</code>
213: * @throws AxionException if format is invalid
214: * @see #convertToTimestamp(String, String)
215: * @see #convertToTimestamp(String, String, java.util.TimeZone)
216: */
217: public static Timestamp convertToTimestamp(String dateStr,
218: String formatStr, TimeZone timezone, Locale locale)
219: throws AxionException {
220: try {
221: String javaFormat = DateTimeUtils
222: .processFormatString(formatStr);
223:
224: SimpleDateFormat sdf = new SimpleDateFormat(javaFormat,
225: locale);
226: sdf.setCalendar(Calendar.getInstance(timezone, locale));
227: sdf.setLenient(false);
228:
229: ParsePosition pos = new ParsePosition(0);
230: Date parsedDate = sdf.parse(dateStr, pos);
231: if (pos.getIndex() == dateStr.length()) {
232: String upperCaseFormat = formatStr.toUpperCase();
233: if (upperCaseFormat.indexOf("YYYY") != -1) {
234: assertDateStrMatchesFourYearFormatStr(dateStr,
235: javaFormat);
236: } else if (upperCaseFormat.indexOf("YY") != -1) {
237: assertDateStrMatchesTwoYearFormatStr(dateStr,
238: javaFormat);
239: }
240: return (Timestamp) TIMESTAMP_TYPE.convert(parsedDate);
241: } else if (parsedDate == null) {
242: throw new AxionException(
243: "Date string does not match format: "
244: + formatStr);
245: } else {
246: throw new AxionException(
247: "Extraneous characters detected in " + dateStr);
248: }
249: } catch (IllegalArgumentException e) {
250: throw new AxionException(e.getLocalizedMessage(), 22007);
251: }
252: }
253:
254: /**
255: * Tests for a two-digit year pattern in the given format string, and tests if the given
256: * date string has the required two-digit year.
257: *
258: * @param dateStr
259: * @param formatStr
260: * @throws AxionException
261: */
262: private static void assertDateStrMatchesTwoYearFormatStr(
263: String dateStr, String formatStr) throws AxionException {
264: // This is a horrible workaround for the limitations of SimpleDateFormat with respect
265: // to parsing two-digit years. SimpleDateFormat is permissive with respect to reading
266: // in four-digit years when a two-digit year pattern is specified, but we need to
267: // raise an exception in that situation.
268: Matcher matcher = YEAR_PATTERN_1.matcher(formatStr);
269: if (matcher.lookingAt()) {
270: String nonYearMatch = matcher.group(1);
271: if (nonYearMatch != null) {
272: char charFollowingYear = nonYearMatch.charAt(0);
273: switch (charFollowingYear) {
274: case '-':
275: case ' ':
276: case '/':
277: case '.':
278: int charFollowingYearPos = matcher.start(1);
279: if (charFollowingYear != dateStr
280: .charAt(charFollowingYearPos)) {
281: throw new AxionException("Date string, "
282: + dateStr + ", does not match format: "
283: + formatStr);
284: }
285: return;
286:
287: default:
288: if (dateStr.length() != formatStr.length()) {
289: throw new AxionException("Date string, "
290: + dateStr + ", does not match format: "
291: + formatStr);
292: }
293: return;
294: }
295: }
296: }
297:
298: matcher = YEAR_PATTERN_2.matcher(formatStr);
299: if (matcher.lookingAt()) {
300: int startOfYearPos = matcher.start(1);
301: if (dateStr.substring(startOfYearPos).length() != 2) {
302: throw new AxionException("Date string, " + dateStr
303: + ", does not match format: " + formatStr);
304: }
305: return;
306: }
307: }
308:
309: /**
310: * Tests for a two-digit year pattern in the given format string, and tests if the given
311: * date string has the required two-digit year.
312: *
313: * @param dateStr
314: * @param formatStr
315: * @throws AxionException
316: */
317: private static void assertDateStrMatchesFourYearFormatStr(
318: String dateStr, String formatStr) throws AxionException {
319: // This is a horrible workaround for the limitations of SimpleDateFormat with respect
320: // to parsing two-digit years. SimpleDateFormat is permissive with respect to reading
321: // in four-digit years when a two-digit year pattern is specified, but we need to
322: // raise an exception in that situation.
323: Matcher matcher = YEAR_4_PATTERN_1.matcher(formatStr);
324: if (matcher.lookingAt()) {
325: String nonYearMatch = matcher.group(1);
326: if (nonYearMatch != null) {
327: char charFollowingYear = nonYearMatch.charAt(0);
328: switch (charFollowingYear) {
329: case '-':
330: case ' ':
331: case '/':
332: case '.':
333: int charFollowingYearPos = matcher.start(1);
334: if (charFollowingYear != dateStr
335: .charAt(charFollowingYearPos)) {
336: throw new AxionException("Date string, "
337: + dateStr + ", does not match format: "
338: + formatStr);
339: }
340: return;
341:
342: default:
343: int dateLength = dateStr.length();
344: // Adjust length of date string to account for fact that T in formatStr is escaped by single quotes.
345: if (formatStr.indexOf("T") != -1) {
346: dateLength += 2;
347: }
348: if (dateLength != formatStr.length()) {
349: throw new AxionException("Date string, "
350: + dateStr + ", does not match format: "
351: + formatStr);
352: }
353: return;
354: }
355: }
356: }
357:
358: matcher = YEAR_4_PATTERN_2.matcher(formatStr);
359: if (matcher.lookingAt()) {
360: int startOfYearPos = matcher.start(1);
361: if (dateStr.substring(startOfYearPos).length() != 4) {
362: throw new AxionException("Date string, " + dateStr
363: + ", does not match format: " + formatStr);
364: }
365: return;
366: }
367: }
368:
369: /**
370: * Extracts the specified date/time element from the given Timestamp,
371: * using the default Locale.
372: *
373: * @param t timestamp from which date/time element will be extracted
374: * @param partIdent date part to extract, e.g., 'yyyy', 'mm', etc.
375: *
376: * @return String representation of extracted date/time element
377: * @throws AxionException if error occurs during extraction
378: * @see #getDatePart(Timestamp, String, Locale)
379: */
380: public static String getDatePart(Timestamp t, String partIdent)
381: throws AxionException {
382: return getDatePart(t, partIdent, Locale.getDefault());
383: }
384:
385: /**
386: * Extracts the specified date/time element from the given Timestamp,
387: * using the given Locale.
388: *
389: * @param t timestamp from which date/time element will be extracted
390: * @param partIdent date part to extract, e.g., 'yyyy', 'mm', etc.
391: * @param locale Locale to use in resolving date components
392: *
393: * @return String representation of extracted date/time element
394: * @throws AxionException if error occurs during extraction
395: * @see #getDatePart(Timestamp, String)
396: */
397: public static String getDatePart(Timestamp t, String partIdent,
398: Locale locale) throws AxionException {
399: if ("q".equals(partIdent)) {
400: int month = Integer.parseInt(DateTimeUtils.convertToChar(t,
401: "mm"));
402: return Integer.toString((month + 2) / 3);
403: } else if ("dow".equals(partIdent)) {
404: String dayOfWeek = DateTimeUtils.convertToChar(t, "dy")
405: .toUpperCase();
406: String[] shortDays = new DateFormatSymbols(locale)
407: .getShortWeekdays();
408: for (int i = Calendar.SUNDAY; i <= Calendar.SATURDAY; i++) {
409: if (shortDays[i].equalsIgnoreCase(dayOfWeek)) {
410: return String.valueOf(i);
411: }
412: }
413:
414: throw new AxionException("Unrecognized day of week: "
415: + dayOfWeek);
416: } else {
417: return DateTimeUtils.convertToChar(t, partIdent);
418: }
419: }
420:
421: /**
422: * Converts the given Axion date format string into a format usable with
423: * java.util.SimpleDateFormat.
424: *
425: * @param rawFormat format string using Axion date/time mnemonics
426: * @return format string using SimpleDateFormat date/time mnemonics
427: * @throws AxionException if <code>rawFormat</code> could not be successfully
428: * converted to the appropriate SimpleDateFormat version
429: */
430: static String processFormatString(String rawFormat)
431: throws AxionException {
432: AxionDateTimeFormatParser parser = new AxionDateTimeFormatParser();
433: String cookedFormat = parser
434: .parseDateTimeFormatToJava(rawFormat);
435:
436: _log.debug("raw format: " + rawFormat + "; cooked format: "
437: + cookedFormat);
438: return cookedFormat;
439: }
440:
441: /**
442: * Converts the given value, which represents a date or time interval,
443: * to its corresponding constant value.
444: *
445: * @param value String representation of date or time interval
446: * @return constant value corresponding to <code>value</code>
447: * @throws AxionException if <code>value</code> does not have a
448: * corresponding constant.
449: */
450: public static int labelToCode(String value) throws AxionException {
451: value = value != null ? value.toUpperCase() : null;
452: ;
453:
454: if ("DAY".equals(value)) {
455: return DAY;
456: } else if ("MONTH".equals(value)) {
457: return MONTH;
458: } else if ("YEAR".equals(value)) {
459: return YEAR;
460: } else if ("HOUR".equals(value)) {
461: return HOUR;
462: } else if ("MINUTE".equals(value)) {
463: return MINUTE;
464: } else if ("SECOND".equals(value)) {
465: return SECOND;
466: } else if ("WEEK".equals(value)) {
467: return WEEK;
468: } else if ("QUARTER".equals(value)) {
469: return QUARTER;
470: } else if ("MILLISECOND".equals(value)) {
471: return MILLISECOND;
472: } else {
473: throw new AxionException(22006);
474: }
475: }
476:
477: private static final TimestampType TIMESTAMP_TYPE = new TimestampType();
478:
479: /**
480: * @param partCode
481: * @return
482: */
483: public static String getPartMnemonicFor(String partString)
484: throws AxionException {
485: partString = partString != null ? partString.toUpperCase()
486: : null;
487: if ("WEEKDAY".equals(partString)) {
488: return "dow";
489: } else if ("WEEKDAY3".equals(partString)) {
490: return "dy";
491: } else if ("WEEKDAYFULL".equals(partString)) {
492: return "day";
493: } else if ("DAY".equals(partString)) {
494: return "dd";
495: } else if ("MONTH".equals(partString)) {
496: return "mm";
497: } else if ("MONTH3".equals(partString)) {
498: return "mon";
499: } else if ("MONTHFULL".equals(partString)) {
500: return "month";
501: } else if ("YEAR".equals(partString)) {
502: return "yyyy";
503: } else if ("YEAR2".equals(partString)) {
504: return "yy";
505: } else if ("HOUR".equals(partString)) {
506: return "h";
507: } else if ("HOUR12".equals(partString)) {
508: return "hh12";
509: } else if ("HOUR24".equals(partString)) {
510: return "hh24";
511: } else if ("MINUTE".equals(partString)) {
512: return "mi";
513: } else if ("SECOND".equals(partString)) {
514: return "ss";
515: } else if ("WEEK".equals(partString)) {
516: return "w";
517: } else if ("QUARTER".equals(partString)) {
518: return "q";
519: } else if ("MILLISECOND".equals(partString)) {
520: return "ff";
521: } else if ("AMPM".equals(partString)) {
522: return "am";
523: } else {
524: throw new AxionException(22006);
525: }
526: }
527:
528: private static Pattern YEAR_PATTERN_1 = Pattern.compile(
529: "^[^Y]*YY([^Y]).*$", Pattern.CASE_INSENSITIVE);
530: private static Pattern YEAR_PATTERN_2 = Pattern.compile(
531: "^.*[^Y](YY)$", Pattern.CASE_INSENSITIVE);
532:
533: private static Pattern YEAR_4_PATTERN_1 = Pattern.compile(
534: "^[^Y]*YYYY([^Y]).*$", Pattern.CASE_INSENSITIVE);
535: private static Pattern YEAR_4_PATTERN_2 = Pattern.compile(
536: "^.*[^Y](YYYY)$", Pattern.CASE_INSENSITIVE);
537:
538: private static Log _log = LogFactory.getLog(DateTimeUtils.class);
539: }
|