All Downloads are FREE. Search and download functionalities are using the official Maven repository.

co.cask.hydrator.common.TimeParser Maven / Gradle / Ivy

There is a newer version: 2.1.2
Show newest version
/*
 * Copyright © 2015 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.hydrator.common;

import com.google.common.base.Preconditions;
import com.google.common.base.Strings;

import java.util.concurrent.TimeUnit;

/**
 * Parses time expressions and does simple time math.
 */
public class TimeParser {
  private final long runtime;

  public TimeParser(long runtime) {
    this.runtime = runtime;
  }

  /**
   * Parses time strings into timestamps, with support for some basic math.
   * Math syntax includes addition and subtraction in seconds, minutes, hours, and days.
   * Periods of time are specified as some number followed by the time unit,
   * where 's' is seconds', 'm' is minutes, 'h' is hours, and 'd' is days.
   * The "runtime" keyword translates to some given runtime, and can be strung together with
   * various additions and subtractions.
   * For example, "runtime-5s" is 5 seconds before runtime, "runtime-1d" is one day before runtime,
   * and "runtime-1d+4h" is 20 hours before runtime.
   *
   * @param str the time string to parse
   * @return milliseconds equivalent of the time string
   */
  public long parseRuntime(String str) {
    MathNode mathNode = parseExpression(str);
    return mathNode.evaluate();
  }

  /**
   * Parses a duration String to its long value.
   * Frequency string consists of a number followed by an unit, with 's' for seconds, 'm' for minutes, 'h' for hours
   * and 'd' for days. For example, an input of '5m' means 5 minutes which will be parsed to 300000 milliseconds.
   *
   * @param durationStr the duration string (ex: 5m, 5h etc).
   * @return milliseconds equivalent of the duration string
   */
  public static long parseDuration(String durationStr) {
    Preconditions.checkArgument(!Strings.isNullOrEmpty(durationStr));
    durationStr = durationStr.trim().toLowerCase();

    String value = durationStr.substring(0, durationStr.length() - 1);
    long parsedValue;
    try {
      parsedValue = Long.parseLong(value);
    } catch (NumberFormatException e) {
      throw new IllegalArgumentException(
        String.format("Error parsing the duration string %s. Cannot parse %s as a long",
                      durationStr, value));
    }

    char lastChar = durationStr.charAt(durationStr.length() - 1);
    switch (lastChar) {
      case 's':
        return TimeUnit.SECONDS.toMillis(parsedValue);
      case 'm':
        return TimeUnit.MINUTES.toMillis(parsedValue);
      case 'h':
        return TimeUnit.HOURS.toMillis(parsedValue);
      case 'd':
        return TimeUnit.DAYS.toMillis(parsedValue);
    }
    throw new IllegalArgumentException(String.format("Time unit not supported: %s", lastChar));
  }

  // parse an expression into an evaluation tree
  private MathNode parseExpression(String expression) {
    expression = expression.trim();
    // we only support + and - right now so just evaluate from left to right
    int idx = Math.max(expression.lastIndexOf('+'), expression.lastIndexOf('-'));

    // if no operator, this should be a duration string
    if (idx < 0) {
      return "runtime".equals(expression) ? new ValueNode(runtime) : new ValueNode(parseDuration(expression));
    }

    char operator = expression.charAt(idx);
    if (idx == expression.length()) {
      throw new IllegalArgumentException(
        String.format("Invalid expression '%s'. Cannot end with %s.", expression, operator));
    }

    // if this in an expression like '-5d'
    if (idx == 0) {
      return new ValueNode(parseDuration(expression));
    }

    MathNode left = parseExpression(expression.substring(0, idx));
    MathNode right = parseExpression(expression.substring(idx + 1));
    if (operator == '+') {
      return new AddNode(left, right);
    }
    return new SubtractNode(left, right);
  }

  private interface MathNode {
    long evaluate();
  }

  private static class ValueNode implements MathNode {
    private long value;

    ValueNode(long value) {
      this.value = value;
    }

    public long evaluate() {
      return value;
    }
  }

  private static class AddNode implements MathNode {
    private final MathNode left;
    private final MathNode right;

    AddNode(MathNode left, MathNode right) {
      this.left = left;
      this.right = right;
    }

    @Override
    public long evaluate() {
      return left.evaluate() + right.evaluate();
    }
  }

  private static class SubtractNode implements MathNode {
    private final MathNode left;
    private final MathNode right;

    SubtractNode(MathNode left, MathNode right) {
      this.left = left;
      this.right = right;
    }

    @Override
    public long evaluate() {
      return left.evaluate() - right.evaluate();
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy