001: /*
002: * ====================================================================
003: * JAFFA - Java Application Framework For All
004: *
005: * Copyright (C) 2002 JAFFA Development Group
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this library; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020: *
021: * Redistribution and use of this software and associated documentation ("Software"),
022: * with or without modification, are permitted provided that the following conditions are met:
023: * 1. Redistributions of source code must retain copyright statements and notices.
024: * Redistributions must also contain a copy of this document.
025: * 2. Redistributions in binary form must reproduce the above copyright notice,
026: * this list of conditions and the following disclaimer in the documentation
027: * and/or other materials provided with the distribution.
028: * 3. The name "JAFFA" must not be used to endorse or promote products derived from
029: * this Software without prior written permission. For written permission,
030: * please contact mail to: jaffagroup@yahoo.com.
031: * 4. Products derived from this Software may not be called "JAFFA" nor may "JAFFA"
032: * appear in their names without prior written permission.
033: * 5. Due credit should be given to the JAFFA Project (http://jaffa.sourceforge.net).
034: *
035: * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
036: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
037: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
038: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
039: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
040: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
041: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
042: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
043: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
044: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
045: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
046: * SUCH DAMAGE.
047: * ====================================================================
048: */
049:
050: // NOTES: The field m_calendar can probably be made STATIC. This would involve use of the synchronizing keyword... lot of overhead... ignore for the time being !!!
051: package org.jaffa.datatypes;
052:
053: import java.io.Serializable;
054: import java.util.Date;
055: import java.util.Calendar;
056: import java.util.GregorianCalendar; //import java.sql.Date;
057: import java.sql.Time;
058: import java.sql.Timestamp;
059: import java.text.*;
060: import org.jaffa.metadata.*;
061: import org.jaffa.presentation.portlet.session.LocaleContext;
062: import org.jaffa.util.LocaleHelper;
063:
064: /**
065: * This class is used to hold Date data. Instances of this class are immutable.
066: */
067: class DateBase implements Cloneable, Comparable, Serializable {
068:
069: // *** PRIVATE FIELDS ***
070: // NOTE: keep the clone() method in sync for all the fields added/removed
071: private java.util.Date m_time = null; // will always exist
072: private Calendar m_calendar = null; //to be created as required
073:
074: // *** CONSTRUCTORS ***
075:
076: /** Creates an instance with the current datetime.
077: */
078: DateBase() {
079: m_time = new java.util.Date();
080: }
081:
082: /** Creates an instance initialized to the input value.
083: */
084: DateBase(long timeInMillis) {
085: m_time = new java.util.Date(timeInMillis);
086: }
087:
088: /** Creates an instance initialized to the input value.
089: */
090: DateBase(java.util.Date utilDate) {
091: this (utilDate.getTime());
092: }
093:
094: /** Creates an instance initialized to the input value.
095: */
096: DateBase(Calendar calendar) {
097: this (calendar.getTime());
098: m_calendar = (Calendar) calendar.clone();
099: }
100:
101: /** Creates an instance initialized to the input value.
102: */
103: DateBase(int year, int month, int day) {
104: this (year, month, day, 0, 0, 0, 0);
105: }
106:
107: /** Creates an instance initialized to the input value.
108: */
109: DateBase(int year, int month, int day, int hourOfDay, int minute,
110: int second, int milli) {
111: m_calendar = new GregorianCalendar(year, month, day, hourOfDay,
112: minute, second);
113: if (milli > 0)
114: m_calendar.set(Calendar.MILLISECOND, milli);
115:
116: // check for validity
117: if (year != m_calendar.get(Calendar.YEAR)
118: || month != m_calendar.get(Calendar.MONTH)
119: || day != m_calendar.get(Calendar.DAY_OF_MONTH)
120: || hourOfDay != m_calendar.get(Calendar.HOUR_OF_DAY)
121: || minute != m_calendar.get(Calendar.MINUTE)
122: || second != m_calendar.get(Calendar.SECOND)
123: || milli != m_calendar.get(Calendar.MILLISECOND))
124: throw new IllegalArgumentException();
125:
126: m_time = new java.util.Date(m_calendar.getTime().getTime());
127: }
128:
129: // - Some standard Methods
130:
131: /** Returns a clone of the object.
132: * @throws CloneNotSupportedException if cloning is not supported. This should never happen.
133: * @return a clone of the object.
134: */
135: public Object clone() throws CloneNotSupportedException {
136: Object obj = super .clone();
137: if (m_time != null)
138: ((DateBase) obj).m_time = (java.util.Date) m_time.clone();
139: if (m_calendar != null)
140: ((DateBase) obj).m_calendar = (Calendar) m_calendar.clone();
141: return obj;
142: }
143:
144: /** Compares this object with another DateBase object.
145: * @param obj the other DateBase object.
146: * @return a negative integer, zero, or a positive integer as this object is less than, equal to, or greater than the specified object.
147: */
148: public int compareTo(Object obj) {
149: DateBase target = (DateBase) obj;
150: return m_time.compareTo(target.m_time);
151: }
152:
153: /** Compares this object with another DateBase object.
154: * Returns a true if both the objects have the same value.
155: * @param obj the other DateBase object.
156: * @return a true if both the objects have the same value.
157: */
158: public boolean equals(Object obj) {
159: if (obj instanceof DateBase) {
160: DateBase target = (DateBase) obj;
161: return m_time.equals(target.m_time);
162: } else {
163: return false;
164: }
165: }
166:
167: /** Returns the hashCode of the value.
168: * @return the hashCode of the value.
169: */
170: public int hashCode() {
171: return m_time.hashCode();
172: }
173:
174: /** Returns the diagnostic information.
175: * @return the diagnostic information.
176: */
177: public String toString() {
178: return getTimestamp().toString();
179: }
180:
181: // - Return standard Java Date Objects
182: long getTime() {
183: return m_time.getTime();
184: }
185:
186: java.util.Date getUtilDate() {
187: return new java.util.Date(getTime());
188: }
189:
190: Calendar getCalendar() {
191: createCalendar();
192: return (Calendar) m_calendar.clone();
193: }
194:
195: java.sql.Date getSQLDate() {
196: return new java.sql.Date(m_time.getTime());
197: }
198:
199: Time getSQLTime() {
200: return new Time(m_time.getTime());
201: }
202:
203: Timestamp getTimestamp() {
204: return new Timestamp(m_time.getTime());
205: }
206:
207: // - Addtional Getters
208: int getYear() {
209: return get(Calendar.YEAR);
210: }
211:
212: int getMonth() {
213: return get(Calendar.MONTH);
214: }
215:
216: int getDay() {
217: return get(Calendar.DAY_OF_MONTH);
218: }
219:
220: int getDayOfWeek() {
221: return get(Calendar.DAY_OF_WEEK);
222: }
223:
224: int getDayOfWeekInMonth() {
225: return get(Calendar.DAY_OF_WEEK_IN_MONTH);
226: }
227:
228: int getDayOfYear() {
229: return get(Calendar.DAY_OF_YEAR);
230: }
231:
232: int getWeekOfMonth() {
233: return get(Calendar.WEEK_OF_MONTH);
234: }
235:
236: int getWeekOfYear() {
237: return get(Calendar.WEEK_OF_YEAR);
238: }
239:
240: int getHour() {
241: return get(Calendar.HOUR);
242: }
243:
244: int getHourOfDay() {
245: return get(Calendar.HOUR_OF_DAY);
246: }
247:
248: int getMinute() {
249: return get(Calendar.MINUTE);
250: }
251:
252: int getSecond() {
253: return get(Calendar.SECOND);
254: }
255:
256: int getMilli() {
257: return get(Calendar.MILLISECOND);
258: }
259:
260: boolean isAM() {
261: return (get(Calendar.AM_PM) == Calendar.AM) ? true : false;
262: }
263:
264: boolean isPM() {
265: return !isAM();
266: }
267:
268: int getFirstDayOfWeek() {
269: createCalendar();
270: return m_calendar.getFirstDayOfWeek();
271: }
272:
273: boolean isAfter(DateBase when) {
274: return (compareTo(when) > 0) ? true : false;
275: }
276:
277: boolean isBefore(DateBase when) {
278: return (compareTo(when) < 0) ? true : false;
279: }
280:
281: /**
282: * @return The Julian day number that begins at noon of
283: * this day
284: * Positive year signifies A.D., negative year B.C.
285: * Remember that the year after 1 B.C. was 1 A.D.
286: *
287: * A convenient reference point is that May 23, 1968 noon
288: * is Julian day 2440000.
289: *
290: * Julian day 0 is a Monday.
291: *
292: * This algorithm is from Press et al., Numerical Recipes
293: * in C, 2nd ed., Cambridge University Press 1992
294: */
295: int getJulian() {
296: int year = getYear();
297: int month = getMonth() + 1;
298: int day = getDay();
299:
300: int jy = year;
301: if (year < 0)
302: jy++;
303: int jm = month;
304: if (month > 2)
305: jm++;
306: else {
307: jy--;
308: jm += 13;
309: }
310:
311: int jul = (int) (java.lang.Math.floor(365.25 * jy)
312: + java.lang.Math.floor(30.6001 * jm) + day + 1720995.0);
313:
314: int IGREG = 15 + 31 * (10 + 12 * 1582);
315: // Gregorian Calendar adopted Oct. 15, 1582
316:
317: if (day + 31 * (month + 12 * year) >= IGREG) {
318: // change over to Gregorian calendar
319: int ja = (int) (0.01 * jy);
320: jul += 2 - ja + (int) (0.25 * ja);
321: }
322: return jul;
323: }
324:
325: // - Date Manipulation methods
326: static DateBase addYear(DateBase aDateBase, int years) {
327: return add(aDateBase, Calendar.YEAR, years);
328: }
329:
330: static DateBase addMonth(DateBase aDateBase, int months) {
331: return add(aDateBase, Calendar.MONTH, months);
332: }
333:
334: static DateBase addDay(DateBase aDateBase, int days) {
335: return add(aDateBase, Calendar.DAY_OF_MONTH, days);
336: }
337:
338: static DateBase addHour(DateBase aDateBase, int hours) {
339: return add(aDateBase, Calendar.HOUR_OF_DAY, hours);
340: }
341:
342: static DateBase addMinute(DateBase aDateBase, int minutes) {
343: return add(aDateBase, Calendar.MINUTE, minutes);
344: }
345:
346: static DateBase addSecond(DateBase aDateBase, int seconds) {
347: return add(aDateBase, Calendar.SECOND, seconds);
348: }
349:
350: static DateBase addMilli(DateBase aDateBase, int millis) {
351: return add(aDateBase, Calendar.MILLISECOND, millis);
352: }
353:
354: // difference in days... milliseconds are ignored !!!
355: static float daysBetween(DateBase date1, DateBase date2) {
356: int dayDiff = date1.getJulian() - date2.getJulian();
357:
358: int sec1 = date1.getHourOfDay() * 3600 + date1.getMinute() * 60
359: + date1.getSecond();
360: int sec2 = date2.getHourOfDay() * 3600 + date2.getMinute() * 60
361: + date2.getSecond();
362: float timeDiff = ((float) (sec1 - sec2)) / (24 * 60 * 60);
363:
364: return dayDiff + timeDiff;
365: }
366:
367: static DateBase clearTime(DateBase aDateBase) {
368: Calendar calendar = new GregorianCalendar(aDateBase.getYear(),
369: aDateBase.getMonth(), aDateBase.getDay(), 0, 0, 0);
370: return new DateBase(calendar);
371: }
372:
373: /** This parses the input String, returning a DateBase object with the corresponding value.
374: * The input String can have values of the type - N, N + 1, N - 6..., where N = current datetime.
375: * It can also have values of the type - T, T - 1, T + 2..., where T = todays date.
376: * Additionally the input String can be a formatted String based on the input layout.
377: * If the layout is null, then the default values are used from DateTimeFieldMetaData(if displayTime is true),
378: * or DateOnlyFieldMetaData.
379: * @param dateString the String to be parsed.
380: * @param layout the format used by the input String.
381: * @param displayTime determines which Default layout is to be used for parsing.
382: * @return a DateBase object which has the value based on the input.
383: * @throws ParseException if any error occurs in parsing.
384: */
385: public static DateBase parse(String dateString, String layout,
386: boolean displayTime) throws ParseException {
387: DateBase time = null;
388: if (dateString != null && dateString.length() > 0) {
389: String upperCaseString = dateString.trim().toUpperCase();
390: char startChar = upperCaseString.charAt(0);
391: if (startChar == 'N') {
392: time = parseNadd(new DateBase(), upperCaseString);
393:
394: // This hack has been put, so that the Finders treat the 'n' as an absolute datetime and do not wild-card the hours/minutes/seconds
395: // Hopefully no one will care if the 'n' doesnt ever return the seconds as zero !!!
396: if (time.getSecond() == 0)
397: time = time.addSecond(time, 1);
398: } else if (startChar == 'T') {
399: time = parseNadd(DateBase.clearTime(new DateBase()),
400: upperCaseString);
401: } else {
402: String[] layouts = null;
403: Date utilDate = null;
404: if (layout != null)
405: layouts = new String[] { layout };
406: else if (displayTime)
407: layouts = DateTimeFieldMetaData.getDateTimeParse();
408: else
409: layouts = new String[] { DateOnlyFieldMetaData
410: .getDateOnlyParse() };
411: for (int i = 0; i < layouts.length; i++) {
412: // parse based on each layout, unitl successful !!!
413: try {
414: layout = LocaleHelper
415: .determineLayout(layouts[i]);
416: SimpleDateFormat df;
417: if (LocaleContext.getLocale() != null)
418: df = new SimpleDateFormat(layout,
419: LocaleContext.getLocale());
420: else
421: df = new SimpleDateFormat(layout);
422: utilDate = df.parse(dateString);
423: break;
424: } catch (ParseException e) {
425: if ((i + 1) == layouts.length)
426: throw e;
427: }
428: }
429: time = new DateBase(utilDate);
430: }
431: }
432: return time;
433: }
434:
435: // *** PRIVATE METHODS ***
436: // create a GregorianCalendar, if not already existing.. i.e only when needed
437: private void createCalendar() {
438: if (m_calendar == null && m_time != null) {
439: m_calendar = new GregorianCalendar();
440: m_calendar.setTime(m_time);
441: }
442: }
443:
444: private int get(int field) {
445: createCalendar();
446: return m_calendar.get(field);
447: }
448:
449: private static DateBase add(DateBase aDateBase, int field, int value) {
450: Calendar calendar = aDateBase.getCalendar();
451: calendar.add(field, value);
452: return new DateBase(calendar);
453: }
454:
455: private static DateBase parseNadd(DateBase time, String aString) {
456: aString = aString.trim().toUpperCase(); //redundant.. but just in case
457: StringBuffer buf = new StringBuffer();
458: char operator = '#'; //some value other than +/-
459: boolean isOperatorSet = false;
460:
461: for (int i = 1, length = aString.length(); i < length; i++) {
462: char ch = aString.charAt(i);
463: if (ch >= '0' && ch <= '9') {
464: buf.append(ch);
465:
466: } else if (ch == '+' || ch == '-') {
467: // evaluate in case buffer already has a value
468: if (buf.length() > 0) {
469: time = parseNadd1(time, 'D', buf, operator);
470: isOperatorSet = false;
471: }
472:
473: // throw an exception in case the operator has already been set
474: if (isOperatorSet) {
475: throw new IllegalArgumentException();
476: } else {
477: operator = ch;
478: isOperatorSet = true;
479: }
480:
481: } else if (ch == ' ') {
482: // evaluate in case buffer already has a value
483: if (buf.length() > 0) {
484: time = parseNadd1(time, 'D', buf, operator);
485: isOperatorSet = false;
486: }
487:
488: } else {
489: // The buffer should have a value
490: if (buf.length() == 0)
491: throw new IllegalArgumentException();
492:
493: time = parseNadd1(time, ch, buf, operator);
494: isOperatorSet = false;
495:
496: //NOTE: the operator itself isnt reset... this is to reuse the old operator
497:
498: }
499: }
500:
501: // one final check
502: if (buf.length() > 0)
503: time = parseNadd1(time, 'D', buf, operator);
504:
505: return time;
506: }
507:
508: private static DateBase parseNadd1(DateBase time, char ch,
509: StringBuffer buf, char operator) {
510: int value = Integer.parseInt(buf.toString());
511: if (operator == '+')
512: value = value;
513: else if (operator == '-')
514: value = -value;
515: else
516: throw new IllegalArgumentException();
517:
518: if (ch == 'D')
519: time = DateBase.addDay(time, value);
520: else if (ch == 'Y')
521: time = DateBase.addYear(time, value);
522: else if (ch == 'M')
523: time = DateBase.addMonth(time, value);
524: else if (ch == 'H')
525: time = DateBase.addHour(time, value);
526: else
527: throw new IllegalArgumentException();
528:
529: // clear the buffer
530: buf.setLength(0);
531:
532: return time;
533: }
534: }
|