KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > pentaho > util > DateMath


1 /*
2  * Copyright 2006 Pentaho Corporation. All rights reserved.
3  * This software was developed by Pentaho Corporation and is provided under the terms
4  * of the Mozilla Public License, Version 1.1, or any later version. You may not use
5  * this file except in compliance with the license. If you need a copy of the license,
6  * please go to http://www.mozilla.org/MPL/MPL-1.1.txt. The Original Code is the Pentaho
7  * BI Platform. The Initial Developer is Pentaho Corporation.
8  *
9  * Software distributed under the Mozilla Public License is distributed on an "AS IS"
10  * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. Please refer to
11  * the license for the specific language governing your rights and limitations.
12  *
13  * @created Nov 3, 2005
14  */

15 package org.pentaho.util;
16
17 import java.util.Calendar JavaDoc;
18 import java.util.Locale JavaDoc;
19 import java.util.StringTokenizer JavaDoc;
20 import java.text.DateFormat JavaDoc;
21 import java.text.SimpleDateFormat JavaDoc;
22
23 import org.pentaho.messages.util.LocaleHelper;
24
25 /**
26  * Provides a utility for calculating relative dates. The class calculates a
27  * date based upon an expression. The syntax of the expression is given below.
28  * <p>
29  * <b>Date Expression</b><br>
30  *
31  * <pre>
32  * &lt;expression&gt; := &lt;expression&gt;+ ( ';' DATESPEC )?
33  * &lt;expression&gt; := OPERATION? OPERAND ':' &lt;unit&gt; &lt;position&gt;?
34  * &lt;unit&gt; := 'Y' | 'M' | 'D' | 'W' | 'h' | 'm' | 's'
35  * &lt;position&gt; := 'S' | 'E'
36  * OPERATION := '+' | '-'
37  * OPERAND := [0..9]+
38  * DATESPEC := &lt;i&gt;any {@link java.text.SimpleDateFormat} format pattern&lt;/i&gt;
39  * </pre>
40  *
41  * The <tt>OPERAND</tt> specifies the positive or negative offset to the date.
42  * The <tt>unit</tt> inidcates the <i>unit</i> of the date to manipulate. The
43  * optional position indicates the relative position for the specified unit:
44  * <i>S</i> for start and <i>E</i> for end. The following are the valid unit
45  * values.
46  *
47  * <pre>
48  * Y Year
49  * M Month
50  * W Week
51  * D Day
52  * h hour
53  * m minute
54  * s second
55  * </pre>
56  *
57  * <p>
58  * <b>Examples</b>:
59  *
60  * <pre>
61  * 0:ME -1:DS 00:00:00.000 of the day before the last day of the current month
62  * 0:MS 0:WE 23:59:59.999 the last day of the first week of the month
63  * 0:ME 23:59:59.999 of the last day of teh current month
64  * 5:Y the current monty, day and time 5 years in the future
65  * 5:YS 00:00:00.000 of the first day of the years 5 years in the future
66  * </pre>
67  */

