DurationParser.java

package net.morimekta.terminal.args.parser;

import net.morimekta.terminal.args.ArgException;
import net.morimekta.terminal.args.ValueParser;

import java.time.Duration;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static java.lang.Integer.parseInt;
import static java.lang.Long.parseLong;
import static java.time.Duration.ofDays;
import static java.time.Duration.ofHours;
import static java.time.Duration.ofMillis;
import static java.time.Duration.ofMinutes;
import static java.time.Duration.ofNanos;
import static java.time.Duration.ofSeconds;

/**
 * A duration parser.
 */
public class DurationParser implements ValueParser<Duration> {
    @Override
    public Duration parse(String value) {
        try {
            Matcher duration = DURATION_COMPLEX.matcher(value);
            if (duration.matches()) {
                var out = Duration.ZERO;
                out = updatedDuration(out, duration.group("weeks"), s -> ofDays(7L * parseInt(s)));
                out = updatedDuration(out, duration.group("days"), s -> ofDays(parseInt(s)));
                out = updatedDuration(out, duration.group("hours"), s -> ofHours(parseInt(s)));
                out = updatedDuration(out, duration.group("minutes"), s -> ofMinutes(parseInt(s)));
                out = updatedDuration(out, duration.group("seconds"), s -> {
                    var parts = s.split("\\.");
                    if (parts.length == 2) {
                        var nanos = parts[1] + "0".repeat(9 - parts[1].length());
                        return ofSeconds(parseLong(parts[0]), parseInt(nanos));
                    }
                    return ofSeconds(parseLong(s));
                });
                out = updatedDuration(out, duration.group("millis"), s -> ofMillis(parseInt(s)));
                out = updatedDuration(out, duration.group("nanos"), s -> ofNanos(parseInt(s)));
                if (duration.group("minus") != null) {
                    return out.negated();
                }
                return out;
            }
        } catch (Exception e) {
            throw new ArgException("Invalid duration: %s", e.getMessage(), e);
        }
        throw new ArgException("Expected duration string, but found: '%s'", value);
    }

    private static Duration updatedDuration(Duration orig, String amount, Function<String, Duration> parser) {
        if (amount == null) {
            return orig;
        } else {
            return orig.plus(parser.apply(amount));
        }
    }

    private static final Pattern DURATION_COMPLEX = Pattern.compile(
            "(?<minus>-)?" +
            "(?:(?<weeks>\\d+) ?(?:w|weeks?) ?)?" +
            "(?:(?<days>\\d+) ?(?:d|days?) ?)?" +
            "(?:(?<hours>\\d+) ?(?:h|hours?) ?)?" +
            "(?:(?<minutes>\\d+) ?(?:m|min|minutes?) ?)?" +
            "(?:(?<seconds>\\d+(?:\\.\\d{1,9})?) ?(?:s|sec|seconds?) ?)?" +
            "(?:(?<millis>\\d+) ?(?:ms|millis?) ?)?" +
            "(?:(?<nanos>\\d+) ?(?:ns|nanos?))?");
}