1
2
3
4
5
6
7
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
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
41
42
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
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
69 public static boolean equalTimestamps(Timestamp t1, Timestamp t2)
70 {
71 return (Math.abs(t1.getTime() - t2.getTime()) < 10L);
72 }
73
74
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
84
85
86
87
88
89
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
250
251
252
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
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285 public static long getDuration(String durationStr) throws InvalidDurationException
286 {
287 return getDurationSeconds(durationStr, DAY, WEEK);
288 }
289
290
291
292
293
294
295
296
297
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
326
327
328
329
330
331
332
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
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
366 {
367 time = Long.parseLong(durationStr.trim()) * MINUTE;
368 }
369 catch (Exception ex)
370 {
371
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 }
404 }
405 }
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
417
418
419
420
421
422
423 public static String getDurationString(long seconds)
424 {
425 return getDurationStringSeconds(seconds, DAY, WEEK);
426 }
427
428
429
430
431
432
433
434
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
496
497
498
499
500
501
502
503
504 public static String getDurationPretty(long numSecs, ResourceBundle resourceBundle)
505 {
506 return getDurationPrettySeconds(numSecs, DAY, WEEK, resourceBundle, false);
507 }
508
509
510
511
512
513
514
515
516
517
518
519
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
530
531
532
533
534
535
536
537 public static String getDurationPrettySecondsResolution(long numSecs, ResourceBundle resourceBundle)
538 {
539 return getDurationPrettySeconds(numSecs, DAY, WEEK, resourceBundle, true);
540 }
541
542
543
544
545
546
547
548
549
550
551
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
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
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)
699 {
700 return result.substring(0, result.length() - 2);
701 }
702 else
703 {
704 return result.toString();
705 }
706 }
707
708
709 public String formatDurationPretty(long l)
710 {
711 return DateUtils.getDurationPretty(l, resourceBundle);
712 }
713
714
715 public String formatDurationPretty(String seconds)
716 {
717 return DateUtils.getDurationPretty(Long.parseLong(seconds), resourceBundle);
718 }
719
720
721
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
742
743
744
745
746
747
748
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
777
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
813
814
815
816
817
818 public static DateRange toDateRange(Calendar date, int period)
819 {
820
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 }