java 8 - ZonedDateTime not equal another ZonedDateTime - java

I created two ZonedDateTime objects and I think they are should be equal:
public static void main(String[] args) {
ZoneId zid = ZoneId.of("America/New_York");
ZoneOffset offset = ZoneOffset.from(LocalDateTime.now().atZone(zid));
ZonedDateTime zdt0 = ZonedDateTime.of(2014, 8, 24, 21, 10, 1, 777000002, offset);
ZonedDateTime zdt1 = ZonedDateTime.of(2014, 8, 24, 21, 10, 1, 777000002, zid);
boolean equals = Objects.equals(zdt0, zdt1);
System.out.println("equals: " + equals);
}
In debugger I see that class of member of ZonedDateTime zone in first case is java.time.ZoneOffset and in second java.time.ZoneRegion and this is makes ZonedDateTime objects not equal. This is confusing...
Any ideas?

You are checking for object equality which evaluates to false as these objects are not equivalent. One is bound to a ZoneId, the other to a ZoneOffset. If you want to check whether they represent the same time, you can use the not very intuitively named method isEqual.
E.g.:
ZoneId zid = ZoneId.of("America/New_York");
ZoneOffset offset = ZoneOffset.from(LocalDateTime.now().atZone(zid));
ZonedDateTime zdt0 = ZonedDateTime.of(2014, 8, 24, 21, 10, 1, 777000002, offset);
ZonedDateTime zdt1 = ZonedDateTime.of(2014, 8, 24, 21, 10, 1, 777000002, zid);
System.out.println("isEqual:" + zdt0.isEqual(zdt1));
System.out.println("equals: " + zdt0.equals(zdt1));
prints:
isEqual:true
equals: false
Btw, note that you don’t need to use Objects.equals(a,b) for two objects you already know to be non-null. You can invoke a.equals(b) directly.

This played hell on me for hours too when using Jackson to serialize / deserialize instances of ZonedDateTime and then compare them against each other for equality to verify that my code was working correctly. I don't fully understand the implications but all I've learned is to use isEqual instead of equals. But this throws a big wrench in testing plans as most assertion utilities will just call the standard .equals().
Here's what I finally came up with after struggling for quite some time:
#Test
public void zonedDateTimeCorrectlyRestoresItself() {
// construct a new instance of ZonedDateTime
ZonedDateTime now = ZonedDateTime.now(ZoneId.of("Z"));
// offset = {ZoneOffset#3820} "Z"
// zone = {ZoneOffset#3820} "Z"
String starting = now.toString();
// restore an instance of ZonedDateTime from String
ZonedDateTime restored = ZonedDateTime.parse(starting);
// offset = {ZoneOffset#3820} "Z"
// zone = {ZoneOffset#3820} "Z"
assertThat(now).isEqualTo(restored); // ALWAYS succeeds
System.out.println("test");
}
#Test
public void jacksonIncorrectlyRestoresZonedDateTime() throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.findAndRegisterModules();
// construct a new instance of ZonedDateTime
ZonedDateTime now = ZonedDateTime.now(ZoneId.of("Z"));
// offset = {ZoneOffset#3820} "Z"
// zone = {ZoneOffset#3820} "Z"
String converted = objectMapper.writeValueAsString(now);
// restore an instance of ZonedDateTime from String
ZonedDateTime restored = objectMapper.readValue(converted, ZonedDateTime.class);
// offset = {ZoneOffset#3820} "Z"
// zone = {ZoneOffset#3821} "UTC"
assertThat(now).isEqualTo(restored); // NEVER succeeds
System.out.println("break point me");
}

The equals() method on ZonedDateTime requires that all component parts of the object are equal. Since a ZoneOffset is not equal to a ZoneRegion (even though both are subclasses of ZoneId), the method returns false. Read about VALJOs to understand more as to why value types are compared in this way.
The isEqual method only compares the instant on the time-line, which may or may not be what you want. You can also use the timeLineOrder() method to compare two ZoneDateTime only using the time-line.

This caused us pain for a while too. The ZonedDateTime values actually represent identical times, however their zone "types" are different, which is why they are not equal.
The above answers are correct, however I thought I'd add a visual that might further help:
In the ZonedDateTime code, we find, which shows the zone comparison:

Related

How to create a ZoneId given custom ZoneRules [duplicate]

I'm attempting to model an iCalendar VTIMEZONE object using Java's ZoneId and ZoneOffsetTransitionRule.
My VTIMEZONE object looks like
BEGIN:VTIMEZONE
TZID:Central European Standard Time
BEGIN:STANDARD
DTSTART:16010101T030000
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=10
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:16010101T020000
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=1;BYDAY=MO
END:DAYLIGHT
END:VTIMEZONE
I need to create my own ZoneId to model this because, as far as I know, there isn't a ZoneId available with these offsets and in which DST starts on the first Monday of January (as opposed to some Sunday of March).
I have the following for creating a ZoneOffsetTransitionRule
ZoneOffsetTransitionRule of =
ZoneOffsetTransitionRule.of(Month.JANUARY, 1, DayOfWeek.MONDAY, LocalTime.of(2, 0),
false, ZoneOffsetTransitionRule.TimeDefinition.STANDARD, ZoneOffset.ofHours(1),
ZoneOffset.ofHours(1), ZoneOffset.ofHours(2));
But I'm not sure if it's correct or how to create a ZoneId from this.
Is that transition rule accurate to model the DAYLIGHT component of my VTIMEZONE?
How can I create a ZoneId from this so I can eventually create a ZonedDateTime?
The only way to get a ZoneId (at least if we’re not very hacky) is through the factory methods of ZoneId and its subclass ZoneOffset. It might seem at first that this leaves of with the built-in ZoneIds. However, there’s a backdoor for specifying additional ZoneIds that ZoneId.of can then produce. It’s called ZoneRulesProvider. We need to specify an new and unique ID and we need to specify the zone rules (hence the name ZoneRulesProvider).
So with your ZoneOffsetTransitionRule you are already on the way. We need two of them, though, yours for transitioning to DST (which would normally have happened in the spring) and one for going the other way in the fall.
The following listing isn’t production code, of course, but is just to demonstrate that it is doable to develop and register your own ZoneRulesProvider.
final String customZoneId = "Custom-CEST-1";
final ZoneOffset standardOffset = ZoneOffset.ofHours(1);
final ZoneOffset summerTimeOffset = ZoneOffset.ofHours(2);
// At least one transistion is required
ZoneOffsetTransition initialTransition = ZoneOffsetTransition.of(
LocalDateTime.of(1601, 1, 1, 3, 0), summerTimeOffset, standardOffset);
List<ZoneOffsetTransition> transitionList = List.of(initialTransition);
// Rules for going to and from summer time (DST)
ZoneOffsetTransitionRule springRule =
ZoneOffsetTransitionRule.of(Month.JANUARY, 1, DayOfWeek.MONDAY, LocalTime.of(2, 0),
false, ZoneOffsetTransitionRule.TimeDefinition.STANDARD, standardOffset,
standardOffset, summerTimeOffset);
ZoneOffsetTransitionRule fallRule =
ZoneOffsetTransitionRule.of(Month.OCTOBER, -1, DayOfWeek.SUNDAY, LocalTime.of(2, 0),
false, ZoneOffsetTransitionRule.TimeDefinition.STANDARD, standardOffset,
summerTimeOffset, standardOffset);
ZoneRules rules = ZoneRules.of(standardOffset, standardOffset,
transitionList, transitionList, List.of(springRule, fallRule));
// The heart of the magic: the ZoneRulesProvider
ZoneRulesProvider customProvider = new ZoneRulesProvider() {
#Override
protected Set<String> provideZoneIds() {
return Set.of(customZoneId);
}
#Override
protected NavigableMap<String, ZoneRules> provideVersions(String zoneId) {
return new TreeMap<>(Map.of(customZoneId, rules));
}
#Override
protected ZoneRules provideRules(String zoneId, boolean forCaching) {
return rules;
}
};
// Registering the ZoneRulesProvider is the key to ZoneId using it
ZoneRulesProvider.registerProvider(customProvider);
// Get an instance of our custom ZoneId
ZoneId customZone = ZoneId.of(customZoneId);
// Transition to standard time was Sunday, October 29, 2017,
// so try the day before and the day after
System.out.println(LocalDate.of(2017, Month.OCTOBER, 28).atStartOfDay(customZone));
System.out.println(LocalDate.of(2017, Month.OCTOBER, 30).atStartOfDay(customZone));
// The special thing about our custom ZoneID is that transition to DST
// happened on Monday, January 1. Try the day before and the day after.
System.out.println(LocalDate.of(2017, Month.DECEMBER, 31).atStartOfDay(customZone));
System.out.println(LocalDate.of(2018, Month.JANUARY, 2).atStartOfDay(customZone));
The code prints:
2017-10-28T00:00+02:00[Custom-CEST-1]
2017-10-30T00:00+01:00[Custom-CEST-1]
2017-12-31T00:00+01:00[Custom-CEST-1]
2018-01-02T00:00+02:00[Custom-CEST-1]
We see that we get the expected DST offset of +02:00 exactly before the transition to standard time and again after the transition to summer time.

