001/* 002 * (C) Copyright 2019 Nuxeo (http://nuxeo.com/) and others. 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 * 016 * Contributors: 017 * pierre 018 */ 019package org.nuxeo.common.utils; 020 021import static java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME; 022import static java.time.format.DateTimeFormatter.ISO_ZONED_DATE_TIME; 023import static java.time.temporal.ChronoField.DAY_OF_MONTH; 024import static java.time.temporal.ChronoField.HOUR_OF_DAY; 025import static java.time.temporal.ChronoField.MINUTE_OF_HOUR; 026import static java.time.temporal.ChronoField.MONTH_OF_YEAR; 027import static java.time.temporal.ChronoField.NANO_OF_SECOND; 028import static java.time.temporal.ChronoField.SECOND_OF_MINUTE; 029 030import java.time.DateTimeException; 031import java.time.Instant; 032import java.time.LocalDate; 033import java.time.ZoneOffset; 034import java.time.ZonedDateTime; 035import java.time.format.DateTimeFormatter; 036import java.time.format.DateTimeFormatterBuilder; 037import java.time.temporal.TemporalAccessor; 038import java.util.Calendar; 039import java.util.Date; 040import java.util.GregorianCalendar; 041 042/** 043 * Java 8 time utilities. 044 * 045 * @since 11.1 046 */ 047public class DateUtils { 048 049 public static final String ISODATETIME_GENERIC_PATTERN = "yyyy[-MM][-dd['T'HH[:mm[:ss[.SSS]]]]][XXX]"; 050 051 public static final DateTimeFormatter ISO_ROBUST_DATE_TIME = robustOfPattern(ISODATETIME_GENERIC_PATTERN); 052 053 public static final DateTimeFormatter[] formatters = { 054 ISO_OFFSET_DATE_TIME, 055 ISO_ZONED_DATE_TIME, 056 ISO_ROBUST_DATE_TIME, }; 057 058 public static final DateTimeFormatter ISO_DATE_ONLY = DateTimeFormatter.ofPattern("yyyy-MM-dd"); 059 060 private DateUtils() { 061 // utility class 062 } 063 064 public static String formatISODateTime(Calendar calendar) { 065 return formatISODateTime(toZonedDateTime(calendar)); 066 } 067 068 public static String formatISODateTime(Date date) { 069 return formatISODateTime(toZonedDateTime(date)); 070 } 071 072 public static String formatISODateTime(ZonedDateTime zdt) { 073 return formatISODateTime(zdt, false); 074 } 075 076 public static String formatISODateTime(ZonedDateTime zdt, boolean dateOnly) { 077 if (zdt == null) { 078 return null; 079 } 080 if (dateOnly) { 081 return ISO_DATE_ONLY.format(zdt); 082 } 083 return ISO_ROBUST_DATE_TIME.format(zdt); 084 } 085 086 public static Date nowIfNull(Date date) { 087 return date == null ? new Date() : date; 088 } 089 090 public static Calendar nowIfNull(Calendar calendar) { 091 return calendar == null ? Calendar.getInstance() : calendar; 092 } 093 094 public static ZonedDateTime nowIfNull(ZonedDateTime zdt) { 095 return zdt == null ? ZonedDateTime.now() : zdt; 096 } 097 098 public static ZonedDateTime parseISODateTime(String string) { 099 return parse(string, formatters); 100 } 101 102 public static ZonedDateTime parse(String string, DateTimeFormatter... formatters) { 103 // workaround to allow space instead of T after the date part 104 if (string.length() > 10 && string.charAt(10) == ' ') { 105 char[] s = string.toCharArray(); 106 s[10] = 'T'; 107 string = new String(s); 108 } 109 for (DateTimeFormatter formatter : formatters) { 110 try { 111 TemporalAccessor ta = formatter.parseBest(string, ZonedDateTime::from, LocalDate::from); 112 if (ta instanceof ZonedDateTime) { 113 return (ZonedDateTime) ta; 114 } else { 115 return ((LocalDate) ta).atStartOfDay(ZoneOffset.UTC); 116 } 117 } catch (Exception e) { 118 // ignore, try another formatter 119 } 120 } 121 throw new DateTimeException("Could not parse '" + string + "'"); 122 } 123 124 public static final DateTimeFormatter robustOfPattern(String pattern) { 125 return new DateTimeFormatterBuilder().appendPattern(pattern) 126 .parseDefaulting(MONTH_OF_YEAR, 1) 127 .parseDefaulting(DAY_OF_MONTH, 1) 128 .parseDefaulting(HOUR_OF_DAY, 0) 129 .parseDefaulting(MINUTE_OF_HOUR, 0) 130 .parseDefaulting(SECOND_OF_MINUTE, 0) 131 .parseDefaulting(NANO_OF_SECOND, 0) 132 .toFormatter() 133 .withZone(ZoneOffset.UTC); 134 } 135 136 public static Calendar toCalendar(Instant instant) { 137 if (instant == null) { 138 return null; 139 } 140 // an Instant is on UTC by definition 141 var zdt = ZonedDateTime.ofInstant(instant, ZoneOffset.UTC); 142 return GregorianCalendar.from(zdt); 143 } 144 145 public static Date toDate(ZonedDateTime zdt) { 146 if (zdt == null) { 147 return null; 148 } 149 return Date.from(zdt.toInstant()); 150 } 151 152 public static Instant toInstant(Calendar calendar) { 153 if (calendar == null) { 154 return null; 155 } 156 return calendar.toInstant(); 157 } 158 159 public static ZonedDateTime toZonedDateTime(Calendar calendar) { 160 if (calendar == null) { 161 return null; 162 } 163 return ZonedDateTime.ofInstant(calendar.toInstant(), ZoneOffset.UTC); 164 } 165 166 public static ZonedDateTime toZonedDateTime(Date date) { 167 if (date == null) { 168 return null; 169 } 170 return ZonedDateTime.ofInstant(date.toInstant(), ZoneOffset.UTC); 171 } 172 173}