From 85176dba21bf38cccc94996114f57f753c1038fd Mon Sep 17 00:00:00 2001 From: MerlinLim Date: Thu, 15 Oct 2020 19:49:17 +0800 Subject: [PATCH 1/2] Add DateTime class DateTime is a field encapsulating a valid date string as a LocalDateTime. DateTime can take in either a date with time or just a date only. DateTime can be compared with other datetime objects based on their respective values. --- .../seedu/address/model/util/DateTime.java | 83 +++++++++++++++++++ .../address/model/util/DateTimeTest.java | 62 ++++++++++++++ 2 files changed, 145 insertions(+) create mode 100644 src/main/java/seedu/address/model/util/DateTime.java create mode 100644 src/test/java/seedu/address/model/util/DateTimeTest.java diff --git a/src/main/java/seedu/address/model/util/DateTime.java b/src/main/java/seedu/address/model/util/DateTime.java new file mode 100644 index 00000000000..9f6d1935823 --- /dev/null +++ b/src/main/java/seedu/address/model/util/DateTime.java @@ -0,0 +1,83 @@ +package seedu.address.model.util; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; + +public class DateTime implements Comparable { + public static final String TIME_REGEX = "(([0-1]\\d)|(2[0-3])):([0-5]\\d)"; + public static final String DATE_REGEX = "(([0-2]\\d)|(3[0-1]))-((0[1-9])|(1[0-2]))-(\\d{4})"; + public static final String MESSAGE_CONSTRAINTS = + "From should be in the format of DD-MM-YYYY or DD-MM-YYYY HH:mm, and should not be blank." + + "Note: Single digit month, day, and minute must start with a leading zero."; + + public static final String VALIDATION_REGEX = String.format("%s(\\s(%s))?", + DATE_REGEX, TIME_REGEX); + + public static final DateTimeFormatter DATE_TIME_FORMATTER = + DateTimeFormatter.ofPattern("dd-MM-yyyy[ HH:mm]"); + + public final LocalDateTime value; + public final String valueString; + + /** + * Constructs a {@code date}. + * + * @param date A valid from. + */ + public DateTime(String date) { + requireNonNull(date); + + // Check for constraints + checkArgument(isValidDateTime(date), MESSAGE_CONSTRAINTS); + + // Parse value + TemporalAccessor temporalAccessor = DATE_TIME_FORMATTER.parseBest(date, + LocalDateTime::from, LocalDate::from); + if (temporalAccessor instanceof LocalDateTime) { + this.value = (LocalDateTime) temporalAccessor; + this.valueString = this.value.format(DATE_TIME_FORMATTER); + } else { + this.value = ((LocalDate) temporalAccessor).atStartOfDay(); + this.valueString = ((LocalDate) temporalAccessor).format(DATE_TIME_FORMATTER); + } + + } + + /** + * Returns true if a given string is a valid DateTime. + * + * @param test string to test. + * @return result of match. + */ + public static boolean isValidDateTime(String test) { + return test.matches(VALIDATION_REGEX); + } + + @Override + public int compareTo(DateTime o) { + return this.value.compareTo(o.value); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DateTime) // instanceof handles nulls + && value.equals((((DateTime) other).value)); // state check + } + + @Override + public String toString() { + return valueString; + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} diff --git a/src/test/java/seedu/address/model/util/DateTimeTest.java b/src/test/java/seedu/address/model/util/DateTimeTest.java new file mode 100644 index 00000000000..a7d6732563c --- /dev/null +++ b/src/test/java/seedu/address/model/util/DateTimeTest.java @@ -0,0 +1,62 @@ +package seedu.address.model.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + + +class DateTimeTest { + + public static final String VALID_SAMPLE_A = "30-11-2020 10:00"; + public static final String VALID_DATE_A = "30-11-2020"; + public static final String VALID_SAMPLE_B = "31-11-2020 23:00"; + public static final String VALID_DATE_B = "30-02-2020"; + public static final String VALID_DATE_C = "29-02-2019"; + public static final String TIME_A = "23:00"; + + @Test + void isValidDateTimeSuccess() { + assertTrue(TIME_A.matches(DateTime.TIME_REGEX)); + assertTrue(VALID_DATE_A.matches(DateTime.DATE_REGEX)); + + // Date no Time + assertTrue(DateTime.isValidDateTime(VALID_DATE_A)); + + // Date and Time + assertTrue(DateTime.isValidDateTime(VALID_SAMPLE_A)); + } + + + @Test + void parseSuccess() { + DateTime dt = new DateTime(VALID_SAMPLE_A); + assertEquals(dt.toString(), VALID_SAMPLE_A); + + DateTime dtNoTime = new DateTime(VALID_DATE_A); + assertEquals(dtNoTime.toString(), VALID_DATE_A); + } + + @Test + void resolveOverflowParseSuccess() { + + DateTime resolveA = new DateTime(VALID_SAMPLE_B); + assertEquals(resolveA.toString(), "30-11-2020 23:00"); + + DateTime resolveB = new DateTime(VALID_DATE_B); + assertEquals(resolveB.toString(), "29-02-2020"); + + DateTime resolveC = new DateTime(VALID_DATE_C); + assertEquals(resolveC.toString(), "28-02-2019"); + } + + @Test + void compareSuccess() { + DateTime a = new DateTime(VALID_SAMPLE_A); + DateTime b = new DateTime(VALID_SAMPLE_B); + DateTime c = new DateTime(VALID_DATE_A); + assertEquals(a.compareTo(b), -1); + assertEquals(a.compareTo(a), 0); + assertEquals(a.compareTo(c), 1); + } +} From a2e73b4e5c4682d1803c3ac5a2143bfab453dccb Mon Sep 17 00:00:00 2001 From: MerlinLim Date: Sat, 17 Oct 2020 00:18:11 +0800 Subject: [PATCH 2/2] Support Optional String inputs DateTime has a an overridden constructor for Optional Strings. Value field is an optional. An empty value string is represented as "-". --- .../seedu/address/model/util/DateTime.java | 79 ++++++++++++++++--- .../address/model/util/DateTimeTest.java | 33 ++++++-- 2 files changed, 96 insertions(+), 16 deletions(-) diff --git a/src/main/java/seedu/address/model/util/DateTime.java b/src/main/java/seedu/address/model/util/DateTime.java index 9f6d1935823..93ba13c7d5a 100644 --- a/src/main/java/seedu/address/model/util/DateTime.java +++ b/src/main/java/seedu/address/model/util/DateTime.java @@ -7,13 +7,15 @@ import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.TemporalAccessor; +import java.util.Optional; public class DateTime implements Comparable { public static final String TIME_REGEX = "(([0-1]\\d)|(2[0-3])):([0-5]\\d)"; public static final String DATE_REGEX = "(([0-2]\\d)|(3[0-1]))-((0[1-9])|(1[0-2]))-(\\d{4})"; public static final String MESSAGE_CONSTRAINTS = - "From should be in the format of DD-MM-YYYY or DD-MM-YYYY HH:mm, and should not be blank." - + "Note: Single digit month, day, and minute must start with a leading zero."; + "Dates should be in the format of DD-MM-YYYY or DD-MM-YYYY HH:mm, " + + "and should not be blank. Note: Single digit month, day, and " + + "minute must start with a leading zero."; public static final String VALIDATION_REGEX = String.format("%s(\\s(%s))?", DATE_REGEX, TIME_REGEX); @@ -21,13 +23,15 @@ public class DateTime implements Comparable { public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("dd-MM-yyyy[ HH:mm]"); - public final LocalDateTime value; + public static final String EMPTY_DATETIME_VALUE_STRING = "-"; + + public final Optional value; public final String valueString; /** * Constructs a {@code date}. * - * @param date A valid from. + * @param date A valid date. */ public DateTime(String date) { requireNonNull(date); @@ -39,11 +43,46 @@ public DateTime(String date) { TemporalAccessor temporalAccessor = DATE_TIME_FORMATTER.parseBest(date, LocalDateTime::from, LocalDate::from); if (temporalAccessor instanceof LocalDateTime) { - this.value = (LocalDateTime) temporalAccessor; - this.valueString = this.value.format(DATE_TIME_FORMATTER); + LocalDateTime dateTimeParsed = (LocalDateTime) temporalAccessor; + this.value = Optional.of(dateTimeParsed); + this.valueString = dateTimeParsed.format(DATE_TIME_FORMATTER); } else { - this.value = ((LocalDate) temporalAccessor).atStartOfDay(); - this.valueString = ((LocalDate) temporalAccessor).format(DATE_TIME_FORMATTER); + LocalDate dateParsed = (LocalDate) temporalAccessor; + this.value = Optional.of((dateParsed).atStartOfDay()); + this.valueString = dateParsed.format(DATE_TIME_FORMATTER); + } + + } + + /** + * Constructs a {@code date}. + * + * @param date A valid optional DateTime. + */ + public DateTime(Optional date) { + // Check for constraints + date.ifPresent(d -> checkArgument(isValidDateTime(d), MESSAGE_CONSTRAINTS)); + + //Parse value + Optional temporalAccessor = date.map(d -> DATE_TIME_FORMATTER + .parseBest(d, LocalDateTime::from, LocalDate::from)); + Optional dateTimeParsed = temporalAccessor + .filter(t -> t instanceof LocalDateTime) + .map(t -> ((LocalDateTime) t)); + Optional dateParsed = temporalAccessor + .filter(t -> t instanceof LocalDate) + .map(t -> ((LocalDate) t)); + + if (dateTimeParsed.isPresent()) { + this.value = dateTimeParsed; + this.valueString = dateTimeParsed + .map(dt -> dt.format(DATE_TIME_FORMATTER)) + .orElse(EMPTY_DATETIME_VALUE_STRING); + } else { + this.value = dateParsed.map(LocalDate::atStartOfDay); + this.valueString = dateParsed + .map(dt -> dt.format(DATE_TIME_FORMATTER)) + .orElse(EMPTY_DATETIME_VALUE_STRING); } } @@ -58,16 +97,36 @@ public static boolean isValidDateTime(String test) { return test.matches(VALIDATION_REGEX); } + /** + * Checks is DateTime exists. + * + * @return true if value in DateTime exists. + */ + public boolean isEmpty() { + return this.value.isEmpty(); + } + @Override public int compareTo(DateTime o) { - return this.value.compareTo(o.value); + if (value.isEmpty() && o.isEmpty()) { + return 0; + } else { + return this.value.map(v1 -> + o.value.map(v2-> v1.compareTo(v2)).orElse(-1)) + .orElse(1); + } + } @Override public boolean equals(Object other) { return other == this // short circuit if same object || (other instanceof DateTime) // instanceof handles nulls - && value.equals((((DateTime) other).value)); // state check + && ((value.isEmpty() && ((DateTime) other).value.isEmpty()) //if v1 and v2 empty, return true + || value.map(v1 -> ((DateTime) other) + .value.map(v2-> v1.equals(v2)) + .orElse(false)) // if v2 not present, return false + .orElse(false)); // if v1 not present return false } @Override diff --git a/src/test/java/seedu/address/model/util/DateTimeTest.java b/src/test/java/seedu/address/model/util/DateTimeTest.java index a7d6732563c..1a40c40c8c9 100644 --- a/src/test/java/seedu/address/model/util/DateTimeTest.java +++ b/src/test/java/seedu/address/model/util/DateTimeTest.java @@ -3,8 +3,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import org.junit.jupiter.api.Test; +import java.util.Optional; +import org.junit.jupiter.api.Test; class DateTimeTest { @@ -31,23 +32,34 @@ void isValidDateTimeSuccess() { @Test void parseSuccess() { DateTime dt = new DateTime(VALID_SAMPLE_A); - assertEquals(dt.toString(), VALID_SAMPLE_A); + assertEquals(VALID_SAMPLE_A, dt.toString()); DateTime dtNoTime = new DateTime(VALID_DATE_A); - assertEquals(dtNoTime.toString(), VALID_DATE_A); + assertEquals(VALID_DATE_A, dtNoTime.toString()); + + //Optional + dt = new DateTime(Optional.of(VALID_SAMPLE_A)); + assertEquals(VALID_SAMPLE_A, dt.toString()); + + dtNoTime = new DateTime(Optional.of(VALID_DATE_A)); + assertEquals(VALID_DATE_A, dtNoTime.toString()); + + //Optional Empty + assertEquals("-", new DateTime(Optional.empty()).toString()); + } @Test void resolveOverflowParseSuccess() { DateTime resolveA = new DateTime(VALID_SAMPLE_B); - assertEquals(resolveA.toString(), "30-11-2020 23:00"); + assertEquals("30-11-2020 23:00", resolveA.toString()); DateTime resolveB = new DateTime(VALID_DATE_B); - assertEquals(resolveB.toString(), "29-02-2020"); + assertEquals("29-02-2020", resolveB.toString()); DateTime resolveC = new DateTime(VALID_DATE_C); - assertEquals(resolveC.toString(), "28-02-2019"); + assertEquals("28-02-2019", resolveC.toString()); } @Test @@ -55,8 +67,17 @@ void compareSuccess() { DateTime a = new DateTime(VALID_SAMPLE_A); DateTime b = new DateTime(VALID_SAMPLE_B); DateTime c = new DateTime(VALID_DATE_A); + DateTime a1 = new DateTime(Optional.empty()); assertEquals(a.compareTo(b), -1); assertEquals(a.compareTo(a), 0); assertEquals(a.compareTo(c), 1); + + //Compare Empty Optionals + //Empty Optionals have highest priority + assertEquals(a1.compareTo(a), 1); + assertEquals(a.compareTo(a1), -1); + + //Both are Empty Optionals + assertEquals(a1.compareTo(a1), 0); } }