Skip to content

Commit

Permalink
google-cloud-core: Optimize Date.parseDate(String) (#4920)
Browse files Browse the repository at this point in the history
* optimized date parsing

Date parsing using a regular expression is slower than a specific
implementation for the only format that is supported.

* removed IntParser and added test cases

* check for invalid date/year/month/day
  • Loading branch information
olavloite authored and sduskis committed Apr 10, 2019
1 parent a1ca855 commit 6846268
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,12 @@
import java.io.Serializable;
import java.util.Calendar;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/** Represents a Date without time, such as 2017-03-17. Date is timezone independent. */
@BetaApi("This is going to be replaced with LocalDate from threetenbp")
public final class Date implements Comparable<Date>, Serializable {

// Date format "yyyy-mm-dd"
private static final Pattern FORMAT_REGEXP = Pattern.compile("(\\d\\d\\d\\d)-(\\d\\d)-(\\d\\d)");
private static final long serialVersionUID = 8067099123096783929L;
private final int year;
private final int month;
Expand Down Expand Up @@ -57,14 +54,19 @@ public static Date fromYearMonthDay(int year, int month, int dayOfMonth) {

/** @param date Data in RFC 3339 date format (yyyy-mm-dd). */
public static Date parseDate(String date) {
Matcher matcher = FORMAT_REGEXP.matcher(date);
if (!matcher.matches()) {
throw new IllegalArgumentException("Invalid date: " + date);
Preconditions.checkNotNull(date);
final String invalidDate = "Invalid date: " + date;
Preconditions.checkArgument(date.length() == 10, invalidDate);
Preconditions.checkArgument(date.charAt(4) == '-', invalidDate);
Preconditions.checkArgument(date.charAt(7) == '-', invalidDate);
try {
int year = Integer.parseInt(date.substring(0, 4));
int month = Integer.parseInt(date.substring(5, 7));
int dayOfMonth = Integer.parseInt(date.substring(8, 10));
return new Date(year, month, dayOfMonth);
} catch (NumberFormatException e) {
throw new IllegalArgumentException(invalidDate, e);
}
int year = Integer.parseInt(matcher.group(1));
int month = Integer.parseInt(matcher.group(2));
int dayOfMonth = Integer.parseInt(matcher.group(3));
return new Date(year, month, dayOfMonth);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import static com.google.common.testing.SerializableTester.reserializeAndAssert;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;

import com.google.common.testing.EqualsTester;
import java.text.ParseException;
Expand All @@ -34,10 +35,80 @@ public class DateTest {

@Test
public void parseDate() {
Date date = Date.parseDate("2016-09-18");
assertThat(date.getYear()).isEqualTo(2016);
assertThat(date.getMonth()).isEqualTo(9);
assertThat(date.getDayOfMonth()).isEqualTo(18);
verifyDate("2016-09-18", 2016, 9, 18);
verifyDate("2000-01-01", 2000, 1, 1);
verifyDate("9999-12-31", 9999, 12, 31);
verifyDate("0001-01-01", 1, 1, 1);
verifyDate("2000-02-29", 2000, 2, 29); // This is a valid leap year.
verifyDate("1900-02-29", 1900, 2, 29); // This is NOT a valid leap year.
verifyDate("2001-02-29", 2001, 2, 29); // Also not a valid leap year.
verifyDate("2000-04-31", 2000, 4, 31); // Not a valid date.
}

private void verifyDate(String input, int year, int month, int day) {
Date date = Date.parseDate(input);
assertThat(date.getYear()).isEqualTo(year);
assertThat(date.getMonth()).isEqualTo(month);
assertThat(date.getDayOfMonth()).isEqualTo(day);
}

@Test
public void parseInvalidDates() {
parseInvalidDate("2016/09/18");
parseInvalidDate("2016 09 18");
parseInvalidDate("2016-9-18");
parseInvalidDate("2016-09-18T10:00");
parseInvalidDate("");
parseInvalidDate("test");
parseInvalidDate("aaaa-bb-cc");
parseInvalidDate("aaaa-01-01");
parseInvalidDate("2019-bb-01");
parseInvalidDate("2019-01-cc");
parseInvalidMonth("2000-13-01");
parseInvalidMonth("2000-00-01");
parseInvalidDay("2000-12-32");
parseInvalidDay("2000-12-00");
parseInvalidDate("10000-01-01");
parseInvalidYear("0000-01-01");
parseInvalidYear("-001-01-01");
parseInvalidMonth("0001--1-01");
parseInvalidDay("0001-01--1");
}

private void parseInvalidDate(String input) {
try {
Date.parseDate(input);
fail("Expected exception");
} catch (IllegalArgumentException e) {
assertThat(e.getMessage()).contains("Invalid date");
}
}

private void parseInvalidYear(String input) {
try {
Date.parseDate(input);
fail("Expected exception");
} catch (IllegalArgumentException e) {
assertThat(e.getMessage()).contains("Invalid year");
}
}

private void parseInvalidMonth(String input) {
try {
Date.parseDate(input);
fail("Expected exception");
} catch (IllegalArgumentException e) {
assertThat(e.getMessage()).contains("Invalid month");
}
}

private void parseInvalidDay(String input) {
try {
Date.parseDate(input);
fail("Expected exception");
} catch (IllegalArgumentException e) {
assertThat(e.getMessage()).contains("Invalid day");
}
}

@Test
Expand Down

0 comments on commit 6846268

Please sign in to comment.