001    // Copyright (c) 2001 Hursh Jain (http://www.mollypages.org) 
002    // The Molly framework is freely distributable under the terms of an
003    // MIT-style license. For details, see the molly pages web site at:
004    // http://www.mollypages.org/. Use, modify, have fun !
005    
006    package fc.util;
007    
008    import java.util.*;
009    import java.text.*;
010    import java.util.concurrent.*;
011    
012    public class CalendarUtil
013    {
014    private static final boolean debug = false;
015    
016    public static enum Duration 
017      { 
018      HOURLY,
019      DAILY, 
020      WEEKLY, 
021      MONTHLY;
022      
023      /** 
024      Returns the date representing start time + duration. Uses the default
025      calendar instance internally.
026      */
027      public static Date getDateAfterDuration(Date start, Duration duration) 
028        {
029        Calendar cal = Calendar.getInstance();
030        
031        Date endDate = null;
032        
033        switch (duration) {
034          case HOURLY:
035            endDate = CalendarUtil.getEndOfHour(cal, start);
036            break;
037          case DAILY:
038            endDate = CalendarUtil.getEndOfDay(cal, start);
039            break;
040          case WEEKLY:
041            endDate = CalendarUtil.getLastDayOfWeek(cal, start);
042            break;
043          case MONTHLY:
044            endDate = CalendarUtil.getLastDayOfMonth(cal, start);
045            break;
046          default: 
047            throw new IllegalArgumentException("Don't know how to handle this duration");
048          }
049          
050        return endDate;
051        }
052      } 
053    
054    /** Useful constant of 1 second (in milliseconds) */
055    public static final long ONE_SEC  = 1000;
056      
057    /** Useful constant of 1 minute (in milliseconds) */
058    public static final long ONE_MIN  = 1  * 60  * 1000;
059    
060    /** Useful constant of 2 minutes (in milliseconds) */
061    public static final long TWO_MIN  = 2  * 60  * 1000;
062    
063    /** Useful constant of five minutes (in milliseconds) */
064    public static final long FIVE_MIN   = 5  * 60  * 1000;
065    
066    /** Useful constant of ten minutes (in milliseconds) */
067    public static final long TEN_MIN  = 10 * 60 * 1000;
068    
069    /** Useful constant of thirty minutes (in milliseconds) */
070    public static final long THIRTY_MIN = 30 * 60 * 1000;
071    
072    /** Useful constant of one hour (in milliseconds) */
073    public static final long ONE_HOUR   = 60 * ONE_MIN;
074    
075    /** Useful constant of two hours (in milliseconds) */
076    public static final long TWO_HOUR   = 2  * ONE_HOUR;
077    
078    /** Useful constant of four hours (in milliseconds) */
079    public static final long FOUR_HOUR  = 4  * ONE_HOUR;
080    
081    /** Useful constant of eight hours (in milliseconds) */
082    public static final long EIGHT_HOUR = 8  * ONE_HOUR;
083    
084    /** Useful constant of twelve hours (in milliseconds) */
085    public static final long TWELVE_HOUR = 12  * ONE_HOUR;
086    
087    /** Useful constant of twenty four hours (in milliseconds) */
088    public static final long TWENTY_FOUR_HOUR = 24  * ONE_HOUR;
089    
090    /** Useful constant of 1 day (in milliseconds) */
091    public static final long ONE_DAY = TWENTY_FOUR_HOUR;
092    
093    /** Useful constant of 3 days (in milliseconds) */
094    public static final long THREE_DAY = 3 * ONE_DAY;
095    
096    /** Useful constant of 1 week (in milliseconds) */
097    public static final long ONE_WEEK = 7  * ONE_DAY;
098    
099    /** Useful constant of 2 weeks (in milliseconds) */
100    public static final long TWO_WEEK = 14  * ONE_DAY;
101    
102    /** Useful constant of 1 month (in milliseconds) [30 day month] */
103    public static final long ONE_MONTH = 30  * ONE_DAY;
104    
105    /** Useful constant of 2 months (in milliseconds) [30 day month] */
106    public static final long TWO_MONTH = 60  * ONE_DAY;
107    
108    /** Useful constant of 3 months (in milliseconds)[30 day month]  */
109    public static final long THREE_MONTH = 90  * ONE_DAY;
110    
111    /** Useful constant of 6 months (in milliseconds) [30 day month] */
112    public static final long SIX_MONTH = 180  * ONE_DAY;
113    
114    /** Useful constant of one year (in milliseconds) [365 days]*/
115    public static final long ONE_YEAR = 365  * ONE_DAY;
116    
117    /**
118    Sets the month in the specified calendar based on the specified 0-based
119    month number. There is no direct setMonth method in Calendar. Therefore,
120    this method is essentially a giant switch statement, like: 
121    <blockquote><pre>
122      case 0: 
123        cal.set(Calendar.MONTH, Calendar.JANUARY); 
124        break;
125      case 1: 
126        cal.set(Calendar.MONTH, Calendar.FEBRUARY); 
127        break;
128      ... etc...
129    </pre></blockquote>
130    */
131    public static final void setMonth(Calendar cal, int monthNum)  
132      { // 0-based
133      switch (monthNum)
134        {
135        case 0: cal.set(Calendar.MONTH, Calendar.JANUARY); break;
136        case 1: cal.set(Calendar.MONTH, Calendar.FEBRUARY); break;
137        case 2: cal.set(Calendar.MONTH, Calendar.MARCH); break;
138        case 3: cal.set(Calendar.MONTH, Calendar.APRIL); break;
139        case 4: cal.set(Calendar.MONTH, Calendar.MAY); break;
140        case 5: cal.set(Calendar.MONTH, Calendar.JUNE); break;
141        case 6: cal.set(Calendar.MONTH, Calendar.JULY); break;
142        case 7: cal.set(Calendar.MONTH, Calendar.AUGUST); break;
143        case 8: cal.set(Calendar.MONTH, Calendar.SEPTEMBER); break;
144        case 9: cal.set(Calendar.MONTH, Calendar.OCTOBER); break;
145        case 10: cal.set(Calendar.MONTH, Calendar.NOVEMBER); break;
146        case 11: cal.set(Calendar.MONTH, Calendar.DECEMBER); break;
147        default:
148          throw new RuntimeException("Month value out of range [" + monthNum + "]");
149        }
150      }
151    
152        
153    /**
154    Returns the date representing the addition/subtraction of a number of hours from the specified
155    starting timestamp. Specify a positive number for future/adding hours or negative for 
156    past/subtracing hours.
157    <p>
158    The state of the calendar is not affected by the calculations performed by this method.
159    */
160    public static Date addHours(Calendar cal, Date d, int hours) 
161      {
162      cal = (Calendar) cal.clone();  //don't affect underlying calendar
163      
164      cal.setTime(d);
165      
166      cal.add(Calendar.HOUR_OF_DAY, hours);
167    
168      return cal.getTime();
169      }
170    
171    
172    /**
173    Returns the closest hour, starting from the specified time and the specified calendar. The state of the calendar is not affected by the calculations performed by this method.
174    <p>
175    For example, any date with time component <tt>12.30pm</tt> returns the same date with a time component <tt>12.00pm</tt>.
176    */
177    public static Date getBeginOfHour(Calendar cal, Date d) 
178      {
179      cal = (Calendar) cal.clone();  //don't affect underlying calendar
180      
181      cal.setTime(d);
182    
183      //don't have to do anything with hour of day, we keep that hour 
184      //cal.roll(Calendar.HOUR_OF_DAY, -1);
185    
186      cal.clear(Calendar.MINUTE);
187      cal.clear(Calendar.SECOND);
188      cal.clear(Calendar.MILLISECOND);
189    
190      return cal.getTime();
191      }
192    
193    
194    /**
195    Returns the closest hour, starting from the current time and the specified calendar.
196    The state of the calendar is not affected by the calculations performed by this
197    method.
198    <p>
199    For example, any date with time component <tt>12.30pm</tt> returns the same date with a time component <tt>12.00pm</tt>.
200    */
201    public static Date getBeginOfCurrentHour(Calendar cal) 
202      {
203      return getBeginOfHour(cal, new Date());
204      }
205    
206    
207    /**
208    Returns the end of the hour, starting from the specified time and the specified calendar. The state of the calendar is not affected by the calculations performed by this method.
209    <p>
210    For example, any date with time component <tt>12.30pm</tt> returns the same date with a time component <tt>1.00pm</tt>.
211    */
212    public static Date getEndOfHour(Calendar cal, Date d) 
213      {
214      cal = (Calendar) cal.clone();  //don't affect underlying calendar
215      
216      cal.setTime(d);
217    
218      cal.roll(Calendar.HOUR_OF_DAY, 1);
219    
220      cal.clear(Calendar.MINUTE);
221      cal.clear(Calendar.SECOND);
222      cal.clear(Calendar.MILLISECOND);
223    
224      return cal.getTime();
225      }
226    
227    
228    /**
229    Returns the end of the hour, starting from the current time and the specified calendar. The state of the calendar is not affected by the calculations performed by this method.
230    <p>
231    For example, any date with time component <tt>12.30pm</tt> returns the same date with a time component <tt>1.00pm</tt>.
232    */
233    public static Date getEndOfCurrentHour(Calendar cal) 
234      {
235      return getEndOfHour(cal, new Date());
236      }
237        
238    /**
239    Returns the date representing the beginning of the specified day (at
240    12:00:00.000 AM), starting from the current time and the specified calendar.
241    The state of the calendar is not affected by the calculations performed by this
242    method.
243    */
244    public static Date getBeginOfDay(Calendar cal, Date d) 
245      {
246      cal = (Calendar) cal.clone();  //don't affect underlying calendar
247      
248      cal.setTime(d);
249    
250      cal.set(Calendar.HOUR_OF_DAY, 0); 
251      cal.clear(Calendar.MINUTE);
252      cal.clear(Calendar.SECOND);
253      cal.clear(Calendar.MILLISECOND);
254    
255      return cal.getTime();
256      }
257    
258    
259    /**
260    Returns the date representing the beginning of the current day (at 12:00:00.000
261    AM), starting from the current time and the specified calendar. The state of
262    the calendar is not affected by the calculations performed by this method.
263    */
264    public static Date getBeginOfCurrentDay(Calendar cal) 
265      {
266      return getBeginOfDay(cal, new Date());
267      }
268      
269    /**
270    Returns the date representing the end of the specified day (11:59:59.999 PM),
271    starting from the current time and the specified calendar. The state of the
272    calendar is not affected by the calculations performed by this method.
273    */
274    public static Date getEndOfDay(Calendar cal, Date d) 
275      {
276      cal = (Calendar) cal.clone();  //don't affect underlying calendar
277      
278      cal.setTime(d);
279    
280      cal.set(Calendar.HOUR_OF_DAY, 23); 
281      cal.set(Calendar.MINUTE, 59);
282      cal.set(Calendar.SECOND, 59);
283      cal.set(Calendar.MILLISECOND, 999);
284    
285      return cal.getTime();
286      }
287    
288    /**
289    Returns the date representing the end of the current day (11:59:59.999 PM),
290    starting from the current time and the specified calendar. The state of the
291    calendar is not affected by the calculations performed by this method.
292    */
293    public static Date getEndOfCurrentDay(Calendar cal) 
294      {
295      return getEndOfDay(cal, new Date());
296      }
297      
298    /**
299    Returns the date representing the first day of the week (at 12:00:00.000 AM),
300    using the specified date as the starting point of the week under consideration
301    and the specified calendar. The state of the calendar is not affected by the
302    calculations performed by this method. 
303    <p>
304    The first day of week can vary based on the locale associated with the specified
305    calendar (sunday in us, monday in fr, etc).
306    */
307    public static Date getFirstDayOfWeek(Calendar cal, Date d) 
308      {
309      cal = (Calendar) cal.clone();  //don't affect underlying calendar
310      
311      cal.setTime(d);
312    
313      cal.set(Calendar.HOUR_OF_DAY, 0); 
314      cal.clear(Calendar.MINUTE);
315      cal.clear(Calendar.SECOND);
316      cal.clear(Calendar.MILLISECOND);
317    
318      cal.set(Calendar.DAY_OF_WEEK, cal.getFirstDayOfWeek());
319    
320      return cal.getTime();
321      }
322    
323    /**
324    Returns the date representing first day of the week (at 12:00:00.000 AM), starting
325    from the current time and the specified calendar. The state of the calendar is
326    not affected by the calculations performed by this method.
327    */
328    public static Date getFirstDayOfCurrentWeek(Calendar cal) 
329      {
330      return getFirstDayOfWeek(cal, new Date());
331      }
332    
333    /**
334    Returns the date representing the last day of the week (at 11:59:59.999 PM)
335    using the specified date as the starting point of the week under consideration
336    and the specified calendar. The state of the calendar is not affected by the
337    calculations performed by this method.
338    <p>
339    The last day of week can vary based on the locale associated with the specified
340    calendar (saturday in us, sunday in fr, etc).
341    */
342    public static Date getLastDayOfWeek(Calendar cal, Date d) 
343      {
344      cal = (Calendar) cal.clone();  //don't affect underlying calendar
345      
346      cal.setTime(d);
347    
348      cal.set(Calendar.HOUR_OF_DAY, 23); 
349      cal.set(Calendar.MINUTE, 59);
350      cal.set(Calendar.SECOND, 59);
351      cal.set(Calendar.MILLISECOND, 999);
352    
353      cal.set(Calendar.DAY_OF_WEEK, cal.getFirstDayOfWeek() + 6);
354    
355      return cal.getTime();
356      }
357    
358    /**
359    Returns the date representing the last day of the week (11:59:59.999 PM), starting from the current
360    time and the specified calendar. The state of the calendar is not affected by
361    the calculations performed by this method.
362    */
363    public static Date getLastDayOfCurrentWeek(Calendar cal) 
364      {
365      return getLastDayOfWeek(cal, new Date());
366      }
367    
368    /**
369    Returns the first day of the specified month with the default locale;
370    */
371    public static Date getFirstDayOfMonth(Calendar cal, Date d) 
372      {
373      List list = getLastNMonths(cal, d, 1, Locale.getDefault());
374      return ((Date[])list.get(0))[0];
375      }
376    
377    /**
378    Returns the first day of the current month with the default locale;
379    */
380    public static Date getFirstDayOfCurrentMonth(Calendar cal) 
381      {
382      return getFirstDayOfMonth(cal, new Date());
383      }
384    
385    /**
386    Returns the last day of the specified month with the default locale;
387    */
388    public static Date getLastDayOfMonth(Calendar cal, Date d) 
389      {
390      List list = getLastNMonths(cal, d, 1, Locale.getDefault());
391      return ((Date[])list.get(0))[1];
392      }
393    
394    /**
395    Returns the last day of the current month with the default locale;
396    */
397    public static Date getLastDayOfCurrentMonth(Calendar cal) 
398      {
399      return getLastDayOfMonth(cal, new Date());
400      } 
401      
402    /**
403    Returns {#getLastNMonths} with the default locale;
404    */
405    public static List getLastNMonths(Calendar cal, int nummonths) 
406      {
407      return getLastNMonths(cal, new Date(), nummonths, Locale.getDefault());
408      }
409      
410    /**
411    Returns a List of Date[][], with each item being a month. The nummonths is the number
412    of months to return and the startingDate is the starting month, to count backwards
413    from. 
414    <p>
415    In the returned list, date[0] represents the first day, 12:00:00.000 AM and date[1] representing the last day, 11:59:59.999 PM. The starting item in the list is the nearest
416    month and each successive item is the prior month.
417    <p>
418    For example: To get last 6 months, say: <tt>getLastNMonths(cal, today, 6)</tt> where
419    cal is a calendar to use and today is today's date.
420    */
421    public static List getLastNMonths(Calendar cal, Date startingDate, int nummonths, Locale locale) 
422      {
423      DateFormatSymbols dfs = new DateFormatSymbols(locale);
424      String[] monthnames_short = dfs.getShortMonths();
425    
426      cal = (Calendar) cal.clone();  //don't affect underlying calendar
427    
428      cal.setTime(startingDate);
429      
430      if (debug) System.out.println("Current Month (0 based): " + cal.get(Calendar.MONTH));
431    
432      List list = new ArrayList();
433      for (int n = 0; n < nummonths; n++) 
434        {
435        int month = cal.get(Calendar.MONTH);
436    
437        int days_in_month = cal.getActualMaximum(Calendar.DAY_OF_MONTH);
438        if (debug) System.out.println("number of days in month: " + days_in_month);
439    
440        Date[] dates = new Date[2];
441    
442        //date[0], start of month
443        cal.set(Calendar.DAY_OF_MONTH, 1);
444        
445        cal.set(Calendar.HOUR_OF_DAY, 0); 
446        cal.clear(Calendar.MINUTE);
447        cal.clear(Calendar.SECOND);
448        cal.clear(Calendar.MILLISECOND);
449        
450        dates[0] = cal.getTime();
451        
452        //date[1], end of month
453        cal.set(Calendar.DAY_OF_MONTH, days_in_month);
454    
455        cal.set(Calendar.HOUR_OF_DAY, 23); 
456        cal.set(Calendar.MINUTE, 59);
457        cal.set(Calendar.SECOND, 59);
458        cal.set(Calendar.MILLISECOND, 999);
459    
460        dates[1] = cal.getTime();
461        list.add(dates);
462        
463        cal.roll(Calendar.MONTH, -1);
464        }
465      
466      return list;
467      }
468    
469    //#mark -- other misc --
470    
471    /**
472    returns true if the specified time is within the specified days, starting from 
473    the current time.
474    */
475    public static boolean withinLastNDays(long time, int n_days)
476      {
477      return System.currentTimeMillis() - time <= n_days * CalendarUtil.ONE_DAY;
478      }
479     
480    /**
481    returns true if the specified time is within the specified days, starting from 
482    the current time.
483    */
484    public static boolean withinLastNDays(Date time, int n_days)
485      {
486      return System.currentTimeMillis() - time.getTime() <= n_days * CalendarUtil.ONE_DAY;
487      } 
488    
489    /**
490    returns true if the specified time is within the specified hours, starting from 
491    the current time.
492    */
493    public static boolean withinLastNHours(long time, int n_hours)
494      {
495      return System.currentTimeMillis() - time <= n_hours * CalendarUtil.ONE_HOUR;
496      }
497    
498    /**
499    returns true if the specified time is within the specified hours, starting from 
500    the current time.
501    */
502    public static boolean withinLastNHours(Date time, int n_hours)
503      {
504      return System.currentTimeMillis() - time.getTime() <= n_hours * CalendarUtil.ONE_HOUR;
505      }
506    
507    
508    /**
509    returns true if the specified time is within the specified min, starting from 
510    the current time.
511    */
512    public static boolean withinLastNMin(Date time, int n_min)
513      {
514      return System.currentTimeMillis() - time.getTime() <= n_min * CalendarUtil.ONE_MIN;
515      }
516    
517    
518    /**
519    returns true if the specified time is within the specified seconds, starting from 
520    the current time.
521    */
522    public static boolean withinLastNSeconds(Date time, int n_seconds)
523      {
524      return System.currentTimeMillis() - time.getTime() <= n_seconds * 1000;
525      }
526      
527      
528    /**
529    returns the number of millis in the specified number of days
530    */
531    public static long daysToMillis(int days)
532      {
533      return days * CalendarUtil.ONE_DAY;
534      } 
535    
536    /**
537    returns the number of millis in the specified number of hours
538    */
539    public static long hoursToMillis(int hours)
540      {
541      return hours * CalendarUtil.ONE_HOUR;
542      } 
543    
544    /**
545    returns the number of millis in the specified hours
546    */
547    public static long minutesToMillis(int minutes)
548      {
549      return minutes * CalendarUtil.ONE_MIN;
550      } 
551    
552    
553    /**
554    returns number of hours between begin and end timestamps. the
555    order of begin/end is irrelevant, uses absolute value of difference
556    between the two for the calculation.
557    */
558    public static int hoursBetween(Date begin, Date end)
559      {
560      final long diff = Math.abs(begin.getTime() - end.getTime());
561      return (int) TimeUnit.HOURS.convert(diff, TimeUnit.MILLISECONDS);
562      }
563    
564    
565    public static void main (String args[])  throws Exception
566      {
567      Args myargs = new Args(args);
568      ToString.Style style = new ToString.Style();
569      style.reflectStatics = true;
570      System.out.println(
571        new ToString(new CalendarUtil(), style).reflect().render());
572            
573      Calendar cal = Calendar.getInstance();
574      Date d = new Date();
575      if (myargs.flagExists("date")) {
576        d = DateFormat.getDateInstance(DateFormat.SHORT)
577            .parse(myargs.get("date"));
578        }
579    
580      System.out.println("========== hour =========");
581      System.out.println("beginOfHour: " + getBeginOfHour(cal, d));
582      System.out.println("endOfHour: " + getEndOfHour(cal, d));
583      System.out.println("beginOfCurrentHour: " + getBeginOfCurrentHour(cal));
584      System.out.println("endOfCurrentHour: " + getEndOfCurrentHour(cal));
585    
586      System.out.println("========== day =========");
587      System.out.println("beginOfDay: " + getBeginOfDay(cal, d));
588      System.out.println("endOfDay: " + getEndOfDay(cal, d));
589      System.out.println("beginOfCurrentDay: " + getBeginOfCurrentDay(cal));
590      System.out.println("endOfCurrentDay: " + getEndOfCurrentDay(cal));
591    
592      System.out.println("========== week =========");  
593      System.out.println("firstDayOfWeek: " + getFirstDayOfWeek(cal, d));
594      System.out.println("lastDayOfWeek: " + getLastDayOfWeek(cal, d));
595      System.out.println("firstDayOfCurrentWeek: " + getFirstDayOfCurrentWeek(cal));
596      System.out.println("lastDayOfCurrentWeek: " + getLastDayOfCurrentWeek(cal));
597    
598      System.out.println("========== month =========");
599      System.out.println("firstDayOfMonth: " + getFirstDayOfMonth(cal,d));
600      System.out.println("lastDayOfMonth: " + getLastDayOfMonth(cal,d));
601      System.out.println("firstDayOfCurrentMonth: " + getFirstDayOfCurrentMonth(cal));
602      System.out.println("lastDayOfCurrentMonth: " + getLastDayOfCurrentMonth(cal));
603    
604      System.out.println("==== lastNMonths(6) ======");
605      System.out.println(Arrays.deepToString(getLastNMonths(cal, d, 6, Locale.getDefault()).toArray()));
606      }
607    }
608