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.LocalDateTime; 022import java.time.ZoneId; 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 date == null ? null : date.toInstant(); 071 } 072 073 /** 074 * Converts this {@link Date} object to a {@link ZonedDateTime}. 075 * @param date The date object 076 * @param zone The zone. If <code>null</code>, the system default zone id is used 077 * @return the {@link ZonedDateTime} formed from this {@link Date} 078 */ 079 public static ZonedDateTime asZonedDateTime(Date date, ZoneId zone) 080 { 081 return date == null ? null : asInstant(date).atZone(zone != null ? zone : ZoneId.systemDefault()); 082 } 083 084 /** 085 * Converts this {@link Calendar} object to a {@link ZonedDateTime}. 086 * @param calendar the calendar 087 * @return the {@link ZonedDateTime} formed from this {@link Calendar} 088 */ 089 public static ZonedDateTime asZonedDateTime(Calendar calendar) 090 { 091 return calendar.toInstant().atZone(ZoneId.systemDefault()); 092 } 093 094 /** 095 * Converts this epoch time to a {@link ZonedDateTime}. 096 * @param epochMilli the number of milliseconds from 1970-01-01T00:00:00Z 097 * @return the {@link ZonedDateTime} formed from this epoch time 098 */ 099 public static ZonedDateTime asZonedDateTime(long epochMilli) 100 { 101 return asZonedDateTime(epochMilli, null); 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 * @param zone the time-zone 108 * @return the {@link ZonedDateTime} formed from this epoch time 109 */ 110 public static ZonedDateTime asZonedDateTime(long epochMilli, ZoneId zone) 111 { 112 Instant instant = Instant.ofEpochMilli(epochMilli); 113 return ZonedDateTime.ofInstant(instant, Optional.ofNullable(zone).orElse(ZoneId.systemDefault())); 114 } 115 116 /** 117 * Converts this {@link Date} object to a {@link LocalDate}. 118 * 119 * This returns a {@link LocalDate} with the same year, month and day as this {@link Date}. 120 * @param date The date object 121 * @param zone The zone 122 * @return the {@link LocalDate} part of this {@link Date} 123 */ 124 public static LocalDate asLocalDate(Date date, ZoneId zone) 125 { 126 return asZonedDateTime(date, zone).toLocalDate(); 127 } 128 129 /** 130 * Converts this {@link Date} object to a {@link LocalDate}. 131 * 132 * This returns a {@link LocalDate} with the same year, month and day as this {@link Date}. 133 * @param date The date object 134 * @return the {@link LocalDate} part of this {@link Date} 135 */ 136 public static LocalDate asLocalDate(Date date) 137 { 138 return asLocalDate(date, ZoneId.systemDefault()); 139 } 140 141 /** 142 * Converts this {@link Calendar} object to a {@link LocalDate}. 143 * @param calendar the calendar 144 * @return the {@link LocalDate} object 145 */ 146 public static LocalDate asLocalDate(Calendar calendar) 147 { 148 return asZonedDateTime(calendar).toLocalDate(); 149 } 150 151 /** 152 * Converts this {@link Date} object to a {@link LocalDateTime}. 153 * 154 * This returns a {@link LocalDateTime} with the same year, month, day and time as this {@link Date}. 155 * @param date The date object 156 * @param zone The zone 157 * @return the {@link LocalDateTime} part of this {@link Date} 158 */ 159 public static LocalDateTime asLocalDateTime(Date date, ZoneId zone) 160 { 161 return asZonedDateTime(date, zone).toLocalDateTime(); 162 } 163 164 /** 165 * Converts this {@link Date} object to a {@link LocalDateTime}. 166 * 167 * This returns a {@link LocalDateTime} with the same year, month, day and time as this {@link Date}. 168 * @param date The date object 169 * @return the {@link LocalDateTime} part of this {@link Date} 170 */ 171 public static LocalDateTime asLocalDateTime(Date date) 172 { 173 return asLocalDateTime(date, ZoneId.systemDefault()); 174 } 175 176 /** 177 * Converts this {@link LocalDate} object to a {@link Date}. 178 * 179 * @param localDate The local date object 180 * @return the {@link Date} part of this {@link LocalDate} 181 */ 182 public static Date asDate(LocalDate localDate) 183 { 184 return asDate(localDate, ZoneId.systemDefault()); 185 } 186 187 /** 188 * Converts this {@link LocalDate} object to a {@link Date}. 189 * 190 * @param localDate The local date object 191 * @param zone The zone 192 * @return the {@link Date} part of this {@link LocalDate} 193 */ 194 public static Date asDate(LocalDate localDate, ZoneId zone) 195 { 196 return Date.from(localDate.atStartOfDay().atZone(zone).toInstant()); 197 } 198 199 /** 200 * Converts this {@link LocalDateTime} object to a {@link Date}. 201 * 202 * @param localDateTime The local date time object 203 * @return the {@link Date} part of this {@link LocalDateTime} 204 */ 205 public static Date asDate(LocalDateTime localDateTime) 206 { 207 return asDate(localDateTime, ZoneId.systemDefault()); 208 } 209 210 /** 211 * Converts this {@link LocalDateTime} object to a {@link Date}. 212 * 213 * @param localDateTime The local date time object 214 * @param zone The zone 215 * @return the {@link Date} part of this {@link LocalDateTime} 216 */ 217 public static Date asDate(LocalDateTime localDateTime, ZoneId zone) 218 { 219 return Date.from(localDateTime.atZone(zone).toInstant()); 220 } 221 222 /** 223 * Converts this {@link ZonedDateTime} object to a {@link Date}. 224 * 225 * @param zonedDateTime The local date time object 226 * @return the {@link Date} part of this {@link LocalDateTime} 227 */ 228 public static Date asDate(ZonedDateTime zonedDateTime) 229 { 230 return Date.from(zonedDateTime.toInstant()); 231 } 232 233 /** 234 * Converts this {@link ZonedDateTime} object to a {@link Calendar}. 235 * @param zonedDateTime the zoned date time 236 * @return the {@link Calendar} object 237 */ 238 public static Calendar asCalendar(ZonedDateTime zonedDateTime) 239 { 240 ZonedDateTime dateTimeOnDefaultZone = zonedDateTime.withZoneSameInstant(ZoneId.systemDefault()); 241 return GregorianCalendar.from(dateTimeOnDefaultZone); 242 } 243 244 /** 245 * Converts this {@link LocalDate} object to a {@link Calendar}. 246 * @param localDate the local date 247 * @return the {@link Calendar} object 248 */ 249 public static Calendar asCalendar(LocalDate localDate) 250 { 251 ZonedDateTime zdt = localDate.atStartOfDay(ZoneId.systemDefault()); 252 return GregorianCalendar.from(zdt); 253 } 254 255 /** 256 * Format a duration for logs 257 * @param duration duration to log 258 * @return a string representing the duration 259 */ 260 public static String formatDuration(Duration duration) 261 { 262 return formatDuration(duration.toMillis()); 263 } 264 265 /** 266 * Format a duration for logs 267 * @param duration miliseconds representing the duration 268 * @return a string representing the duration 269 */ 270 public static String formatDuration(long duration) 271 { 272 StringBuilder sb = new StringBuilder(); 273 long durationCopy = duration; 274 long ms = durationCopy % 1000; 275 durationCopy /= 1000; 276 long s = durationCopy % 60; 277 durationCopy /= 60; 278 long m = durationCopy % 60; 279 durationCopy /= 60; 280 long h = durationCopy % 24; 281 durationCopy /= 24; 282 283 boolean showDays = durationCopy > 0; 284 boolean showHours = showDays || h > 0; 285 boolean showMinuts = showHours || m > 0; 286 boolean showSeconds = showMinuts || s > 0; 287 288 if (showDays) 289 { 290 sb.append(durationCopy); 291 sb.append("j "); 292 } 293 if (showHours) 294 { 295 sb.append(formatNumber(h, 2)); 296 sb.append("h "); 297 } 298 if (showMinuts) 299 { 300 sb.append(formatNumber(m, 2)); 301 sb.append("m "); 302 } 303 if (showSeconds) 304 { 305 sb.append(formatNumber(s, 2)); 306 sb.append("s "); 307 } 308 sb.append(formatNumber(ms, 3)); 309 sb.append("ms"); 310 return sb.toString(); 311 } 312 private static String formatNumber(long number, int nbNumbers) 313 { 314 String numberFormatted = String.valueOf(number); 315 while (numberFormatted.length() < nbNumbers) 316 { 317 numberFormatted = "0" + numberFormatted; 318 } 319 return numberFormatted; 320 } 321 322 /** 323 * 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'. 324 * This formatter is similar to {@link DateTimeFormatter#ISO_OFFSET_DATE_TIME} but force 3-digits milliseconds. 325 * @return ISO date-time formatter 326 */ 327 public static DateTimeFormatter getISODateTimeFormatter() 328 { 329 return __ISO_OFFSET_DATE_TIME; 330 } 331 332 /** 333 * Converts a {@link Date} object to {@link String} using the ISO date formatter 334 * @param value the value to convert 335 * @return the date as a {@link String} 336 */ 337 public static String dateToString(Date value) 338 { 339 if (value == null) 340 { 341 return null; 342 } 343 344 ZonedDateTime zdt = DateUtils.asZonedDateTime(value, null); 345 return zonedDateTimeToString(zdt); 346 } 347 348 /** 349 * Converts this epoch time to a {@link String} using the ISO date formatter 350 * @param epochMilli the number of milliseconds from 1970-01-01T00:00:00Z 351 * @return the epoch time to a {@link String} 352 */ 353 public static String epochMilliToString(long epochMilli) 354 { 355 ZonedDateTime zdt = DateUtils.asZonedDateTime(epochMilli); 356 return zonedDateTimeToString(zdt); 357 } 358 359 /** 360 * Converts a {@link ZonedDateTime} object to {@link String} using the {@link #__ISO_OFFSET_DATE_TIME ISO date formatter} 361 * @param zonedDateTime the zoned date time 362 * @return the zoned date time as a {@link String} 363 */ 364 public static String zonedDateTimeToString(ZonedDateTime zonedDateTime) 365 { 366 return zonedDateTime.format(getISODateTimeFormatter()); 367 } 368 369 /** 370 * Converts a {@link ZonedDateTime} object to {@link String} using the given pattern for formatting, 371 * in the given zone, so as to format the instant in another zone 372 * <br>For instance, if provided pattern is 'uuuu-MM-dd'T'HH:mm:ss.SSSXXX': 373 * <ul> 374 * <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> 375 * <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> 376 * </ul> 377 * @param zonedDateTime the zoned date time 378 * @param zoneId the target zone 379 * @param pattern the pattern for formatting 380 * @return the zoned date time as a {@link String}, in the given zone 381 * @throws IllegalArgumentException if the pattern is invalid 382 */ 383 public static String zonedDateTimeToString(ZonedDateTime zonedDateTime, ZoneId zoneId, String pattern) throws IllegalArgumentException 384 { 385 ZonedDateTime sameInstantAtZone = zonedDateTime.withZoneSameInstant(zoneId); 386 DateTimeFormatter formatter = _createFormatter(pattern); 387 return sameInstantAtZone.format(formatter); 388 } 389 390 /** 391 * Converts a {@link LocalDate} object to {@link String} using the ISO date formatter 392 * @param localDate the local date 393 * @return the local date as a {@link String} 394 */ 395 public static String localDateToString(LocalDate localDate) 396 { 397 return localDate.format(DateTimeFormatter.ISO_LOCAL_DATE); 398 } 399 400 /** 401 * Parses a String into a {@link Date}, using ISO 8601 format. 402 * @param value an ISO 8601 formatted String. 403 * @return the corresponding Date, or null if the input is null. 404 */ 405 public static Date parse(String value) 406 { 407 if (StringUtils.isEmpty(value)) 408 { 409 return null; 410 } 411 412 LocalDateTime ldt = LocalDateTime.parse(value, DateTimeFormatter.ISO_DATE_TIME); 413 return asDate(ldt); 414 } 415 416 /** 417 * Parses a String into a {@link ZonedDateTime}, using ISO date formatter. 418 * @param zonedDateTimeAsString the zoned date time as string 419 * @return the {@link ZonedDateTime} object or null if the input is null. 420 */ 421 public static ZonedDateTime parseZonedDateTime(String zonedDateTimeAsString) 422 { 423 if (StringUtils.isEmpty(zonedDateTimeAsString)) 424 { 425 return null; 426 } 427 428 return ZonedDateTime.parse(zonedDateTimeAsString, DateTimeFormatter.ISO_DATE_TIME); 429 } 430 431 /** 432 * Parses a String into a {@link LocalDate}, using ISO date formatter. 433 * @param localDateAsString the local date as string 434 * @return the {@link LocalDate} object or null if the input is null. 435 */ 436 public static LocalDate parseLocalDate(String localDateAsString) 437 { 438 if (StringUtils.isEmpty(localDateAsString)) 439 { 440 return null; 441 } 442 443 return LocalDate.parse(localDateAsString, DateTimeFormatter.ISO_LOCAL_DATE); 444 } 445}