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.Date;
028
029import org.apache.commons.lang3.StringUtils;
030
031/**
032 * Helper for converting dates from the old ({@link Date}) to the new ({@link java.time}) JDK
033 * Special thanks to http://stackoverflow.com/questions/21242110/convert-java-util-date-to-java-time-localdate#answer-27378709
034 * which inspired this code
035 * 
036 * See also http://stackoverflow.com/questions/19431234/converting-between-java-time-localdatetime-and-java-util-date
037 */
038public final class DateUtils
039{
040    /**
041     * 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'. 
042     */
043    private static DateTimeFormatter __ISO_OFFSET_DATE_TIME = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX").withResolverStyle(ResolverStyle.STRICT).withChronology(IsoChronology.INSTANCE);
044    
045    private DateUtils()
046    {
047        // empty
048    }
049    
050    /**
051     * Converts this {@link Date} object to an {@link Instant}.
052     * @param date The date object
053     * @return an instant representing the same point on the time-line as this {@link Date} object
054     */
055    public static Instant asInstant(Date date)
056    {
057        return date == null ? null : date.toInstant();
058    }
059    
060    /**
061     * Converts this {@link Date} object to an {@link Instant}.
062     * @param date The date object
063     * @param zone The zone
064     * @return an instant representing the same point on the time-line as this {@link Date} object
065     */
066    public static ZonedDateTime asZonedDateTime(Date date, ZoneId zone)
067    {
068        return date == null ? null : asInstant(date).atZone(zone != null ? zone : ZoneId.systemDefault());
069    }
070    
071    /**
072     * Converts this {@link Date} object to a {@link LocalDate}.
073     * 
074     * This returns a {@link LocalDate} with the same year, month and day as this {@link Date}.
075     * @param date The date object
076     * @param zone The zone
077     * @return the {@link LocalDate} part of this {@link Date}
078     */
079    public static LocalDate asLocalDate(Date date, ZoneId zone)
080    {
081        return asZonedDateTime(date, zone).toLocalDate();
082    }
083    
084    /**
085     * Converts this {@link Date} object to a {@link LocalDate}.
086     * 
087     * This returns a {@link LocalDate} with the same year, month and day as this {@link Date}.
088     * @param date The date object
089     * @return the {@link LocalDate} part of this {@link Date}
090     */
091    public static LocalDate asLocalDate(Date date)
092    {
093        return asLocalDate(date, ZoneId.systemDefault());
094    }
095    
096    /**
097     * Converts this {@link Date} object to a {@link LocalDateTime}.
098     * 
099     * This returns a {@link LocalDateTime} with the same year, month, day and time as this {@link Date}.
100     * @param date The date object
101     * @param zone The zone
102     * @return the {@link LocalDateTime} part of this {@link Date}
103     */
104    public static LocalDateTime asLocalDateTime(Date date, ZoneId zone)
105    {
106        return asZonedDateTime(date, zone).toLocalDateTime();
107    }
108    
109    /**
110     * Converts this {@link Date} object to a {@link LocalDateTime}.
111     * 
112     * This returns a {@link LocalDateTime} with the same year, month, day and time as this {@link Date}.
113     * @param date The date object
114     * @return the {@link LocalDateTime} part of this {@link Date}
115     */
116    public static LocalDateTime asLocalDateTime(Date date)
117    {
118        return asLocalDateTime(date, ZoneId.systemDefault());
119    }
120    
121    /**
122     * Converts this {@link LocalDate} object to a {@link Date}.
123     * 
124     * @param localDate The local date object
125     * @return the {@link Date} part of this {@link LocalDate}
126     */
127    public static Date asDate(LocalDate localDate)
128    {
129        return asDate(localDate, ZoneId.systemDefault());
130    }
131    
132    /**
133     * Converts this {@link LocalDate} object to a {@link Date}.
134     * 
135     * @param localDate The local date object
136     * @param zone The zone
137     * @return the {@link Date} part of this {@link LocalDate}
138     */
139    public static Date asDate(LocalDate localDate, ZoneId zone)
140    {
141        return Date.from(localDate.atStartOfDay().atZone(zone).toInstant());
142    }
143
144    /**
145     * Converts this {@link LocalDateTime} object to a {@link Date}.
146     * 
147     * @param localDateTime The local date time object
148     * @return the {@link Date} part of this {@link LocalDateTime}
149     */
150    public static Date asDate(LocalDateTime localDateTime)
151    {
152        return asDate(localDateTime, ZoneId.systemDefault());
153    }
154    
155    /**
156     * Converts this {@link LocalDateTime} object to a {@link Date}.
157     * 
158     * @param localDateTime The local date time object
159     * @param zone The zone
160     * @return the {@link Date} part of this {@link LocalDateTime}
161     */
162    public static Date asDate(LocalDateTime localDateTime, ZoneId zone)
163    {
164        return Date.from(localDateTime.atZone(zone).toInstant());
165    }
166
167    /**
168     * Converts this {@link ZonedDateTime} object to a {@link Date}.
169     * 
170     * @param zonedDateTime The local date time object
171     * @return the {@link Date} part of this {@link LocalDateTime}
172     */
173    public static Date asDate(ZonedDateTime zonedDateTime)
174    {
175        return Date.from(zonedDateTime.toInstant());
176    }
177    
178    /**
179     * Format a duration for logs
180     * @param duration duration to log
181     * @return a string representing the duration
182     */
183    public static String formatDuration(Duration duration)
184    {
185        return formatDuration(duration.toMillis());
186    }
187    
188    /**
189     * Format a duration for logs
190     * @param duration miliseconds representing the duration
191     * @return a string representing the duration
192     */
193    public static String formatDuration(long duration)
194    {
195        StringBuilder sb = new StringBuilder();
196        long durationCopy = duration;
197        long ms = durationCopy % 1000;
198        durationCopy /= 1000;
199        long s = durationCopy % 60;
200        durationCopy /= 60;
201        long m = durationCopy % 60;
202        durationCopy /= 60;
203        long h = durationCopy % 24;
204        durationCopy /= 24;
205        
206        boolean showDays = durationCopy > 0;
207        boolean showHours = showDays || h > 0;
208        boolean showMinuts = showHours || m > 0;
209        boolean showSeconds = showMinuts || s > 0;
210
211        if (showDays)
212        {
213            sb.append(durationCopy);
214            sb.append("j ");
215        }
216        if (showHours)
217        {
218            sb.append(formatNumber(h, 2));
219            sb.append("h ");
220        }
221        if (showMinuts)
222        {
223            sb.append(formatNumber(m, 2));
224            sb.append("m ");
225        }
226        if (showSeconds)
227        {
228            sb.append(formatNumber(s, 2));
229            sb.append("s ");
230        }
231        sb.append(formatNumber(ms, 3));
232        sb.append("ms");
233        return sb.toString();
234    }
235    private static String formatNumber(long number, int nbNumbers)
236    {
237        String numberFormatted = String.valueOf(number);
238        while (numberFormatted.length() < nbNumbers)
239        {
240            numberFormatted = "0" + numberFormatted;
241        }
242        return numberFormatted;
243    }
244    
245    /**
246     * 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'. 
247     * This formatter is similar to {@link DateTimeFormatter#ISO_OFFSET_DATE_TIME} but force 3-digits milliseconds.
248     * @return ISO date-time formatter
249     */
250    public static DateTimeFormatter getISODateTimeFormatter()
251    {
252        return __ISO_OFFSET_DATE_TIME;
253    }
254    
255    /**
256     * Converts a {@link Date} object to {@link String} using the ISO date formatter
257     * @param value the value to convert
258     * @return the date as a {@link String}
259     */
260    public static String dateToString(Date value)
261    {
262        if (value == null)
263        {
264            return null;
265        }
266        
267        ZonedDateTime zdt = DateUtils.asZonedDateTime(value, null);           
268        return zdt.format(getISODateTimeFormatter());
269    }
270    
271    /**
272     * Parses a String into a {@link Date}, using ISO 8601 format.
273     * @param value an ISO 8601 formatted String.
274     * @return the corresponding Date, or null if the input is null.
275     */
276    public static Date parse(String value)
277    {
278        if (StringUtils.isEmpty(value))
279        {
280            return null;
281        }
282        
283        LocalDateTime ldt = LocalDateTime.parse(value, DateTimeFormatter.ISO_DATE_TIME);
284        return asDate(ldt);
285    }
286}