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