Custom ZoneIds / Time Zones in Java

I'm attempting to model an iCalendar VTIMEZONE object using Java's ZoneId and ZoneOffsetTransitionRule.
My VTIMEZONE object looks like
BEGIN:VTIMEZONE
TZID:Central European Standard Time
BEGIN:STANDARD
DTSTART:16010101T030000
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=10
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:16010101T020000
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=1;BYDAY=MO
END:DAYLIGHT
END:VTIMEZONE
I need to create my own ZoneId to model this because, as far as I know, there isn't a ZoneId available with these offsets and in which DST starts on the first Monday of January (as opposed to some Sunday of March).
I have the following for creating a ZoneOffsetTransitionRule
ZoneOffsetTransitionRule of =
ZoneOffsetTransitionRule.of(Month.JANUARY, 1, DayOfWeek.MONDAY, LocalTime.of(2, 0),
false, ZoneOffsetTransitionRule.TimeDefinition.STANDARD, ZoneOffset.ofHours(1),
ZoneOffset.ofHours(1), ZoneOffset.ofHours(2));
But I'm not sure if it's correct or how to create a ZoneId from this.
Is that transition rule accurate to model the DAYLIGHT component of my VTIMEZONE?
How can I create a ZoneId from this so I can eventually create a ZonedDateTime?
The only way to get a ZoneId (at least if we’re not very hacky) is through the factory methods of ZoneId and its subclass ZoneOffset. It might seem at first that this leaves of with the built-in ZoneIds. However, there’s a backdoor for specifying additional ZoneIds that ZoneId.of can then produce. It’s called ZoneRulesProvider. We need to specify an new and unique ID and we need to specify the zone rules (hence the name ZoneRulesProvider).
So with your ZoneOffsetTransitionRule you are already on the way. We need two of them, though, yours for transitioning to DST (which would normally have happened in the spring) and one for going the other way in the fall.
The following listing isn’t production code, of course, but is just to demonstrate that it is doable to develop and register your own ZoneRulesProvider.
final String customZoneId = "Custom-CEST-1";
final ZoneOffset standardOffset = ZoneOffset.ofHours(1);
final ZoneOffset summerTimeOffset = ZoneOffset.ofHours(2);
// At least one transistion is required
ZoneOffsetTransition initialTransition = ZoneOffsetTransition.of(
LocalDateTime.of(1601, 1, 1, 3, 0), summerTimeOffset, standardOffset);
List<ZoneOffsetTransition> transitionList = List.of(initialTransition);
// Rules for going to and from summer time (DST)
ZoneOffsetTransitionRule springRule =
ZoneOffsetTransitionRule.of(Month.JANUARY, 1, DayOfWeek.MONDAY, LocalTime.of(2, 0),
false, ZoneOffsetTransitionRule.TimeDefinition.STANDARD, standardOffset,
standardOffset, summerTimeOffset);
ZoneOffsetTransitionRule fallRule =
ZoneOffsetTransitionRule.of(Month.OCTOBER, -1, DayOfWeek.SUNDAY, LocalTime.of(2, 0),
false, ZoneOffsetTransitionRule.TimeDefinition.STANDARD, standardOffset,
summerTimeOffset, standardOffset);
ZoneRules rules = ZoneRules.of(standardOffset, standardOffset,
transitionList, transitionList, List.of(springRule, fallRule));
// The heart of the magic: the ZoneRulesProvider
ZoneRulesProvider customProvider = new ZoneRulesProvider() {
#Override
protected Set<String> provideZoneIds() {
return Set.of(customZoneId);
}
#Override
protected NavigableMap<String, ZoneRules> provideVersions(String zoneId) {
return new TreeMap<>(Map.of(customZoneId, rules));
}
#Override
protected ZoneRules provideRules(String zoneId, boolean forCaching) {
return rules;
}
};
// Registering the ZoneRulesProvider is the key to ZoneId using it
ZoneRulesProvider.registerProvider(customProvider);
// Get an instance of our custom ZoneId
ZoneId customZone = ZoneId.of(customZoneId);
// Transition to standard time was Sunday, October 29, 2017,
// so try the day before and the day after
System.out.println(LocalDate.of(2017, Month.OCTOBER, 28).atStartOfDay(customZone));
System.out.println(LocalDate.of(2017, Month.OCTOBER, 30).atStartOfDay(customZone));
// The special thing about our custom ZoneID is that transition to DST
// happened on Monday, January 1. Try the day before and the day after.
System.out.println(LocalDate.of(2017, Month.DECEMBER, 31).atStartOfDay(customZone));
System.out.println(LocalDate.of(2018, Month.JANUARY, 2).atStartOfDay(customZone));
The code prints:
2017-10-28T00:00+02:00[Custom-CEST-1]
2017-10-30T00:00+01:00[Custom-CEST-1]
2017-12-31T00:00+01:00[Custom-CEST-1]
2018-01-02T00:00+02:00[Custom-CEST-1]
We see that we get the expected DST offset of +02:00 exactly before the transition to standard time and again after the transition to summer time.

