PandaLib/src/main/java/fr/pandacube/util/measurement/TimeUtil.java

354 lines
13 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package fr.pandacube.util.measurement;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import fr.pandacube.Pandacube;
import fr.pandacube.util.StringUtil;
import fr.pandacube.util.commands.SuggestionsSupplier;
public class TimeUtil {
private static DateTimeFormatter cmpDayOfWeekFormatter = DateTimeFormatter.ofPattern("EEE", Pandacube.LOCALE);
private static DateTimeFormatter dayOfWeekFormatter = DateTimeFormatter.ofPattern("EEEE", Pandacube.LOCALE);
private static DateTimeFormatter dayOfMonthFormatter = DateTimeFormatter.ofPattern("d", Pandacube.LOCALE);
private static DateTimeFormatter cmpMonthFormatter = DateTimeFormatter.ofPattern("MMM", Pandacube.LOCALE);
private static DateTimeFormatter monthFormatter = DateTimeFormatter.ofPattern("MMMM", Pandacube.LOCALE);
private static DateTimeFormatter yearFormatter = DateTimeFormatter.ofPattern("uuuu", Pandacube.LOCALE);
private static DateTimeFormatter HMSFormatter = DateTimeFormatter.ofPattern("HH:mm:ss", Pandacube.LOCALE);
private static DateTimeFormatter HMFormatter = DateTimeFormatter.ofPattern("HH:mm", Pandacube.LOCALE);
public static String relativeDateFr(long displayTime, boolean showSeconds, boolean compactWords) {
long currentTime = System.currentTimeMillis();
LocalDateTime displayDateTime = toLocalDateTime(displayTime);
LocalDateTime currentDateTime = toLocalDateTime(currentTime);
long timeDiff = currentTime - displayTime;
long timeDiffSec = timeDiff / 1000;
if (timeDiffSec < -1) {
// in the future
if (timeDiffSec > -60) {
if (showSeconds)
return "dans " + (-timeDiffSec) + " secondes";
else
return "dans moins dune minute";
}
if (timeDiffSec > -60*2) // dans 2 min
return "dans ̈" + (int)Math.floor((-timeDiffSec)/60) + " minute";
if (timeDiffSec > -3600) // dans 1h
return "dans " + (int)Math.floor((-timeDiffSec)/60) + " minutes";
if (timeDiffSec > -3600*2) // dans 2h
return "dans " + (int)Math.floor((-timeDiffSec)/(3600)) + " heure";
if (timeDiffSec > -3600*12) // dans 12h
return "dans " + (int)Math.floor((-timeDiffSec)/(3600)) + " heures";
LocalDateTime nextMidnight = LocalDateTime.of(currentDateTime.getYear(), currentDateTime.getMonth(), currentDateTime.getDayOfMonth(), 0, 0).plusDays(1);
if (displayDateTime.isBefore(nextMidnight)) // aujourd'hui
return "aujourdhui à " + (showSeconds ? HMSFormatter : HMFormatter).format(displayDateTime);
if (displayDateTime.isBefore(nextMidnight.plusDays(1))) // demain
return "demain à " + (showSeconds ? HMSFormatter : HMFormatter).format(displayDateTime);
if (displayDateTime.isBefore(nextMidnight.plusDays(5))) // dans moins d'1 semaine
return (compactWords ? cmpDayOfWeekFormatter : dayOfWeekFormatter).format(displayDateTime) + " "
+ dayOfMonthFormatter.format(displayDateTime) + " à "
+ (showSeconds ? HMSFormatter : HMFormatter).format(displayDateTime);
return fullDateFr(displayTime, showSeconds, true, compactWords);
}
else {
// present and past
if (timeDiffSec <= 1)
return "maintenant";
if (timeDiffSec < 60) { // ya moins d'1 min
if (showSeconds)
return "il y a " + timeDiffSec + " secondes";
else
return "il y a moins dune minute";
}
if (timeDiffSec < 60*2) // ya moins de 2 min
return "il y a " + (int)Math.floor((timeDiffSec)/60) + " minute";
if (timeDiffSec < 3600) // ya moins d'1h
return "il y a " + (int)Math.floor((timeDiffSec)/60) + " minutes";
if (timeDiffSec < 3600*2) // ya moins de 2h
return "il y a " + (int)Math.floor((timeDiffSec)/(3600)) + " heure";
if (timeDiffSec < 3600*12) // ya moins de 12h
return "il y a " + (int)Math.floor((timeDiffSec)/(3600)) + " heures";
LocalDateTime lastMidnight = LocalDateTime.of(currentDateTime.getYear(), currentDateTime.getMonth(), currentDateTime.getDayOfMonth(), 0, 0);
if (!displayDateTime.isBefore(lastMidnight)) // aujourd'hui
return "aujourdhui à " + (showSeconds ? HMSFormatter : HMFormatter).format(displayDateTime);
if (!displayDateTime.isBefore(lastMidnight.minusDays(1))) // hier
return "hier à " + (showSeconds ? HMSFormatter : HMFormatter).format(displayDateTime);
if (!displayDateTime.isBefore(lastMidnight.minusDays(6))) // ya moins d'1 semaine
return (compactWords ? cmpDayOfWeekFormatter : dayOfWeekFormatter).format(displayDateTime) + " dernier à "
+ (showSeconds ? HMSFormatter : HMFormatter).format(displayDateTime);
return fullDateFr(displayTime, showSeconds, true, compactWords);
}
}
public static String fullDateFr(long displayTime, boolean showSeconds, boolean showWeekday, boolean compactWords) {
LocalDateTime displayDateTime = toLocalDateTime(displayTime);
return (showWeekday ? ((compactWords ? cmpDayOfWeekFormatter : dayOfWeekFormatter).format(displayDateTime) + " ") : "")
+ dayOfMonthFormatter.format(displayDateTime) + " "
+ (compactWords ? cmpMonthFormatter : monthFormatter).format(displayDateTime) + " "
+ yearFormatter.format(displayDateTime) + " à "
+ (showSeconds ? HMSFormatter : HMFormatter).format(displayDateTime);
}
private static LocalDateTime toLocalDateTime(long msTime) {
return Instant.ofEpochMilli(msTime).atZone(Pandacube.TIMEZONE.toZoneId()).toLocalDateTime();
}
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)
.sorted((u1, u2) -> u2.compareTo(u1))
.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));
return toString(v, leadingZeros ? timeUnitToLeftPadLength(u) : 1) + timeUnitToSuffix(u, fr);
})
.collect(Collectors.joining(spaces ? " " : ""));
// ensure there is at least something to display (for instance : "0s")
return oneDisplayed.get() ? ret : (toString(0, leadingZeros ? timeUnitToLeftPadLength(lUnit) : 1) + timeUnitToSuffix(lUnit, fr));
}
public static String timeUnitToSuffix(TimeUnit u, boolean fr) {
switch (u) {
case DAYS:
return fr ? "j" : "d";
case HOURS:
return "h";
case MINUTES:
return "m";
case SECONDS:
return "s";
case MILLISECONDS:
return "ms";
case MICROSECONDS:
return "μs";
case NANOSECONDS:
return "ns";
default:
throw new IllegalArgumentException("Invalid TimeUnit: " + Objects.toString(u));
}
}
public static int timeUnitToLeftPadLength(TimeUnit u) {
switch (u) {
case NANOSECONDS:
case MICROSECONDS:
case MILLISECONDS:
return 3;
case SECONDS:
case MINUTES:
case HOURS:
return 2;
case DAYS:
default:
return 1;
}
}
public static String toString(long value, int leftPad) {
String valueStr = Long.toString(value);
int padding = leftPad - valueStr.length();
if (padding <= 0)
return valueStr;
return StringUtil.repeat("0", padding) + valueStr;
}
/**
* 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
* @return
*/
public static String durationToString(long msDuration, boolean milliseconds) {
return durationToLongString(msDuration, TimeUnit.DAYS, milliseconds ? TimeUnit.MILLISECONDS : TimeUnit.SECONDS, true, true, false);
}
/**
* Equivalent to {@link #durationToLongString(long, TimeUnit, TimeUnit, boolean, boolean, boolean) TimeUnit.durationToLongString(msDuration, TimeUnit.DAYS, TimeUnit.SECONDS, true, true, false)}
* @param msDuration
* @return
*/
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)}
* @param msDuration
* @return
*/
public static String durationToParsableString(long msDuration) {
return durationToLongString(msDuration, TimeUnit.DAYS, TimeUnit.SECONDS, false, false, false);
}
/**
* @see {@link com.earth2me.essentials.utils.DateUtil#parseDuration(String, boolean)}
*/
public static long parseDuration(String time, boolean future) throws Exception {
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()) {
if (m.group() == null || m.group().isEmpty()) continue;
for (int i = 0; i < m.groupCount(); i++)
if (m.group(i) != null && !m.group(i).isEmpty()) {
found = true;
break;
}
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;
}
}
if (!found) throw new Exception("Format de durée invalide");
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);
if (c.after(max)) return max.getTimeInMillis();
return c.getTimeInMillis();
}
public static <S> SuggestionsSupplier<S> suggestDuration() {
return (s, ti, token, args) -> {
if (token.isEmpty()) {
return emptyTokenSuggestions;
}
List<String> remainingSuffixes = new ArrayList<>(allSuffixes);
char[] tokenChars = token.toCharArray();
String accSuffix = "";
for (int i = 0; i < tokenChars.length; i++) {
char c = tokenChars[i];
if (Character.isDigit(c)) {
scanAndRemovePastSuffixes(remainingSuffixes, accSuffix);
accSuffix = "";
continue;
}
else if (Character.isLetter(c)) {
accSuffix += c;
}
else
return Collections.emptyList();
}
String prefixToken = token.substring(0, token.length() - accSuffix.length());
return SuggestionsSupplier.collectFilteredStream(remainingSuffixes.stream(), accSuffix)
.stream()
.map(str -> prefixToken + str)
.collect(Collectors.toList());
};
}
private static List<String> allSuffixes = Arrays.asList("y", "mo", "w", "d", "h", "m", "s");
private static List<String> emptyTokenSuggestions = allSuffixes.stream().map(p -> "1" + p).collect(Collectors.toList());
private static void scanAndRemovePastSuffixes(List<String> suffixes, String foundSuffix) {
for (int i = 0; i < suffixes.size(); i++) {
if (foundSuffix.startsWith(suffixes.get(i))) {
for (int j = i; j >= 0; j--) {
suffixes.remove(j);
}
return;
}
}
}
}