68 public class DateMath {
69
70     private static final char POSITION_END = 'E';
71
72     private static final char POSITION_START = 'S';
73
74     private static final char UNIT_YEAR = 'Y';
75
76     private static final char UNIT_MONTH = 'M';
77
78     private static final char UNIT_WEEK = 'W';
79
80     private static final char UNIT_DAY = 'D';
81
82     private static final char UNIT_HOUR = 'h';
83
84     private static final char UNIT_MINUTE = 'm';
85
86     private static final char UNIT_SECOND = 's';
87
88     /**
89      * Calculates a date, returning the formatted string version of the
90      * calculated date. The method is a short cut for
91      * {@link #calculateDate(Calendar,String,Locale) calculateDate(null,expressionWithFormat,null)}.
92      * If the date format is omitted, the short format for the
93      * {@link PentahoSystem#getLocale()} is used.
94      *
95      * @param expressionWithFormat
96      * the relative date expression with optional format
97      * specification.
98      * @return The calculated date as a string.
99      * @throws IllegalArgumentException
100      * if <tt>expressionWithFormat</tt> is invalid.
101      */

102     public static String JavaDoc claculateDateString(String JavaDoc expressionWithFormat) {
103         return calculateDateString(null, expressionWithFormat, null);
104     }
105
106     /**
107      * Calculates a date, returning the formatted string version of the
108      * calculated date. The method is a short cut for
109      * {@link #calculateDate(Calendar,String,Locale) calculateDate(date,expressionWithFormat,null)}.
110      *
111      * @param date
112      * the target date against the expression will be applied.
113      * @param expressionWithFormat
114      * the relative date expression with optional format
115      * specification.
116      * @return The calculated date as a string.
117      * @throws IllegalArgumentException
118      * if <tt>expressionWithFormat</tt> is invalid.
119      */

120     public static String JavaDoc calculateDateString(Calendar JavaDoc date, String JavaDoc expressionWithFormat) {
121         return calculateDateString(date, expressionWithFormat, null);
122     }
123
124     /**
125      * Calculates a date, returning the formatted string version of the
126      * calculated date.
127      *
128      * @param date
129      * the target date against the expression will be applied. If
130      * <tt>null</tt>, the current date is used.
131      * @param expressionWithFormat
132      * the relative date expression with optional format
133      * specification.
134      * @param locale
135      * the desired locale for the formatted string.
136      * @return The calculated date as a string.
137      * @throws IllegalArgumentException
138      * if <tt>expressionWithFormat</tt> is invalid.
139      */

140     public static String JavaDoc calculateDateString(Calendar JavaDoc date, String JavaDoc expressionWithFormat, Locale JavaDoc locale) {
141         int index = expressionWithFormat.indexOf(';');
142         String JavaDoc expression;
143         String JavaDoc pattern = null;
144         Calendar JavaDoc target = (date == null) ? Calendar.getInstance() : date;
145         DateFormat JavaDoc format;
146         Locale JavaDoc myLocale;
147
148         if (index >= 0) {
149             pattern = expressionWithFormat.substring(index + 1);
150             expression = expressionWithFormat.substring(0, index);
151         } else {
152             expression = expressionWithFormat;
153         }
154
155         target = calculateDate(date, expression);
156
157         myLocale = (locale == null) ? LocaleHelper.getLocale() : locale;
158         if (myLocale == null) {
159             myLocale = LocaleHelper.getDefaultLocale();
160         }
161
162         if (pattern != null) {
163             format = new SimpleDateFormat JavaDoc(pattern, myLocale);
164         } else {
165             format = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, myLocale);
166         }
167
168         return format.format(target.getTime());
169     }
170
171     /**
172      * Calculates the date specified by the expression, relative to the current
173      * date/time. The method is a short cut for
174      * {@link #calculate(Calendar, String) calculate(null,expression)}.
175      *
176      * @param expression
177      * the date expression as described above.
178      * @return The calculated date.
179      * @throws IllegalArgumentException
180      * if <tt>expression</tt> is invalid.
181      */

182     public static Calendar JavaDoc calculateDate(String JavaDoc expression) {
183         return calculateDate(null, expression);
184     }
185
186     /**
187      * Calculates the date specified by the expression, relative to the
188      * indicated date/time.
189      *
190      * @param date
191      * the target date against the expression is evaluated. If
192      * <tt>null</tt>, the current date/time is used. If not
193      * <tt>null</tt>, the object is manipulated by the expression.
194      * @param expression
195      * the date expression as described above.
196      * @return The calculated date. This will be <tt>date</tt> if
197      * <tt>date</tt> is not <tt>null</tt>.
198      */

199     public static Calendar JavaDoc calculateDate(Calendar JavaDoc date, String JavaDoc expression) {
200         StringTokenizer JavaDoc tok;
201         String JavaDoc myExpression;
202         Calendar JavaDoc target = date;
203         int index = expression.indexOf(';');
204
205         if (index >= 0) {
206             myExpression = expression.substring(index + 1);
207         } else {
208             myExpression = expression;
209         }
210
211         tok = new StringTokenizer JavaDoc(myExpression, " \t;"); //$NON-NLS-1$
212
while (tok.hasMoreElements()) {
213             target = parseAndCalculateDate(target, (String JavaDoc) tok.nextElement());
214         }
215
216         return target;
217     }
218
219     /**
220      * Parses and executes a single expression, one without subexpressions.
221      *
222      * @param date
223      * the target date against the expression is evaluated. If
224      * <tt>null</tt>, the current date/time is used. If not
225      * <tt>null</tt>, the object is manipulated by the expression.
226      * @param expression
227      * the date expression as described above.
228      * @return The calculated date. This will be <tt>date</tt> if
229      * <tt>date</tt> is not <tt>null</tt>.
230      */

