|
7 | 7 | import java.time.OffsetDateTime; |
8 | 8 | import java.time.ZoneOffset; |
9 | 9 | import java.time.temporal.ChronoUnit; |
| 10 | +import java.util.ArrayList; |
| 11 | +import java.util.List; |
10 | 12 | import java.util.Map; |
11 | 13 | import java.util.regex.Pattern; |
| 14 | +import java.util.regex.Matcher; |
| 15 | +import java.util.regex.MatchResult; |
12 | 16 |
|
13 | 17 | /** |
14 | | - * A helper class that is meant to be used by services that need to parse upload dates in the |
15 | | - * format '2 days ago' or similar. |
| 18 | + * A helper class that is meant to be used by services that need to parse durations such as |
| 19 | + * {@code 23 seconds} and/or upload dates in the format {@code 2 days ago} or similar. |
16 | 20 | */ |
17 | 21 | public class TimeAgoParser { |
| 22 | + |
| 23 | + private static final Pattern DURATION_PATTERN = Pattern.compile("(?:(\\d+) )?([A-z]+)"); |
| 24 | + |
18 | 25 | private final PatternsHolder patternsHolder; |
19 | 26 | private final OffsetDateTime now; |
20 | 27 |
|
@@ -60,6 +67,48 @@ public DateWrapper parse(final String textualDate) throws ParsingException { |
60 | 67 | return getResultFor(parseTimeAgoAmount(textualDate), parseChronoUnit(textualDate)); |
61 | 68 | } |
62 | 69 |
|
| 70 | + /** |
| 71 | + * Parses a textual duration into a duration computer number. |
| 72 | + * |
| 73 | + * @param textualDuration the textual duration to parse |
| 74 | + * @return the textual duration parsed, as a primitive {@code long} |
| 75 | + * @throws ParsingException if the textual duration could not be parsed |
| 76 | + */ |
| 77 | + public long parseDuration(final String textualDuration) throws ParsingException { |
| 78 | + // We can't use Matcher.results, as it is only available on Android 14 and above |
| 79 | + final Matcher matcher = DURATION_PATTERN.matcher(textualDuration); |
| 80 | + final List<MatchResult> results = new ArrayList<>(); |
| 81 | + while (matcher.find()) { |
| 82 | + results.add(matcher.toMatchResult()); |
| 83 | + } |
| 84 | + |
| 85 | + return results.stream() |
| 86 | + .map(match -> { |
| 87 | + final String digits = match.group(1); |
| 88 | + final String word = match.group(2); |
| 89 | + |
| 90 | + int amount; |
| 91 | + try { |
| 92 | + amount = Integer.parseInt(digits); |
| 93 | + } catch (final NumberFormatException ignored) { |
| 94 | + amount = 1; |
| 95 | + } |
| 96 | + |
| 97 | + final ChronoUnit unit; |
| 98 | + try { |
| 99 | + unit = parseChronoUnit(word); |
| 100 | + } catch (final ParsingException ignored) { |
| 101 | + return 0L; |
| 102 | + } |
| 103 | + |
| 104 | + return amount * unit.getDuration().getSeconds(); |
| 105 | + }) |
| 106 | + .filter(n -> n > 0) |
| 107 | + .reduce(Long::sum) |
| 108 | + .orElseThrow(() -> new ParsingException( |
| 109 | + "Could not parse duration \"" + textualDuration + "\"")); |
| 110 | + } |
| 111 | + |
63 | 112 | private int parseTimeAgoAmount(final String textualDate) { |
64 | 113 | try { |
65 | 114 | return Integer.parseInt(textualDate.replaceAll("\\D+", "")); |
|
0 commit comments