DateTimeTest.java

/*
 * Copyright (c) 2010 Google 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 com.google.api.client.util;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import com.google.api.client.util.DateTime.SecondsAndNanos;
import java.util.Date;
import java.util.TimeZone;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

/**
 * Tests {@link DateTime}.
 *
 * @author Yaniv Inbar
 */
@RunWith(JUnit4.class)
public class DateTimeTest {

  private TimeZone originalTimeZone;

  @Before
  public void setUp() throws Exception {
    originalTimeZone = TimeZone.getDefault();
  }

  @Before
  public void tearDown() throws Exception {
    TimeZone.setDefault(originalTimeZone);
  }

  @Test
  public void testToStringRfc3339() {
    TimeZone.setDefault(TimeZone.getTimeZone("GMT-4"));

    assertEquals(
        "Check with explicit Date and Timezone.",
        "2012-11-06T12:10:44.000-08:00",
        new DateTime(new Date(1352232644000L), TimeZone.getTimeZone("GMT-8")).toStringRfc3339());

    assertEquals(
        "Check with explicit Date but no explicit Timezone.",
        "2012-11-06T16:10:44.000-04:00",
        new DateTime(new Date(1352232644000L)).toStringRfc3339());

    assertEquals(
        "Check with explicit Date and Timezone-Shift.",
        "2012-11-06T17:10:44.000-03:00",
        new DateTime(1352232644000L, -180).toStringRfc3339());

    assertEquals(
        "Check with explicit Date and Zulu Timezone Offset.",
        "2012-11-06T20:10:44.000Z",
        new DateTime(1352232644000L, 0).toStringRfc3339());

    TimeZone.setDefault(TimeZone.getTimeZone("UTC"));

    assertEquals(
        "Check with explicit Date but no explicit Timezone.",
        "2012-11-06T20:10:44.000Z",
        new DateTime(new Date(1352232644000L)).toStringRfc3339());
  }

  @Test
  public void testToStringRfc3339_dateOnly() {
    for (String timeZoneString : new String[] {"GMT-4", "UTC", "UTC-7"}) {
      TimeZone.setDefault(TimeZone.getTimeZone(timeZoneString));
      assertEquals("2012-11-06", new DateTime(true, 1352232644000L, 1).toStringRfc3339());
      assertEquals("2012-11-06", new DateTime(true, 1352232644000L, null).toStringRfc3339());
      assertEquals("2000-01-01", new DateTime("2000-01-01").toStringRfc3339());
    }
  }

  @Test
  public void testEquals() throws InterruptedException {
    assertFalse(
        "Check equals with two different tz specified.",
        new DateTime(1234567890L).equals(new DateTime(1234567890L, 120)));
    assertTrue(
        "Check equals with two identical tz specified.",
        new DateTime(1234567890L, -240).equals(new DateTime(1234567890L, -240)));
    assertFalse(
        "Check equals with two different tz specified.",
        new DateTime(1234567890L, 60).equals(new DateTime(1234567890L, 240)));

    assertFalse("Check not equal.", new DateTime(1234567890L).equals(new DateTime(9876543210L)));
    assertFalse(
        "Check not equal with tz.",
        new DateTime(1234567890L, 120).equals(new DateTime(9876543210L, 120)));
    assertFalse(
        "Check not equal with Date.", new DateTime(1234567890L).equals(new Date(9876543210L)));

    DateTime dateTime1 = new DateTime("2011-01-01");
    Thread.sleep(10);
    DateTime dateTime2 = new DateTime("2011-01-01");
    assertEquals(dateTime1, dateTime2);
  }