231     private static Calendar JavaDoc parseAndCalculateDate(Calendar JavaDoc date, String JavaDoc expression) {
232         int index = expression.indexOf(':'); // $NON-NLS-1$
233
char operation = '+'; // $NON-NLS-1$
234
char unit = ' '; // $NON-NLS-1$
235
char position = ' '; // $NON-NLS-1$
236
int operand = 0;
237         Calendar JavaDoc result;
238
239         if (index >= 0) {
240             try {
241                 String JavaDoc number = expression.substring(0, index);
242
243                 operation = number.charAt(0);
244                 if ((operation == '+') || (operation == '-')) { // $NON-NLS-1$
245
// Integer.praseInt doesn't handle '+' for positive numbers
246
//
247
number = number.substring(1);
248                 } else {
249                     operation = '+';
250                 }
251
252                 operand = Integer.parseInt(number);
253
254                 index++;
255                 unit = expression.charAt(index);
256
257                 index++;
258                 if (index < expression.length()) {
259                     position = expression.charAt(index);
260                 }
261
262                 result = calculateDate(date, operation, operand, unit, position);
263             } catch (Exception JavaDoc ex) {
264                 IllegalArgumentException JavaDoc err = new IllegalArgumentException JavaDoc(expression);
265
266                 err.initCause(ex);
267                 throw err;
268             }
269         } else {
270             throw new IllegalArgumentException JavaDoc(expression);
271         }
272
273         return result;
274     }
275
276     /**
277      * Calculates the relative date based upon the values of the BNF
278      * non-terminals above.
279      *
280      * @param date
281      * the target date against the expression is evaluated. If
282      * <tt>null</tt>, the current date/time is used. If not
283      * <tt>null</tt>, the object is manipulated by the expression.
284      * @param operation
285      * the value of the operation. Currently, this is the sign on the
286      * operand. However, in the future, it could be some value to
287      * indicate a relative or specific value.
288      * @param operand
289      * the value of the NUM token.
290      * @param unit
291      * the value of the &lt;unit&gt; non-terminal
292      * @param position
293      * the value of teh &lt;position&gt; non-terminal
294      * @return The calculated date. This will be <tt>date</tt> if
295      * <tt>date</tt> is not <tt>null</tt>.
296      */

