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
006package fc.util;
007
008import java.util.*;
009import java.text.*;
010import java.util.concurrent.*;
011
012public final class CalendarUtil
013{
014private static final boolean debug = false;
015
016public 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) */
055public static final long ONE_SEC  = 1000;
056  
057/** Useful constant of 1 minute (in milliseconds) */
058public static final long ONE_MIN  = 1  * 60  * 1000;
059
060/** Useful constant of 2 minutes (in milliseconds) */
061public static final long TWO_MIN  = 2  * 60  * 1000;
062
063/** Useful constant of five minutes (in milliseconds) */
064public static final long FIVE_MIN   = 5  * 60  * 1000;
065
066/** Useful constant of ten minutes (in milliseconds) */
067public static final long TEN_MIN  = 10 * 60 * 1000;
068
069/** Useful constant of thirty minutes (in milliseconds) */
070public static final long THIRTY_MIN = 30 * 60 * 1000;
071
072/** Useful constant of one hour (in milliseconds) */
073public static final long ONE_HOUR   = 60 * ONE_MIN;
074
075/** Useful constant of two hours (in milliseconds) */
076public static final long TWO_HOUR   = 2  * ONE_HOUR;
077
078/** Useful constant of four hours (in milliseconds) */
079public static final long FOUR_HOUR  = 4  * ONE_HOUR;
080
081/** Useful constant of eight hours (in milliseconds) */
082public static final long EIGHT_HOUR = 8  * ONE_HOUR;
083
084/** Useful constant of twelve hours (in milliseconds) */
085public static final long TWELVE_HOUR = 12  * ONE_HOUR;
086
087/** Useful constant of twenty four hours (in milliseconds) */
088public static final long TWENTY_FOUR_HOUR = 24  * ONE_HOUR;
089
090/** Useful constant of 1 day (in milliseconds) */
091public static final long ONE_DAY = TWENTY_FOUR_HOUR;
092
093/** Useful constant of 3 days (in milliseconds) */
094public static final long THREE_DAY = 3 * ONE_DAY;
095
096/** Useful constant of 1 week (in milliseconds) */
097public static final long ONE_WEEK = 7  * ONE_DAY;
098
099/** Useful constant of 2 weeks (in milliseconds) */
100public static final long TWO_WEEK = 14  * ONE_DAY;
101
102/** Useful constant of 1 month (in milliseconds) [30 day month] */
103public static final long ONE_MONTH = 30  * ONE_DAY;
104
105/** Useful constant of 2 months (in milliseconds) [30 day month] */
106public static final long TWO_MONTH = 60  * ONE_DAY;
107
108/** Useful constant of 3 months (in milliseconds)[30 day month]  */
109public static final long THREE_MONTH = 90  * ONE_DAY;
110
111/** Useful constant of 6 months (in milliseconds) [30 day month] */
112public static final long SIX_MONTH = 180  * ONE_DAY;
113
114/** Useful constant of one year (in milliseconds) [365 days]*/
115public static final long ONE_YEAR = 365  * ONE_DAY;
116
117/**
118Sets the month in the specified calendar based on the specified 0-based
119month number. There is no direct setMonth method in Calendar. Therefore,
120this 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*/
131public 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/**
154Returns the date representing the addition/subtraction of a number of hours from the specified
155starting timestamp. Specify a positive number for future/adding hours or negative for 
156past/subtracing hours.
157<p>
158The state of the calendar is not affected by the calculations performed by this method.
159*/
160public 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/**
173Returns 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>
175For example, any date with time component <tt>12.30pm</tt> returns the same date with a time component <tt>12.00pm</tt>.
176*/
177public 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/**
195Returns the closest hour, starting from the current time and the specified calendar.
196The state of the calendar is not affected by the calculations performed by this
197method.
198<p>
199For example, any date with time component <tt>12.30pm</tt> returns the same date with a time component <tt>12.00pm</tt>.
200*/
201public static Date getBeginOfCurrentHour(Calendar cal) 
202  {
203  return getBeginOfHour(cal, new Date());
204  }
205
206
207/**
208Returns 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>
210For example, any date with time component <tt>12.30pm</tt> returns the same date with a time component <tt>1.00pm</tt>.
211*/
212public 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/**
229Returns 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>
231For example, any date with time component <tt>12.30pm</tt> returns the same date with a time component <tt>1.00pm</tt>.
232*/
233public static Date getEndOfCurrentHour(Calendar cal) 
234  {
235  return getEndOfHour(cal, new Date());
236  }
237    
238/**
239Returns the date representing the beginning of the specified day (at
24012:00:00.000 AM), starting from the current time and the specified calendar.
241The state of the calendar is not affected by the calculations performed by this
242method.
243*/
244public 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/**
260Returns the date representing the beginning of the current day (at 12:00:00.000
261AM), starting from the current time and the specified calendar. The state of
262the calendar is not affected by the calculations performed by this method.
263*/
264public static Date getBeginOfCurrentDay(Calendar cal) 
265  {
266  return getBeginOfDay(cal, new Date());
267  }
268  
269/**
270Returns the date representing the end of the specified day (11:59:59.999 PM),
271starting from the current time and the specified calendar. The state of the
272calendar is not affected by the calculations performed by this method.
273*/
274public 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/**
289Returns the date representing the end of the current day (11:59:59.999 PM),
290starting from the current time and the specified calendar. The state of the
291calendar is not affected by the calculations performed by this method.
292*/
293public static Date getEndOfCurrentDay(Calendar cal) 
294  {
295  return getEndOfDay(cal, new Date());
296  }
297  
298/**
299Returns the date representing the first day of the week (at 12:00:00.000 AM),
300using the specified date as the starting point of the week under consideration
301and the specified calendar. The state of the calendar is not affected by the
302calculations performed by this method. 
303<p>
304The first day of week can vary based on the locale associated with the specified
305calendar (sunday in us, monday in fr, etc).
306*/
307public 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/**
324Returns the date representing first day of the week (at 12:00:00.000 AM), starting
325from the current time and the specified calendar. The state of the calendar is
326not affected by the calculations performed by this method.
327*/
328public static Date getFirstDayOfCurrentWeek(Calendar cal) 
329  {
330  return getFirstDayOfWeek(cal, new Date());
331  }
332
333/**
334Returns the date representing the last day of the week (at 11:59:59.999 PM)
335using the specified date as the starting point of the week under consideration
336and the specified calendar. The state of the calendar is not affected by the
337calculations performed by this method.
338<p>
339The last day of week can vary based on the locale associated with the specified
340calendar (saturday in us, sunday in fr, etc).
341*/
342public 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/**
359Returns the date representing the last day of the week (11:59:59.999 PM), starting from the current
360time and the specified calendar. The state of the calendar is not affected by
361the calculations performed by this method.
362*/
363public static Date getLastDayOfCurrentWeek(Calendar cal) 
364  {
365  return getLastDayOfWeek(cal, new Date());
366  }
367
368/**
369Returns the first day of the specified month with the default locale;
370*/
371public 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/**
378Returns the first day of the current month with the default locale;
379*/
380public static Date getFirstDayOfCurrentMonth(Calendar cal) 
381  {
382  return getFirstDayOfMonth(cal, new Date());
383  }
384
385/**
386Returns the last day of the specified month with the default locale;
387*/
388public 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/**
395Returns the last day of the current month with the default locale;
396*/
397public static Date getLastDayOfCurrentMonth(Calendar cal) 
398  {
399  return getLastDayOfMonth(cal, new Date());
400  } 
401  
402/**
403Returns {#getLastNMonths} with the default locale;
404*/
405public static List getLastNMonths(Calendar cal, int nummonths) 
406  {
407  return getLastNMonths(cal, new Date(), nummonths, Locale.getDefault());
408  }
409  
410/**
411Returns a List of Date[][], with each item being a month. The nummonths is the number
412of months to return and the startingDate is the starting month, to count backwards
413from. 
414<p>
415In 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
416month and each successive item is the prior month.
417<p>
418For example: To get last 6 months, say: <tt>getLastNMonths(cal, today, 6)</tt> where
419cal is a calendar to use and today is today's date.
420*/
421public 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/**
472returns true if the specified time is within the specified days, starting from 
473the current time.
474*/
475public static boolean withinLastNDays(long time, int n_days)
476  {
477  return System.currentTimeMillis() - time <= n_days * CalendarUtil.ONE_DAY;
478  }
479 
480/**
481returns true if the specified time is within the specified days, starting from 
482the current time.
483*/
484public static boolean withinLastNDays(Date time, int n_days)
485  {
486  return System.currentTimeMillis() - time.getTime() <= n_days * CalendarUtil.ONE_DAY;
487  } 
488
489/**
490returns true if the specified time is within the specified hours, starting from 
491the current time.
492*/
493public static boolean withinLastNHours(long time, int n_hours)
494  {
495  return System.currentTimeMillis() - time <= n_hours * CalendarUtil.ONE_HOUR;
496  }
497
498/**
499returns true if the specified time is within the specified hours, starting from 
500the current time.
501*/
502public static boolean withinLastNHours(Date time, int n_hours)
503  {
504  return System.currentTimeMillis() - time.getTime() <= n_hours * CalendarUtil.ONE_HOUR;
505  }
506
507
508/**
509returns true if the specified time is within the specified min, starting from 
510the current time.
511*/
512public static boolean withinLastNMin(Date time, int n_min)
513  {
514  return System.currentTimeMillis() - time.getTime() <= n_min * CalendarUtil.ONE_MIN;
515  }
516
517
518/**
519returns true if the specified time is within the specified seconds, starting from 
520the current time.
521*/
522public static boolean withinLastNSeconds(Date time, int n_seconds)
523  {
524  return System.currentTimeMillis() - time.getTime() <= n_seconds * 1000;
525  }
526  
527  
528/**
529returns the number of millis in the specified number of days
530*/
531public static long daysToMillis(int days)
532  {
533  return days * CalendarUtil.ONE_DAY;
534  } 
535
536/**
537returns the number of millis in the specified number of hours
538*/
539public static long hoursToMillis(int hours)
540  {
541  return hours * CalendarUtil.ONE_HOUR;
542  } 
543
544/**
545returns the number of millis in the specified hours
546*/
547public static long minutesToMillis(int minutes)
548  {
549  return minutes * CalendarUtil.ONE_MIN;
550  } 
551
552
553/**
554returns number of hours between begin and end timestamps. the
555order of begin/end is irrelevant, uses absolute value of difference
556between the two for the calculation.
557*/
558public 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/**
565returns now as java.sql.Timestamp
566*/
567public static java.sql.Timestamp getNowTimestamp()
568  {
569  return new java.sql.Timestamp(System.currentTimeMillis());
570  } 
571
572/**
573returns now as java.sql.Timestamp
574*/
575public static java.sql.Timestamp now()
576  {
577  return getNowTimestamp();
578  } 
579
580public static void main (String args[])  throws Exception
581  {
582  Args myargs = new Args(args);
583  ToString.Style style = new ToString.Style();
584  style.reflectStatics = true;
585  System.out.println(
586    new ToString(new CalendarUtil(), style).reflect().render());
587        
588  Calendar cal = Calendar.getInstance();
589  Date d = new Date();
590  if (myargs.flagExists("date")) {
591    d = DateFormat.getDateInstance(DateFormat.SHORT)
592        .parse(myargs.get("date"));
593    }
594
595  System.out.println("========== hour =========");
596  System.out.println("beginOfHour: " + getBeginOfHour(cal, d));
597  System.out.println("endOfHour: " + getEndOfHour(cal, d));
598  System.out.println("beginOfCurrentHour: " + getBeginOfCurrentHour(cal));
599  System.out.println("endOfCurrentHour: " + getEndOfCurrentHour(cal));
600
601  System.out.println("========== day =========");
602  System.out.println("beginOfDay: " + getBeginOfDay(cal, d));
603  System.out.println("endOfDay: " + getEndOfDay(cal, d));
604  System.out.println("beginOfCurrentDay: " + getBeginOfCurrentDay(cal));
605  System.out.println("endOfCurrentDay: " + getEndOfCurrentDay(cal));
606
607  System.out.println("========== week =========");  
608  System.out.println("firstDayOfWeek: " + getFirstDayOfWeek(cal, d));
609  System.out.println("lastDayOfWeek: " + getLastDayOfWeek(cal, d));
610  System.out.println("firstDayOfCurrentWeek: " + getFirstDayOfCurrentWeek(cal));
611  System.out.println("lastDayOfCurrentWeek: " + getLastDayOfCurrentWeek(cal));
612
613  System.out.println("========== month =========");
614  System.out.println("firstDayOfMonth: " + getFirstDayOfMonth(cal,d));
615  System.out.println("lastDayOfMonth: " + getLastDayOfMonth(cal,d));
616  System.out.println("firstDayOfCurrentMonth: " + getFirstDayOfCurrentMonth(cal));
617  System.out.println("lastDayOfCurrentMonth: " + getLastDayOfCurrentMonth(cal));
618
619  System.out.println("==== lastNMonths(6) ======");
620  System.out.println(Arrays.deepToString(getLastNMonths(cal, d, 6, Locale.getDefault()).toArray()));
621  }
622}
623