001package arcommon;
002
003import java.text.ParseException;
004import java.text.SimpleDateFormat;
005import java.util.Calendar;
006import java.util.Date;
007import java.util.GregorianCalendar;
008import java.util.regex.Pattern;
009
010public class DateUtil {
011    public static final SimpleDateFormat DATE_FORMAT = createSimpleDateFormatLenient("yyyy-MM-dd");
012    public static final Pattern DATE_FORMAT_PATTERN = calculatePattern(DATE_FORMAT);
013    public static final SimpleDateFormat DATE_FORMAT_DANISH = createSimpleDateFormatLenient("dd-MM-yyyy");
014    public static final SimpleDateFormat DATE_FORMAT_WITHOUT_YEAR = createSimpleDateFormatLenient("dd-MM");
015    public static final SimpleDateFormat DATE_FORMAT_YEAR_ONLY = createSimpleDateFormatLenient("yyyy");
016    public static final SimpleDateFormat TIME_FORMAT = createSimpleDateFormatLenient("HH:mm:ss");
017    public static final SimpleDateFormat DATE_AND_TIME_FORMAT = createSimpleDateFormatLenient("yyyy-MM-dd HH:mm:ss");
018    public static final SimpleDateFormat DATE_AND_TIME_WITH_MILLISECONDS_FORMAT = createSimpleDateFormatLenient("yyyy-MM-dd HH:mm:ss SSS");
019    public static final SimpleDateFormat WEEKDAY_FORMAT = createSimpleDateFormatLenient("EEEEEEEE");
020    public static final SimpleDateFormat WEEK_NUMBER_FORMAT = createSimpleDateFormatLenient("w");
021
022    public static final long MILLISECOND_IN_NANOSECONDS = 1000000;
023    public static final int SECOND_IN_MILLISECONDS = 1000;
024    public static final long SECOND_IN_NANOSECONDS = SECOND_IN_MILLISECONDS * MILLISECOND_IN_NANOSECONDS;
025    public static final int MINUTE_IN_SECONDS = 60;
026    public static final int MINUTE_IN_MILLISECONDS = SECOND_IN_MILLISECONDS * MINUTE_IN_SECONDS;
027    public static final long MINUTE_IN_NANOSECONDS = SECOND_IN_NANOSECONDS * MINUTE_IN_SECONDS;
028    public static final int HOUR_IN_MINUTES = 60;
029    public static final int HOUR_IN_SECONDS = HOUR_IN_MINUTES * MINUTE_IN_SECONDS;
030    public static final long HOUR_IN_MILLISECONDS = MINUTE_IN_MILLISECONDS * HOUR_IN_MINUTES;
031    public static final long HOUR_IN_NANOSECONDS = MINUTE_IN_NANOSECONDS * HOUR_IN_MINUTES;
032    public static final int DAY_IN_HOURS = 24;
033    public static final int DAY_IN_SECONDS = DAY_IN_HOURS * HOUR_IN_SECONDS;
034    public static final long DAY_IN_MILLISECONDS = HOUR_IN_MILLISECONDS * DAY_IN_HOURS;
035    public static final long DAY_IN_NANOSECONDS = HOUR_IN_NANOSECONDS * DAY_IN_HOURS;
036    public static final int YEAR_IN_DAYS = 365;
037    public static final int YEAR_IN_MONTHS = 12;
038    public static final long YEAR_IN_SECONDS = YEAR_IN_DAYS * DAY_IN_SECONDS;
039    public static final long YEAR_IN_MILLISECONDS = DAY_IN_MILLISECONDS * YEAR_IN_DAYS;
040    public static final long YEAR_IN_NANOSECONDS = DAY_IN_NANOSECONDS * YEAR_IN_DAYS;
041    
042    public static final long MONTHS_INTERVAL = -1;
043    
044    public static int calculateWeekday(Date date) {
045        Calendar c = Calendar.getInstance();
046        c.setTime(date);
047        int dayOfWeek = c.get(Calendar.DAY_OF_WEEK);
048        return dayOfWeek;
049    }
050    
051    public static int calculateWeekNumber(Date date) {
052        String string = dateToString(WEEK_NUMBER_FORMAT, date);
053        int week = Integer.parseInt(string);
054        return week;
055    }
056    
057    public static int calculateYear(Date date) {
058        String string = dateToString(DATE_FORMAT_YEAR_ONLY, date);
059        int year = Integer.parseInt(string);
060        return year;
061    }
062    
063    public static String dateToDanishString(Date date) {
064        return dateToString(DATE_FORMAT_DANISH, date);
065    }
066
067    public static String dateToDateSortableString(Date date) {
068        return dateToString(DATE_FORMAT, date);
069    }
070
071    public static String dateToStringWithoutYear(Date date) {
072        return dateToString(DATE_FORMAT_WITHOUT_YEAR, date);
073    }
074    
075    public static String dateToDateAndTimeString(Date date) {
076        return dateToString(DATE_AND_TIME_FORMAT, date);
077    }
078    
079    public static String dateToTimeString(Date date) {
080        return dateToString(TIME_FORMAT, date);
081    }
082
083    public static String dateToWeekdayWithDateString(Date date, String delimiter) {
084        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("EEEEEEEE'" + delimiter + "'dd-MM-yyyy");
085        return simpleDateFormat.format(date);
086    }
087
088    public static String dateToWeekdayString(Date date) {
089        return dateToString(WEEKDAY_FORMAT, date);
090    }
091    
092    public static String dateToString(SimpleDateFormat format, Date date) {
093        synchronized (format) { // To make it thread safe
094            return format.format(date);
095        }
096    }
097    
098    public static boolean isStringADate(SimpleDateFormat format, String string) {
099        try {
100            Date date = stringToDate(format, string, false);
101            return date != null;
102        } catch (ParseException e) {
103            throw new RuntimeException(e);
104        }
105    }
106    
107    public static Date stringToDate(SimpleDateFormat format, String string) {
108        try {
109            return stringToDate(format, string, true);
110        } catch (ParseException e) {
111            throw new RuntimeException(e);
112        }
113    }
114    
115    public static Date stringToDate(SimpleDateFormat format, String string, boolean throwOnFail) throws ParseException {
116        synchronized (format) { // To make it thread safe
117            try {
118                return format.parse(string);
119            } catch (ParseException e) {
120                if (throwOnFail) {
121                    throw e;
122                }
123                return null;
124            }
125        }
126    }
127    
128    public static SimpleDateFormat createSimpleDateFormatLenient(String dateFormat) {
129        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(dateFormat);
130        simpleDateFormat.setLenient(false);
131        return simpleDateFormat;
132    }
133
134    private static Pattern calculatePattern(SimpleDateFormat dateFormat) {
135        String patternString = dateFormat.toPattern();
136        patternString = patternString.replace("yyyy", "\\d{4}");
137        patternString = patternString.replace("MM", "\\d{1,2}");
138        patternString = patternString.replace("dd", "\\d{1,2}");
139        return Pattern.compile(patternString);
140    }
141
142    public static String dateIntervalToDateString(Date date, boolean sortable, long interval) {
143        String result;
144        
145        // Years
146        if (interval >= YEAR_IN_MILLISECONDS) {
147            if (interval % YEAR_IN_MILLISECONDS != 0) {
148                throw new RuntimeException("interval % YEAR_IN_MILLISECONDS != 0");
149            }
150            long dateLong = date.getTime();
151            long year = calculateYear(new Date(dateLong));
152            interval = interval / YEAR_IN_MILLISECONDS;
153            year = (year / interval) * interval;
154            if (interval > 1) {
155                if (sortable) {
156                    result = year + "-01-01";
157                } else {
158                    result = "01-01-" + year;
159                }
160                result = result + "  -  ";
161                year = year + interval - 1;
162                if (sortable) {
163                    result = result + year + "-12-31";
164                } else {
165                    result = result + "31-12-" + year;
166                }
167            } else {
168                result = "" + year;
169            }
170            return result;
171        }
172
173        if (interval <= MONTHS_INTERVAL && interval % YEAR_IN_MONTHS == 0) {
174            interval = YEAR_IN_MILLISECONDS * (-interval / YEAR_IN_MONTHS);
175        }
176        
177        // Months
178        if (interval <= MONTHS_INTERVAL) {
179            interval = -interval;
180            Calendar calendar = Calendar.getInstance();
181            calendar.setTime(date);
182            int year = calendar.get(Calendar.YEAR);
183            int month = calendar.get(Calendar.MONTH) + 1;
184            calendar.set(Calendar.DAY_OF_MONTH, 1);
185            int yearMonth = year * 12 + month;
186            int yearMonthInterval = yearMonth / (int)interval * (int)interval + 1;
187            if (yearMonthInterval != yearMonth) {
188                calendar.add(Calendar.MONTH, yearMonthInterval - yearMonth);
189            }
190            year = calendar.get(Calendar.YEAR);
191            month = calendar.get(Calendar.MONTH) + 1;
192            result = StringUtil.longToStringWithFixLengthWith0(year, 4);
193            result = result + "-" + StringUtil.longToStringWithFixLengthWith0(month, 2);
194            if (interval > 1) {
195                result = result + "-01";
196                calendar.set(Calendar.MONTH, month + (int)interval - 1);
197                calendar.set(Calendar.DAY_OF_MONTH, 1);
198                calendar.add(Calendar.DAY_OF_MONTH, -1);
199                year = calendar.get(Calendar.YEAR);
200                month = calendar.get(Calendar.MONTH) + 1;
201                result = result + " - " + StringUtil.longToStringWithFixLengthWith0(year, 4);
202                result = result + "-" + StringUtil.longToStringWithFixLengthWith0(month, 2);
203                result = result + "-" + StringUtil.longToStringWithFixLengthWith0(calendar.get(Calendar.DAY_OF_MONTH), 2);
204            }
205            return result;
206        }
207        
208        // Days
209        if (interval >= DAY_IN_MILLISECONDS && interval < DAY_IN_MILLISECONDS * YEAR_IN_DAYS) {
210            if (interval % DAY_IN_MILLISECONDS != 0) {
211                throw new RuntimeException("interval % DAY_IN_MILLISECONDS != 0");
212            }
213            long dateLong = date.getTime();
214            dateLong = (dateLong / interval) * interval;
215            if (sortable) {
216                result = dateToDateSortableString(new Date(dateLong));
217            } else {
218                result = dateToDanishString(new Date(dateLong));
219            }
220            if (interval / DAY_IN_MILLISECONDS > 1) {
221                dateLong += interval - DAY_IN_MILLISECONDS;
222                result = result + " 00:00 - ";
223                if (sortable) {
224                    result = result + dateToDateSortableString(new Date(dateLong));
225                } else {
226                    result = result + dateToDanishString(new Date(dateLong));
227                }
228                result = result + " 23:59";
229            }
230            return result;
231        }
232        
233        if (sortable) {
234            result = dateToDateSortableString(date);
235        } else {
236            result = dateToDanishString(date);
237        }
238        Calendar calendar = Calendar.getInstance();
239        calendar.setTime(date);
240        
241        // Hours
242        if (interval >= HOUR_IN_MILLISECONDS && interval < DAY_IN_MILLISECONDS) {
243            if (interval % HOUR_IN_MILLISECONDS != 0) {
244                throw new RuntimeException("interval % HOUR_IN_MILLISECONDS != 0");
245            }
246            int hour = calendar.get(Calendar.HOUR_OF_DAY);
247            interval = interval / HOUR_IN_MILLISECONDS;
248            if (DAY_IN_HOURS % interval != 0) {
249                throw new RuntimeException("DAY_IN_HOURS % interval != 0  " + DAY_IN_HOURS % interval + "   " + interval);
250            }
251            result = result + " " + StringUtil.longToStringWithFixLengthWith0((hour / interval) * interval, 2) + ":00";
252            if (interval > 1) {
253                result = result + " - ";
254                result = result + StringUtil.longToStringWithFixLengthWith0((hour / interval + 1) * interval - 1, 2) + ":59";
255            }
256            return result;
257        }
258
259        // Minutes
260        if (interval >= MINUTE_IN_MILLISECONDS && interval < HOUR_IN_MILLISECONDS) {
261            String hour = StringUtil.longToStringWithFixLengthWith0(calendar.get(Calendar.HOUR_OF_DAY), 2);
262            int minute = calendar.get(Calendar.MINUTE);
263            if (interval % MINUTE_IN_MILLISECONDS != 0) {
264                throw new RuntimeException("interval % MINUTE_IN_MILLISECONDS != 0");
265            }
266            interval = interval / MINUTE_IN_MILLISECONDS;
267            if (HOUR_IN_MINUTES % interval != 0) {
268                throw new RuntimeException("HOUR_IN_MINUTES % interval != 0  " + HOUR_IN_MINUTES % interval + "   " + interval);
269            }
270            result = result + " " + hour + ":" + StringUtil.longToStringWithFixLengthWith0(minute / interval * interval, 2);
271            if (interval > 1) {
272                result = result + " - " + hour + ":";
273                result = result + StringUtil.longToStringWithFixLengthWith0((minute / interval + 1) * interval - 1, 2);
274            }
275            return result;
276        }
277        
278        // Seconds
279        if (interval >= SECOND_IN_MILLISECONDS && interval < MINUTE_IN_MILLISECONDS) {
280            String hour = StringUtil.longToStringWithFixLengthWith0(calendar.get(Calendar.HOUR_OF_DAY), 2);
281            String minute = StringUtil.longToStringWithFixLengthWith0(calendar.get(Calendar.MINUTE), 2);
282            int second = calendar.get(Calendar.SECOND);
283            if (interval % SECOND_IN_MILLISECONDS != 0) {
284                throw new RuntimeException("interval % SECOND_IN_MILLISECONDS != 0   " + interval % SECOND_IN_MILLISECONDS);
285            }
286            interval = interval / SECOND_IN_MILLISECONDS;
287            if (MINUTE_IN_SECONDS % interval != 0) {
288                throw new RuntimeException("MINUTE_IN_SECONDS % interval != 0   " + MINUTE_IN_SECONDS % interval + "   " + interval);
289            }
290            result = result + " " + hour + ":" + minute + ":" + StringUtil.longToStringWithFixLengthWith0(second / interval * interval, 2);
291            if (interval > 1) {
292                result = result + " - " + hour + ":" + minute + ":";
293                result = result + StringUtil.longToStringWithFixLengthWith0((second / interval + 1) * interval - 1, 2);
294            }
295            return result;
296        }
297        
298        throw new RuntimeException("Internal error: " + interval);
299    }
300
301    public static String nanoTimeDiffToString(long timeInNano, boolean withUnit) {
302        int sign = MathUtil.signum(timeInNano);
303        long nano = Math.abs(timeInNano);
304        long micro = nano / 1000;
305        long milliseconds = micro / 1000;
306        long seconds = milliseconds / 1000;
307        long minutes = seconds / 60;
308        long hours = minutes / 60;
309        long days = hours / 24;
310        nano = nano % 1000;
311        micro = micro % 1000;
312        milliseconds = milliseconds % 1000;
313        seconds = seconds % 60;
314        minutes = minutes % 60;
315        hours = hours % 24;
316        String result = "";
317        String unit = null;
318        
319        if (days > 0) {
320            result = result + days + " ";
321            unit = "day" + (days != 1 ? "s" : "");
322        }
323        
324        if (hours > 0 || result.length() > 0) {
325            if (result.length() > 0) {
326                result = result + StringUtil.longToStringWithFixLengthWith0(hours, 2) + ":";
327            } else {
328                result = result + hours + ":";
329            }
330            if (unit == null) {
331                unit = "hour" + (hours != 1 ? "s" : "");
332            }
333        }
334        
335        if (minutes > 0 || result.length() > 0) {
336            if (result.length() > 0) {
337                result = result + StringUtil.longToStringWithFixLengthWith0(minutes, 2) + ":";
338            } else {
339                result = result + minutes + ":";
340            }
341            if (unit == null) {
342                unit = "minute" + (minutes != 1 ? "s" : "");
343            }
344        }
345        
346        if (seconds > 0 || result.length() > 0) {
347            if (result.length() > 0) {
348                result = result + StringUtil.longToStringWithFixLengthWith0(seconds, 2);
349            } else {
350                result = result + seconds;
351                if (seconds <= 9 && milliseconds > 0) {
352                    result = result + "," + StringUtil.longToStringWithFixLengthWith0(milliseconds, 3);
353                }
354            }
355            if (unit == null) {
356                unit = "second" + (seconds != 1 ? "s" : "");
357            }
358        }
359        
360        if (milliseconds > 0 && result.length() == 0) {
361            result = "" + milliseconds;
362            if (milliseconds <= 9 && micro > 0) {
363                result = result + "," + StringUtil.longToStringWithFixLengthWith0(micro, 3);
364            }
365            unit = "ms";
366        }
367
368        if (micro > 0 && result.length() == 0) {
369            result = "" + micro;
370            if (micro <= 9 && nano > 0) {
371                result = result + "," + StringUtil.longToStringWithFixLengthWith0(nano, 3);
372            }
373            unit = "\u00B5s";
374        }
375        
376        if (result.length() == 0) {
377            result = "" + nano;
378            unit = "ns";
379        }
380        
381        if (withUnit) {
382            result += " " + unit;
383        }
384        
385        if (sign < 0) {
386            result = "-" + result;
387        }
388
389        return result;
390    }
391    
392    public static String millisecondsDiffToString(long time, boolean withUnit) {
393        return timeDiffToString(time, true, withUnit);
394    }
395    
396    public static String secondsDiffToString(long time, boolean withUnit) {
397        return timeDiffToString(time, false, withUnit);
398    }
399    
400    private static String timeDiffToString(long time, boolean isMilliseconds, boolean withUnit) {
401        long milliseconds;
402        long seconds;
403        if (isMilliseconds) {
404            milliseconds = time;
405            seconds = milliseconds / 1000;
406        } else {
407            milliseconds = -1;
408            seconds = time;
409        }
410        long minutes = seconds / 60;
411        long hours = minutes / 60;
412        long days = hours / 24;
413        milliseconds = milliseconds % 1000;
414        seconds = seconds % 60;
415        minutes = minutes % 60;
416        hours = hours % 24;
417        String result = "";
418        String unit = null;
419        if (days > 0) {
420            result = result + days + " ";
421            unit = "day" + (days != 1 ? "s" : "");
422        }
423        
424        if (hours > 0 || result.length() > 0) {
425            if (result.length() > 0) {
426                result = result + StringUtil.longToStringWithFixLengthWith0(hours, 2) + ":";
427            } else {
428                result = result + hours + ":";
429            }
430            if (unit == null) {
431                unit = "hour" + (hours != 1 ? "s" : "");
432            }
433        }
434        
435        if (minutes > 0 || result.length() > 0) {
436            if (result.length() > 0) {
437                result = result + StringUtil.longToStringWithFixLengthWith0(minutes, 2) + ":";
438            } else {
439                result = result + minutes + ":";
440            }
441            if (unit == null) {
442                unit = "minute" + (minutes != 1 ? "s" : "");
443            }
444        }
445
446        if (result.length() > 0) {
447            result = result + StringUtil.longToStringWithFixLengthWith0(seconds, 2);
448        } else {
449            result = result + seconds;
450        }
451        if (isMilliseconds) {
452            result = result + ",";
453        }
454        if (unit == null) {
455            unit = "second" + (seconds != 1 ? "s" : "");
456        }
457
458        if (isMilliseconds) {
459            result = result + StringUtil.longToStringWithFixLengthWith0(milliseconds, 3);
460        }
461        if (withUnit) {
462            result += " " + unit;
463        }
464        return result;
465    }
466    
467    public static void copyDate(Date from, Date to) {
468        Calendar calendar = Calendar.getInstance();
469        calendar.setTime(from);
470        int d = calendar.get(Calendar.DAY_OF_MONTH);
471        int m = calendar.get(Calendar.MONTH) + 1;
472        int y = calendar.get(Calendar.YEAR);
473        calendar.setTime(to);
474        calendar.set(Calendar.DAY_OF_MONTH, d);
475        calendar.set(Calendar.MONTH, m - 1);
476        calendar.set(Calendar.YEAR, y);
477        to.setTime(calendar.getTimeInMillis());
478    }
479    
480    public static Date clearTime(Date date) {
481        GregorianCalendar gregorianCalendar = new GregorianCalendar();
482        gregorianCalendar.setTime(date);
483        clearTime(gregorianCalendar);
484        return gregorianCalendar.getTime();
485    }
486    
487    public static void clearTime(GregorianCalendar gregorianCalendar) {
488        gregorianCalendar.set(GregorianCalendar.HOUR_OF_DAY, 0);
489        gregorianCalendar.set(GregorianCalendar.MINUTE, 0);
490        gregorianCalendar.set(GregorianCalendar.SECOND, 0);
491        gregorianCalendar.set(GregorianCalendar.MILLISECOND, 0);
492    }
493    
494    public static Date newDate(int year, int month, int date) {
495        GregorianCalendar gregorianCalendar = new GregorianCalendar(year, month - 1, date);
496        return gregorianCalendar.getTime();
497    }
498    
499    public static boolean beforeOrEquals(Date d1, Date d2) {
500        return d1.before(d2) || d1.equals(d2);
501    }
502    
503    public static boolean afterOrEquals(Date d1, Date d2) {
504        return d1.after(d2) || d1.equals(d2);
505    }
506 
507    public static Date calculateEaster(int year) {
508        // The algorithm is from http://en.wikipedia.org/wiki/Computus
509        int a = year % 19;                         // a = year mod 19
510        int b = year % 4;                          // b = year mod 4
511        int c = year % 7;                          // c = year mod 7
512        int k = year / 100;                        // k = floor (year/100)
513        int p = (13 + 8 * k) / 25;                 // p = floor ((13 + 8k)/25)
514        int q = k / 4;                             // q = floor (k/4)
515        int M = (15 - p + k - q) % 30;             // M = (15 - p + k - q) mod 30
516        int N = (4 + k - q) % 7;                   // N = (4 + k - q) mod 7
517        int d = (19 * a + M) % 30;                 // d = (19a + M) mod 30
518        int e = (2 * b + 4 * c + 6 * d + N) % 7;   // e = (2b + 4c + 6d + N) mod 7
519        
520        // Gregorian Easter is 22 + d + e March or d + e - 9 April
521        if (22 + d + e <= 30) {
522            return newDate(year, 3, 22 + d + e);
523        }
524        int aprilDate = d + e - 9;
525        
526        // if d = 29 and e = 6, replace 26 April with 19 April
527        if (d == 29 && e == 6) {
528            aprilDate = 19;
529        }
530        
531        // if d = 28, e = 6, and (11M + 11) mod 30 < 19, replace 25 April with 18 April
532        if (d == 28 && e == 6 && (11 * M + 11) % 30 < 19) {
533            aprilDate = 18;
534        }
535        
536        return newDate(year, 4, aprilDate);
537    }
538}