  @Test
  public void testParseRfc3339() {
    expectExceptionForParseRfc3339("");
    expectExceptionForParseRfc3339("abc");
    expectExceptionForParseRfc3339("2013-01-01 09:00:02");
    expectExceptionForParseRfc3339("2013-01-01T"); // missing time
    expectExceptionForParseRfc3339("1937--3-55T12:00:27+00:20"); // invalid month
    expectExceptionForParseRfc3339("2013-01-01Z"); // can't have time zone shift without time

    DateTime value = DateTime.parseRfc3339("2007-06-01");
    assertTrue(value.isDateOnly());
    value = DateTime.parseRfc3339("2007-06-01T10:11:30.057");
    assertFalse(value.isDateOnly());
    value = DateTime.parseRfc3339("2007-06-01T10:11:30Z");
    assertEquals(0, value.getValue() % 100);
    value = DateTime.parseRfc3339("1997-01-01T12:00:27.87+00:20");
    assertFalse(value.isDateOnly());
    assertEquals(870, value.getValue() % 1000); // check milliseconds

    value = new DateTime("2007-06-01");
    assertTrue(value.isDateOnly());
    value = new DateTime("2007-06-01T10:11:30.057");
    assertFalse(value.isDateOnly());
    value = new DateTime("2007-06-01T10:11:30Z");
    assertEquals(0, value.getValue() % 100);

    // From the RFC3339 Standard
    assertEquals(
        DateTime.parseRfc3339("1996-12-19T16:39:57-08:00").getValue(),
        DateTime.parseRfc3339("1996-12-20T00:39:57Z").getValue()); // from Section 5.8 Examples
    assertEquals(
        DateTime.parseRfc3339("1990-12-31T23:59:60Z").getValue(),
        DateTime.parseRfc3339("1990-12-31T15:59:60-08:00").getValue()); // from Section 5.8 Examples
    assertEquals(
        DateTime.parseRfc3339("2007-06-01t18:50:00-04:00").getValue(),
        DateTime.parseRfc3339("2007-06-01t22:50:00Z").getValue()); // from Section 4.2 Local Offsets

    // Test truncating beyond millisecond precision.
    assertEquals(
        DateTime.parseRfc3339(
            "2018-12-31T23:59:59.999999999Z"), // This value would be rounded up prior to version
        // 1.30.2
        DateTime.parseRfc3339("2018-12-31T23:59:59.999Z"));
    assertEquals(
        DateTime.parseRfc3339(
            "2018-12-31T23:59:59.9999Z"), // This value would be truncated prior to version 1.30.2
        DateTime.parseRfc3339("2018-12-31T23:59:59.999Z"));

    // The beginning of Gregorian Calendar
    assertEquals(
        -12219287774877L, // Result from Joda time's Instant.parse
        DateTime.parseRfc3339("1582-10-15T01:23:45.123Z").getValue());
  }