How to normalise ZonedDateTime so that .equals() works?

I have code, similar to this:
import java.time._
object app {
def main (args :Array[String]) = {
println("app started")
// create two ZonedDateTime objects for 1st Jan 2018, 10am UTC
// using separate methods
val zdt1 = ZonedDateTime.of(2018, 1, 1, 10, 0, 0, 0, ZoneId.of("UTC"))
val zdt2 = ZonedDateTime.parse("2018-01-01T10:00:00Z")
println(s"COMPARING: $zdt1 and $zdt2")
println("== check: " + (zdt1 == zdt2))
println(".equals check: " + (zdt1.equals(zdt2)))
println(".isEqual check " + (zdt1.isEqual(zdt2)))
println("app finished")
}
}
Code available here: https://ideone.com/43zf8B
The issue:
these ARE both typed ZonedDateTime objects
they are equivalent according to the .isEqual() method..
they are not equivalent according to .equals() method
However my test suite uses deep matching using beEquals
operations against the classes these datetime instances are
in, therefore I need a way to normalise them so that
.equals() returns true.
how can I normalise them please?
If I create zdt1 with ZonedDateTime.of(2018, 1, 1, 10, 0, 0, 0, ZoneOffset.UTC), the two objects are equal under equals() (still not under == in Java).
Apparently it’s not enough for the zones to be equivalent when their names are different. By using ZoneOffset.UTC for constructing the first ZonedDateTime, both will have the same time zone and will thus be equal. With my change, at least on my Mac, zdt1.getZone() == zdt2.getZone() now evaluates to true.
As a more direct answer to your question, you may normalize your ZonedDateTime objects this way (Java syntax with semicolon, please translate yourself):
zdt1 = zdt1.withZoneSameInstant(zdt1.getZone().normalized());
Similarly for zdt2, of course. ZoneId.normalized() promises to return a ZoneOffset where possible, which it is in your case. So in your case it does make two objects that are equal under equals(). I’m not sure it will in all other cases.
A safer way would be to have the comparison explicitly take care of different but equal time zones:
zdt1.toInstant().equals(zdt2.toInstant())
&& zdt1.getZone().getRules().equals(zdt2.getZone().getRules())
This evaluates to true with your two date-times from the question.
BTW isEqual() compares the instants in time only, not the zones at all, which is why it didn’t care.
ZoneOffset.UTC
What about the predefined constant ZoneOffset.UTC?
val zdt1 = ZonedDateTime.of(2018, 1, 1, 10, 0, 0, 0, ZoneOffset.UTC)
val zdt2 = ZonedDateTime.parse("2018-01-01T10:00:00Z")
All three methods return true(==, equals and isEqual)

