All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opentripplanner.framework.time.DurationUtils Maven / Gradle / Ivy

The newest version!
package org.opentripplanner.framework.time;

import static java.util.Locale.ROOT;

import java.time.Duration;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * This class extend the Java {@link Duration} with utility functionality to parse and convert
 * integer and text to a {@link Duration}. This class also contains methods to validate durations.
 * 

* OTP make have use of the Duration in a lenient ISO-8601 duration format. For example: *

 *   1s          // one second
 *   15m         // 15 minutes
 *   2h          // 2 hours
 *   2d          // 2 days
 *   2d3h12m40s  // 2 days, 3 hours, 12 minutes and 40 seconds
 *   -3m30s      // 3.5 minutes before  (relative to time in context)
 * 
*/ public class DurationUtils { private static final Pattern DECIMAL_NUMBER_PATTERN = Pattern.compile("[-+]?\\d+"); private DurationUtils() {} /** * Convert a duration in seconds to a readable string. This a follows the ISO-8601 notation for * most parts, but make it a bit more readable: {@code P2DT2H12M40S => 2d2h12m40s}. For negative * durations {@code -P2dT3s => -2d3s, not -2d-3s or -(2d3s)} is used. */ public static String durationToStr(int timeSeconds) { StringBuilder buf = new StringBuilder(); if (timeSeconds < 0) { buf.append("-"); } int time = Math.abs(timeSeconds); int sec = time % 60; time = time / 60; int min = time % 60; time = time / 60; int hour = time % 24; int day = time / 24; if (day != 0) { buf.append(day).append('d'); } if (hour != 0) { buf.append(hour).append('h'); } if (min != 0) { buf.append(min).append('m'); } if (sec != 0) { buf.append(sec).append('s'); } return buf.length() == 0 ? "0s" : buf.toString(); } public static String durationToStr(Duration duration) { return duration == null ? "" : durationToStr((int) duration.toSeconds()); } public static String durationToStr(int timeSeconds, int notSetValue) { return timeSeconds == notSetValue ? "" : durationToStr(timeSeconds); } /** * Parse a sting on format {@code nHnMn.nS} or {@link Duration#parse(CharSequence)}. The prefix * "PT" is added if not present before handed of the {@link Duration#parse(CharSequence)} method. * * @return the duration in seconds */ public static int durationInSeconds(String duration) { Duration d = duration(duration); return (int) d.toSeconds(); } /** * Parses the given duration string. If the string contains an integer number the string is * converted to a duration using the given unit. If not, the {@link #durations(String)} is used * and the given unit is ignored. */ public static Duration duration(String duration, ChronoUnit unit) { if (duration.matches("-?\\d+")) { return Duration.of(Integer.parseInt(duration), unit); } return duration(duration); } /** * Same as {@link #durationInSeconds(String)}, but returns the {@link Duration}, not the {@code * int} seconds. */ public static Duration duration(String duration) { var d = duration.toUpperCase(); if (!(d.startsWith("P") || d.startsWith("-P"))) { int pos = d.indexOf('D') + 1; if (pos > 0) { var days = d.substring(0, pos); d = pos == d.length() ? "P" + days : "P" + days + "T" + d.substring(pos); } else { d = "PT" + d; } } try { return Duration.parse(d); } catch (DateTimeParseException e) { throw new DateTimeParseException( e.getMessage() + ": '" + duration + "'", duration, e.getErrorIndex() ); } } /** * This is used to parse a string which may be a number {@code NNNN}(number of seconds) or a * duration with format {@code NhNmNs}, where {@code N} is a decimal number and * {@code h} is hours, {@code m} minutes and {@code s} seconds. *

* This method */ public static Optional parseSecondsOrDuration(String duration) { if (duration == null || duration.isBlank()) { return Optional.empty(); } var s = duration.trim(); return Optional.of( DECIMAL_NUMBER_PATTERN.matcher(s).matches() ? Duration.ofSeconds(Integer.parseInt(s)) : duration(s) ); } /** * Parse a list of durations using white-space, comma or semicolon as a separator. *

* Example: {@code "2h 3m;5s"} will result in a list with 3 durations. */ public static List durations(String durations) { if (durations == null || durations.isBlank()) { return List.of(); } return Arrays .stream(durations.split("[,;\\s]+")) .map(DurationUtils::duration) .collect(Collectors.toList()); } public static String msToSecondsStr(long timeMs) { if (timeMs == 0) { return "0 seconds"; } if (timeMs == 1000) { return "1 second"; } if (timeMs < 100) { return msToSecondsStr("%.3f", timeMs); } if (timeMs < 995) { return msToSecondsStr("%.2f", timeMs); } if (timeMs < 9950) { return msToSecondsStr("%.1f", timeMs); } else { return msToSecondsStr("%.0f", timeMs); } } public static Duration requireNonNegative(Duration value) { Objects.requireNonNull(value); if (value.isNegative()) { throw new IllegalArgumentException("Duration can't be negative: " + value); } return value; } /** * Checks that duration is not negative and not over 2 days. * * @param subject used to identify name of the problematic value when throwing an exception. */ public static Duration requireNonNegativeLong(Duration value, String subject) { Objects.requireNonNull(value); if (value.isNegative()) { throw new IllegalArgumentException( "Duration %s can't be negative: %s.".formatted(subject, value) ); } if (value.compareTo(Duration.ofDays(2)) > 0) { throw new IllegalArgumentException( "Duration %s can't be longer than two days: %s.".formatted(subject, value) ); } return value; } /** * Checks that duration is not negative and not over 2 hours. * * @param subject used to identify name of the problematic value when throwing an exception. */ public static Duration requireNonNegativeMedium(Duration value, String subject) { Objects.requireNonNull(value); if (value.isNegative()) { throw new IllegalArgumentException( "Duration %s can't be negative: %s.".formatted(subject, value) ); } if (value.compareTo(Duration.ofHours(2)) > 0) { throw new IllegalArgumentException( "Duration %s can't be longer than two hours: %s.".formatted(subject, value) ); } return value; } /** * Checks that duration is not negative and not over 30 minutes. * * @param subject used to identify name of the problematic value when throwing an exception. */ public static Duration requireNonNegativeShort(Duration value, String subject) { Objects.requireNonNull(value); if (value.isNegative()) { throw new IllegalArgumentException( "Duration %s can't be negative: %s.".formatted(subject, value) ); } if (value.compareTo(Duration.ofMinutes(30)) > 0) { throw new IllegalArgumentException( "Duration %s can't be longer than 30 minutes: %s.".formatted(subject, value) ); } return value; } /** * Convert duration to an int with unit milliseconds. */ public static int toIntMilliseconds(Duration timeout, int defaultValue) { return timeout == null ? defaultValue : (int) timeout.toMillis(); } private static String msToSecondsStr(String formatSeconds, double timeMs) { return String.format(ROOT, formatSeconds, timeMs / 1000.0) + " seconds"; } /** * Formats a duration and if it's a negative amount, it places the minus before the "P" rather * than in the middle of the value. *

* Background: There are multiple ways to express -1.5 hours: "PT-1H-30M" and "-PT1H30M". *

* The first version is what you get when calling toString() but it's quite confusing. Therefore, * this method makes sure that you get the second form "-PT1H30M". */ public static String formatDurationWithLeadingMinus(Duration duration) { if (duration.isNegative()) { var positive = duration.abs().toString(); return "-" + positive; } else { return duration.toString(); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy