/*
 * 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.hadoop.hive.ql.util;

import java.util.TimeZone;

import org.apache.hadoop.hive.common.type.Date;
import org.apache.hadoop.hive.common.type.HiveIntervalYearMonth;
import org.apache.hadoop.hive.common.type.Timestamp;
import org.apache.hadoop.hive.common.type.HiveIntervalDayTime;
import org.apache.hadoop.hive.ql.util.DateTimeMath;
import org.junit.*;

import static org.junit.Assert.*;

public class TestDateTimeMath {

  @Test
  public void testTimestampIntervalYearMonthArithmetic() throws Exception {
    char plus = '+';
    char minus = '-';

    checkTimestampIntervalYearMonthArithmetic("2001-01-01 01:02:03", plus, "0-0",
        "2001-01-01 01:02:03");
    checkTimestampIntervalYearMonthArithmetic("2001-01-01 01:02:03.456", plus, "1-1",
        "2002-02-01 01:02:03.456");
    checkTimestampIntervalYearMonthArithmetic("2001-01-01 01:02:03.456", plus, "10-0",
        "2011-01-01 01:02:03.456");
    checkTimestampIntervalYearMonthArithmetic("2001-01-01 01:02:03.456", plus, "0-11",
        "2001-12-01 01:02:03.456");
    checkTimestampIntervalYearMonthArithmetic("2001-03-01 01:02:03.500", plus, "1-11",
        "2003-02-01 01:02:03.500");
    checkTimestampIntervalYearMonthArithmetic("2001-01-01 01:02:03.500", plus, "-1-1",
        "1999-12-01 01:02:03.500");
    checkTimestampIntervalYearMonthArithmetic("2001-01-01 01:02:03.500", plus, "-0-0",
        "2001-01-01 01:02:03.500");
    checkTimestampIntervalYearMonthArithmetic("2001-01-01 01:02:03.123456789", plus, "-0-0",
        "2001-01-01 01:02:03.123456789");

    checkTimestampIntervalYearMonthArithmetic("2001-01-01 01:02:03", minus, "0-0",
        "2001-01-01 01:02:03");
    checkTimestampIntervalYearMonthArithmetic("2001-01-01 01:02:03", minus, "10-0",
        "1991-01-01 01:02:03");
    checkTimestampIntervalYearMonthArithmetic("2001-01-01 01:02:03", minus, "-10-0",
        "2011-01-01 01:02:03");
    checkTimestampIntervalYearMonthArithmetic("2001-01-01 01:02:03", minus, "8-2",
        "1992-11-01 01:02:03");
    checkTimestampIntervalYearMonthArithmetic("2001-01-01 01:02:03", minus, "-8-2",
        "2009-03-01 01:02:03");
    checkTimestampIntervalYearMonthArithmetic("2001-01-01 01:02:03.123456789", minus, "8-2",
        "1992-11-01 01:02:03.123456789");

    checkTimestampIntervalYearMonthArithmetic(null, plus, "1-1",
        null);
    checkTimestampIntervalYearMonthArithmetic("2001-01-01 01:02:03", plus, null,
        null);
    checkTimestampIntervalYearMonthArithmetic(null, minus, "1-1",
        null);
    checkTimestampIntervalYearMonthArithmetic("2001-01-01 01:02:03", minus, null,
        null);

    // End of the month behavior
    checkTimestampIntervalYearMonthArithmetic("2001-01-28 01:02:03", plus, "0-1",
        "2001-02-28 01:02:03");
    checkTimestampIntervalYearMonthArithmetic("2001-01-29 01:02:03", plus, "0-1",
        "2001-02-28 01:02:03");
    checkTimestampIntervalYearMonthArithmetic("2001-01-30 01:02:03", plus, "0-1",
        "2001-02-28 01:02:03");
    checkTimestampIntervalYearMonthArithmetic("2001-01-31 01:02:03", plus, "0-1",
        "2001-02-28 01:02:03");
    checkTimestampIntervalYearMonthArithmetic("2001-02-28 01:02:03", plus, "0-1",
        "2001-03-28 01:02:03");

    // Test that timestamp arithmetic is done in UTC and then converted back to local timezone,
    // matching Oracle behavior.
    TimeZone originalTz = TimeZone.getDefault();
    try {
      TimeZone.setDefault(TimeZone.getTimeZone("America/Los_Angeles"));
      checkTimestampIntervalYearMonthArithmetic("2001-01-01 01:02:03", plus, "0-6",
          "2001-07-01 01:02:03");
        checkTimestampIntervalYearMonthArithmetic("2001-07-01 01:02:03", plus, "0-6",
          "2002-01-01 01:02:03");

      TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
      checkTimestampIntervalYearMonthArithmetic("2001-01-01 01:02:03", plus, "0-6",
          "2001-07-01 01:02:03");
        checkTimestampIntervalYearMonthArithmetic("2001-07-01 01:02:03", plus, "0-6",
          "2002-01-01 01:02:03");
    } finally {
      TimeZone.setDefault(originalTz);
    }
  }

  @Test
  public void testDateIntervalYearMonthArithmetic() throws Exception {
    char plus = '+';
    char minus = '-';

    checkDateIntervalDayTimeArithmetic("2001-01-01", plus, "0-0", "2001-01-01");
    checkDateIntervalDayTimeArithmetic("2001-01-01", plus, "0-1", "2001-02-01");
    checkDateIntervalDayTimeArithmetic("2001-01-01", plus, "0-6", "2001-07-01");
    checkDateIntervalDayTimeArithmetic("2001-01-01", plus, "1-0", "2002-01-01");
    checkDateIntervalDayTimeArithmetic("2001-01-01", plus, "1-1", "2002-02-01");
    checkDateIntervalDayTimeArithmetic("2001-10-10", plus, "1-6", "2003-04-10");
    checkDateIntervalDayTimeArithmetic("2003-04-10", plus, "-1-6", "2001-10-10");

    checkDateIntervalDayTimeArithmetic("2001-01-01", minus, "0-0", "2001-01-01");
    checkDateIntervalDayTimeArithmetic("2001-01-01", minus, "0-1", "2000-12-01");
    checkDateIntervalDayTimeArithmetic("2001-01-01", minus, "1-0", "2000-01-01");
    checkDateIntervalDayTimeArithmetic("2001-01-01", minus, "1-1", "1999-12-01");
    checkDateIntervalDayTimeArithmetic("2001-10-10", minus, "1-6", "2000-04-10");
    checkDateIntervalDayTimeArithmetic("2003-04-10", minus, "-1-6", "2004-10-10");

    // end of month behavior
    checkDateIntervalDayTimeArithmetic("2001-01-28", plus, "0-1", "2001-02-28");
    checkDateIntervalDayTimeArithmetic("2001-01-29", plus, "0-1", "2001-02-28");
    checkDateIntervalDayTimeArithmetic("2001-01-30", plus, "0-1", "2001-02-28");
    checkDateIntervalDayTimeArithmetic("2001-01-31", plus, "0-1", "2001-02-28");
    checkDateIntervalDayTimeArithmetic("2001-01-31", plus, "0-2", "2001-03-31");
    checkDateIntervalDayTimeArithmetic("2001-02-28", plus, "0-1", "2001-03-28");
    // leap year
    checkDateIntervalDayTimeArithmetic("2004-01-28", plus, "0-1", "2004-02-28");
    checkDateIntervalDayTimeArithmetic("2004-01-29", plus, "0-1", "2004-02-29");
    checkDateIntervalDayTimeArithmetic("2004-01-30", plus, "0-1", "2004-02-29");
    checkDateIntervalDayTimeArithmetic("2004-01-31", plus, "0-1", "2004-02-29");
  }

  @Test
  public void testIntervalYearMonthArithmetic() throws Exception {
    char plus = '+';
    char minus = '-';

    checkIntervalYearMonthArithmetic("0-0", plus, "0-0", "0-0");
    checkIntervalYearMonthArithmetic("0-0", plus, "4-5", "4-5");
    checkIntervalYearMonthArithmetic("4-5", plus, "0-0", "4-5");
    checkIntervalYearMonthArithmetic("0-0", plus, "1-1", "1-1");
    checkIntervalYearMonthArithmetic("1-1", plus, "0-0", "1-1");

    checkIntervalYearMonthArithmetic("0-0", minus, "0-0", "0-0");
    checkIntervalYearMonthArithmetic("0-0", minus, "1-0", "-1-0");
    checkIntervalYearMonthArithmetic("1-2", minus, "1-1", "0-1");
    checkIntervalYearMonthArithmetic("0-0", minus, "1-1", "-1-1");
    checkIntervalYearMonthArithmetic("-1-1", minus, "1-1", "-2-2");
    checkIntervalYearMonthArithmetic("-1-1", minus, "-1-1", "0-0");

    checkIntervalYearMonthArithmetic(null, plus, "1-1", null);
    checkIntervalYearMonthArithmetic("1-1", plus, null, null);
    checkIntervalYearMonthArithmetic(null, minus, "1-1", null);
    checkIntervalYearMonthArithmetic("1-1", minus, null, null);
  }

  @Test
  public void testTimestampIntervalDayTimeArithmetic() throws Exception {
    char plus = '+';
    char minus = '-';

    checkTsIntervalDayTimeArithmetic("2001-01-01 01:02:03", plus, "1 1:1:1",
        "2001-01-02 02:03:04");
    checkTsIntervalDayTimeArithmetic("2001-01-01 01:02:03.456", plus, "1 1:1:1",
        "2001-01-02 02:03:04.456");
    checkTsIntervalDayTimeArithmetic("2001-01-01 01:02:03.456", plus, "1 1:1:1.555",
        "2001-01-02 02:03:05.011");
    checkTsIntervalDayTimeArithmetic("2001-01-01 01:02:03", plus, "1 1:1:1.555555555",
        "2001-01-02 02:03:04.555555555");
    checkTsIntervalDayTimeArithmetic("2001-01-01 01:02:03.456", plus, "1 1:1:1.555555555",
        "2001-01-02 02:03:05.011555555");
    checkTsIntervalDayTimeArithmetic("2001-01-01 01:02:03.500", plus, "1 1:1:1.499",
        "2001-01-02 02:03:04.999");
    checkTsIntervalDayTimeArithmetic("2001-01-01 01:02:03.500", plus, "1 1:1:1.500",
        "2001-01-02 02:03:05.0");
    checkTsIntervalDayTimeArithmetic("2001-01-01 01:02:03.500", plus, "1 1:1:1.501",
        "2001-01-02 02:03:05.001");
    checkTsIntervalDayTimeArithmetic("2001-01-01 01:02:03.500000000", plus, "1 1:1:1.4999999999",
        "2001-01-02 02:03:04.999999999");
    checkTsIntervalDayTimeArithmetic("2001-01-01 01:02:03.500000000", plus, "1 1:1:1.500",
        "2001-01-02 02:03:05.0");
    checkTsIntervalDayTimeArithmetic("2001-01-01 01:02:03.500000000", plus, "1 1:1:1.500000001",
        "2001-01-02 02:03:05.000000001");

    checkTsIntervalDayTimeArithmetic("2001-01-01 01:02:03", minus, "0 01:02:03",
        "2001-01-01 00:00:00");
    checkTsIntervalDayTimeArithmetic("2001-01-01 01:02:03", minus, "0 0:0:0",
        "2001-01-01 01:02:03");

    checkTsIntervalDayTimeArithmetic(null, plus, "1 1:1:1.555555555",
        null);
    checkTsIntervalDayTimeArithmetic("2001-01-01 01:02:03", plus, null,
        null);
    checkTsIntervalDayTimeArithmetic(null, minus, "1 1:1:1.555555555",
        null);
    checkTsIntervalDayTimeArithmetic("2001-01-01 01:02:03", minus, null,
        null);

    // Try some time zone boundaries
    TimeZone originalTz = TimeZone.getDefault();
    try {
      // America/Los_Angeles DST dates - 2015-03-08 02:00:00/2015-11-01 02:00:00
      TimeZone.setDefault(TimeZone.getTimeZone("America/Los_Angeles"));

      checkTsIntervalDayTimeArithmetic("2015-03-08 01:59:58", plus, "0 0:0:01",
          "2015-03-08 01:59:59");
      checkTsIntervalDayTimeArithmetic("2015-03-08 01:59:59", plus, "0 0:0:01",
          "2015-03-08 02:00:00");
      checkTsIntervalDayTimeArithmetic("2015-03-08 03:00:00", minus, "0 0:0:01",
          "2015-03-08 02:59:59");
      checkTsIntervalDayTimeArithmetic("2015-03-08 01:59:59.995", plus, "0 0:0:0.005",
          "2015-03-08 02:00:00");
      checkTsIntervalDayTimeArithmetic("2015-03-08 01:59:59.995", plus, "0 0:0:0.0051",
          "2015-03-08 02:00:00.0001");
      checkTsIntervalDayTimeArithmetic("2015-03-08 03:00:00", minus, "0 0:0:0.005",
          "2015-03-08 02:59:59.995");
      checkTsIntervalDayTimeArithmetic("2015-11-01 01:59:58", plus, "0 0:0:01",
          "2015-11-01 01:59:59");
      checkTsIntervalDayTimeArithmetic("2015-11-01 01:59:59", plus, "0 0:0:01",
          "2015-11-01 02:00:00");
      checkTsIntervalDayTimeArithmetic("2015-03-08 06:00:00", minus, "1 0:0:00",
          "2015-03-07 06:00:00");
      checkTsIntervalDayTimeArithmetic("2015-11-01 06:00:00", minus, "1 0:0:00",
          "2015-10-31 06:00:00");
      checkTsIntervalDayTimeArithmetic("2015-03-07 06:00:00", plus, "1 0:0:00",
          "2015-03-08 06:00:00");
      checkTsIntervalDayTimeArithmetic("2015-10-31 06:00:00", plus, "1 0:0:00",
          "2015-11-01 06:00:00");

      // UTC has no such adjustment
      TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
      checkTsIntervalDayTimeArithmetic("2015-03-08 01:59:58", plus, "0 0:0:01",
          "2015-03-08 01:59:59");
      checkTsIntervalDayTimeArithmetic("2015-03-08 01:59:59", plus, "0 0:0:01",
          "2015-03-08 02:00:00");
    } finally {
      TimeZone.setDefault(originalTz);
    }
  }

  @Test
  public void testIntervalDayTimeArithmetic() throws Exception {
    char plus = '+';
    char minus = '-';

    checkIntervalDayTimeArithmetic("0 0:0:0", plus, "0 0:0:0", "0 0:0:0");
    checkIntervalDayTimeArithmetic("0 01:02:03", plus, "6 0:0:0.0001", "6 01:02:03.0001");
    checkIntervalDayTimeArithmetic("6 0:0:0.0001", plus, "0 01:02:03", "6 01:02:03.0001");
    checkIntervalDayTimeArithmetic("0 01:02:03", plus, "1 10:10:10.0001", "1 11:12:13.0001");
    checkIntervalDayTimeArithmetic("1 10:10:10.0001", plus, "0 01:02:03", "1 11:12:13.0001");
    checkIntervalDayTimeArithmetic("0 0:0:0.900000000", plus, "0 0:0:0.099999999", "0 0:0:0.999999999");
    checkIntervalDayTimeArithmetic("0 0:0:0.900000001", plus, "0 0:0:0.099999999", "0 0:0:1");
    checkIntervalDayTimeArithmetic("0 0:0:0.900000002", plus, "0 0:0:0.099999999", "0 0:0:1.000000001");

    checkIntervalDayTimeArithmetic("0 0:0:0", minus, "0 0:0:0", "0 0:0:0");
    checkIntervalDayTimeArithmetic("0 0:0:0", minus, "0 0:0:0.123", "-0 0:0:0.123");
    checkIntervalDayTimeArithmetic("3 4:5:6.789", minus, "1 1:1:1.111", "2 3:4:5.678");
    checkIntervalDayTimeArithmetic("0 0:0:0.0", minus, "1 1:1:1.111", "-1 1:1:1.111");
    checkIntervalDayTimeArithmetic("-1 1:1:1.222", minus, "1 1:1:1.111", "-2 2:2:2.333");
    checkIntervalDayTimeArithmetic("-1 1:1:1.111", minus, "-1 1:1:1.111", "0 0:0:0");

    checkIntervalDayTimeArithmetic(null, plus, "1 1:1:1.111", null);
    checkIntervalDayTimeArithmetic("1 1:1:1.111", plus, null, null);
    checkIntervalDayTimeArithmetic(null, minus, "1 1:1:1.111", null);
    checkIntervalDayTimeArithmetic("1 1:1:1.111", minus, null, null);
  }

  @Test
  public void testTimestampSubtraction() throws Exception {
    checkTsArithmetic("2001-01-01 00:00:00", "2001-01-01 00:00:00", "0 0:0:0");
    checkTsArithmetic("2002-02-02 01:01:01", "2001-01-01 00:00:00", "397 1:1:1");
    checkTsArithmetic("2001-01-01 00:00:00", "2002-02-02 01:01:01", "-397 1:1:1");
    checkTsArithmetic("2015-01-01 00:00:00", "2014-12-31 00:00:00", "1 0:0:0");
    checkTsArithmetic("2014-12-31 00:00:00", "2015-01-01 00:00:00", "-1 0:0:0");
    checkTsArithmetic("2015-01-01 00:00:00", "2014-12-31 23:59:59", "0 0:0:01");
    checkTsArithmetic("2014-12-31 23:59:59", "2015-01-01 00:00:00", "-0 0:0:01");
    checkTsArithmetic("2015-01-01 00:00:00", "2014-12-31 23:59:59.9999", "0 0:0:00.0001");
    checkTsArithmetic("2014-12-31 23:59:59.9999", "2015-01-01 00:00:00", "-0 0:0:00.0001");
    checkTsArithmetic("2015-01-01 00:00:00", "2014-12-31 11:12:13.000000001", "0 12:47:46.999999999");
    checkTsArithmetic("2014-12-31 11:12:13.000000001", "2015-01-01 00:00:00", "-0 12:47:46.999999999");

    // Test that timestamp arithmetic is done in UTC and then converted back to local timezone,
    // matching Oracle behavior.
    TimeZone originalTz = TimeZone.getDefault();
    try {
      TimeZone.setDefault(TimeZone.getTimeZone("America/Los_Angeles"));
      checkTsArithmetic("1999-12-15 00:00:00", "1999-09-15 00:00:00", "91 0:0:0");
      checkTsArithmetic("1999-09-15 00:00:00", "1999-12-15 00:00:00", "-91 0:0:0");
      checkTsArithmetic("1999-12-15 00:00:00", "1995-09-15 00:00:00", "1552 0:0:0");
      checkTsArithmetic("1995-09-15 00:00:00", "1999-12-15 00:00:00", "-1552 0:0:0");

      TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
      checkTsArithmetic("1999-12-15 00:00:00", "1999-09-15 00:00:00", "91 0:0:0");
      checkTsArithmetic("1999-09-15 00:00:00", "1999-12-15 00:00:00", "-91 0:0:0");
      checkTsArithmetic("1999-12-15 00:00:00", "1995-09-15 00:00:00", "1552 0:0:0");
      checkTsArithmetic("1995-09-15 00:00:00", "1999-12-15 00:00:00", "-1552 0:0:0");
    } finally {
      TimeZone.setDefault(originalTz);
    }
  }

  private static void checkTimestampIntervalYearMonthArithmetic(
      String left, char operationType, String right, String expected) throws Exception {
    Timestamp leftTs = null;
    if (left != null) {
      leftTs = Timestamp.valueOf(left);
    }
    HiveIntervalYearMonth rightInterval = null;
    if (right != null) {
      rightInterval = HiveIntervalYearMonth.valueOf(right);
    }
    Timestamp expectedResult = null;
    if (expected != null) {
      expectedResult = Timestamp.valueOf(expected);
    }
    Timestamp testResult = null;

    DateTimeMath dtm = new DateTimeMath();
    switch (operationType) {
      case '-':
        testResult = dtm.subtract(leftTs, rightInterval);
        break;
      case '+':
        testResult = dtm.add(leftTs, rightInterval);
        break;
      default:
        throw new IllegalArgumentException("Invalid operation " + operationType);
    }

    assertEquals(String.format("%s %s %s", leftTs, operationType, rightInterval),
        expectedResult, testResult);
  }

  private static void checkDateIntervalDayTimeArithmetic(
      String left, char operationType, String right, String expected) throws Exception {
    Date leftDt = null;
    if (left != null) {
      leftDt = Date.valueOf(left);
    }
    HiveIntervalYearMonth rightInterval = null;
    if (right != null) {
      rightInterval = HiveIntervalYearMonth.valueOf(right);
    }
    Date expectedResult = null;
    if (expected != null) {
      expectedResult = Date.valueOf(expected);
    }
    Date testResult = null;

    DateTimeMath dtm = new DateTimeMath();
    switch (operationType) {
      case '-':
        testResult = dtm.subtract(leftDt, rightInterval);
        break;
      case '+':
        testResult = dtm.add(leftDt, rightInterval);
        break;
      default:
        throw new IllegalArgumentException("Invalid operation " + operationType);
    }

    assertEquals(String.format("%s %s %s", leftDt, operationType, rightInterval),
        expectedResult, testResult);
  }

  private static void checkIntervalYearMonthArithmetic(
      String left, char operationType, String right, String expected) throws Exception {
    HiveIntervalYearMonth leftInterval = left == null ? null: HiveIntervalYearMonth.valueOf(left);
    HiveIntervalYearMonth rightInterval = right == null ? null : HiveIntervalYearMonth.valueOf(right);
    HiveIntervalYearMonth expectedResult = expected == null ? null : HiveIntervalYearMonth.valueOf(expected);
    HiveIntervalYearMonth testResult = null;

    DateTimeMath dtm = new DateTimeMath();
    switch (operationType) {
      case '-':
        testResult = dtm.subtract(leftInterval, rightInterval);
        break;
      case '+':
        testResult = dtm.add(leftInterval,  rightInterval);
        break;
      default:
        throw new IllegalArgumentException("Invalid operation " + operationType);
    }

    assertEquals(String.format("%s %s %s", leftInterval, operationType, rightInterval),
        expectedResult, testResult);
  }

  private static void checkTsIntervalDayTimeArithmetic(
      String left, char operationType, String right, String expected) throws Exception {
    Timestamp leftTs = null;
    if (left != null) {
      leftTs = Timestamp.valueOf(left);
    }
    HiveIntervalDayTime rightInterval = right == null ? null : HiveIntervalDayTime.valueOf(right);
    Timestamp expectedResult = null;
    if (expected != null) {
      expectedResult = Timestamp.valueOf(expected);
    }
    Timestamp testResult = null;

    DateTimeMath dtm = new DateTimeMath();
    switch (operationType) {
      case '-':
        testResult = dtm.subtract(leftTs, rightInterval);
        break;
      case '+':
        testResult = dtm.add(leftTs, rightInterval);
        break;
      default:
        throw new IllegalArgumentException("Invalid operation " + operationType);
    }

    assertEquals(String.format("%s %s %s", leftTs, operationType, rightInterval),
        expectedResult, testResult);
  }

  private static void checkIntervalDayTimeArithmetic(
      String left, char operationType, String right, String expected) throws Exception {
    HiveIntervalDayTime leftInterval = left == null ? null : HiveIntervalDayTime.valueOf(left);
    HiveIntervalDayTime rightInterval = right == null ? null : HiveIntervalDayTime.valueOf(right);
    HiveIntervalDayTime expectedResult = expected == null ? null : HiveIntervalDayTime.valueOf(expected);
    HiveIntervalDayTime testResult = null;

    DateTimeMath dtm = new DateTimeMath();
    switch (operationType) {
      case '-':
        testResult = dtm.subtract(leftInterval, rightInterval);
        break;
      case '+':
        testResult = dtm.add(leftInterval,  rightInterval);
        break;
      default:
        throw new IllegalArgumentException("Invalid operation " + operationType);
    }

    assertEquals(String.format("%s %s %s", leftInterval, operationType, rightInterval),
        expectedResult, testResult);
  }

  private static void checkTsArithmetic(
      String left, String right, String expected) throws Exception {
    Timestamp leftTs = null;
    if (left != null) {
      leftTs = Timestamp.valueOf(left);
    }
    Timestamp rightTs = null;
    if (left != null) {
      rightTs = Timestamp.valueOf(right);
    }
    HiveIntervalDayTime expectedResult = null;
    if (expected != null) {
      expectedResult = HiveIntervalDayTime.valueOf(expected);
    }
    DateTimeMath dtm = new DateTimeMath();
    HiveIntervalDayTime testResult =
        dtm.subtract(leftTs, rightTs);

    assertEquals(String.format("%s - %s", leftTs, rightTs),
        expectedResult, testResult);
  }
}