Convert LocalDateTime to LocalDateTime in UTC

Convert LocalDateTime to LocalDateTime in UTC.
LocalDateTime convertToUtc(LocalDateTime date) {
//do conversion
}
I searched over net. But did not get a solution
I personally prefer
LocalDateTime.now(ZoneOffset.UTC);
as it is the most readable option.
LocalDateTime does not contain Zone information. ZonedDatetime does.
If you want to convert LocalDateTime to UTC, you need to wrap by ZonedDateTime fist.
You can convert like the below.
LocalDateTime ldt = LocalDateTime.now();
System.out.println(ldt.toLocalTime());
ZonedDateTime ldtZoned = ldt.atZone(ZoneId.systemDefault());
ZonedDateTime utcZoned = ldtZoned.withZoneSameInstant(ZoneId.of("UTC"));
System.out.println(utcZoned.toLocalTime());
There is an even simpler way
LocalDateTime.now(Clock.systemUTC())
Question?
Looking at the answers and the question, it seems the question has been modified significantly. So to answer the current question:
Convert LocalDateTime to LocalDateTime in UTC.
Timezone?
LocalDateTime does not store any information about the time-zone, it just basically holds the values of year, month, day, hour, minute, second, and smaller units. So an important question is: What is the timezone of the original LocalDateTime? It might as well be UTC already, therefore no conversion has to be made.
System Default Timezone
Considering that you asked the question anyway, you probably meant that the original time is in your system-default timezone and you want to convert it to UTC. Because usually a LocalDateTime object is created by using LocalDateTime.now() which returns the current time in the system-default timezone. In this case, the conversion would be the following:
LocalDateTime convertToUtc(LocalDateTime time) {
return time.atZone(ZoneId.systemDefault()).withZoneSameInstant(ZoneOffset.UTC).toLocalDateTime();
}
An example of the conversion process:
2019-02-25 11:39 // [time] original LocalDateTime without a timezone
2019-02-25 11:39 GMT+1 // [atZone] converted to ZonedDateTime (system timezone is Madrid)
2019-02-25 10:39 GMT // [withZoneSameInstant] converted to UTC, still as ZonedDateTime
2019-02-25 10:39 // [toLocalDateTime] losing the timezone information
Explicit Timezone
In any other case, when you explicitly specify the timezone of the time to convert, the conversion would be the following:
LocalDateTime convertToUtc(LocalDateTime time, ZoneId zone) {
return time.atZone(zone).withZoneSameInstant(ZoneOffset.UTC).toLocalDateTime();
}
An example of the conversion process:
2019-02-25 11:39 // [time] original LocalDateTime without a timezone
2019-02-25 11:39 GMT+2 // [atZone] converted to ZonedDateTime (zone is Europe/Tallinn)
2019-02-25 09:39 GMT // [withZoneSameInstant] converted to UTC, still as ZonedDateTime
2019-02-25 09:39 // [toLocalDateTime] losing the timezone information
The atZone() Method
The result of the atZone() method depends on the time passed as its argument, because it considers all the rules of the timezone, including Daylight Saving Time (DST). In the examples, the time was 25th February, in Europe this means winter time (no DST).
If we were to use a different date, let's say 25th August from last year, the result would be different, considering DST:
2018-08-25 11:39 // [time] original LocalDateTime without a timezone
2018-08-25 11:39 GMT+3 // [atZone] converted to ZonedDateTime (zone is Europe/Tallinn)
2018-08-25 08:39 GMT // [withZoneSameInstant] converted to UTC, still as ZonedDateTime
2018-08-25 08:39 // [toLocalDateTime] losing the timezone information
The GMT time does not change. Therefore the offsets in the other timezones are adjusted. In this example, the summer time of Estonia is GMT+3, and winter time GMT+2.
Also, if you specify a time within the transition of changing clocks back one hour. E.g. October 28th, 2018 03:30 for Estonia, this can mean two different times:
2018-10-28 03:30 GMT+3 // summer time [UTC 2018-10-28 00:30]
2018-10-28 04:00 GMT+3 // clocks are turned back 1 hour [UTC 2018-10-28 01:00]
2018-10-28 03:00 GMT+2 // same as above [UTC 2018-10-28 01:00]
2018-10-28 03:30 GMT+2 // winter time [UTC 2018-10-28 01:30]
Without specifying the offset manually (GMT+2 or GMT+3), the time 03:30 for the timezone Europe/Tallinn can mean two different UTC times, and two different offsets.
Summary
As you can see, the end result depends on the timezone of the time passed as an argument. Because the timezone cannot be extracted from the LocalDateTime object, you have to know yourself which timezone it is coming from in order to convert it to UTC.
Use the below. It takes the local datetime and converts it to UTC using the timezone. You do not need to create it function.
ZonedDateTime nowUTC = ZonedDateTime.now(ZoneOffset.UTC);
System.out.println(nowUTC.toString());
If you need to obtain the LocalDateTime part of the ZonedDateTime then you can use the following.
nowUTC.toLocalDateTime();
Here is a static method i use in my application to insert UTC time in mysql since i cannot add a default value UTC_TIMESTAMP to a datetime column.
public static LocalDateTime getLocalDateTimeInUTC(){
ZonedDateTime nowUTC = ZonedDateTime.now(ZoneOffset.UTC);
return nowUTC.toLocalDateTime();
}
Here's a simple little utility class that you can use to convert local date times from zone to zone, including a utility method directly to convert a local date time from the current zone to UTC (with main method so you can run it and see the results of a simple test):
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
public final class DateTimeUtil {
private DateTimeUtil() {
super();
}
public static void main(final String... args) {
final LocalDateTime now = LocalDateTime.now();
final LocalDateTime utc = DateTimeUtil.toUtc(now);
System.out.println("Now: " + now);
System.out.println("UTC: " + utc);
}
public static LocalDateTime toZone(final LocalDateTime time, final ZoneId fromZone, final ZoneId toZone) {
final ZonedDateTime zonedtime = time.atZone(fromZone);
final ZonedDateTime converted = zonedtime.withZoneSameInstant(toZone);
return converted.toLocalDateTime();
}
public static LocalDateTime toZone(final LocalDateTime time, final ZoneId toZone) {
return DateTimeUtil.toZone(time, ZoneId.systemDefault(), toZone);
}
public static LocalDateTime toUtc(final LocalDateTime time, final ZoneId fromZone) {
return DateTimeUtil.toZone(time, fromZone, ZoneOffset.UTC);
}
public static LocalDateTime toUtc(final LocalDateTime time) {
return DateTimeUtil.toUtc(time, ZoneId.systemDefault());
}
}
Try this using this method.
convert your LocalDateTime to ZonedDateTime by using the of method and pass system default time zone or you can use ZoneId of your zone like ZoneId.of("Australia/Sydney");
LocalDateTime convertToUtc(LocalDateTime dateTime) {
ZonedDateTime dateTimeInMyZone = ZonedDateTime.
of(dateTime, ZoneId.systemDefault());
return dateTimeInMyZone
.withZoneSameInstant(ZoneOffset.UTC)
.toLocalDateTime();
}
To convert back to your zone local date time use:
LocalDateTime convertFromUtc(LocalDateTime utcDateTime){
return ZonedDateTime.
of(utcDateTime, ZoneId.of("UTC"))
.toOffsetDateTime()
.atZoneSameInstant(ZoneId.systemDefault())
.toLocalDateTime();
}
tldr: there is simply no way to do that; if you are trying to do that, you get LocalDateTime wrong.
The reason is that LocalDateTime does not record Time Zone after instances are created. You cannot convert a date time without time zone to another date time based on a specific time zone.
As a matter of fact, LocalDateTime.now() should never be called in production code unless your purpose is getting random results. When you construct a LocalDateTime instance like that, this instance contains date time ONLY based on current server's time zone, which means this piece of code will generate different result if it is running a server with a different time zone config.
LocalDateTime can simplify date calculating. If you want a real universally usable data time, use ZonedDateTime or OffsetDateTime: https://docs.oracle.com/javase/8/docs/api/java/time/OffsetDateTime.html.
you can implement a helper doing something like that :
public static LocalDateTime convertUTCFRtoUTCZ(LocalDateTime dateTime) {
ZoneId fr = ZoneId.of("Europe/Paris");
ZoneId utcZ = ZoneId.of("Z");
ZonedDateTime frZonedTime = ZonedDateTime.of(dateTime, fr);
ZonedDateTime utcZonedTime = frZonedTime.withZoneSameInstant(utcZ);
return utcZonedTime.toLocalDateTime();
}
public static String convertFromGmtToLocal(String gmtDtStr, String dtFormat, TimeZone lclTimeZone) throws Exception{
if (gmtDtStr == null || gmtDtStr.trim().equals("")) return null;
SimpleDateFormat format = new SimpleDateFormat(dtFormat);
format.setTimeZone(getGMTTimeZone());
Date dt = format.parse(gmtDtStr);
format.setTimeZone(lclTimeZone);
return
format.format(dt);
}