  /**
   * The following test values have been generated and verified using the {@link DateTimeFormatter}
   * in Java 8.
   *
   * <pre>
   * Timestamp                           |   Seconds     |   Nanos
   * 2018-03-01T10:11:12.999Z            |   1519899072  |   999000000
   * 2018-10-28T02:00:00+02:00           |   1540684800  |   0
   * 2018-10-28T03:00:00+01:00           |   1540692000  |   0
   * 2018-01-01T00:00:00.000000001Z      |   1514764800  |   1
   * 2018-10-28T02:00:00Z                |   1540692000  |   0
   * 2018-12-31T23:59:59.999999999Z      |   1546300799  |   999999999
   * 2018-03-01T10:11:12.9999Z           |   1519899072  |   999900000
   * 2018-03-01T10:11:12.000000001Z      |   1519899072  |   1
   * 2018-03-01T10:11:12.100000000Z      |   1519899072  |   100000000
   * 2018-03-01T10:11:12.100000001Z      |   1519899072  |   100000001
   * 2018-03-01T10:11:12-10:00           |   1519935072  |   0
   * 2018-03-01T10:11:12.999999999Z      |   1519899072  |   999999999
   * 2018-03-01T10:11:12-12:00           |   1519942272  |   0
   * 2018-10-28T03:00:00Z                |   1540695600  |   0
   * 2018-10-28T02:30:00Z                |   1540693800  |   0
   * 2018-03-01T10:11:12.123Z            |   1519899072  |   123000000
   * 2018-10-28T02:30:00+02:00           |   1540686600  |   0
   * 2018-03-01T10:11:12.123456789Z      |   1519899072  |   123456789
   * 2018-03-01T10:11:12.1000Z           |   1519899072  |   100000000
   * </pre>
   */
  @Test
  public void testParseRfc3339ToSecondsAndNanos() {
    assertParsedRfc3339(
        "2018-03-01T10:11:12.999Z", SecondsAndNanos.ofSecondsAndNanos(1519899072L, 999000000));
    assertParsedRfc3339(
        "2018-10-28T02:00:00+02:00", SecondsAndNanos.ofSecondsAndNanos(1540684800L, 0));
    assertParsedRfc3339(
        "2018-10-28T03:00:00+01:00", SecondsAndNanos.ofSecondsAndNanos(1540692000L, 0));
    assertParsedRfc3339(
        "2018-01-01T00:00:00.000000001Z", SecondsAndNanos.ofSecondsAndNanos(1514764800L, 1));
    assertParsedRfc3339("2018-10-28T02:00:00Z", SecondsAndNanos.ofSecondsAndNanos(1540692000L, 0));
    assertParsedRfc3339(
        "2018-12-31T23:59:59.999999999Z",
        SecondsAndNanos.ofSecondsAndNanos(1546300799L, 999999999));
    assertParsedRfc3339(
        "2018-03-01T10:11:12.9999Z", SecondsAndNanos.ofSecondsAndNanos(1519899072L, 999900000));
    assertParsedRfc3339(
        "2018-03-01T10:11:12.000000001Z", SecondsAndNanos.ofSecondsAndNanos(1519899072L, 1));
    assertParsedRfc3339(
        "2018-03-01T10:11:12.100000000Z",
        SecondsAndNanos.ofSecondsAndNanos(1519899072L, 100000000));
    assertParsedRfc3339(
        "2018-03-01T10:11:12.100000001Z",
        SecondsAndNanos.ofSecondsAndNanos(1519899072L, 100000001));
    assertParsedRfc3339(
        "2018-03-01T10:11:12-10:00", SecondsAndNanos.ofSecondsAndNanos(1519935072L, 0));
    assertParsedRfc3339(
        "2018-03-01T10:11:12.999999999Z",
        SecondsAndNanos.ofSecondsAndNanos(1519899072L, 999999999));
    assertParsedRfc3339(
        "2018-03-01T10:11:12-12:00", SecondsAndNanos.ofSecondsAndNanos(1519942272L, 0));
    assertParsedRfc3339("2018-10-28T03:00:00Z", SecondsAndNanos.ofSecondsAndNanos(1540695600L, 0));
    assertParsedRfc3339("2018-10-28T02:30:00Z", SecondsAndNanos.ofSecondsAndNanos(1540693800L, 0));
    assertParsedRfc3339(
        "2018-03-01T10:11:12.123Z", SecondsAndNanos.ofSecondsAndNanos(1519899072L, 123000000));
    assertParsedRfc3339(
        "2018-10-28T02:30:00+02:00", SecondsAndNanos.ofSecondsAndNanos(1540686600L, 0));
    assertParsedRfc3339(
        "2018-03-01T10:11:12.123456789Z",
        SecondsAndNanos.ofSecondsAndNanos(1519899072L, 123456789));
    assertParsedRfc3339(
        "2018-03-01T10:11:12.1000Z", SecondsAndNanos.ofSecondsAndNanos(1519899072L, 100000000));
  }

  @Test
  public void testEpoch() {
    assertParsedRfc3339("1970-01-01T00:00:00.000Z", SecondsAndNanos.ofSecondsAndNanos(0, 0));
  }

  @Test
  public void testOneSecondBeforeEpoch() {
    assertParsedRfc3339("1969-12-31T23:59:59.000Z", SecondsAndNanos.ofSecondsAndNanos(-1, 0));
  }

  private static void assertParsedRfc3339(String input, SecondsAndNanos expected) {
    SecondsAndNanos actual = DateTime.parseRfc3339ToSecondsAndNanos(input);
    assertEquals(
        "Seconds for " + input + " do not match", expected.getSeconds(), actual.getSeconds());
    assertEquals("Nanos for " + input + " do not match", expected.getNanos(), actual.getNanos());
  }

  @Test
  public void testParseAndFormatRfc3339() {
    // .12 becomes .120
    String input = "1996-12-19T16:39:57.12-08:00";
    String expected = "1996-12-19T16:39:57.120-08:00";
    DateTime dt = DateTime.parseRfc3339(input);
    String output = dt.toStringRfc3339();
    assertEquals(expected, output);

    // Truncated to milliseconds.
    input = "1996-12-19T16:39:57.123456789-08:00";
    expected = "1996-12-19T16:39:57.123-08:00";
    dt = DateTime.parseRfc3339(input);
    output = dt.toStringRfc3339();
    assertEquals(expected, output);
  }

  private static void expectExceptionForParseRfc3339(String input) {
    try {
      DateTime.parseRfc3339(input);
      fail("expected NumberFormatException");
    } catch (NumberFormatException e) {
      // expected
    }
  }
}