blob: f06e3c867b1766927a9d0ba67f981e684960d392 [file] [log] [blame]
/*
* 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 org.apache.tephra.util;
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-5s" is 5 seconds before now, "now-1d" is one day before now, and "now-1d+4h" 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
*/
@SuppressWarnings({"unused", "WeakerAccess"})
public static long nowInSeconds() {
return TimeUnit.SECONDS.convert(System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
@SuppressWarnings("WeakerAccess")
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
*/
@SuppressWarnings("WeakerAccess")
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
*/
@SuppressWarnings("WeakerAccess")
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
*/
@SuppressWarnings("WeakerAccess")
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
*/
@SuppressWarnings("WeakerAccess")
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);
}
}