/* * Copyright © 2014 Cask Data, Inc. * * Licensed 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 co.cask.cdap.common.utils; import com.google.common.base.Preconditions; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Utility class for parsing time strings into timestamps, with support for some basic time math. * Math syntax includes addition and subtraction in minutes, hours, and days. * The "NOW" keyword translates to the current epoch time in seconds, and can be strung together with * various additions and subtractions. For example, "NOW" will translate into the current epoch time in seconds. * "NOW-5SECONDS" is 5 seconds before now, "NOW-1DAY" is one day before now, and "NOW-1DAY+4HOURS" is 20 hours * before now. * */ public class TimeMathParser { private static final String NOW = "now"; private static final String VALID_UNITS = "ms|s|m|h|d"; private static final Pattern OP_PATTERN = Pattern.compile("(\\-|\\+)(\\d+)(" + VALID_UNITS + ")"); private static final Pattern RESOLUTION_PATTERN = Pattern.compile("(\\d+)(" + VALID_UNITS + ")"); private static final Pattern TIMESTAMP_PATTERN = Pattern.compile("^(\\d+)$"); private static long convertToMilliseconds(String op, long num, String unitStr) { long milliseconds; if ("ms".equals(unitStr)) { milliseconds = num; } else if ("s".equals(unitStr)) { milliseconds = TimeUnit.SECONDS.toMillis(num); } else if ("m".equals(unitStr)) { milliseconds = TimeUnit.MINUTES.toMillis(num); } else if ("h".equals(unitStr)) { milliseconds = TimeUnit.HOURS.toMillis(num); } else if ("d".equals(unitStr)) { milliseconds = TimeUnit.DAYS.toMillis(num); } else { throw new IllegalArgumentException("invalid time unit " + unitStr + ", should be one of 'ms', 's', 'm', 'h', 'd'"); } if ("+".equals(op)) { return milliseconds; } else if ("-".equals(op)) { return 0 - milliseconds; } else { throw new IllegalArgumentException("invalid operation " + op + ", should be either '+' or '-'"); } } private static long convertToMilliseconds(long num, String unitStr) { return convertToMilliseconds("+", num, unitStr); } /** * @return the current time in seconds */ public static long nowInSeconds() { return TimeUnit.SECONDS.convert(System.currentTimeMillis(), TimeUnit.MILLISECONDS); } public static int resolutionInSeconds(String resolutionStr) { Matcher matcher = RESOLUTION_PATTERN.matcher(resolutionStr); int output = 0; while (matcher.find()) { output += TimeUnit.MILLISECONDS.toSeconds(convertToMilliseconds(Long.parseLong(matcher.group(1)), matcher.group(2))); } return output; } /** * Parses a time in String format into a long value. If the input is numeric we assume the input is in seconds. * * @param timeStr the string to parse * @return the parsed time in seconds */ public static long parseTimeInSeconds(String timeStr) { return parseTime(timeStr, TimeUnit.SECONDS); } /** * Parses a time in String format into a long value * * @param timeStr the string to parse * @param timeUnit the unit of time to return, if timeStr is numeric then it is assumed to be in the unit timeUnit * @return the parsed time */ public static long parseTime(String timeStr, TimeUnit timeUnit) { return parseTime(System.currentTimeMillis(), timeStr, timeUnit); } /** * Parses a time in String format into a long value. If the input is numeric we assume the input is in seconds. * * @param now the present time in seconds * @param timeStr the string to parse * @return the parsed time in seconds */ public static long parseTimeInSeconds(long now, String timeStr) { return parseTime(TimeUnit.MILLISECONDS.convert(now, TimeUnit.SECONDS), timeStr, TimeUnit.SECONDS); } /** * Parses a time in String format into a long value * * @param now the present time in milliseconds * @param timeStr the string to parse * @param timeUnit the unit of time to return, if timeStr is numeric then it is assumed to be in the unit timeUnit * @return the parsed time * @throws IllegalArgumentException if the format of timeStr is bad */ public static long parseTime(long now, String timeStr, TimeUnit timeUnit) { Preconditions.checkNotNull(timeStr); if (NOW.equals(timeStr.toUpperCase())) { return now; } // if its a numeric timestamp, assume units are correct Matcher matcher = TIMESTAMP_PATTERN.matcher(timeStr); if (matcher.matches()) { return Long.parseLong(timeStr); } // if its some time math pattern like now-1d-6h long output = now; if (timeStr.startsWith(NOW)) { matcher = OP_PATTERN.matcher(timeStr); // start at 3 to take into account the NOW at the start of the string int prevEndPos = 3; while (matcher.find()) { // happens if there are unexpected things in-between, like "now 2h-1m" if (matcher.start() != prevEndPos) { throw new IllegalArgumentException("invalid time format " + timeStr); } // group 1 should be '+' or '-', group 2 is the number of units, and group 3 is the unit. ex: 6h output += convertToMilliseconds(matcher.group(1), Long.parseLong(matcher.group(2)), matcher.group(3)); prevEndPos = matcher.end(); } // happens if the end of the string is invalid, like "now-6h 30m" if (prevEndPos != timeStr.length()) { throw new IllegalArgumentException("invalid time format " + timeStr); } } else { throw new IllegalArgumentException("invalid time format " + timeStr); } return timeUnit.convert(output, TimeUnit.MILLISECONDS); } }