001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.camel.util;
018
019import java.text.DecimalFormat;
020import java.text.DecimalFormatSymbols;
021import java.text.NumberFormat;
022import java.util.Locale;
023import java.util.regex.Matcher;
024import java.util.regex.Pattern;
025
026import org.slf4j.Logger;
027import org.slf4j.LoggerFactory;
028
029/**
030 * Time utils.
031 */
032public final class TimeUtils {
033
034    private static final Logger LOG = LoggerFactory.getLogger(TimeUtils.class);
035    private static final Pattern NUMBERS_ONLY_STRING_PATTERN = Pattern.compile("^[-]?(\\d)+$", Pattern.CASE_INSENSITIVE);
036    private static final Pattern HOUR_REGEX_PATTERN = Pattern.compile("((\\d)*(\\d))h(our(s)?)?", Pattern.CASE_INSENSITIVE);
037    private static final Pattern MINUTES_REGEX_PATTERN = Pattern.compile("((\\d)*(\\d))m(in(ute(s)?)?)?", Pattern.CASE_INSENSITIVE);
038    private static final Pattern SECONDS_REGEX_PATTERN = Pattern.compile("((\\d)*(\\d))s(ec(ond)?(s)?)?", Pattern.CASE_INSENSITIVE);
039
040    private TimeUtils() {
041    }
042
043    /**
044     * Prints the duration in a human readable format as X days Y hours Z minutes etc.
045     *
046     * @param uptime the uptime in millis
047     * @return the time used for displaying on screen or in logs
048     */
049    public static String printDuration(double uptime) {
050        // Code taken from Karaf
051        // https://svn.apache.org/repos/asf/karaf/trunk/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/InfoAction.java
052
053        NumberFormat fmtI = new DecimalFormat("###,###", new DecimalFormatSymbols(Locale.ENGLISH));
054        NumberFormat fmtD = new DecimalFormat("###,##0.000", new DecimalFormatSymbols(Locale.ENGLISH));
055
056        uptime /= 1000;
057        if (uptime < 60) {
058            return fmtD.format(uptime) + " seconds";
059        }
060        uptime /= 60;
061        if (uptime < 60) {
062            long minutes = (long) uptime;
063            String s = fmtI.format(minutes) + (minutes > 1 ? " minutes" : " minute");
064            return s;
065        }
066        uptime /= 60;
067        if (uptime < 24) {
068            long hours = (long) uptime;
069            long minutes = (long) ((uptime - hours) * 60);
070            String s = fmtI.format(hours) + (hours > 1 ? " hours" : " hour");
071            if (minutes != 0) {
072                s += " " + fmtI.format(minutes) + (minutes > 1 ? " minutes" : " minute");
073            }
074            return s;
075        }
076        uptime /= 24;
077        long days = (long) uptime;
078        long hours = (long) ((uptime - days) * 24);
079        String s = fmtI.format(days) + (days > 1 ? " days" : " day");
080        if (hours != 0) {
081            s += " " + fmtI.format(hours) + (hours > 1 ? " hours" : " hour");
082        }
083        return s;
084    }
085
086    public static long toMilliSeconds(String source) throws IllegalArgumentException {
087        // quick conversion if its only digits
088        boolean digit = true;
089        for (int i = 0; i < source.length(); i++) {
090            char ch = source.charAt(i);
091            // special for fist as it can be negative number
092            if (i == 0 && ch == '-') {
093                continue;
094            }
095            // quick check if its 0..9
096            if (ch < '0' || ch > '9') {
097                digit = false;
098                break;
099            }
100        }
101        if (digit) {
102            return Long.valueOf(source);
103        }
104
105        long milliseconds = 0;
106        boolean foundFlag = false;
107
108        checkCorrectnessOfPattern(source);
109        Matcher matcher;
110
111        matcher = createMatcher(NUMBERS_ONLY_STRING_PATTERN, source);
112        if (matcher.find()) {
113            // Note: This will also be used for regular numeric strings.
114            //       This String -> long converter will be used for all strings.
115            milliseconds = Long.valueOf(source);
116        } else {
117            matcher = createMatcher(HOUR_REGEX_PATTERN, source);
118            if (matcher.find()) {
119                milliseconds = milliseconds + (3600000 * Long.valueOf(matcher.group(1)));
120                foundFlag = true;
121            }
122
123            matcher = createMatcher(MINUTES_REGEX_PATTERN, source);
124            if (matcher.find()) {
125                long minutes = Long.valueOf(matcher.group(1));
126                if ((minutes > 59) && foundFlag) {
127                    throw new IllegalArgumentException("Minutes should contain a valid value between 0 and 59: " + source);
128                }
129                foundFlag = true;
130                milliseconds = milliseconds + (60000 * minutes);
131            }
132
133            matcher = createMatcher(SECONDS_REGEX_PATTERN, source);
134            if (matcher.find()) {
135                long seconds = Long.valueOf(matcher.group(1));
136                if ((seconds > 59) && foundFlag) {
137                    throw new IllegalArgumentException("Seconds should contain a valid value between 0 and 59: " + source);
138                }
139                foundFlag = true;
140                milliseconds = milliseconds + (1000 * seconds);
141            }
142
143            // No pattern matched... initiating fallback check and conversion (if required).
144            // The source at this point may contain illegal values or special characters
145            if (!foundFlag) {
146                milliseconds = Long.valueOf(source);
147            }
148        }
149
150        LOG.trace("source: [{}], milliseconds: {}", source, milliseconds);
151
152        return milliseconds;
153    }
154
155    private static void checkCorrectnessOfPattern(String source) {
156        //replace only numbers once
157        Matcher matcher = createMatcher(NUMBERS_ONLY_STRING_PATTERN, source);
158        String replaceSource = matcher.replaceFirst("");
159
160        //replace hour string once
161        matcher = createMatcher(HOUR_REGEX_PATTERN, replaceSource);
162        if (matcher.find() && matcher.find()) {
163            throw new IllegalArgumentException("Hours should not be specified more then once: " + source);
164        }
165        replaceSource = matcher.replaceFirst("");
166
167        //replace minutes once
168        matcher = createMatcher(MINUTES_REGEX_PATTERN, replaceSource);
169        if (matcher.find() && matcher.find()) {
170            throw new IllegalArgumentException("Minutes should not be specified more then once: " + source);
171        }
172        replaceSource = matcher.replaceFirst("");
173
174        //replace seconds once
175        matcher = createMatcher(SECONDS_REGEX_PATTERN, replaceSource);
176        if (matcher.find() && matcher.find()) {
177            throw new IllegalArgumentException("Seconds should not be specified more then once: " + source);
178        }
179        replaceSource = matcher.replaceFirst("");
180
181        if (replaceSource.length() > 0) {
182            throw new IllegalArgumentException("Illegal characters: " + source);
183        }
184    }
185
186    private static Matcher createMatcher(Pattern pattern, String source) {
187        return pattern.matcher(source);
188    }
189
190}