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