2022-07-20 13:18:57 +02:00
|
|
|
|
package fr.pandacube.lib.util;
|
2022-07-10 00:55:56 +02:00
|
|
|
|
|
2020-11-02 23:23:41 +01:00
|
|
|
|
import java.time.Instant;
|
|
|
|
|
import java.time.LocalDateTime;
|
|
|
|
|
import java.time.format.DateTimeFormatter;
|
|
|
|
|
import java.util.Arrays;
|
2020-06-08 16:16:18 +02:00
|
|
|
|
import java.util.Calendar;
|
2022-07-10 00:55:56 +02:00
|
|
|
|
import java.util.Comparator;
|
2020-06-08 16:16:18 +02:00
|
|
|
|
import java.util.GregorianCalendar;
|
2021-03-21 20:17:31 +01:00
|
|
|
|
import java.util.Locale;
|
|
|
|
|
import java.util.TimeZone;
|
2020-11-02 23:23:41 +01:00
|
|
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
|
|
|
import java.util.concurrent.atomic.AtomicLong;
|
2020-06-08 16:16:18 +02:00
|
|
|
|
import java.util.regex.Matcher;
|
|
|
|
|
import java.util.regex.Pattern;
|
2020-11-02 23:23:41 +01:00
|
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
2022-07-28 01:13:35 +02:00
|
|
|
|
/**
|
|
|
|
|
* Utility class providing methods to display human readable time and duration, and parse duration strings.
|
|
|
|
|
*
|
|
|
|
|
* The methods that return date and daytime are hardcoded in French.
|
|
|
|
|
*/
|
2016-02-16 20:07:51 +01:00
|
|
|
|
public class TimeUtil {
|
2020-11-02 23:23:41 +01:00
|
|
|
|
|
2016-07-14 14:22:23 +02:00
|
|
|
|
|
2022-07-28 01:13:35 +02:00
|
|
|
|
private static final DateTimeFormatter cmpDayOfWeekFormatter = DateTimeFormatter.ofPattern("EEE", Locale.FRENCH);
|
|
|
|
|
private static final DateTimeFormatter dayOfWeekFormatter = DateTimeFormatter.ofPattern("EEEE", Locale.FRENCH);
|
|
|
|
|
private static final DateTimeFormatter dayOfMonthFormatter = DateTimeFormatter.ofPattern("d", Locale.FRENCH);
|
|
|
|
|
private static final DateTimeFormatter cmpMonthFormatter = DateTimeFormatter.ofPattern("MMM", Locale.FRENCH);
|
|
|
|
|
private static final DateTimeFormatter monthFormatter = DateTimeFormatter.ofPattern("MMMM", Locale.FRENCH);
|
|
|
|
|
private static final DateTimeFormatter yearFormatter = DateTimeFormatter.ofPattern("uuuu", Locale.FRENCH);
|
|
|
|
|
|
|
|
|
|
private static final DateTimeFormatter HMSFormatter = DateTimeFormatter.ofPattern("HH:mm:ss", Locale.FRENCH);
|
|
|
|
|
private static final DateTimeFormatter HMFormatter = DateTimeFormatter.ofPattern("HH:mm", Locale.FRENCH);
|
|
|
|
|
private static final DateTimeFormatter HFormatter = DateTimeFormatter.ofPattern("H'h'", Locale.FRENCH);
|
2016-07-14 14:22:23 +02:00
|
|
|
|
|
2021-09-02 14:27:03 +02:00
|
|
|
|
|
2022-07-28 01:13:35 +02:00
|
|
|
|
/**
|
|
|
|
|
* Provides a human readable date of the provided time, with ability to adapt the text relatively to the current
|
|
|
|
|
* time (for instance "il y a 13 minutes" (french for "13 minutes ago"))
|
|
|
|
|
* <p>
|
|
|
|
|
* <b>This method renders the text in French.</b>
|
|
|
|
|
*
|
|
|
|
|
* @param time the timestamp in milliseconds of the time to diplay.
|
|
|
|
|
* @param showSeconds if the returned string should includes seconds (true) or not (false). To have more control
|
|
|
|
|
* over the precision, call {@link #relativeDateFr(long, RelativePrecision, DisplayPrecision,
|
|
|
|
|
* boolean)}.
|
|
|
|
|
* @param compactWords true to use compact words, false to use full words.
|
|
|
|
|
* @return a human readable {@link String} representation of the provided time.
|
|
|
|
|
*/
|
|
|
|
|
public static String relativeDateFr(long time, boolean showSeconds, boolean compactWords) {
|
|
|
|
|
return relativeDateFr(time,
|
2021-10-10 01:16:12 +02:00
|
|
|
|
showSeconds ? RelativePrecision.SECONDS : RelativePrecision.MINUTES,
|
|
|
|
|
showSeconds ? DisplayPrecision.SECONDS : DisplayPrecision.MINUTES,
|
2021-09-02 14:27:03 +02:00
|
|
|
|
compactWords);
|
|
|
|
|
}
|
2022-07-28 01:13:35 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Provides a human readable date of the provided time, with ability to adapt the text relatively to the current
|
|
|
|
|
* time (for instance "il y a 13 minutes" (french for "13 minutes ago"))
|
|
|
|
|
* <p>
|
|
|
|
|
* <b>This method renders the text in French.</b>
|
|
|
|
|
*
|
|
|
|
|
* @param time the timestamp in milliseconds of the time to diplay.
|
|
|
|
|
* @param relPrecision the precision of the relative text.
|
|
|
|
|
* @param dispPrecision the precision of the full date and time.
|
|
|
|
|
* @param compactWords true to use compact words, false to use full words.
|
|
|
|
|
* @return a human readable {@link String} representation of the provided time.
|
|
|
|
|
*/
|
|
|
|
|
public static String relativeDateFr(long time, RelativePrecision relPrecision, DisplayPrecision dispPrecision, boolean compactWords) {
|
2020-11-02 23:23:41 +01:00
|
|
|
|
long currentTime = System.currentTimeMillis();
|
|
|
|
|
|
|
|
|
|
|
2022-07-28 01:13:35 +02:00
|
|
|
|
LocalDateTime displayDateTime = toLocalDateTime(time);
|
2020-11-02 23:23:41 +01:00
|
|
|
|
LocalDateTime currentDateTime = toLocalDateTime(currentTime);
|
|
|
|
|
|
|
|
|
|
|
2022-07-28 01:13:35 +02:00
|
|
|
|
long timeDiff = currentTime - time;
|
2020-11-02 23:23:41 +01:00
|
|
|
|
long timeDiffSec = timeDiff / 1000;
|
|
|
|
|
|
|
|
|
|
if (timeDiffSec < -1) {
|
|
|
|
|
// in the future
|
2021-09-02 14:27:03 +02:00
|
|
|
|
if (relPrecision == RelativePrecision.SECONDS) {
|
|
|
|
|
if (timeDiffSec > -60)
|
|
|
|
|
return "dans " + (-timeDiffSec) + (compactWords ? "s" : " secondes");
|
|
|
|
|
}
|
2022-07-28 01:13:35 +02:00
|
|
|
|
if (relPrecision.ordinal() >= RelativePrecision.MINUTES.ordinal()) {
|
2021-09-02 14:27:03 +02:00
|
|
|
|
if (timeDiffSec > -60)
|
|
|
|
|
return compactWords ? "dans moins d’1min" : "dans moins d’une minute";
|
|
|
|
|
if (timeDiffSec > -60*2) // dans 2 min
|
|
|
|
|
return compactWords ? "dans 1min" : "dans une minute";
|
|
|
|
|
if (timeDiffSec > -3600) // dans moins d’1h
|
2022-07-10 00:55:56 +02:00
|
|
|
|
return "dans " + (-timeDiffSec/60) + (compactWords ? "min" : " minutes");
|
2021-09-02 14:27:03 +02:00
|
|
|
|
}
|
2022-07-28 01:13:35 +02:00
|
|
|
|
if (relPrecision.ordinal() >= RelativePrecision.HOURS.ordinal()) {
|
2021-09-02 14:27:03 +02:00
|
|
|
|
if (timeDiffSec > -3600) // dans moins d’1h
|
|
|
|
|
return compactWords ? "dans moins d’1h" : "dans moins d’une heure";
|
|
|
|
|
if (timeDiffSec > -3600*2) // dans moins de 2h
|
|
|
|
|
return compactWords ? "dans 1h" : "dans une heure";
|
|
|
|
|
if (timeDiffSec > -3600*12) // dans moins de 12h
|
2022-07-10 00:55:56 +02:00
|
|
|
|
return "dans " + (-timeDiffSec/3600) + (compactWords ? "h" : " heures");
|
2021-09-02 14:27:03 +02:00
|
|
|
|
}
|
2022-07-28 01:13:35 +02:00
|
|
|
|
if (relPrecision.ordinal() >= RelativePrecision.DAYS.ordinal()) {
|
2021-09-02 14:27:03 +02:00
|
|
|
|
LocalDateTime nextMidnight = LocalDateTime.of(currentDateTime.getYear(), currentDateTime.getMonth(), currentDateTime.getDayOfMonth(), 0, 0).plusDays(1);
|
|
|
|
|
if (displayDateTime.isBefore(nextMidnight)) // aujourd'hui
|
2022-07-28 01:13:35 +02:00
|
|
|
|
return "aujourd’hui à " + dayTimeFr(time, dispPrecision);
|
2021-09-02 14:27:03 +02:00
|
|
|
|
if (displayDateTime.isBefore(nextMidnight.plusDays(1))) // demain
|
2022-07-28 01:13:35 +02:00
|
|
|
|
return "demain à " + dayTimeFr(time, dispPrecision);
|
2021-09-02 14:27:03 +02:00
|
|
|
|
if (displayDateTime.isBefore(nextMidnight.plusDays(5))) // dans moins d'1 semaine
|
|
|
|
|
return (compactWords ? cmpDayOfWeekFormatter : dayOfWeekFormatter).format(displayDateTime) + " "
|
|
|
|
|
+ dayOfMonthFormatter.format(displayDateTime) + " à "
|
2022-07-28 01:13:35 +02:00
|
|
|
|
+ dayTimeFr(time, dispPrecision);
|
2020-11-02 23:23:41 +01:00
|
|
|
|
}
|
2022-07-10 00:55:56 +02:00
|
|
|
|
|
2016-02-16 20:07:51 +01:00
|
|
|
|
}
|
2020-11-02 23:23:41 +01:00
|
|
|
|
else {
|
|
|
|
|
// present and past
|
|
|
|
|
if (timeDiffSec <= 1)
|
|
|
|
|
return "maintenant";
|
2021-09-02 14:27:03 +02:00
|
|
|
|
|
|
|
|
|
if (relPrecision == RelativePrecision.SECONDS) {
|
|
|
|
|
if (timeDiffSec < 60) // ya moins d'1 min
|
|
|
|
|
return "il y a " + timeDiffSec + (compactWords ? "s" : " secondes");
|
|
|
|
|
}
|
2022-07-28 01:13:35 +02:00
|
|
|
|
if (relPrecision.ordinal() >= RelativePrecision.MINUTES.ordinal()) {
|
2021-09-02 14:27:03 +02:00
|
|
|
|
if (timeDiffSec < 60) // ya moins d'1 min
|
|
|
|
|
return compactWords ? "il y a moins d’1min" : "il y a moins d’une minute";
|
|
|
|
|
if (timeDiffSec < 60*2) // ya moins de 2 min
|
|
|
|
|
return compactWords ? "il y a 1min" : "il y a une minute";
|
|
|
|
|
if (timeDiffSec < 3600) // ya moins d'1h
|
2022-07-10 00:55:56 +02:00
|
|
|
|
return "il y a " + (timeDiffSec/60) + (compactWords ? "min" : " minutes");
|
2021-09-02 14:27:03 +02:00
|
|
|
|
}
|
2022-07-28 01:13:35 +02:00
|
|
|
|
if (relPrecision.ordinal() >= RelativePrecision.HOURS.ordinal()) {
|
2021-09-02 14:27:03 +02:00
|
|
|
|
if (timeDiffSec < 3600) // ya moins d'1h
|
|
|
|
|
return "il y a moins d’une heure";
|
|
|
|
|
if (timeDiffSec < 3600*2) // ya moins de 2h
|
|
|
|
|
return "il y a une heure";
|
|
|
|
|
if (timeDiffSec < 3600*12) // ya moins de 12h
|
2022-07-10 00:55:56 +02:00
|
|
|
|
return "il y a " + (timeDiffSec/3600) + " heures";
|
2021-09-02 14:27:03 +02:00
|
|
|
|
}
|
2022-07-28 01:13:35 +02:00
|
|
|
|
if (relPrecision.ordinal() >= RelativePrecision.DAYS.ordinal()) {
|
2021-09-02 14:27:03 +02:00
|
|
|
|
LocalDateTime lastMidnight = LocalDateTime.of(currentDateTime.getYear(), currentDateTime.getMonth(), currentDateTime.getDayOfMonth(), 0, 0);
|
|
|
|
|
if (!displayDateTime.isBefore(lastMidnight)) // aujourd'hui
|
2022-07-28 01:13:35 +02:00
|
|
|
|
return "aujourd’hui à " + dayTimeFr(time, dispPrecision);
|
2021-09-02 14:27:03 +02:00
|
|
|
|
if (!displayDateTime.isBefore(lastMidnight.minusDays(1))) // hier
|
2022-07-28 01:13:35 +02:00
|
|
|
|
return "hier à " + dayTimeFr(time, dispPrecision);
|
2021-09-02 14:27:03 +02:00
|
|
|
|
if (!displayDateTime.isBefore(lastMidnight.minusDays(6))) // ya moins d'1 semaine
|
|
|
|
|
return (compactWords ? cmpDayOfWeekFormatter : dayOfWeekFormatter).format(displayDateTime) + " dernier à "
|
2022-07-28 01:13:35 +02:00
|
|
|
|
+ dayTimeFr(time, dispPrecision);
|
2020-11-02 23:23:41 +01:00
|
|
|
|
}
|
2022-07-10 00:55:56 +02:00
|
|
|
|
|
2020-11-02 23:23:41 +01:00
|
|
|
|
}
|
2022-07-28 01:13:35 +02:00
|
|
|
|
return fullDateFr(time, dispPrecision, true, compactWords);
|
2022-07-10 00:55:56 +02:00
|
|
|
|
|
2020-11-02 23:23:41 +01:00
|
|
|
|
}
|
2021-09-02 14:27:03 +02:00
|
|
|
|
|
2022-07-28 01:13:35 +02:00
|
|
|
|
/**
|
|
|
|
|
* Enumaration of different level of precision to display a relative time.
|
|
|
|
|
*/
|
|
|
|
|
public enum RelativePrecision {
|
|
|
|
|
/**
|
|
|
|
|
* No relative display.
|
|
|
|
|
*/
|
|
|
|
|
NONE,
|
|
|
|
|
/**
|
|
|
|
|
* Days precision for relative display.
|
|
|
|
|
*/
|
|
|
|
|
DAYS,
|
|
|
|
|
/**
|
|
|
|
|
* Hours precision for relative display.
|
|
|
|
|
*/
|
|
|
|
|
HOURS,
|
|
|
|
|
/**
|
|
|
|
|
* Minutes precision for relative display.
|
|
|
|
|
*/
|
|
|
|
|
MINUTES,
|
|
|
|
|
/**
|
|
|
|
|
* Seconds precision for relative display.
|
|
|
|
|
*/
|
2023-04-10 19:17:18 +02:00
|
|
|
|
SECONDS
|
2021-09-02 14:27:03 +02:00
|
|
|
|
}
|
2022-07-28 01:13:35 +02:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Enumaration of different level of precision to display a date and daytime.
|
|
|
|
|
*/
|
2021-09-02 14:27:03 +02:00
|
|
|
|
public enum DisplayPrecision {
|
2022-07-28 01:13:35 +02:00
|
|
|
|
/**
|
|
|
|
|
* Display only the date.
|
|
|
|
|
*/
|
|
|
|
|
DAYS,
|
|
|
|
|
/**
|
|
|
|
|
* Display the date and the hour of the day.
|
|
|
|
|
*/
|
|
|
|
|
HOURS,
|
|
|
|
|
/**
|
|
|
|
|
* Display the date and the time of the day up to the minute.
|
|
|
|
|
*/
|
|
|
|
|
MINUTES,
|
|
|
|
|
/**
|
|
|
|
|
* Display the date and the time of the day up to the second.
|
|
|
|
|
*/
|
|
|
|
|
SECONDS
|
2021-09-02 14:27:03 +02:00
|
|
|
|
}
|
|
|
|
|
|
2022-07-28 01:13:35 +02:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns a string representation of the date (and eventually day time) of the provided timestamp.
|
|
|
|
|
* <p>
|
|
|
|
|
* <b>This method renders the text in French.</b>
|
|
|
|
|
*
|
|
|
|
|
* @param timestamp the time to represent in the returned string.
|
|
|
|
|
* @param showSeconds if the returned string should includes seconds (true) or not (false). To have more control
|
|
|
|
|
* over the precision, call {@link #fullDateFr(long, DisplayPrecision, boolean, boolean)}.
|
|
|
|
|
* @param showWeekday true to show the week day, false otherwise.
|
|
|
|
|
* @param compactWords true to use compact words, false to use full words.
|
|
|
|
|
* @return a string representation of the date (and eventually day time) of the provided timestamp.
|
|
|
|
|
*/
|
|
|
|
|
public static String fullDateFr(long timestamp, boolean showSeconds, boolean showWeekday, boolean compactWords) {
|
|
|
|
|
return fullDateFr(timestamp, showSeconds ? DisplayPrecision.SECONDS : DisplayPrecision.MINUTES, showWeekday, compactWords);
|
2021-09-02 14:27:03 +02:00
|
|
|
|
}
|
2022-07-28 01:13:35 +02:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns a string representation of the date (and eventually day time) of the provided timestamp.
|
|
|
|
|
* <p>
|
|
|
|
|
* <b>This method renders the text in French.</b>
|
|
|
|
|
*
|
|
|
|
|
* @param timestamp the time to represent in the returned string.
|
|
|
|
|
* @param precision the {@link DisplayPrecision} fo the returned string.
|
|
|
|
|
* @param showWeekday true to show the week day, false otherwise.
|
|
|
|
|
* @param compactWords true to use compact words, false to use full words.
|
|
|
|
|
* @return a string representation of the date (and eventually day time) of the provided timestamp.
|
|
|
|
|
*/
|
|
|
|
|
public static String fullDateFr(long timestamp, DisplayPrecision precision, boolean showWeekday, boolean compactWords) {
|
|
|
|
|
LocalDateTime displayDateTime = toLocalDateTime(timestamp);
|
2021-09-02 14:27:03 +02:00
|
|
|
|
String ret = (showWeekday ? ((compactWords ? cmpDayOfWeekFormatter : dayOfWeekFormatter).format(displayDateTime) + " ") : "")
|
2020-11-02 23:23:41 +01:00
|
|
|
|
+ dayOfMonthFormatter.format(displayDateTime) + " "
|
|
|
|
|
+ (compactWords ? cmpMonthFormatter : monthFormatter).format(displayDateTime) + " "
|
2021-09-02 14:27:03 +02:00
|
|
|
|
+ yearFormatter.format(displayDateTime);
|
|
|
|
|
if (precision == DisplayPrecision.DAYS)
|
|
|
|
|
return ret;
|
2022-07-28 01:13:35 +02:00
|
|
|
|
return ret + " à " + dayTimeFr(timestamp, precision);
|
2021-09-02 14:27:03 +02:00
|
|
|
|
}
|
2022-07-28 01:13:35 +02:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns a string representation of the time of the day of the provided timestamp.
|
|
|
|
|
* <p>
|
|
|
|
|
* <b>This method renders the text in French.</b>
|
|
|
|
|
*
|
|
|
|
|
* @param timestamp the time to represent in the returned string.
|
|
|
|
|
* @param precision the {@link DisplayPrecision} fo the returned string.
|
|
|
|
|
* @return a string representation of the time of the day of the provided timestamp.
|
|
|
|
|
*/
|
|
|
|
|
public static String dayTimeFr(long timestamp, DisplayPrecision precision) {
|
2021-09-02 14:27:03 +02:00
|
|
|
|
DateTimeFormatter tFormatter = switch(precision) {
|
|
|
|
|
case HOURS -> HFormatter;
|
|
|
|
|
case MINUTES -> HMFormatter;
|
|
|
|
|
case SECONDS -> HMSFormatter;
|
|
|
|
|
default -> throw new IllegalArgumentException("precision");
|
|
|
|
|
};
|
2022-07-28 01:13:35 +02:00
|
|
|
|
return tFormatter.format(toLocalDateTime(timestamp));
|
2020-11-02 23:23:41 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static LocalDateTime toLocalDateTime(long msTime) {
|
2021-03-21 20:17:31 +01:00
|
|
|
|
return Instant.ofEpochMilli(msTime).atZone(TimeZone.getDefault().toZoneId()).toLocalDateTime();
|
2020-11-02 23:23:41 +01:00
|
|
|
|
}
|
2022-07-28 01:13:35 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Converts the provided duration into a human readable {@link String}.
|
|
|
|
|
* @param msDuration the duration in millisecond.
|
|
|
|
|
* @param hUnit the biggest unit of time to display.
|
|
|
|
|
* @param lUnit the smallest unit of time to display.
|
|
|
|
|
* @param spaces true to put spaces between time units (e.g.: {@code "1s 500ms"}) or false otherwise (e.g.: {@code "1s500ms"})).
|
|
|
|
|
* @param fr true to use French unit symbols (it only changes the day symbol from "d" to "j").
|
|
|
|
|
* @param leadingZeros to use leading zeros when necessary in front of some durations.
|
|
|
|
|
* @return a {@link String} representation of the duration.
|
|
|
|
|
*/
|
2020-11-02 23:23:41 +01:00
|
|
|
|
public static String durationToLongString(long msDuration, TimeUnit hUnit, TimeUnit lUnit, boolean spaces, boolean fr, boolean leadingZeros) {
|
|
|
|
|
if (lUnit.compareTo(hUnit) > 0) {
|
|
|
|
|
TimeUnit tmp = lUnit;
|
|
|
|
|
lUnit = hUnit;
|
|
|
|
|
hUnit = tmp;
|
|
|
|
|
}
|
|
|
|
|
if (lUnit.compareTo(TimeUnit.MILLISECONDS) < 0)
|
|
|
|
|
lUnit = TimeUnit.MILLISECONDS;
|
|
|
|
|
if (hUnit.compareTo(TimeUnit.MILLISECONDS) < 0)
|
|
|
|
|
hUnit = TimeUnit.MILLISECONDS;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
AtomicLong remainingTime = new AtomicLong(msDuration);
|
|
|
|
|
AtomicBoolean oneDisplayed = new AtomicBoolean(false);
|
|
|
|
|
final TimeUnit fLUnit = lUnit, fHUnit = hUnit;
|
|
|
|
|
|
|
|
|
|
String ret = Arrays.stream(TimeUnit.values())
|
|
|
|
|
.sequential()
|
|
|
|
|
.filter(u -> u.compareTo(fLUnit) >= 0 && u.compareTo(fHUnit) <= 0)
|
2022-07-10 00:55:56 +02:00
|
|
|
|
.sorted(Comparator.reverseOrder())
|
2020-11-02 23:23:41 +01:00
|
|
|
|
.filter(u -> {
|
|
|
|
|
if (u.convert(remainingTime.get(), TimeUnit.MILLISECONDS) == 0 && !oneDisplayed.get())
|
|
|
|
|
return false;
|
|
|
|
|
oneDisplayed.set(true);
|
|
|
|
|
return true;
|
|
|
|
|
})
|
|
|
|
|
.map(u -> {
|
|
|
|
|
long v = u.convert(remainingTime.get(), TimeUnit.MILLISECONDS);
|
|
|
|
|
remainingTime.addAndGet(TimeUnit.MILLISECONDS.convert(-v, u));
|
2022-07-28 01:13:35 +02:00
|
|
|
|
return toStringWithPaddingZeros(v, leadingZeros ? timeUnitToLeftPadLength(u) : 1) + timeUnitToSuffix(u, fr);
|
2020-11-02 23:23:41 +01:00
|
|
|
|
})
|
|
|
|
|
.collect(Collectors.joining(spaces ? " " : ""));
|
|
|
|
|
// ensure there is at least something to display (for instance : "0s")
|
2022-07-28 01:13:35 +02:00
|
|
|
|
return oneDisplayed.get() ? ret : (toStringWithPaddingZeros(0, leadingZeros ? timeUnitToLeftPadLength(lUnit) : 1) + timeUnitToSuffix(lUnit, fr));
|
2020-11-02 23:23:41 +01:00
|
|
|
|
}
|
2022-07-28 01:13:35 +02:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Provides a unit symbol for the provided {@link TimeUnit}.
|
|
|
|
|
* @param u the {@link TimeUnit}.
|
|
|
|
|
* @param fr true to use French unit symbols (it only changes the {@link TimeUnit#DAYS} symbol from "d" to "j").
|
|
|
|
|
* @return a unit symbol for the provided {@link TimeUnit}.
|
|
|
|
|
*/
|
2020-11-02 23:23:41 +01:00
|
|
|
|
public static String timeUnitToSuffix(TimeUnit u, boolean fr) {
|
2022-07-10 00:55:56 +02:00
|
|
|
|
return switch (u) {
|
|
|
|
|
case DAYS -> fr ? "j" : "d";
|
|
|
|
|
case HOURS -> "h";
|
|
|
|
|
case MINUTES -> "m";
|
|
|
|
|
case SECONDS -> "s";
|
|
|
|
|
case MILLISECONDS -> "ms";
|
|
|
|
|
case MICROSECONDS -> "μs";
|
|
|
|
|
case NANOSECONDS -> "ns";
|
|
|
|
|
};
|
2020-11-02 23:23:41 +01:00
|
|
|
|
}
|
2022-07-28 01:13:35 +02:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Indicate the 0-padded length of a number for the provided {@link TimeUnit}.
|
|
|
|
|
* Will returns 3 for below-second time units, 2 for seconds, munutes and hours and 1 otherwise.
|
|
|
|
|
* @param u the {@link TimeUnit}
|
|
|
|
|
* @return the 0-padded length of a number for the provided {@link TimeUnit}.
|
|
|
|
|
*/
|
2020-11-02 23:23:41 +01:00
|
|
|
|
public static int timeUnitToLeftPadLength(TimeUnit u) {
|
2022-07-10 00:55:56 +02:00
|
|
|
|
return switch (u) {
|
|
|
|
|
case NANOSECONDS, MICROSECONDS, MILLISECONDS -> 3;
|
|
|
|
|
case SECONDS, MINUTES, HOURS -> 2;
|
|
|
|
|
case DAYS -> 1;
|
|
|
|
|
};
|
2020-11-02 23:23:41 +01:00
|
|
|
|
}
|
2022-07-28 01:13:35 +02:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Converts the provided long to a {@link String} and eventually prepend any {@code "0"} necessary to make the
|
|
|
|
|
* returned string’s length at least {@code leftPad}.
|
|
|
|
|
* @param value the value to convert to {@link String}.
|
|
|
|
|
* @param leftPad the minimal length of the returned String.
|
|
|
|
|
* @return the string representation of the provided value, with eventual zeros prepended.
|
|
|
|
|
*/
|
|
|
|
|
public static String toStringWithPaddingZeros(long value, int leftPad) {
|
2020-11-02 23:23:41 +01:00
|
|
|
|
String valueStr = Long.toString(value);
|
|
|
|
|
int padding = leftPad - valueStr.length();
|
|
|
|
|
if (padding <= 0)
|
|
|
|
|
return valueStr;
|
2021-08-15 03:24:56 +02:00
|
|
|
|
return "0".repeat(padding) + valueStr;
|
2020-11-02 23:23:41 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Equivalent to {@link #durationToLongString(long, TimeUnit, TimeUnit, boolean, boolean, boolean) TimeUnit.durationToLongString(msDuration, TimeUnit.DAYS, milliseconds ? TimeUnit.MILLISECONDS : TimeUnit.SECONDS, true, true, false)}
|
|
|
|
|
* @param msDuration the duration in ms
|
|
|
|
|
* @param milliseconds if the milliseconds are displayed or not
|
2022-07-28 01:13:35 +02:00
|
|
|
|
* @return a {@link String} representation of the duration.
|
2020-11-02 23:23:41 +01:00
|
|
|
|
*/
|
|
|
|
|
public static String durationToString(long msDuration, boolean milliseconds) {
|
|
|
|
|
return durationToLongString(msDuration, TimeUnit.DAYS, milliseconds ? TimeUnit.MILLISECONDS : TimeUnit.SECONDS, true, true, false);
|
2016-02-16 20:07:51 +01:00
|
|
|
|
}
|
2016-07-14 14:22:23 +02:00
|
|
|
|
|
2020-11-02 23:23:41 +01:00
|
|
|
|
/**
|
|
|
|
|
* Equivalent to {@link #durationToLongString(long, TimeUnit, TimeUnit, boolean, boolean, boolean) TimeUnit.durationToLongString(msDuration, TimeUnit.DAYS, TimeUnit.SECONDS, true, true, false)}
|
2022-07-10 00:55:56 +02:00
|
|
|
|
* @param msDuration the duration in ms
|
2022-07-28 01:13:35 +02:00
|
|
|
|
* @return a {@link String} representation of the duration.
|
2020-11-02 23:23:41 +01:00
|
|
|
|
*/
|
|
|
|
|
public static String durationToString(long msDuration) {
|
|
|
|
|
return durationToLongString(msDuration, TimeUnit.DAYS, TimeUnit.SECONDS, true, true, false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Equivalent to {@link #durationToLongString(long, TimeUnit, TimeUnit, boolean, boolean, boolean) TimeUnit.durationToLongString(msDuration, TimeUnit.DAYS, TimeUnit.SECONDS, false, false, false)}
|
2022-07-10 00:55:56 +02:00
|
|
|
|
* @param msDuration the duration in ms
|
2022-07-28 01:13:35 +02:00
|
|
|
|
* @return a {@link String} representation of the duration.
|
2020-11-02 23:23:41 +01:00
|
|
|
|
*/
|
|
|
|
|
public static String durationToParsableString(long msDuration) {
|
|
|
|
|
return durationToLongString(msDuration, TimeUnit.DAYS, TimeUnit.SECONDS, false, false, false);
|
2016-02-16 20:07:51 +01:00
|
|
|
|
}
|
2020-06-08 16:16:18 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2022-07-28 01:13:35 +02:00
|
|
|
|
* Parse a duration string into a time in the past of future, relative to now.
|
|
|
|
|
* Source: <a href="https://github.com/EssentialsX/Essentials/blob/2.x/Essentials/src/main/java/com/earth2me/essentials/utils/DateUtil.java">Essentials DateUtil#parseDuration(String, boolean)</a>
|
|
|
|
|
* @param time the duration to parse.
|
|
|
|
|
* @param future thur to return the time in the future, false for the time in the past.
|
|
|
|
|
* @return the computed timestamp in millisecond.
|
|
|
|
|
* @throws IllegalArgumentException if the format is not valid.
|
2020-06-08 16:16:18 +02:00
|
|
|
|
*/
|
2022-07-28 01:13:35 +02:00
|
|
|
|
public static long parseDuration(String time, boolean future) {
|
2022-07-10 00:55:56 +02:00
|
|
|
|
@SuppressWarnings("RegExpSimplifiable")
|
2020-06-08 16:16:18 +02:00
|
|
|
|
Pattern timePattern = Pattern.compile("(?:([0-9]+)\\s*y[a-z]*[,\\s]*)?" + "(?:([0-9]+)\\s*mo[a-z]*[,\\s]*)?"
|
|
|
|
|
+ "(?:([0-9]+)\\s*w[a-z]*[,\\s]*)?" + "(?:([0-9]+)\\s*d[a-z]*[,\\s]*)?"
|
|
|
|
|
+ "(?:([0-9]+)\\s*h[a-z]*[,\\s]*)?" + "(?:([0-9]+)\\s*m[a-z]*[,\\s]*)?"
|
|
|
|
|
+ "(?:([0-9]+)\\s*(?:s[a-z]*)?)?", Pattern.CASE_INSENSITIVE);
|
|
|
|
|
Matcher m = timePattern.matcher(time);
|
|
|
|
|
int years = 0;
|
|
|
|
|
int months = 0;
|
|
|
|
|
int weeks = 0;
|
|
|
|
|
int days = 0;
|
|
|
|
|
int hours = 0;
|
|
|
|
|
int minutes = 0;
|
|
|
|
|
int seconds = 0;
|
|
|
|
|
boolean found = false;
|
|
|
|
|
while (m.find()) {
|
2022-07-28 01:13:35 +02:00
|
|
|
|
if (m.group() == null || m.group().isEmpty())
|
|
|
|
|
continue;
|
|
|
|
|
for (int i = 0; i < m.groupCount(); i++) {
|
2020-06-08 16:16:18 +02:00
|
|
|
|
if (m.group(i) != null && !m.group(i).isEmpty()) {
|
|
|
|
|
found = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2022-07-28 01:13:35 +02:00
|
|
|
|
}
|
2020-06-08 16:16:18 +02:00
|
|
|
|
if (found) {
|
|
|
|
|
if (m.group(1) != null && !m.group(1).isEmpty()) years = Integer.parseInt(m.group(1));
|
|
|
|
|
if (m.group(2) != null && !m.group(2).isEmpty()) months = Integer.parseInt(m.group(2));
|
|
|
|
|
if (m.group(3) != null && !m.group(3).isEmpty()) weeks = Integer.parseInt(m.group(3));
|
|
|
|
|
if (m.group(4) != null && !m.group(4).isEmpty()) days = Integer.parseInt(m.group(4));
|
|
|
|
|
if (m.group(5) != null && !m.group(5).isEmpty()) hours = Integer.parseInt(m.group(5));
|
|
|
|
|
if (m.group(6) != null && !m.group(6).isEmpty()) minutes = Integer.parseInt(m.group(6));
|
|
|
|
|
if (m.group(7) != null && !m.group(7).isEmpty()) seconds = Integer.parseInt(m.group(7));
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-07-28 01:13:35 +02:00
|
|
|
|
if (!found)
|
|
|
|
|
throw new IllegalArgumentException("Invalid duration format");
|
2020-06-08 16:16:18 +02:00
|
|
|
|
Calendar c = new GregorianCalendar();
|
|
|
|
|
if (years > 0) c.add(Calendar.YEAR, years * (future ? 1 : -1));
|
|
|
|
|
if (months > 0) c.add(Calendar.MONTH, months * (future ? 1 : -1));
|
|
|
|
|
if (weeks > 0) c.add(Calendar.WEEK_OF_YEAR, weeks * (future ? 1 : -1));
|
|
|
|
|
if (days > 0) c.add(Calendar.DAY_OF_MONTH, days * (future ? 1 : -1));
|
|
|
|
|
if (hours > 0) c.add(Calendar.HOUR_OF_DAY, hours * (future ? 1 : -1));
|
|
|
|
|
if (minutes > 0) c.add(Calendar.MINUTE, minutes * (future ? 1 : -1));
|
|
|
|
|
if (seconds > 0) c.add(Calendar.SECOND, seconds * (future ? 1 : -1));
|
|
|
|
|
Calendar max = new GregorianCalendar();
|
|
|
|
|
max.add(Calendar.YEAR, 10);
|
2022-07-28 01:13:35 +02:00
|
|
|
|
return c.after(max) ? max.getTimeInMillis() : c.getTimeInMillis();
|
2020-06-08 16:16:18 +02:00
|
|
|
|
}
|
2022-07-20 13:18:57 +02:00
|
|
|
|
|
|
|
|
|
|
2016-02-16 20:07:51 +01:00
|
|
|
|
}
|