You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tephra.apache.org by po...@apache.org on 2017/09/22 02:42:05 UTC
[6/6] incubator-tephra git commit: TEPHRA-245 Add TimeMathParser to
parse relative time
TEPHRA-245 Add TimeMathParser to parse relative time
Signed-off-by: poorna <po...@apache.org>
Project: http://git-wip-us.apache.org/repos/asf/incubator-tephra/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-tephra/commit/4ade1438
Tree: http://git-wip-us.apache.org/repos/asf/incubator-tephra/tree/4ade1438
Diff: http://git-wip-us.apache.org/repos/asf/incubator-tephra/diff/4ade1438
Branch: refs/heads/master
Commit: 4ade1438399d9e932979fdefa273b037f73b570f
Parents: cd7bd2b
Author: poorna <po...@cask.co>
Authored: Wed Sep 13 17:08:29 2017 -0700
Committer: poorna <po...@apache.org>
Committed: Thu Sep 21 19:41:32 2017 -0700
----------------------------------------------------------------------
.../org/apache/tephra/util/TimeMathParser.java | 175 +++++++++++++++++++
.../apache/tephra/util/TimeMathParserTest.java | 117 +++++++++++++
2 files changed, 292 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-tephra/blob/4ade1438/tephra-core/src/main/java/org/apache/tephra/util/TimeMathParser.java
----------------------------------------------------------------------
diff --git a/tephra-core/src/main/java/org/apache/tephra/util/TimeMathParser.java b/tephra-core/src/main/java/org/apache/tephra/util/TimeMathParser.java
new file mode 100644
index 0000000..f06e3c8
--- /dev/null
+++ b/tephra-core/src/main/java/org/apache/tephra/util/TimeMathParser.java
@@ -0,0 +1,175 @@
+/*
+ * 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);
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-tephra/blob/4ade1438/tephra-core/src/test/java/org/apache/tephra/util/TimeMathParserTest.java
----------------------------------------------------------------------
diff --git a/tephra-core/src/test/java/org/apache/tephra/util/TimeMathParserTest.java b/tephra-core/src/test/java/org/apache/tephra/util/TimeMathParserTest.java
new file mode 100644
index 0000000..553e030
--- /dev/null
+++ b/tephra-core/src/test/java/org/apache/tephra/util/TimeMathParserTest.java
@@ -0,0 +1,117 @@
+/*
+ * 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 org.junit.Assert;
+import org.junit.Test;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test {@link TimeMathParser} class.
+ */
+public class TimeMathParserTest {
+
+ @Test
+ public void testGetNowInSeconds() {
+ long now = TimeUnit.SECONDS.convert(System.currentTimeMillis(), TimeUnit.MILLISECONDS);
+ // in case we're on the border between seconds
+ long result = TimeMathParser.nowInSeconds();
+ Assert.assertTrue((result - now == 1) || result == now);
+ }
+
+ @Test
+ public void testParseTimestamp() {
+ Assert.assertEquals(1234567890, TimeMathParser.parseTimeInSeconds("1234567890"));
+ Assert.assertEquals(1234567890, TimeMathParser.parseTimeInSeconds(0, "1234567890"));
+ }
+
+ @Test
+ public void testParseNow() {
+ long now = TimeMathParser.nowInSeconds();
+ long result = TimeMathParser.parseTimeInSeconds("now");
+ // in case we're on the border between seconds
+ Assert.assertTrue((result - now) == 1 || (result == now));
+ Assert.assertEquals(1234567890, TimeMathParser.parseTimeInSeconds(1234567890, "now"));
+ }
+
+ @Test
+ public void testOneOperation() {
+ long now = TimeMathParser.nowInSeconds();
+ Assert.assertEquals(now - 7, TimeMathParser.parseTimeInSeconds(now, "now-7s"));
+ Assert.assertEquals(now - 7 * 60, TimeMathParser.parseTimeInSeconds(now, "now-7m"));
+ Assert.assertEquals(now - 7 * 3600, TimeMathParser.parseTimeInSeconds(now, "now-7h"));
+ Assert.assertEquals(now - 7 * 86400, TimeMathParser.parseTimeInSeconds(now, "now-7d"));
+ Assert.assertEquals(now + 7, TimeMathParser.parseTimeInSeconds(now, "now+7s"));
+ Assert.assertEquals(now + 7 * 60, TimeMathParser.parseTimeInSeconds(now, "now+7m"));
+ Assert.assertEquals(now + 7 * 3600, TimeMathParser.parseTimeInSeconds(now, "now+7h"));
+ Assert.assertEquals(now + 7 * 86400, TimeMathParser.parseTimeInSeconds(now, "now+7d"));
+ Assert.assertEquals(System.currentTimeMillis() - 10, TimeMathParser.parseTime("now-10ms",
+ TimeUnit.MILLISECONDS), 1);
+ Assert.assertEquals(System.currentTimeMillis() + 50, TimeMathParser.parseTime("now+50ms",
+ TimeUnit.MILLISECONDS), 1);
+ }
+
+ @Test
+ public void testResolutionParsing() {
+ String resolution = "60s";
+ Assert.assertEquals(60, TimeMathParser.resolutionInSeconds(resolution));
+ resolution = "2m";
+ Assert.assertEquals(120, TimeMathParser.resolutionInSeconds(resolution));
+ resolution = "3h";
+ Assert.assertEquals(10800, TimeMathParser.resolutionInSeconds(resolution));
+ resolution = "1d";
+ Assert.assertEquals(86400, TimeMathParser.resolutionInSeconds(resolution));
+ resolution = "1h3m";
+ Assert.assertEquals(3780, TimeMathParser.resolutionInSeconds(resolution));
+ }
+
+ @Test
+ public void testMultipleOperations() {
+ long now = TimeMathParser.nowInSeconds();
+ Assert.assertEquals(now - 7 * 86400 + 3 * 3600 - 13 * 60 + 11,
+ TimeMathParser.parseTimeInSeconds(now, "now-7d+3h-13m+11s"));
+ }
+
+ // happens if input is supposed to be url encoded but is not
+ @Test(expected = IllegalArgumentException.class)
+ public void testSpaceInsteadOfPlusThrowsException() {
+ long now = TimeMathParser.nowInSeconds();
+ TimeMathParser.parseTimeInSeconds(now, "now 6h");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testGibberishInMiddleThrowsException() {
+ long now = TimeMathParser.nowInSeconds();
+ TimeMathParser.parseTimeInSeconds(now, "now-3d+23lnkfasd-6h");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testInvalidOperationThrowsException() {
+ long now = TimeMathParser.nowInSeconds();
+ TimeMathParser.parseTimeInSeconds(now, "now/1d");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testInvalidUnitThrowsException() {
+ long now = TimeMathParser.nowInSeconds();
+ TimeMathParser.parseTimeInSeconds(now, "now-1w");
+ }
+}