001/* 002 * Copyright 2017 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.core.util; 017 018import java.time.Duration; 019import java.time.Instant; 020import java.time.LocalDate; 021import java.time.ZoneId; 022import java.time.ZoneOffset; 023import java.time.ZonedDateTime; 024import java.time.chrono.IsoChronology; 025import java.time.format.DateTimeFormatter; 026import java.time.format.ResolverStyle; 027import java.util.Calendar; 028import java.util.Date; 029import java.util.GregorianCalendar; 030import java.util.Optional; 031 032import org.apache.commons.lang3.StringUtils; 033 034/** 035 * Helper for converting dates from the old ({@link Date}) to the new ({@link java.time}) JDK 036 * Special thanks to http://stackoverflow.com/questions/21242110/convert-java-util-date-to-java-time-localdate#answer-27378709 037 * which inspired this code 038 * 039 * See also http://stackoverflow.com/questions/19431234/converting-between-java-time-localdatetime-and-java-util-date 040 */ 041public final class DateUtils 042{ 043 /** The ISO date-time pattern that formats or parses a date-time with an offset */ 044 public static final String ISO_OFFSET_DATE_TIME_PATTERN = "uuuu-MM-dd'T'HH:mm:ss.SSSXXX"; 045 046 /** 047 * The ISO date-time formatter that formats or parses a date-time with an offset, such as '2011-12-03T10:15:30.000+01:00'. 048 */ 049 private static final DateTimeFormatter __ISO_OFFSET_DATE_TIME = _createFormatter(ISO_OFFSET_DATE_TIME_PATTERN); 050 051 private DateUtils() 052 { 053 // empty 054 } 055 056 private static DateTimeFormatter _createFormatter(String pattern) throws IllegalArgumentException 057 { 058 return DateTimeFormatter.ofPattern(pattern) 059 .withResolverStyle(ResolverStyle.STRICT) 060 .withChronology(IsoChronology.INSTANCE); 061 } 062 063 /** 064 * Converts this {@link Date} object to an {@link Instant}. 065 * @param date The date object 066 * @return an instant representing the same point on the time-line as this {@link Date} object 067 */ 068 public static Instant asInstant(Date date) 069 { 070 return Optional.ofNullable(date).map(Date::toInstant).orElse(null); 071 } 072 073 /** 074 * Converts this {@link Date} object to a {@link ZonedDateTime}, at UTC. 075 * @param date The date object 076 * @return the {@link ZonedDateTime} formed from this {@link Date} 077 */ 078 public static ZonedDateTime asZonedDateTime(Date date) 079 { 080 return asZonedDateTime(date, null); 081 } 082 083 /** 084 * Converts this {@link Date} object to a {@link ZonedDateTime}. 085 * @param date The date object 086 * @param zone The zone. If <code>null</code>, UTC is used 087 * @return the {@link ZonedDateTime} formed from this {@link Date} 088 */ 089 public static ZonedDateTime asZonedDateTime(Date date, ZoneId zone) 090 { 091 return asZonedDateTime(asInstant(date), zone); 092 } 093 094 /** 095 * Converts this {@link Calendar} object to a {@link ZonedDateTime}. 096 * @param calendar the calendar 097 * @return the {@link ZonedDateTime} formed from this {@link Calendar} 098 */ 099 public static ZonedDateTime asZonedDateTime(Calendar calendar) 100 { 101 return asZonedDateTime(calendar.toInstant(), ZoneOffset.UTC); 102 } 103 104 /** 105 * Converts this epoch time to a {@link ZonedDateTime}. 106 * @param epochMilli the number of milliseconds from 1970-01-01T00:00:00Z 107 * @return the {@link ZonedDateTime} formed from this epoch time 108 */ 109 public static ZonedDateTime asZonedDateTime(long epochMilli) 110 { 111 return asZonedDateTime(epochMilli, null); 112 } 113 114 /** 115 * Converts this epoch time to a {@link ZonedDateTime}. 116 * @param epochMilli the number of milliseconds from 1970-01-01T00:00:00Z 117 * @param zone the time-zone 118 * @return the {@link ZonedDateTime} formed from this epoch time 119 */ 120 public static ZonedDateTime asZonedDateTime(long epochMilli, ZoneId zone) 121 { 122 Instant instant = Instant.ofEpochMilli(epochMilli); 123 return asZonedDateTime(instant, zone); 124 } 125 126 /** 127 * Converts a {@link LocalDate} to a {@link ZonedDateTime} 128 * @param localDate the local date 129 * @param zone The time zone. If <code>null</code>, UTC is used 130 * @return the {@link ZonedDateTime} formed from this local date 131 */ 132 public static ZonedDateTime asZonedDateTime(LocalDate localDate, ZoneId zone) 133 { 134 Date date = asDate(localDate, zone); 135 return asZonedDateTime(date); 136 } 137 138 /** 139 * Converts an {@link Instant} to a {@link ZonedDateTime} 140 * @param instant the instant 141 * @return the {@link ZonedDateTime} formed from this local date 142 */ 143 public static ZonedDateTime asZonedDateTime(Instant instant) 144 { 145 return asZonedDateTime(instant, null); 146 } 147 148 /** 149 * Converts an {@link Instant} to a {@link ZonedDateTime} 150 * @param instant the instant 151 * @param zone The time zone. If <code>null</code>, UTC is used 152 * @return the {@link ZonedDateTime} formed from this local date 153 */ 154 public static ZonedDateTime asZonedDateTime(Instant instant, ZoneId zone) 155 { 156 return Optional.ofNullable(instant) 157 .map(i -> i.atZone(Optional.ofNullable(zone).orElse(ZoneOffset.UTC))) 158 .orElse(null); 159 } 160 161 /** 162 * Converts this {@link Date} object to a {@link LocalDate}. 163 * 164 * This returns a {@link LocalDate} with the same year, month and day as this {@link Date}. 165 * @param date The date object 166 * @param zone The zone 167 * @return the {@link LocalDate} part of this {@link Date} 168 */ 169 public static LocalDate asLocalDate(Date date, ZoneId zone) 170 { 171 return asZonedDateTime(date, zone).toLocalDate(); 172 } 173 174 /** 175 * Converts this {@link Date} object to a {@link LocalDate}. 176 * 177 * This returns a {@link LocalDate} with the same year, month and day as this {@link Date}. 178 * @param date The date object 179 * @return the {@link LocalDate} part of this {@link Date} 180 */ 181 public static LocalDate asLocalDate(Date date) 182 { 183 return asLocalDate(date, null); 184 } 185 186 /** 187 * Converts this {@link Calendar} object to a {@link LocalDate}. <br> 188 * <b>Warning</b>: this conversion looses the Calendar's time components. 189 * @param calendar the calendar 190 * @return the {@link LocalDate} object 191 */ 192 public static LocalDate asLocalDate(Calendar calendar) 193 { 194 return LocalDate.of(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH) + 1, calendar.get(Calendar.DAY_OF_MONTH)); 195 } 196 197 /** 198 * Converts an {@link Instant} to a {@link LocalDate} 199 * @param instant the instant 200 * @return the {@link LocalDate} formed from this local date 201 */ 202 public static LocalDate asLocalDate(Instant instant) 203 { 204 return asLocalDate(instant, null); 205 } 206 207 /** 208 * Converts an {@link Instant} to a {@link LocalDate} 209 * @param instant the instant 210 * @param zone The time zone. If <code>null</code>, UTC is used 211 * @return the {@link LocalDate} formed from this local date 212 */ 213 public static LocalDate asLocalDate(Instant instant, ZoneId zone) 214 { 215 return LocalDate.ofInstant(instant, Optional.ofNullable(zone).orElse(ZoneOffset.UTC)); 216 } 217 218 /** 219 * Converts this {@link LocalDate} object to a {@link Date}. 220 * 221 * @param localDate The local date object 222 * @return the {@link Date} part of this {@link LocalDate} 223 */ 224 public static Date asDate(LocalDate localDate) 225 { 226 return asDate(localDate, ZoneOffset.UTC); 227 } 228 229 /** 230 * Converts this {@link LocalDate} object to a {@link Date}. 231 * 232 * @param localDate The local date object 233 * @param zone The zone. If <code>null</code>, UTC is used 234 * @return the {@link Date} part of this {@link LocalDate} 235 */ 236 public static Date asDate(LocalDate localDate, ZoneId zone) 237 { 238 return Optional.ofNullable(localDate) 239 .map(ld -> ld.atStartOfDay(Optional.ofNullable(zone).orElse(ZoneOffset.UTC))) 240 .map(ZonedDateTime::toInstant) 241 .map(Date::from) 242 .orElse(null); 243 } 244 245 /** 246 * Converts this {@link ZonedDateTime} object to a {@link Date}. 247 * 248 * @param zonedDateTime The local date time object 249 * @return the {@link Date} part of this {@link ZonedDateTime} 250 */ 251 public static Date asDate(ZonedDateTime zonedDateTime) 252 { 253 return Date.from(zonedDateTime.toInstant()); 254 } 255 256 /** 257 * Converts this {@link ZonedDateTime} object to a {@link Calendar}, setting the time zone to UTC. 258 * @param zonedDateTime the zoned date time. 259 * @return the converted {@link Calendar} object. 260 */ 261 public static Calendar asCalendar(ZonedDateTime zonedDateTime) 262 { 263 ZonedDateTime dateTimeOnDefaultZone = zonedDateTime.withZoneSameInstant(ZoneOffset.UTC); 264 return GregorianCalendar.from(dateTimeOnDefaultZone); 265 } 266 267 /** 268 * Converts this {@link LocalDate} object to a {@link Calendar}. 269 * @param localDate the local date 270 * @return the {@link Calendar} object 271 */ 272 public static Calendar asCalendar(LocalDate localDate) 273 { 274 ZonedDateTime zdt = localDate.atStartOfDay(ZoneOffset.UTC); 275 return GregorianCalendar.from(zdt); 276 } 277 278 /** 279 * Format a duration for logs 280 * @param duration duration to log 281 * @return a string representing the duration 282 */ 283 public static String formatDuration(Duration duration) 284 { 285 return formatDuration(duration.toMillis()); 286 } 287 288 /** 289 * Format a duration for logs 290 * @param duration miliseconds representing the duration 291 * @return a string representing the duration 292 */ 293 public static String formatDuration(long duration) 294 { 295 StringBuilder sb = new StringBuilder(); 296 long durationCopy = duration; 297 long ms = durationCopy % 1000; 298 durationCopy /= 1000; 299 long s = durationCopy % 60; 300 durationCopy /= 60; 301 long m = durationCopy % 60; 302 durationCopy /= 60; 303 long h = durationCopy % 24; 304 durationCopy /= 24; 305 306 boolean showDays = durationCopy > 0; 307 boolean showHours = showDays || h > 0; 308 boolean showMinuts = showHours || m > 0; 309 boolean showSeconds = showMinuts || s > 0; 310 311 if (showDays) 312 { 313 sb.append(durationCopy); 314 sb.append("j "); 315 } 316 if (showHours) 317 { 318 sb.append(formatNumber(h, 2)); 319 sb.append("h "); 320 } 321 if (showMinuts) 322 { 323 sb.append(formatNumber(m, 2)); 324 sb.append("m "); 325 } 326 if (showSeconds) 327 { 328 sb.append(formatNumber(s, 2)); 329 sb.append("s "); 330 } 331 sb.append(formatNumber(ms, 3)); 332 sb.append("ms"); 333 return sb.toString(); 334 } 335 private static String formatNumber(long number, int nbNumbers) 336 { 337 String numberFormatted = String.valueOf(number); 338 while (numberFormatted.length() < nbNumbers) 339 { 340 numberFormatted = "0" + numberFormatted; 341 } 342 return numberFormatted; 343 } 344 345 /** 346 * Get the ISO date-time formatter that formats or parses a date-time with an offset, such as '2011-12-03T10:15:30.000+01:00'. 347 * This formatter is similar to {@link DateTimeFormatter#ISO_OFFSET_DATE_TIME} but force 3-digits milliseconds. 348 * @return ISO date-time formatter 349 */ 350 public static DateTimeFormatter getISODateTimeFormatter() 351 { 352 return __ISO_OFFSET_DATE_TIME; 353 } 354 355 /** 356 * Converts a {@link Date} object to {@link String} using the ISO date formatter, at UTC time zone. 357 * @param value the value to convert 358 * @return the date as a {@link String} 359 */ 360 public static String dateToString(Date value) 361 { 362 if (value == null) 363 { 364 return null; 365 } 366 367 ZonedDateTime zdt = DateUtils.asZonedDateTime(value, null); 368 return zonedDateTimeToString(zdt); 369 } 370 371 /** 372 * Converts this epoch time to a {@link String} using the ISO date formatter 373 * @param epochMilli the number of milliseconds from 1970-01-01T00:00:00Z 374 * @return the epoch time to a {@link String} 375 */ 376 public static String epochMilliToString(long epochMilli) 377 { 378 ZonedDateTime zdt = DateUtils.asZonedDateTime(epochMilli, ZoneOffset.UTC); 379 return zonedDateTimeToString(zdt); 380 } 381 382 /** 383 * Converts a {@link ZonedDateTime} object to {@link String} in the given zone, so as to format the instant in another zone, 384 * using the ISO date time formatter with pattern 'uuuu-MM-dd'T'HH:mm:ss.SSSXXX'. For instance: 385 * <ul> 386 * <li>for UTC zone, the zone date time corresponding to '2011-12-03T11:15:30+01:00' will be formatted as '2011-12-03T10:15:30Z'</li> 387 * <li>for +02:00 zone, the zone date time corresponding to '2011-12-03T11:15:30+01:00' will be formatted as '2011-12-03T12:15:30+02:00'</li> 388 * </ul> 389 * @param zonedDateTime the zoned date time 390 * @param zoneId the target zone 391 * @return the zoned date time as a {@link String}, in the given zone 392 */ 393 public static String zonedDateTimeToString(ZonedDateTime zonedDateTime, ZoneId zoneId) 394 { 395 ZonedDateTime sameInstantAtZone = zonedDateTime.withZoneSameInstant(zoneId); 396 return zonedDateTimeToString(sameInstantAtZone); 397 } 398 399 /** 400 * Converts a {@link ZonedDateTime} object to {@link String} using the {@link #__ISO_OFFSET_DATE_TIME ISO date formatter} 401 * @param zonedDateTime the zoned date time 402 * @return the zoned date time as a {@link String} 403 */ 404 public static String zonedDateTimeToString(ZonedDateTime zonedDateTime) 405 { 406 return zonedDateTime.format(getISODateTimeFormatter()); 407 } 408 409 /** 410 * Converts a {@link ZonedDateTime} object to {@link String} using the given pattern for formatting, 411 * in the given zone, so as to format the instant in another zone 412 * <br>For instance, if provided pattern is 'uuuu-MM-dd'T'HH:mm:ss.SSSXXX': 413 * <ul> 414 * <li>for UTC zone, the zone date time corresponding to '2011-12-03T11:15:30+01:00' will be formatted as '2011-12-03T10:15:30Z'</li> 415 * <li>for +02:00 zone, the zone date time corresponding to '2011-12-03T11:15:30+01:00' will be formatted as '2011-12-03T12:15:30+02:00'</li> 416 * </ul> 417 * @param zonedDateTime the zoned date time 418 * @param zoneId the target zone 419 * @param pattern the pattern for formatting 420 * @return the zoned date time as a {@link String}, in the given zone 421 * @throws IllegalArgumentException if the pattern is invalid 422 */ 423 public static String zonedDateTimeToString(ZonedDateTime zonedDateTime, ZoneId zoneId, String pattern) throws IllegalArgumentException 424 { 425 ZonedDateTime sameInstantAtZone = zonedDateTime.withZoneSameInstant(zoneId); 426 DateTimeFormatter formatter = _createFormatter(pattern); 427 return sameInstantAtZone.format(formatter); 428 } 429 430 /** 431 * Converts a {@link LocalDate} object to {@link String} using the ISO date formatter 432 * @param localDate the local date 433 * @return the local date as a {@link String} 434 */ 435 public static String localDateToString(LocalDate localDate) 436 { 437 return localDate.format(DateTimeFormatter.ISO_LOCAL_DATE); 438 } 439 440 /** 441 * Converts a {@link LocalDate} object to {@link String} using the given pattern for formatting 442 * @param localDate the local date 443 * @param pattern the pattern for formatting 444 * @return the local date as a {@link String} 445 * @throws IllegalArgumentException if the pattern is invalid 446 */ 447 public static String localDateToString(LocalDate localDate, String pattern) throws IllegalArgumentException 448 { 449 DateTimeFormatter formatter = _createFormatter(pattern); 450 return localDate.format(formatter); 451 } 452 453 /** 454 * Parses a String into a {@link Date}, using ISO 8601 format. 455 * @param value an ISO 8601 formatted String. 456 * @return the corresponding Date, or null if the input is null. 457 */ 458 public static Date parse(String value) 459 { 460 if (StringUtils.isEmpty(value)) 461 { 462 return null; 463 } 464 465 ZonedDateTime zdt = ZonedDateTime.parse(value, DateTimeFormatter.ISO_DATE_TIME); 466 return asDate(zdt); 467 } 468 469 /** 470 * Parses a String into a {@link ZonedDateTime}, using ISO date time formatter. 471 * @param zonedDateTimeAsString the zoned date time as string 472 * @return the {@link ZonedDateTime} object or null if the input is null. 473 */ 474 public static ZonedDateTime parseZonedDateTime(String zonedDateTimeAsString) 475 { 476 return parseZonedDateTime(zonedDateTimeAsString, Optional.empty()); 477 } 478 479 /** 480 * Parses a String into a {@link ZonedDateTime}, using ISO date time formatter. 481 * @param zonedDateTimeAsString the zoned date time as string 482 * @param pattern the pattern to use to parse the given date 483 * @return the {@link ZonedDateTime} object or null if the input is null. 484 * @throws IllegalArgumentException if the pattern is invalid 485 */ 486 public static ZonedDateTime parseZonedDateTime(String zonedDateTimeAsString, String pattern) throws IllegalArgumentException 487 { 488 DateTimeFormatter formatter = _createFormatter(pattern); 489 return parseZonedDateTime(zonedDateTimeAsString, Optional.of(formatter)); 490 } 491 492 /** 493 * Parses a String into a {@link ZonedDateTime}, using the given formatter. 494 * If no formatter, the ISO date time formatter is used 495 * @param zonedDateTimeAsString the zoned date time as string 496 * @param formatter the date time formatter 497 * @return the {@link ZonedDateTime} object or null if the input is null. 498 */ 499 public static ZonedDateTime parseZonedDateTime(String zonedDateTimeAsString, Optional<DateTimeFormatter> formatter) 500 { 501 if (StringUtils.isEmpty(zonedDateTimeAsString)) 502 { 503 return null; 504 } 505 506 return ZonedDateTime.parse(zonedDateTimeAsString, formatter.orElse(DateTimeFormatter.ISO_DATE_TIME)); 507 } 508 509 /** 510 * Parses a String into a {@link LocalDate}, using ISO local date formatter. 511 * @param localDateAsString the local date as string 512 * @return the {@link LocalDate} object or null if the input is null. 513 */ 514 public static LocalDate parseLocalDate(String localDateAsString) 515 { 516 return parseLocalDate(localDateAsString, Optional.empty()); 517 } 518 519 /** 520 * Parses a String into a {@link LocalDate}, using ISO local date formatter. 521 * @param localDateAsString the local date as string 522 * @param pattern the pattern to use to parse the given date 523 * @return the {@link LocalDate} object or null if the input is null. 524 * @throws IllegalArgumentException if the pattern is invalid 525 */ 526 public static LocalDate parseLocalDate(String localDateAsString, String pattern) throws IllegalArgumentException 527 { 528 DateTimeFormatter formatter = _createFormatter(pattern); 529 return parseLocalDate(localDateAsString, Optional.of(formatter)); 530 } 531 532 /** 533 * Parses a String into a {@link LocalDate}, using the given formatter. 534 * If no formatter, the ISO local date formatter is used 535 * @param localDateAsString the local date as string 536 * @param formatter the date time formatter 537 * @return the {@link LocalDate} object or null if the input is null. 538 */ 539 public static LocalDate parseLocalDate(String localDateAsString, Optional<DateTimeFormatter> formatter) 540 { 541 if (StringUtils.isEmpty(localDateAsString)) 542 { 543 return null; 544 } 545 546 return LocalDate.parse(localDateAsString, formatter.orElse(DateTimeFormatter.ISO_LOCAL_DATE)); 547 } 548 549 /** 550 * Determines if a date is at midnight (00:00) for the given time-zone 551 * @param zonedDateTimeAsString the zoned date time as string 552 * @param zoneId the time-zone id. If empty the system default time-zone will be used. 553 * @return true if the date is at midnight 554 */ 555 public static boolean isAtMidnight(String zonedDateTimeAsString, String zoneId) 556 { 557 ZonedDateTime zonedDateTime = parseZonedDateTime(zonedDateTimeAsString); 558 zonedDateTime = zonedDateTime.withZoneSameInstant(StringUtils.isEmpty(zoneId) ? ZoneId.systemDefault() : ZoneId.of(zoneId)); 559 return zonedDateTime.getHour() == 0 && zonedDateTime.getMinute() == 0; 560 } 561}