001/*
002 * (C) Copyright 2019 Nuxeo (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *       Kevin Leturc <kleturc@nuxeo.com>
018 */
019package org.nuxeo.common.utils;
020
021import static org.apache.commons.lang3.StringUtils.isBlank;
022
023import java.time.Duration;
024import java.time.format.DateTimeParseException;
025import java.util.regex.Matcher;
026import java.util.regex.Pattern;
027
028/**
029 * @since 11.1
030 */
031public final class DurationUtils {
032
033    public static final Pattern DURATION_SIMPLE_FORMAT = Pattern.compile(
034            "(?:(\\d+)d)?(?:(\\d+)h)?(?:(\\d+)m)?(?:(\\d+)s)?(?:(\\d+)ms)?");
035
036    private DurationUtils() {
037        // utility class
038    }
039
040    /**
041     * Obtains a {@code Duration} from a text string such as {@code PnDTnHnMn.nS} or {@code _d_h_m_s_ms}.
042     * <p>
043     * See {@link Duration#parse(CharSequence)} for {@code PnDTnHnMn.nS} format.
044     * <p>
045     * For {@code _d_h_m_s_ms}, there are five sections, each consisting of a number and a suffix. The suffixes are "d",
046     * "h", "m", "s" and "ms" for days, hours, minutes, seconds and milliseconds. The suffixes must occur in order and
047     * at least one of them must be present.
048     *
049     * @throws DateTimeParseException if the text cannot be parsed to a duration
050     * @see Duration#parse(CharSequence)
051     */
052    public static Duration parse(String value) {
053        if (value.startsWith("P") || value.startsWith("-P")) {
054            // Duration JDK format
055            return Duration.parse(value);
056        }
057        Matcher matcher = DURATION_SIMPLE_FORMAT.matcher(value);
058        if (matcher.matches()) {
059
060            long days = 0;
061            long hours = 0;
062            long minutes = 0;
063            long seconds = 0;
064            long millis = 0;
065            if (matcher.group(1) != null) {
066                days = Long.parseLong(matcher.group(1));
067            }
068            if (matcher.group(2) != null) {
069                hours = Long.parseLong(matcher.group(2));
070            }
071            if (matcher.group(3) != null) {
072                minutes = Long.parseLong(matcher.group(3));
073            }
074            if (matcher.group(4) != null) {
075                seconds = Long.parseLong(matcher.group(4));
076            }
077            if (matcher.group(5) != null) {
078                millis = Long.parseLong(matcher.group(5));
079            }
080            return Duration.ofDays(days).plusHours(hours).plusMinutes(minutes).plusSeconds(seconds).plusMillis(millis);
081        }
082        throw new DateTimeParseException("Text cannot be parsed to a Duration", value, 0);
083    }
084
085    /**
086     * Obtains a {@code Duration} from a text string according to {@link #parse(String)}, but in case of invalid, zero
087     * or negative duration returns a default.
088     *
089     * @param value the value to parse
090     * @param defaultDuration the default duration to return for invalid, zero or negative duration
091     * @return the parsed duration (positive), or the default
092     * @since 11.1
093     */
094    public static Duration parsePositive(String value, Duration defaultDuration) {
095        if (isBlank(value)) {
096            return defaultDuration;
097        }
098        try {
099            Duration duration = parse(value);
100            if (duration.isZero() || duration.isNegative()) {
101                return defaultDuration;
102            } else {
103                return duration;
104            }
105        } catch (DateTimeParseException e) {
106            return defaultDuration;
107        }
108    }
109
110}