Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Relative functions for DateTimeType #4276

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoUnit;
import java.time.zone.ZoneRulesException;
import java.util.Locale;

Expand All @@ -37,6 +38,7 @@
* @author Wouter Born - increase parsing and formatting precision
* @author Laurent Garnier - added methods toLocaleZone and toZone
* @author Gaël L'hopital - added ability to use second and milliseconds unix time
* @author Gaël L'hopital - added isToday, isTomorrow, isYesterday, sameDay
*/
@NonNullByDefault
public class DateTimeType implements PrimitiveType, State, Command {
Expand Down Expand Up @@ -248,4 +250,85 @@ private ZonedDateTime parse(String value) throws DateTimeParseException {

return date;
}

public boolean isToday() {
return sameDay(ZonedDateTime.now());
}

public boolean isTomorrow() {
return sameDay(ZonedDateTime.now().plusDays(1));
}

public boolean isYesterday() {
return sameDay(ZonedDateTime.now().minusDays(1));
}

public boolean sameDay(DateTimeType other) {
return sameDay(other.zonedDateTime);
}

public boolean sameDay(ZonedDateTime other) {
return zonedDateTime.truncatedTo(ChronoUnit.DAYS)
.isEqual(other.withZoneSameInstant(zonedDateTime.getZone()).truncatedTo(ChronoUnit.DAYS));
Comment on lines +271 to +272
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return zonedDateTime.truncatedTo(ChronoUnit.DAYS)
.isEqual(other.withZoneSameInstant(zonedDateTime.getZone()).truncatedTo(ChronoUnit.DAYS));
return zonedDateTime.truncatedTo(ChronoUnit.DAYS).isEqual(other.truncatedTo(ChronoUnit.DAYS));

isEqual already compares the instant so it's not necessary to equalise the timezone prior to comparison.

jshell> var d1 = java.time.ZonedDateTime.parse("2024-01-01T00:00:00Z")
d1 ==> 2024-01-01T00:00Z

jshell> var d2 = java.time.ZonedDateTime.parse("2024-01-01T10:00:00+10:00")
d2 ==> 2024-01-01T10:00+10:00

jshell> d1.isEqual(d2)
$17 ==> true

jshell> d1.getZone()
$18 ==> Z

jshell> d2.getZone()
$19 ==> +10:00

Copy link
Contributor Author

@clinique clinique Jul 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the truncation to ChronoUnit.DAYS, the comparison is false if they are not moved in the same time zone.

}

public DateTimeType toToday() {
return shiftDaysFromToday(0);
}

public DateTimeType toTomorrow() {
return shiftDaysFromToday(1);
}

public DateTimeType toYesterday() {
return shiftDaysFromToday(-1);
}

private DateTimeType shiftDaysFromToday(int days) {
ZonedDateTime now = ZonedDateTime.now().plusDays(days);
return new DateTimeType(zonedDateTime.withYear(now.getYear()).withMonth(now.getMonthValue())
.withDayOfMonth(now.getDayOfMonth()));
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also has the same ambiguity issue related to differences in time zones between what's in zonedDateTime vs the system time zone.

jshell> var d1 = java.time.ZonedDateTime.parse("2024-01-01T00:00:00+11:00")
d1 ==> 2024-01-01T00:00+11:00

jshell> var now = java.time.ZonedDateTime.parse("2025-05-01T00:00:00-11:00")
now ==> 2025-05-01T00:00-11:00

jshell> d1.withYear(now.getYear()).withMonth(now.getMonthValue())
   ...>                 .withDayOfMonth(now.getDayOfMonth()).withZoneSameInstant(now.getZone())
$38 ==> 2025-04-30T02:00-11:00

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made it relative to zonedDateTime zone's.

public boolean isBefore(DateTimeType other) {
return zonedDateTime.isBefore(other.zonedDateTime);
}

public boolean isAfter(DateTimeType other) {
return zonedDateTime.isAfter(other.zonedDateTime);
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might also need the versions of isBefore and isAfter that accept a ZonedDateTime

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These already exists, reason why I thought it wasn't needed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically it's for convenience, so you can do DateTimeItem.state.isBefore(zdt) instead of DateTimeItem.state.getZonedDateTime().isBefore(zdt), and also for uniformity since you are adding the ability to do
DateTimeItem.state.isBefore(anotherDateTimeItem), but, I'll leave the decision whether to have it to the maintainers.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added

public boolean isBeforeDate(DateTimeType other) {
return isBeforeDate(other.zonedDateTime);
}

public boolean isBeforeDate(ZonedDateTime other) {
return zonedDateTime.truncatedTo(ChronoUnit.DAYS).isBefore(other.truncatedTo(ChronoUnit.DAYS));
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am against the introduction of isBeforeDate, isBeforeTime, etc. because their meaning is ambiguous depending on the time zones of the two objects involved.

Take two zdt objects containing the same date but within two different time zones. They would fail the given comparison.

jshell> var d1 = java.time.ZonedDateTime.parse("2024-01-01T00:00:00-11:00")
d1 ==> 2024-01-01T00:00-11:00

jshell> var d2 = java.time.ZonedDateTime.parse("2024-01-01T10:00:00+11:00")
d2 ==> 2024-01-01T10:00+11:00

jshell> d2.withZoneSameInstant(d1.getZone())
$22 ==> 2023-12-31T12:00-11:00

jshell> d1.truncatedTo(java.time.temporal.ChronoUnit.DAYS).isBefore(d2.truncatedTo(java.time.temporal.ChronoUnit.DAYS))
$23 ==> false

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So do you recommend I should align Timezones before comparison, rename the methods or drop them ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer to drop them, but that's just an opinion. Again, I defer to the maintainers to decide.

public boolean isBeforeTime(DateTimeType other) {
return isBeforeTime(other.zonedDateTime);
}

public boolean isBeforeTime(ZonedDateTime other) {
return zonedDateTime.withYear(other.getYear()).withMonth(other.getMonthValue())
.withDayOfMonth(other.getDayOfMonth()).isBefore(other);
}

public boolean isAfterTime(DateTimeType other) {
return isAfterTime(other.zonedDateTime);
}

public boolean isAfterTime(ZonedDateTime other) {
return zonedDateTime.withYear(other.getYear()).withMonth(other.getMonthValue())
.withDayOfMonth(other.getDayOfMonth()).isAfter(other);
}

public boolean isAfterDate(DateTimeType other) {
return isAfterDate(other.zonedDateTime);
}

public boolean isAfterDate(ZonedDateTime other) {
return zonedDateTime.truncatedTo(ChronoUnit.DAYS).isAfter(other.truncatedTo(ChronoUnit.DAYS));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
* @author Erdoan Hadzhiyusein - Added ZonedDateTime tests
* @author Laurent Garnier - Enhanced tests
* @author Gaël L'hopital - added ability to use second and milliseconds unix time
* @author Gaël L'hopital - added isToday, isTomorrow, isYesterday tests
*/
@NonNullByDefault
public class DateTimeTypeTest {
Expand Down Expand Up @@ -290,6 +291,46 @@ public void epochTest() {
assertThat(epochStandard, is(zdtStandard));
}

@Test
public void relativeTest() {
DateTimeType dt1 = new DateTimeType("2019-06-13T01:10:00+02");
DateTimeType dt2 = new DateTimeType("2019-06-12T23:00:00Z");
assertTrue(dt1.isAfter(dt2));
assertTrue(dt2.isBefore(dt1));

assertTrue(dt1.sameDay(dt2));
assertTrue(new DateTimeType().isToday());

DateTimeType now = new DateTimeType();
DateTimeType tomorrow = new DateTimeType(now.getZonedDateTime().plusDays(1));
DateTimeType yesterday = new DateTimeType(now.getZonedDateTime().minusDays(1));
assertTrue(tomorrow.isTomorrow());
assertTrue(yesterday.isYesterday());

DateTimeType dt1ToToday = dt1.toToday();
assertTrue(dt1ToToday.sameDay(now));
assertEquals(dt1ToToday.getZonedDateTime().getHour(), dt1.getZonedDateTime().getHour());
assertEquals(dt1ToToday.getZonedDateTime().getMinute(), dt1.getZonedDateTime().getMinute());
assertEquals(dt1ToToday.getZonedDateTime().getSecond(), dt1.getZonedDateTime().getSecond());
assertEquals(dt1ToToday.getZonedDateTime().getNano(), dt1.getZonedDateTime().getNano());

DateTimeType dt1ToTomorrow = dt1.toTomorrow();
assertTrue(dt1ToTomorrow.isAfterDate(now));

DateTimeType dt1ToYesterday = dt1.toYesterday();
assertTrue(dt1ToYesterday.isBeforeDate(now));

DateTimeType dt3 = new DateTimeType("2019-06-11T23:10:00Z");
DateTimeType dt4 = new DateTimeType("2019-06-12T23:00:00Z");
assertFalse(dt3.sameDay(dt4));

assertTrue(dt3.isBeforeDate(dt4));
assertTrue(dt3.isAfterTime(dt4));

assertTrue(dt4.isAfterDate(dt3));
assertTrue(dt4.isBeforeTime(dt3));
}

@ParameterizedTest
@MethodSource("parameters")
public void createDate(ParameterSet parameterSet) {
Expand Down