View Javadoc

1   /*
2    * Created by IntelliJ IDEA.
3    * User: Administrator
4    * Date: 5/03/2002
5    * Time: 14:31:36
6    * To change template for new class use
7    * Code Style | Class Templates options (Tools | IDE Options).
8    */
9   package com.atlassian.core.util;
10  
11  import com.opensymphony.util.TextUtils;
12  import org.apache.log4j.Category;
13  
14  import java.sql.Timestamp;
15  import java.text.DateFormat;
16  import java.text.SimpleDateFormat;
17  import java.util.*;
18  
19  public class DateUtils
20  {
21      private static Category log = Category.getInstance(DateUtils.class);
22  
23      /** Constants for time computation */
24      private static final int SECOND = 1;
25      private static final int MINUTE = 60 * SECOND;
26      private static final int HOUR = 60 * MINUTE;
27      private static final int DAY = 24 * HOUR;
28      private static final int WEEK = 7 * DAY;
29      private static final int MONTH = 31 * DAY;
30      private static final int YEAR = 365 * DAY;
31  
32      public static final long SECOND_MILLIS = 1000L * SECOND;
33      public static final long MINUTE_MILLIS = 1000L * MINUTE;
34      public static final long HOUR_MILLIS = 1000L * HOUR;
35      public static final long DAY_MILLIS = 1000L * DAY;
36      public static final long MONTH_MILLIS = 1000L * MONTH;
37      public static final long YEAR_MILLIS = 1000L * YEAR;
38  
39      /*
40         It's important these stay ordered from biggest to smallest.
41         Everything in this must be able to be zeroed without changing anything
42         that comes before it in the list. So don't add WEEK!
43       */
44      private static final int[] CALENDAR_PERIODS = { Calendar.YEAR, Calendar.MONTH, Calendar.DAY_OF_MONTH,
45                                                      Calendar.HOUR_OF_DAY, Calendar.MINUTE, Calendar.SECOND, Calendar.MILLISECOND };
46  
47      // This is used by the Velocity templates as a bean
48      private final ResourceBundle resourceBundle;
49  
50      public static class DateRange
51      {
52          public final java.util.Date startDate;
53          public final java.util.Date endDate;
54  
55          public DateRange(java.util.Date startDate, java.util.Date endDate)
56          {
57              this.startDate = startDate;
58              this.endDate = endDate;
59          }
60      }
61  
62  
63      public DateUtils(ResourceBundle resourceBundle)
64      {
65          this.resourceBundle = resourceBundle;
66      }
67  
68      /** compares if these two timestamps are within 10 milliseconds of each other (precision error) */
69      public static boolean equalTimestamps(Timestamp t1, Timestamp t2)
70      {
71          return (Math.abs(t1.getTime() - t2.getTime()) < 10L);
72      }
73  
74      /** Date Format to be used for internal logging operations */
75      public static final DateFormat ISO8601DateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,SSS");
76  
77      public String dateDifferenceBean(long dateA, long dateB, long resolution, ResourceBundle resourceBundle)
78      {
79          return dateDifference(dateA, dateB, resolution, resourceBundle);
80      }
81  
82      /**
83       * Resolution is the degree of difference.
84       * <p/>
85       * 0 = months
86       * 1 = days
87       * 2 = hours
88       * 3 = minutes
89       * 4 = seconds
90       */
91      public static String dateDifference(long dateA, long dateB, long resolution, ResourceBundle resourceBundle)
92      {
93          long months, days, hours, minutes, seconds;
94          StringBuffer sb = new StringBuffer();
95          long difference = Math.abs(dateB - dateA);
96  
97          resolution--;
98          months = difference / MONTH_MILLIS;
99          if (months > 0)
100         {
101             difference = difference % MONTH_MILLIS;
102             if (months > 1)
103             {
104                 sb.append(months).append(" ").append(getText(resourceBundle, "core.dateutils.months")).append(", ");
105             }
106             else
107             {
108                 sb.append(months).append(" ").append(getText(resourceBundle, "core.dateutils.month")).append(", ");
109             }
110         }
111 
112         if (resolution < 0)
113         {
114             if (sb.length() == 0)
115             {
116                 return "0 " + getText(resourceBundle, "core.dateutils.months");
117             }
118             else
119             {
120                 return sb.substring(0, sb.length() - 2);
121             }
122         }
123         else
124         {
125             resolution--;
126             days = difference / DAY_MILLIS;
127             if (days > 0)
128             {
129                 difference = difference % DAY_MILLIS;
130                 if (days > 1)
131                 {
132                     sb.append(days).append(" ").append(getText(resourceBundle, "core.dateutils.days")).append(", ");
133                 }
134                 else
135                 {
136                     sb.append(days).append(" ").append(getText(resourceBundle, "core.dateutils.day")).append(", ");
137                 }
138             }
139         }
140 
141         if (resolution < 0)
142         {
143             if (sb.length() == 0)
144             {
145                 return "0 " + getText(resourceBundle, "core.dateutils.days");
146             }
147             else
148             {
149                 return sb.substring(0, sb.length() - 2);
150             }
151         }
152         else
153         {
154             resolution--;
155             hours = difference / HOUR_MILLIS;
156             if (hours > 0)
157             {
158                 difference = difference % HOUR_MILLIS;
159                 if (hours > 1)
160                 {
161                     sb.append(hours).append(" ").append(getText(resourceBundle, "core.dateutils.hours")).append(", ");
162                 }
163                 else
164                 {
165                     sb.append(hours).append(" ").append(getText(resourceBundle, "core.dateutils.hour")).append(", ");
166                 }
167             }
168         }
169 
170         if (resolution < 0)
171         {
172             if (sb.length() == 0)
173             {
174                 return "0 " + getText(resourceBundle, "core.dateutils.hours");
175             }
176             else
177             {
178                 return sb.substring(0, sb.length() - 2);
179             }
180         }
181         else
182         {
183             resolution--;
184             minutes = difference / MINUTE_MILLIS;
185             if (minutes > 0)
186             {
187                 difference = difference % MINUTE_MILLIS;
188                 if (minutes > 1)
189                 {
190                     sb.append(minutes).append(" ").append(getText(resourceBundle, "core.dateutils.minutes")).append(", ");
191                 }
192                 else
193                 {
194                     sb.append(minutes).append(" ").append(getText(resourceBundle, "core.dateutils.minute")).append(", ");
195                 }
196             }
197         }
198 
199         if (resolution < 0)
200         {
201             if (sb.length() == 0)
202             {
203                 return "0 " + getText(resourceBundle, "core.dateutils.minutes");
204             }
205             else
206             {
207                 return sb.substring(0, sb.length() - 2);
208             }
209         }
210         else
211         {
212             resolution--;
213             seconds = difference / SECOND_MILLIS;
214             if (seconds > 0)
215             {
216                 if (seconds > 1)
217                 {
218                     sb.append(seconds).append(" ").append(getText(resourceBundle, "core.dateutils.seconds")).append(", ");
219                 }
220                 else
221                 {
222                     sb.append(seconds).append(" ").append(getText(resourceBundle, "core.dateutils.second")).append(", ");
223                 }
224             }
225         }
226 
227         if (resolution <= 0 && sb.length() == 0)
228         {
229             return "0 " + getText(resourceBundle, "core.dateutils.seconds");
230         }
231 
232         if (sb.length() > 2)
233         {
234             return sb.substring(0, sb.length() - 2);
235         }
236         else
237         {
238             return "";
239         }
240     }
241 
242 
243     public static String formatDateISO8601(Date ts)
244     {
245         return ISO8601DateFormat.format(ts);
246     }
247 
248     /**
249      * Check whether a given duration string is valid
250      *
251      * @param s the duration string
252      * @return true if it a valid duration
253      */
254     public static boolean validDuration(String s)
255     {
256         try
257         {
258             getDuration(s);
259             return true;
260         }
261         catch (InvalidDurationException e)
262         {
263             return false;
264         }
265     }
266 
267     /**
268      * Given a duration string, get the number of minutes it represents (all case insensitive):
269      * <ul>
270      * <li>w = weeks
271      * <li>d = days
272      * <li>h = hours
273      * <li>m = minutes
274      * </ul>
275      * If no category is specified, assume minutes.<br>
276      * Each field must be separated by a space, and they can come in any order.
277      * Case is ignored.
278      * <p/>
279      * ie 2h = 120, 60m = 60, 3d = 24 * 60, 30m
280      *
281      * @param durationStr the duration string
282      * @return the duration in seconds
283      * @throws InvalidDurationException if the duration is invalid
284      */
285     public static long getDuration(String durationStr) throws InvalidDurationException
286     {
287         return getDurationSeconds(durationStr, DAY, WEEK);
288     }
289 
290     /**
291      * Get a duration string with the possibility of a negative.
292      * <p/>
293      * A duration will be considered negative if the first non-space character is a - sign.
294      *
295      * @param durationStr the duration string
296      * @return the duration in seconds, which can be negative
297      * @throws InvalidDurationException if its a badly formatted duration
298      */
299     public static long getDurationWithNegative(String durationStr) throws InvalidDurationException
300     {
301         String cleanedDurationStr = TextUtils.noNull(durationStr).trim();
302         if (!TextUtils.stringSet(cleanedDurationStr))
303         {
304             return 0;
305         }
306 
307         boolean negative = false;
308 
309         if (cleanedDurationStr.charAt(0) == '-')
310         {
311             negative = true;
312         }
313 
314         if (negative)
315         {
316             return 0 - getDuration(cleanedDurationStr.substring(1));
317         }
318         else
319         {
320             return getDuration(cleanedDurationStr);
321         }
322     }
323 
324     /**
325      * This function retrieves a duration in seconds that depends on number of hours in a day and
326      * days in a week
327      *
328      * @param durationStr to convert to a duration
329      * @param hoursPerDay Number of hourse i day
330      * @param daysPerWeek Days Per Week
331      * @return the duration in seconds
332      * @throws InvalidDurationException if its badly formatted duration
333      */
334     public static long getDuration(String durationStr, int hoursPerDay, int daysPerWeek) throws InvalidDurationException
335     {
336         int secondsInDay = hoursPerDay * HOUR;
337         int secondsPerWeek = daysPerWeek * secondsInDay;
338         return getDurationSeconds(durationStr, secondsInDay, secondsPerWeek);
339     }
340 
341     private static long getDurationSeconds(String durationStr, int secondsPerDay, int secondsPerWeek)
342             throws InvalidDurationException
343     {
344         long time = 0;
345         String remainingDurationStr = null;
346 
347         if (!TextUtils.stringSet(durationStr))
348         {
349             return 0;
350         }
351 
352         durationStr = durationStr.trim().toLowerCase();
353 
354         // if we have more than one 'token', parse each separately
355         if (durationStr.indexOf(" ") > 0)
356         {
357             StringTokenizer st = new StringTokenizer(durationStr, ", ");
358             while (st.hasMoreTokens())
359             {
360                 time += getDurationSeconds(st.nextToken(), secondsPerDay, secondsPerWeek);
361             }
362         }
363         else
364         {
365             try // detect if we just have a number
366             {
367                 time = Long.parseLong(durationStr.trim()) * MINUTE;
368             }
369             catch (Exception ex) // otherwise get the value
370             {
371                 //Extract number and adjust this number with the time factor
372                 for (int i = 0; i < durationStr.length(); i++)
373                 {
374                     if (!Character.isDigit(durationStr.charAt(i)))
375                     {
376                         if (i == 0)
377                         {
378                             throw new InvalidDurationException("Must have integer preceding duration type");
379                         }
380 
381                         time = Long.parseLong(durationStr.substring(0, i));
382                         remainingDurationStr = durationStr.substring(i + 1);
383 
384                         switch ((int) durationStr.charAt(i))
385                         {
386                             case(int) 'm':
387                                 time *= MINUTE;
388                                 break;
389                             case(int) 'h':
390                                 time *= HOUR;
391                                 break;
392                             case(int) 'd':
393                                 time *= secondsPerDay;
394                                 break;
395                             case(int) 'w':
396                                 time *= secondsPerWeek;
397                                 break;
398                             default:
399                                 throw new InvalidDurationException("Not a valid duration string");
400                         }
401 
402                         break;
403                     } // if
404                 } // for
405             } // catch
406         }
407 
408         if (remainingDurationStr != null && remainingDurationStr.length() != 0 && Character.isDigit(remainingDurationStr.charAt(0)))
409         {
410             time += getDurationSeconds(remainingDurationStr, secondsPerDay, secondsPerWeek);
411         }
412         return time;
413     }
414 
415     /**
416      * Get String representation of a duration
417      * <p/>
418      *
419      * @param seconds Number of seconds
420      * @return String representing duration, eg: "1h 30m"
421      * @see #getDurationStringWithNegative(long)
422      */
423     public static String getDurationString(long seconds)
424     {
425         return getDurationStringSeconds(seconds, DAY, WEEK);
426     }
427 
428     /**
429      * Get String representation of a (possibly negative) duration.
430      * <p/>
431      *
432      * @param seconds Number of seconds
433      * @return String representing duration, eg: "-1h 30m"
434      * @see #getDurationString(long)
435      */
436     public static String getDurationStringWithNegative(long seconds)
437     {
438         if (seconds < 0)
439         {
440             return "-" + getDurationString(-seconds);
441         }
442         else
443         {
444             return getDurationString(seconds);
445         }
446     }
447 
448     public static String getDurationString(long l, int hoursPerDay, int daysPerWeek)
449     {
450         int secondsInDay = hoursPerDay * HOUR;
451         int secondsPerWeek = daysPerWeek * secondsInDay;
452         return getDurationStringSeconds(l, secondsInDay, secondsPerWeek);
453     }
454 
455     private static String getDurationStringSeconds(long l, int secondsPerDay, int secondsPerWeek)
456     {
457         if (l == 0)
458         {
459             return "0m";
460         }
461 
462         StringBuffer result = new StringBuffer();
463 
464         if (l >= secondsPerWeek)
465         {
466             result.append((l / secondsPerWeek));
467             result.append("w ");
468             l = l % secondsPerWeek;
469         }
470 
471         if (l >= secondsPerDay)
472         {
473             result.append((l / secondsPerDay));
474             result.append("d ");
475             l = l % secondsPerDay;
476         }
477 
478         if (l >= HOUR)
479         {
480             result.append((l / HOUR));
481             result.append("h ");
482             l = l % HOUR;
483         }
484 
485         if (l >= MINUTE)
486         {
487             result.append((l / MINUTE));
488             result.append("m ");
489         }
490 
491         return result.toString().trim();
492     }
493 
494     /**
495      * Converts a number of seconds into a pretty formatted data string.  The resolution is in minutes.  So if the number of seconds is greater than a minute, it will
496      * only be shown down top minute resolution.  If the number of seconds is less than a minute it will be shown in seconds.
497      * <p/>
498      * So for example <code>76</code> becomes <code>'1 minute'</code>, while <code>42</code> becomes <code>'42 seconds'</code>
499      *
500      * @param numSecs        the number of seconds in the duration
501      * @param resourceBundle a resouce bundle for i18n
502      * @return a string in readable pretty duration format, using minute resolution
503      */
504     public static String getDurationPretty(long numSecs, ResourceBundle resourceBundle)
505     {
506         return getDurationPrettySeconds(numSecs, DAY, WEEK, resourceBundle, false);
507     }
508 
509     /**
510      * Converts a number of seconds into a pretty formatted data string.  The resolution is in minutes.  So if the number of seconds is greater than a minute, it will
511      * only be shown down top minute resolution.  If the number of seconds is less than a minute it will be shown in seconds.
512      * <p/>
513      * So for example <code>76</code> becomes <code>'1 minute'</code>, while <code>42</code> becomes <code>'42 seconds'</code>
514      *
515      * @param numSecs        the number of seconds in the duration
516      * @param hoursPerDay    the hours in a day
517      * @param daysPerWeek    the number of days in a week
518      * @param resourceBundle a resouce bundle for i18n
519      * @return a string in readable pretty duration format, using minute resolution
520      */
521     public static String getDurationPretty(long numSecs, int hoursPerDay, int daysPerWeek, ResourceBundle resourceBundle)
522     {
523         int secondsInDay = hoursPerDay * HOUR;
524         int secondsPerWeek = daysPerWeek * secondsInDay;
525         return getDurationPrettySeconds(numSecs, secondsInDay, secondsPerWeek, resourceBundle, false);
526     }
527 
528     /**
529      * Converts a number of seconds into a pretty formatted data string.  The resolution is in seconds.
530      * <p/>
531      * So for example <code>76</code> becomes <code>'1 minute, 16 seconds'</code>, while <code>42</code> becomes <code>'42 seconds'</code>
532      *
533      * @param numSecs        the number of seconds in the duration
534      * @param resourceBundle a resouce bundle for i18n
535      * @return a string in readable pretty duration format, using second resolution
536      */
537     public static String getDurationPrettySecondsResolution(long numSecs, ResourceBundle resourceBundle)
538     {
539         return getDurationPrettySeconds(numSecs, DAY, WEEK, resourceBundle, true);
540     }
541 
542     /**
543      * Converts a number of seconds into a pretty formatted data string.  The resolution is in seconds.
544      * <p/>
545      * So for example <code>76</code> becomes <code>'1 minute, 16 seconds'</code>, while <code>42</code> becomes <code>'42 seconds'</code>
546      *
547      * @param numSecs        the number of seconds in the duration
548      * @param hoursPerDay    the hours in a day
549      * @param daysPerWeek    the number of days in a week
550      * @param resourceBundle a resouce bundle for i18n
551      * @return a string in readable pretty duration format, using second resolution
552      */
553     public static String getDurationPrettySecondsResolution(long numSecs, int hoursPerDay, int daysPerWeek, ResourceBundle resourceBundle)
554     {
555         int secondsInDay = hoursPerDay * HOUR;
556         int secondsPerWeek = daysPerWeek * secondsInDay;
557         return getDurationPrettySeconds(numSecs, secondsInDay, secondsPerWeek, resourceBundle, true);
558     }
559 
560     private static String getDurationPrettySeconds(long numSecs, int secondsPerDay, int secondsPerWeek, ResourceBundle resourceBundle, boolean secondsDuration)
561     {
562         return getDurationPrettySeconds(numSecs, YEAR, secondsPerDay, secondsPerWeek, resourceBundle, secondsDuration);
563     }
564 
565     /*
566      * This implementation method returns things in "minute resolution" unless the secondResolution flag is true
567      */
568     private static String getDurationPrettySeconds(long numSecs, int secondsPerYear, int secondsPerDay, int secondsPerWeek, ResourceBundle resourceBundle, boolean secondResolution)
569     {
570         if (numSecs == 0)
571         {
572             if (secondResolution)
573             {
574                 return "0 " + getText(resourceBundle, "core.dateutils.seconds");
575             }
576             else
577             {
578                 return "0 " + getText(resourceBundle, "core.dateutils.minutes");
579             }
580         }
581 
582         StringBuffer result = new StringBuffer();
583 
584         if (numSecs >= secondsPerYear)
585         {
586             long years = numSecs / secondsPerYear;
587             result.append(years).append(' ');
588 
589             if (years > 1)
590             {
591                 result.append(getText(resourceBundle, "core.dateutils.years"));
592             }
593             else
594             {
595                 result.append(getText(resourceBundle, "core.dateutils.year"));
596             }
597 
598             result.append(", ");
599             numSecs = numSecs % secondsPerYear;
600         }
601 
602         if (numSecs >= secondsPerWeek)
603         {
604             long weeks = numSecs / secondsPerWeek;
605             result.append(weeks).append(' ');
606 
607             if (weeks > 1)
608             {
609                 result.append(getText(resourceBundle, "core.dateutils.weeks"));
610             }
611             else
612             {
613                 result.append(getText(resourceBundle, "core.dateutils.week"));
614             }
615 
616             result.append(", ");
617             numSecs = numSecs % secondsPerWeek;
618         }
619 
620         if (numSecs >= secondsPerDay)
621         {
622             long days = numSecs / secondsPerDay;
623             result.append(days).append(' ');
624 
625             if (days > 1)
626             {
627                 result.append(getText(resourceBundle, "core.dateutils.days"));
628             }
629             else
630             {
631                 result.append(getText(resourceBundle, "core.dateutils.day"));
632             }
633 
634             result.append(", ");
635             numSecs = numSecs % secondsPerDay;
636         }
637 
638         if (numSecs >= HOUR)
639         {
640             long hours = numSecs / HOUR;
641             result.append(hours).append(' ');
642 
643             if (hours > 1)
644             {
645                 result.append(getText(resourceBundle, "core.dateutils.hours"));
646             }
647             else
648             {
649                 result.append(getText(resourceBundle, "core.dateutils.hour"));
650             }
651 
652             result.append(", ");
653             numSecs = numSecs % HOUR;
654         }
655 
656         if (numSecs >= MINUTE)
657         {
658             long minute = numSecs / MINUTE;
659             result.append(minute).append(' ');
660 
661 
662             if (minute > 1)
663             {
664                 result.append(getText(resourceBundle, "core.dateutils.minutes"));
665             }
666             else
667             {
668                 result.append(getText(resourceBundle, "core.dateutils.minute"));
669             }
670 
671             result.append(", ");
672 
673             // if we want seconds resolution we need to reduce it down to seconds here
674             if (secondResolution)
675             {
676                 numSecs = numSecs % MINUTE;
677             }
678         }
679 
680         if (numSecs >= SECOND && numSecs < MINUTE)
681         {
682             long sec = numSecs;
683             result.append(sec).append(' ');
684 
685 
686             if (sec > 1)
687             {
688                 result.append(getText(resourceBundle, "core.dateutils.seconds"));
689             }
690             else
691             {
692                 result.append(getText(resourceBundle, "core.dateutils.second"));
693             }
694 
695             result.append(", ");
696         }
697 
698         if (result.length() > 2) // remove the ", " on th end
699         {
700             return result.substring(0, result.length() - 2);
701         }
702         else
703         {
704             return result.toString();
705         }
706     }
707 
708     /** This is used by the Velocity templates as a bean */
709     public String formatDurationPretty(long l)
710     {
711         return DateUtils.getDurationPretty(l, resourceBundle);
712     }
713 
714     /** This is used by the Velocity templates as a bean */
715     public String formatDurationPretty(String seconds)
716     {
717         return DateUtils.getDurationPretty(Long.parseLong(seconds), resourceBundle);
718     }
719 
720 
721     /** This is used by the WebWork tags as a bean */
722     public String formatDurationString(long l)
723     {
724         return DateUtils.getDurationPretty(l, resourceBundle);
725     }
726 
727     private static String getText(ResourceBundle resourceBundle, String key)
728     {
729         try
730         {
731             return resourceBundle.getString(key);
732         }
733         catch (MissingResourceException e)
734         {
735             log.error(e);
736             return "";
737         }
738     }
739 
740     /**
741      * Change the date of a Calendar object so that it has the maximum resolution
742      * of "period" where period is one of the constants in CALENDAR_PERIODS above.
743      * <p/>
744      * e.g. to obtain the maximum value for a month, call toEndOfPeriod(calendarObject, Calendar.MONTH)
745      *
746      * @param calendar The Calendar to change
747      * @param period   The period to "maximise"
748      * @return A modified Calendar object
749      */
750     public static Calendar toEndOfPeriod(Calendar calendar, int period)
751     {
752         boolean zero = false;
753 
754         for (int i = 0; i < CALENDAR_PERIODS.length; i++)
755         {
756             if (zero)
757             {
758                 calendar.set(CALENDAR_PERIODS[i], calendar.getMaximum(CALENDAR_PERIODS[i]));
759             }
760 
761             if (CALENDAR_PERIODS[i] == period)
762             {
763                 zero = true;
764             }
765         }
766 
767         if (!zero)
768         {
769             throw new IllegalArgumentException("unknown Calendar period: " + period);
770         }
771 
772         return calendar;
773     }
774 
775     /**
776      * Change the date of a Calendar object so that it has the minimum resolution
777      * of "period" where period is one of the constants in CALENDAR_PERIODS above.
778      */
779     public static Calendar toStartOfPeriod(Calendar calendar, int period)
780     {
781         boolean zero = false;
782         for (int i = 0; i < CALENDAR_PERIODS.length; i++)
783         {
784             if (zero)
785             {
786                 if (CALENDAR_PERIODS[i] == Calendar.DAY_OF_MONTH)
787                 {
788                     calendar.set(Calendar.DAY_OF_MONTH, 1);
789                 }
790                 else
791                 {
792                     calendar.set(CALENDAR_PERIODS[i], 0);
793                 }
794             }
795 
796             if (CALENDAR_PERIODS[i] == period)
797             {
798                 zero = true;
799             }
800 
801         }
802 
803         if (!zero)
804         {
805             throw new IllegalArgumentException("unknown Calendar period: " + period);
806         }
807 
808         return calendar;
809     }
810 
811     /**
812      * Given a period, and a date that falls within that period, create a range of dates such
813      * that the period is contained exactly within [startDate <= {range} < endDate]
814      *
815      * @param date   a calendar object of a date falling in that range
816      * @param period something in CALENDAR_PERIODS
817      */
818     public static DateRange toDateRange(Calendar date, int period)
819     {
820         // defensively copy the calendar so we don't break anything outside.
821         Calendar cal = (Calendar) date.clone();
822         toStartOfPeriod(cal, period);
823         Date startDate = new Date(cal.getTimeInMillis());
824         cal.add(period, 1);
825         Date endDate = new Date(cal.getTimeInMillis());
826 
827         return new DateRange(startDate, endDate);
828     }
829 
830     public static Calendar getCalendarDay(int year, int month, int day)
831     {
832         return initCalendar(year, month, day, 0, 0, 0, 0);
833     }
834 
835     public static Date getDateDay(int year, int month, int day)
836     {
837         return getCalendarDay(year, month, day).getTime();
838     }
839 
840     public static Date getSqlDateDay(int year, int month, int day)
841     {
842         return new java.sql.Date(getCalendarDay(year, month, day).getTimeInMillis());
843     }
844 
845 
846     public static Date tomorrow()
847     {
848         Calendar cal = Calendar.getInstance();
849         cal.add(Calendar.DAY_OF_MONTH, 1);
850         return cal.getTime();
851     }
852 
853     public static Date yesterday()
854     {
855         Calendar cal = Calendar.getInstance();
856         cal.add(Calendar.DAY_OF_MONTH, -1);
857         return cal.getTime();
858     }
859 
860     private static Calendar initCalendar(int year, int month, int day, int hour, int minute, int second, int millis)
861     {
862         Calendar calendar = Calendar.getInstance();
863         calendar.set(year, month, day, hour, minute, second);
864         calendar.set(Calendar.MILLISECOND, millis);
865         return calendar;
866     }
867 }