DurationParser.java
/*
* Copyright 2023 Terminal Utils Authors
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
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
@SuppressWarnings("StringSplitter")
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?))?");
}