View Javadoc
1   package org.duniter.elasticsearch.utils;
2   
3   /*-
4    * #%L
5    * Cesium+ pod :: Model
6    * %%
7    * Copyright (C) 2014 - 2023 Duniter Team
8    * %%
9    * This program is free software: you can redistribute it and/or modify
10   * it under the terms of the GNU Affero General Public License as published by
11   * the Free Software Foundation, either version 3 of the License, or
12   * (at your option) any later version.
13   * 
14   * This program is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   * GNU General Public License for more details.
18   * 
19   * You should have received a copy of the GNU Affero General Public License
20   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
21   * #L%
22   */
23  
24  import org.apache.commons.lang3.time.DateUtils;
25  import org.duniter.core.exception.TechnicalException;
26  import org.duniter.core.util.Preconditions;
27  import org.duniter.core.util.StringUtils;
28  import org.nuiton.util.DateUtil;
29  
30  import java.sql.Timestamp;
31  import java.text.ParseException;
32  import java.text.SimpleDateFormat;
33  import java.util.Calendar;
34  import java.util.Date;
35  import java.util.TimeZone;
36  import java.util.regex.Pattern;
37  
38  public class Dates extends org.apache.commons.lang3.time.DateUtils{
39  
40      // See https://www.w3.org/TR/NOTE-datetime
41      // Full precision (with millisecond and timezone)
42      public static String ISO_TIMESTAMP_REGEXP = "\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d\\.\\d+([+-][0-2]\\d:[0-5]\\d|Z)";
43      public static Pattern ISO_TIMESTAMP_PATTERN = Pattern.compile("^" + ISO_TIMESTAMP_REGEXP + "$");
44  
45      public static String ISO_TIMESTAMP_SPEC = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX";
46  
47      /**
48       * Remove a amount of month to a date
49       *
50       * @param date a {@link Date} object.
51       * @param amount the amount to remove, in month
52       * @return a new date (= the given date - amount in month)
53       */
54      public static Date removeMonth(Date date, int amount) {
55      	Preconditions.checkNotNull(date);
56      	Preconditions.checkArgument(amount > 0);
57  
58      	// Compute the start date
59          Calendar calendar = Calendar.getInstance();
60  		calendar.setTimeInMillis(date.getTime());
61  		calendar.set(Calendar.HOUR_OF_DAY, 0);
62  		calendar.set(Calendar.DAY_OF_MONTH, 1);
63  		calendar.set(Calendar.MONTH, calendar.get(Calendar.MONTH)-amount);
64  		return calendar.getTime();
65      }
66  
67      /**
68       * Get the number of days between two dates
69       *
70       * @param startDate a {@link Date} object.
71       * @param endDate a {@link Date} object.
72       * @return a number of hours
73       */
74      public static double hoursBetween(Date startDate, Date endDate){
75      	double millis = endDate.getTime() - startDate.getTime();
76          return millis / (1000 * 60 * 60);
77      }
78  
79      /**
80       * Add to date some hours
81       *
82       * @param date a {@link Date} object.
83       * @param amount a {@link Double} object.
84       * @return a date (= date + amount)
85       */
86      public static Date addHours(Date date, Double amount){
87      	long millis = (long) (date.getTime() + amount * (1000 * 60 * 60));
88          return new Date(millis);
89      }
90  
91  
92      /**
93       * Get the last second time of a day: 23:59:59 (0 millisecond)
94       *
95       * @param date a {@link Date} object.
96       * @return a {@link Date} object.
97       */
98      public static Date lastSecondOfTheDay(Date date) {
99          if (date == null) {
100             return null;
101         }
102         Calendar calendar = Calendar.getInstance();
103         calendar.setTime(date);
104         calendar.set(Calendar.HOUR_OF_DAY, 23);
105         calendar.set(Calendar.MINUTE, 59);
106         calendar.set(Calendar.SECOND, 59);
107         calendar.set(Calendar.MILLISECOND, 0);
108         return calendar.getTime();
109     }
110 
111     /**
112      * reset to 00h00m00s (and 0 millisecond)
113      *
114      * @param date a {@link Date} object.
115      * @param timezone a {@link TimeZone} object.
116      * @return a {@link Date} object.
117      */
118     public static Date resetTime(Date date, TimeZone timezone) {
119         if (date == null) {
120             return null;
121         }
122         Calendar calendar = timezone != null ? Calendar.getInstance(timezone) : Calendar.getInstance();
123         calendar.setTime(date);
124         resetTime(calendar);
125         return calendar.getTime();
126     }
127 
128     /**
129      * reset to 00h00m00s (and 0 millisecond)
130      *
131      * @param date a {@link Date} object.
132      * @return a {@link Date} object.
133      */
134     public static Date resetTime(Date date) {
135         return resetTime(date, null);
136     }
137 
138     /**
139      * reset to 00h00m00s (and 0 millisecond)
140      *
141      * @param calendar a {@link Calendar} object.
142      * @return a {@link Calendar} object.
143      */
144     public static Calendar resetTime(Calendar calendar) {
145         if (calendar == null) return null;
146         calendar.set(Calendar.HOUR_OF_DAY, 0);
147         calendar.set(Calendar.MINUTE, 0);
148         calendar.set(Calendar.SECOND, 0);
149         calendar.set(Calendar.MILLISECOND, 0);
150 
151         return calendar;
152     }
153 
154     /**
155      * reset to 0 millisecond
156      *
157      * @param date a {@link Timestamp} object.
158      * @return a {@link Timestamp} object.
159      */
160     public static Timestamp resetMillisecond(Timestamp date) {
161         if (date == null) return null;
162         Calendar calendar = Calendar.getInstance();
163         calendar.setTime(date);
164         calendar.set(Calendar.MILLISECOND, 0);
165 
166         return new Timestamp(calendar.getTimeInMillis());
167     }
168 
169     /**
170      * reset to 0 millisecond
171      *
172      * @param date a {@link Date} object.
173      * @return a {@link Timestamp} object.
174      */
175     public static Timestamp resetMillisecond(Date date) {
176         if (date == null) return null;
177         Calendar calendar = Calendar.getInstance();
178         calendar.setTimeInMillis(date.getTime());
179         calendar.set(Calendar.MILLISECOND, 0);
180 
181         return new Timestamp(calendar.getTime().getTime());
182     }
183 
184     /**
185      * reset to 0 millisecond
186      *
187      * @param calendar a {@link Calendar} object.
188      * @return a {@link Calendar} object.
189      */
190     public static Calendar resetMillisecond(Calendar calendar) {
191         calendar.set(Calendar.MILLISECOND, 0);
192 
193         return calendar;
194     }
195 
196     /**
197      * <p>getDifferenceInDays.</p>
198      *
199      * @param startDate a {@link Date} object.
200      * @param endDate a {@link Date} object.
201      * @return a int.
202      */
203     public static int getDifferenceInDays(Date startDate, Date endDate) {
204     	return DateUtil.getDifferenceInDays(startDate, endDate);
205     }
206 
207     /**
208      * <p>formatDate.</p>
209      *
210      * @param date a {@link Date} object.
211      * @param pattern a {@link String} object.
212      * @return a {@link String} object.
213      */
214     public static String formatDate(Date date, String pattern) {
215         return formatDate(date, pattern, null);
216     }
217 
218     /**
219      * <p>formatDate.</p>
220      *
221      * @param date a {@link Date} object.
222      * @param pattern a {@link String} object.
223      * @return a {@link String} object.
224      */
225     public static String formatDate(Date date, String pattern, TimeZone tz) {
226         if (date == null) return null;
227         SimpleDateFormat sdf = new SimpleDateFormat(pattern);
228         if (tz != null) sdf.setTimeZone(tz);
229         return sdf.format(date);
230     }
231 
232     /**
233      * Convert to a date, or return null if parse error
234      *
235      * @param date a {@link String} object.
236      * @param pattern a {@link String} object.
237      * @return a {@link Date} object.
238      */
239     public static Date safeParseDate(String date, String pattern) {
240         Date result = null;
241         if (StringUtils.isNotBlank(date)) {
242             try {
243                 SimpleDateFormat sdf = new SimpleDateFormat(pattern);
244                 result = sdf.parse(date);
245             } catch (ParseException ignored) {
246             }
247         }
248         return result;
249     }
250 
251     /**
252      * Convert to a date, or return null if parse error
253      *
254      * @param date a {@link String} object.
255      * @param patterns a {@link String} object.
256      * @return a {@link Date} object.
257      */
258     public static Date safeParseDate(String date, String... patterns) {
259         Date result = null;
260         if (StringUtils.isNotBlank(date)) {
261             for (String pattern: patterns) {
262                 try {
263                     SimpleDateFormat sdf = new SimpleDateFormat(pattern);
264                     result = sdf.parse(date);
265                 } catch (ParseException ignored) {
266                     // Continue: try next pattern
267                 }
268             }
269         }
270         return result;
271     }
272 
273     /**
274      * Adds a number of seconds to a date returning a new object.
275      * The original {@code Timestamp} is unchanged.
276      *
277      * @param date  the Timestamp, not null
278      * @param amount  the amount to add, may be negative
279      * @return the new {@code Timestamp} with the amount added
280      * @throws IllegalArgumentException if the date is null
281      */
282     public static Timestamp addSeconds(Timestamp date, int amount) {
283         if(date == null) {
284             throw new IllegalArgumentException("The date must not be null");
285         } else {
286             Calendar c = Calendar.getInstance();
287             c.setTime(date);
288             c.add(Calendar.SECOND, amount);
289             return new Timestamp(c.getTimeInMillis());
290         }
291     }
292 
293     /**
294      * <p>newCreateDate.</p>
295      *
296      * @return a {@link Date} object.
297      */
298     protected Date newCreateDate() {
299         return dateWithNoTime(new Date());
300     }
301 
302     /**
303      * <p>newUpdateTimestamp.</p>
304      *
305      * @return a {@link Timestamp} object.
306      */
307     protected Timestamp newUpdateTimestamp() {
308         return new Timestamp((new Date()).getTime());
309     }
310 
311     /**
312      * <p>dateWithNoTime.</p>
313      *
314      * @param date a {@link Date} object.
315      * @return a {@link Date} object.
316      */
317     protected Date dateWithNoTime(Date date) {
318         Calendar calendar = Calendar.getInstance();
319         calendar.setTime(date);
320         calendar.set(Calendar.HOUR_OF_DAY, 0);
321         calendar.set(Calendar.MINUTE, 0);
322         calendar.set(Calendar.SECOND, 0);
323         calendar.set(Calendar.MILLISECOND, 0);
324         return calendar.getTime();
325     }
326 
327     /**
328      * <p>dateWithNoMillisecond.</p>
329      *
330      * @param date a {@link Date} object.
331      * @return a {@link Date} object.
332      */
333     protected Date dateWithNoMillisecond(Date date) {
334         Calendar calendar = Calendar.getInstance();
335         calendar.setTime(date);
336         calendar.set(Calendar.MILLISECOND, 0);
337         return calendar.getTime();
338     }
339 
340     /**
341      * <p>dateWithNoSecondAndMillisecond.</p>
342      *
343      * @param date a {@link Date} object.
344      * @return a {@link Date} object.
345      */
346     protected Date dateWithNoSecondAndMillisecond(Date date) {
347         Calendar calendar = Calendar.getInstance();
348         calendar.setTime(date);
349         calendar.set(Calendar.SECOND, 0);
350         calendar.set(Calendar.MILLISECOND, 0);
351         return calendar.getTime();
352     }
353 
354     /**
355      * <p>dateWithNoSecondAndOneMillisecond.</p>
356      *
357      * @param date a {@link Date} object.
358      * @return a {@link Date} object.
359      */
360     protected Date dateWithNoSecondAndOneMillisecond(Date date) {
361         Calendar calendar = Calendar.getInstance();
362         calendar.setTime(date);
363         calendar.add(Calendar.SECOND, 0);
364         calendar.add(Calendar.MILLISECOND, 1);
365         return calendar.getTime();
366     }
367 
368     /**
369      * <p>dateWithOneMillisecond.</p>
370      *
371      * @param date a {@link Date} object.
372      * @return a {@link Date} object.
373      */
374     protected Date dateWithOneMillisecond(Date date) {
375         Calendar calendar = Calendar.getInstance();
376         calendar.setTime(date);
377         calendar.add(Calendar.MILLISECOND, 1);
378         return calendar.getTime();
379     }
380 
381     /**
382      * <p>dateOfYearWithOneMillisecond.</p>
383      *
384      * @param year a int.
385      * @return a {@link Date} object.
386      */
387     protected Date dateOfYearWithOneMillisecond(int year) {
388         Calendar calendar = Calendar.getInstance();
389         calendar.setTimeInMillis(0);
390         calendar.set(Calendar.YEAR, year);
391         calendar.set(Calendar.MILLISECOND, 1);
392         return calendar.getTime();
393     }
394 
395     /**
396      * <p>dateOfYearWithOneMillisecondInMillisecond.</p>
397      *
398      * @param year a int.
399      * @return a long.
400      */
401     protected long dateOfYearWithOneMillisecondInMillisecond(int year) {
402         Calendar calendar = Calendar.getInstance();
403         calendar.setTimeInMillis(0);
404         calendar.set(Calendar.YEAR, year);
405         calendar.set(Calendar.MILLISECOND, 1);
406         return calendar.getTimeInMillis();
407     }
408 
409     /**
410      * Test if the date has millisecond set. This yes, return null, then return the date itself.
411      *
412      * @param databaseValue the date stored in the database (could be fake date, not null only because of database constraints)
413      * @return null if the date is a fake date
414      */
415     protected Date convertDatabase2UI(Timestamp databaseValue) {
416         Date result;
417         if (databaseValue == null) {
418             result = null;
419         } else {
420             Calendar calendar = Calendar.getInstance();
421             calendar.setTimeInMillis(databaseValue.getTime());
422             if (calendar.get(Calendar.MILLISECOND) != 0) {
423                 result = null;
424             } else {
425                 result = calendar.getTime();
426             }
427         }
428         return result;
429     }
430 
431     /**
432      * Convert a UI date, when the database value is mandatory.
433      * If the given value is null, use the default date, then set millisecond to '1', to be able to retrieve the null value later.
434      *
435      * @param uiValue the date used in the UI
436      * @return null if the date is a fake date
437      * @param defaultNotEmptyDate a {@link Date} object.
438      * @param addOneSecondToDefaultDate a boolean.
439      */
440     protected Date convertUI2DatabaseMandatoryDate(Date uiValue,
441                                                    Date defaultNotEmptyDate,
442                                                    boolean addOneSecondToDefaultDate) {
443         Date result;
444 
445         // if ui date is not empty, then use it (but reset millisecond)
446         if (uiValue == null) {
447 
448             Preconditions.checkArgument(
449                     defaultNotEmptyDate != null,
450                     "'defaultNotEmptyDate' could not be null.");
451             Calendar calendar = Calendar.getInstance();
452             calendar.setTime(defaultNotEmptyDate);
453             if (addOneSecondToDefaultDate) {
454                 calendar.add(Calendar.SECOND, 1);
455             }
456             calendar.set(Calendar.MILLISECOND, 1);
457             result = calendar.getTime();
458         } else {
459 
460             result = dateWithNoMillisecond(uiValue);
461         }
462 
463         return result;
464     }
465 
466     public static Date getFirstDayOfYear(int year) {
467         Calendar calendar = Calendar.getInstance();
468         calendar.set(Calendar.YEAR, year);
469         calendar.set(Calendar.DAY_OF_YEAR, 1);
470         resetTime(calendar);
471         return calendar.getTime();
472     }
473     public static Date getLastSecondOfYear(int year) {
474         Calendar calendar = Calendar.getInstance();
475         calendar.set(Calendar.YEAR, year+1);
476         calendar.set(Calendar.DAY_OF_YEAR, 1);
477         resetTime(calendar);
478         calendar.add(Calendar.SECOND, -1);
479         return calendar.getTime();
480     }
481 
482     public static String elapsedTime(long timeInMs) {
483         long elapsedTime = System.currentTimeMillis() - timeInMs;
484         StringBuilder sb = new StringBuilder();
485         sb.append("in ");
486         if (elapsedTime < 1000) {
487             return sb.append(elapsedTime).append("ms").toString();
488         }
489         double seconds = (double) elapsedTime / 1_000;
490         if (seconds < 60) {
491             return sb.append(seconds).append("s").toString();
492         }
493         int minutesFloor = (int) Math.floor(seconds / 60);
494         int secondsFloor = (int) Math.floor(seconds - minutesFloor * 60);
495         int millis = (int) Math.floor((seconds - secondsFloor) * 1_000);
496 
497         return sb.append(minutesFloor).append("min ")
498             .append(secondsFloor).append("s ")
499             .append(millis).append("ms")
500             .toString();
501     }
502 
503     public static String checkISODateTimeString(String isoDate) throws TechnicalException {
504         if (isoDate == null) return null;
505         if (!ISO_TIMESTAMP_PATTERN.matcher(isoDate).matches()) {
506             throw new TechnicalException(String.format("Invalid date time '%s'. Expected ISO format 'YYYY-MM-DDThh:mm:ss.sssZ'.", isoDate));
507         }
508         return isoDate;
509     }
510 
511     public static String toISODateTimeString(Date date) {
512         return formatDate(date, ISO_TIMESTAMP_SPEC, null);
513     }
514 
515     public static String toISODateTimeString(Date date, TimeZone tz) {
516         return formatDate(date, ISO_TIMESTAMP_SPEC, tz);
517     }
518 
519     public static Date fromISODateTimeString(String dateStr) {
520         try {
521             return parseDate(dateStr, ISO_TIMESTAMP_SPEC);
522         } catch(ParseException e) {
523             throw new TechnicalException(e);
524         }
525     }
526 
527     public static long toUnixTimestamp(Date date) {
528         return date.getTime() / 1000;
529     }
530 
531     /**
532      * Allow to compare dates, ignoring nanoseconds.
533      * This allow to compare a java.util.Date with a java.sql.Timestamp
534      * @param d
535      * @return
536      */
537     public static boolean equals(Date d1, Date d2) {
538         return (d1 == null && d2 == null)
539         || (d1 != null && d2 != null && d1.getTime() == d2.getTime());
540     }
541 }