Skip to content

Commit

Permalink
Merge pull request nus-cs2103-AY2021S1#71 from MerlinLim/util-DateTime
Browse files Browse the repository at this point in the history
Add DateTime class
  • Loading branch information
MerlinLim authored Oct 17, 2020
2 parents 0db7dd2 + a2e73b4 commit 562b095
Show file tree
Hide file tree
Showing 2 changed files with 225 additions and 0 deletions.
142 changes: 142 additions & 0 deletions src/main/java/seedu/address/model/util/DateTime.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
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;
import java.util.Optional;

public class DateTime implements Comparable<DateTime> {
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 =
"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);

public static final DateTimeFormatter DATE_TIME_FORMATTER =
DateTimeFormatter.ofPattern("dd-MM-yyyy[ HH:mm]");

public static final String EMPTY_DATETIME_VALUE_STRING = "-";

public final Optional<LocalDateTime> value;
public final String valueString;

/**
* Constructs a {@code date}.
*
* @param date A valid date.
*/
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) {
LocalDateTime dateTimeParsed = (LocalDateTime) temporalAccessor;
this.value = Optional.of(dateTimeParsed);
this.valueString = dateTimeParsed.format(DATE_TIME_FORMATTER);
} else {
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<String> date) {
// Check for constraints
date.ifPresent(d -> checkArgument(isValidDateTime(d), MESSAGE_CONSTRAINTS));

//Parse value
Optional<TemporalAccessor> temporalAccessor = date.map(d -> DATE_TIME_FORMATTER
.parseBest(d, LocalDateTime::from, LocalDate::from));
Optional<LocalDateTime> dateTimeParsed = temporalAccessor
.filter(t -> t instanceof LocalDateTime)
.map(t -> ((LocalDateTime) t));
Optional<LocalDate> 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);
}

}

/**
* 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);
}

/**
* Checks is DateTime exists.
*
* @return true if value in DateTime exists.
*/
public boolean isEmpty() {
return this.value.isEmpty();
}

@Override
public int compareTo(DateTime o) {
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.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
public String toString() {
return valueString;
}

@Override
public int hashCode() {
return value.hashCode();
}

}
83 changes: 83 additions & 0 deletions src/test/java/seedu/address/model/util/DateTimeTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package seedu.address.model.util;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.Optional;

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(VALID_SAMPLE_A, dt.toString());

DateTime dtNoTime = new DateTime(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("30-11-2020 23:00", resolveA.toString());

DateTime resolveB = new DateTime(VALID_DATE_B);
assertEquals("29-02-2020", resolveB.toString());

DateTime resolveC = new DateTime(VALID_DATE_C);
assertEquals("28-02-2019", resolveC.toString());
}

@Test
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);
}
}

0 comments on commit 562b095

Please sign in to comment.