297     private static Calendar JavaDoc calculateDate(Calendar JavaDoc date, char operation, int operand, char unit, char position) {
298         Calendar JavaDoc target = (date == null) ? Calendar.getInstance() : date;
299         int calendarField = -1;
300
301         switch (unit) {
302         case UNIT_YEAR:
303             calendarField = Calendar.YEAR;
304             break;
305         case UNIT_MONTH:
306             calendarField = Calendar.MONTH;
307             break;
308         case UNIT_WEEK:
309             calendarField = Calendar.DAY_OF_YEAR;
310             operand = operand * 7;
311             break;
312         case UNIT_DAY:
313             calendarField = Calendar.DAY_OF_YEAR;
314             break;
315         case UNIT_HOUR:
316             calendarField = Calendar.HOUR_OF_DAY;
317             break;
318         case UNIT_MINUTE:
319             calendarField = Calendar.MINUTE;
320             break;
321         case UNIT_SECOND:
322             calendarField = Calendar.SECOND;
323             break;
324
325         default:
326             throw new IllegalArgumentException JavaDoc();
327         }
328
329         if (operation == ' ') {
330             target.set(calendarField, operand);
331         } else if (operation == '+') {
332             target.add(calendarField, operand);
333         } else if (operation == '-') {
334             target.add(calendarField, -Math.abs(operand));
335         }
336
337         if (unit == UNIT_YEAR) {
338             if (position == POSITION_START) {
339                 target.set(Calendar.DAY_OF_YEAR, 1);
340                 setTimeToStart(target);
341             } else if (position == POSITION_END) {
342                 target.set(Calendar.DAY_OF_YEAR, target.getActualMaximum(Calendar.DAY_OF_YEAR));
343                 setTimeToEnd(target);
344             }
345         } else if (unit == UNIT_MONTH) {
346             if (position == POSITION_START) {
347                 target.set(Calendar.DAY_OF_MONTH, 1);
348                 setTimeToStart(target);
349             } else if (position == POSITION_END) {
350                 target.set(Calendar.DAY_OF_MONTH, target.getActualMaximum(Calendar.DAY_OF_MONTH));
351                 setTimeToEnd(target);
352             }
353         } else if (unit == UNIT_WEEK) {
354             int firstDOW = target.getFirstDayOfWeek();
355             int dayOfWeek = target.get(Calendar.DAY_OF_WEEK); // force
356
// calculation
357
int dayOffset = 0;
358
359             if (position == POSITION_START) {
360                 if (dayOfWeek > firstDOW) {
361
362                     // Past first day of week; go backwards to first day
363
//
364
dayOffset = firstDOW - dayOfWeek;
365                 } else if (dayOfWeek < firstDOW) {
366
367                     // Before the first day; go back a week and move forward to
368
// first day
369
// Should only happen if first day is not Sunday.
370
//
371
dayOffset = -7 + (firstDOW - dayOfWeek);
372                 }
373
374                 setTimeToStart(target);
375             } else if (position == POSITION_END) {
376                 int lastDOW;
377
378                 if (firstDOW == Calendar.SUNDAY) {
379                     lastDOW = Calendar.SATURDAY;
380                 } else {
381                     lastDOW = firstDOW - 1;
382                 }
383
384                 if (dayOfWeek < lastDOW) {
385
386                     // Before the last day of week; move forward to last day
387
//
388
dayOffset = lastDOW - dayOfWeek;
389                 } else if (dayOfWeek > lastDOW) {
390
391                     // Should only happen if last day is anything but Saturday;
392
// Move to next week; roll back to last day.
393
dayOffset = 7 - (dayOfWeek - lastDOW);
394                 }
395
396                 setTimeToEnd(target);
397             }
398
399             if (dayOffset != 0) {
400                 target.add(Calendar.DAY_OF_YEAR, dayOffset);
401             }
402         } else if (unit == UNIT_DAY) {
403             if (position == POSITION_START) {
404                 setTimeToStart(target);
405             } else if (position == POSITION_END) {
406                 setTimeToEnd(target);
407             }
408         } else if (unit == UNIT_HOUR) {
409             if (position == POSITION_START) {
410                 target.set(Calendar.MINUTE, 0);
411                 target.set(Calendar.SECOND, 0);
412                 target.set(Calendar.MILLISECOND, 0);
413             } else if (position == POSITION_END) {
414                 target.set(Calendar.MINUTE, 59);
415                 target.set(Calendar.SECOND, 59);
416                 target.set(Calendar.MILLISECOND, 999);
417             }
418         } else if (unit == UNIT_MINUTE) {
419             if (position == POSITION_START) {
420                 target.set(Calendar.SECOND, 0);
421                 target.set(Calendar.MILLISECOND, 0);
422             } else if (position == POSITION_END) {
423                 target.set(Calendar.SECOND, 59);
424                 target.set(Calendar.MILLISECOND, 999);
425             }
426         } else if (unit == UNIT_SECOND) {
427             if (position == POSITION_START) {
428                 target.set(Calendar.MILLISECOND, 0);
429             } else if (position == POSITION_END) {
430                 target.set(Calendar.MILLISECOND, 999);
431             }
432         }
433
434         target.getTimeInMillis(); // force calculations
435

436         return target;
437     }
438
439     /**
440      * Sets the time to the start of the day (00:00:00.000).
441      *
442      * @param target
443      * the target calendar for which the time will be set.
444      */

445     private static void setTimeToStart(Calendar JavaDoc target) {
446         target.set(Calendar.MILLISECOND, 0);
447         target.set(Calendar.SECOND, 0);
448         target.set(Calendar.MINUTE, 0);
449         target.set(Calendar.HOUR_OF_DAY, 0);
450     }
451
452     /**
453      * Sets the time to the endof the day (23:59:59.999).
454      *
455      * @param target
456      * the target calendar for which the time will be set.
457      */

458     private static void setTimeToEnd(Calendar JavaDoc target) {
459         target.set(Calendar.MILLISECOND, 999);
460         target.set(Calendar.SECOND, 59);
461         target.set(Calendar.MINUTE, 59);
462         target.set(Calendar.HOUR_OF_DAY, 23);
463     }
464 }
465
Popular Tags