Comparing dates with JUnit testing

Hello I'm new to the site and have a issue with my application using JUnit testing. My issue is when I try to compare the Date method with itself it always fails. I printed the Date object in the test to see the problem and always end up with the package name and random letters. Here is the Date constructor:
public class Date
{
SimpleDateFormat dformat = new SimpleDateFormat("dd-MM-yyyy");
private int day;
private int month;
private int year;
public Date()
{
String today;
Calendar present = Calendar.getInstance();
day = present.get(Calendar.DAY_OF_MONTH);
month = present.get(Calendar.MONTH);
year = present.get(Calendar.YEAR);
present.setLenient(false);
present.set(year, month - 1, day, 0, 0);
today = dformat.format(present.getTime());
System.out.println(today);
}
Here is my test:
#Test
public void currentDay()
{
Date current = new Date();
System.out.println(current);
assertEquals("today:", current, new Date());
}
Yet the result always fails and I get something on the lines of:
comp.work.wk.Date#d3ade7
Any help would be appreciated.
Overriding default equals method is not necessary. You simply use Date.compareTo() to compare two date objects.
Although the answer by #Shrikanth solves it, this issue also arises with normal Date objects. Two possible solutions are given here:
Make use of DateUtils.truncate (or even DateUtils.truncatedEquals) to compare the dates. This is something you could use in your equals method, or for normal Date objects directly in you assertEquals/assertTrue.
assertEquals(DateUtils.truncate(date1,Calendar.SECOND),
DateUtils.truncate(date2,Calendar.SECOND));
Don't check whether the dates are the same, but whether they are close enough to eachother (for JUnit test sake):
assertTrue("Dates aren't close enough to each other!",
Math.abs(date2.getTime() - date1.getTime()) < 1000);
The default equals object compares memory locations. If both objects are pointing to same memory location then only it will print equals which is not the case in your program. since they are pointing to two different memory locations it is always giving false which is expected.
If you feel your assertEquals(date1,date2) method should return true since the contents are equal then you should override the equals method. And when ever you override equals you should override hashcode() method also to ensure that you can confidently use your class instance as a key in any hashing based collection like HashMap or HashSet.
Here is a link explaining how to override equals() and hashcode() method
http://javarevisited.blogspot.in/2011/02/how-to-write-equals-method-in-java.html
And don't name your class same as any API class as Jon Skeet suggested.
Hope this helps.
Update The Joda-Time project is now in maintenance mode, with the team recommending migration to the java.time classes.
This kind of work is much easier with the Joda-Time library instead of the notoriously troublesome Date/Calendar classes.
Example Code in Joda-Time 2.3
Set up some data…
DateTime now = new DateTime();
DateTime yesterday = now.minusDays( 1 );
DateTime nowAgain = new DateTime( now.toString() ); // New object, but same value inside.
Compare…
boolean isNowEqualToYesterday = now.equals( yesterday );
boolean isNowEqualToNowAgain = now.equals( nowAgain );
Dump to console…
System.out.println( "now: " + now );
System.out.println( "yesterday: " + yesterday );
System.out.println( "nowAgain: " + nowAgain );
System.out.println( "isNowEqualToYesterday: " + isNowEqualToYesterday );
System.out.println( "isNowEqualToNowAgain: " + isNowEqualToNowAgain );
When run…
now: 2014-02-06T01:31:43.157-08:00
yesterday: 2014-02-05T01:31:43.157-08:00
nowAgain: 2014-02-06T01:31:43.157-08:00
isNowEqualToYesterday: false
isNowEqualToNowAgain: true
Convert
You can convert in and out of Joda-Time if need be.
org.joda.time.DateTime dateTime = new DateTime( date ); // From Date to DateTime.
java.util.Date date = dateTime.toDate(); // From DateTime to Date.
You need to override both equals() and toString(). If you override equals, always override hashcode() so that your maps work properly.
I have this solution to test date order
// date1 has to be before date2
if( !date1.before(date2) ) Assert.fail("Date1 is not before date2");
// date1 has to be before or equals to date2
if( date1.after() ) Asssert.fail("Date1 is after date2")
java.time.LocalDate
For production code never create your own Date class (as an exercise it’s fine). Use LocalDate from java.time, the modern Java date and time API.
For example:
ZoneId zone = ZoneId.of("Africa/Niamey");
LocalDate current = LocalDate.now(zone);
System.out.println(current);
System.out.println("Is equal? " + current.isEqual(LocalDate.now(zone)));
Output when I ran this code just now:
2021-04-07
Is equal? true
If the code happens to run just over midnight so now() is called just before and just after the day changes, then you will get two different dates, and the second line will be Is equal? false.
Since it is never the same date in all time zones, specifying the desired time zone is crucial.
If this was for an exercise
If you are doing an exercise requiring you to write your own date class, then it’s a fine exercise. In this case the answers stating that in your date class you should override equals(), hashCode() and toString() are the correct ones.
In your own date class you should not base its functionality on Calendar nor SimpleDateformat. Calendar is poorly designed, SimpleDateFormat is notoriously troublesome, and both are long outdated. Instead use LocalDate and DateTimeFormatter from java.time.
Tutorial link
Oracle tutorial: Date Time explaining how to use java.time.

Categories