Related
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)
In Java I have the following test that passes fine
// 42 bits of time is good enough for the next 100 years.
// An IEEE double has 52 bits of mantissa, so our dates can be easily fit.
#Test
public void testMaxBits() throws ParseException {
// Maximum 42 bit integer
long millis = (1L << 42) - 1;
Date date = new Date(millis);
//DateTime maxDate = new DateTime(2109, 5, 15, 8, 35, 11, 103);
Date maxDate = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS").parse("2109-05-15T08:35:11.103");
Assert.assertEquals(maxDate, date);
}
Now, I want to do the same sort of thing in C#, so I have a test in LinqPAD that test the C# implementation for correctness
DateTime maxDate = new DateTime(2109, 5, 15, 8, 35, 11, 103);
long beginTicks = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks;
long l = (1L << 42) - 1;
DateTime date = new DateTime(beginTicks + l, DateTimeKind.Utc);
maxDate.Dump();
date.Dump();
The output don't match, the values outputted ToString() values are
maxDate = 15/05/2109 08:35:11
date = 06/01/1970 02:10:04
What am I missing here?
Edit. I have see a great answer below from #zmitrok, I have changed
DateTime date = new DateTime(beginTicks + l, DateTimeKind.Utc);
to
DateTime date = new DateTime(beginTicks +
l * TimeSpan.TicksPerMillisecond, DateTimeKind.Utc);
but now get
date = 15/05/2109 07:35:11
Where has the hour gone?
Your test is basically confusing ticks with milliseconds. If you only need to store a number of milliseconds since the unix epoch, then do so - but I'd recommend using something like this to perform the conversion:
public static readonly DateTime UnixEpoch
= new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
public DateTime FromMillisecondsSinceUnixEpoch(long milliseconds)
{
return UnixEpoch.AddMilliseconds(milliseconds);
}
(As a side-note, that method already exists in my Noda Time project... hint hint :)
Your test would then be:
[TestMethod]
public void TestMaxBits()
{
long maxMillis = (1L << 42) - 1;
DateTime maxDate = DateTimeHelper.FromMillisecondsSinceUnixEpoch(maxMillis);
Assert.Greater(maxDate, new DateTime(2100, 1, 1, 0, 0, 0));
}
Note that:
This code doesn't mention ticks at all, because you're not interested in ticks
This code doesn't assert that the maximum date is some very specific value, because that's not what you care about; you care that 42 bits of time will carry you until the end of the century. (The "next 100 years" comment is somewhat specious, as 2109 is less than 100 years away from now, so I'll assume it really means "until the end of the 21st century.")
That of course make your question of "Where has the hour gone?" irrelevant - but the answer to that is simply that SimpleDateFormat defaults to using the system time zone, so you're actually relying on the time zone of the system you're running the test on, which is a really bad idea. If you set the time zone of the SimpleDateFormat to UTC, you'll find that it's 07:35:11 in Java as well.
The constructor you are using takes ticks as the first argument, however you are passing a value that was added to milliseconds.
Ticks: A date and time expressed in the number of 100-nanosecond intervals that have elapsed since January 1, 0001 at 00:00:00.000 in the Gregorian calendar.
I think you need to multiply ticks by this constant: https://msdn.microsoft.com/en-us/library/system.timespan.tickspermillisecond%28v=vs.110%29.aspx
I want to compare two dates, however I'm running into trouble. 1 date is created from a java.util.date object and the other is manually crafted. The following code is an example:
Date ds = new Date();
DateTime d = new DateTime(ds);
DateTime e = new DateTime(2012,12,07, 0, 0);
System.out.println(d.isEqual(e));
However the test turns out false. I am guessing that it is because of the time. How can I check if these two dates are equal to each other (I mean the Year, month, date are identical)?
System.out.println(d.toDateMidnight().isEqual(e.toDateMidnight()));
or
System.out.println(d.withTimeAtStartOfDay().isEqual(e.withTimeAtStartOfDay()));
You should use toLocalDate():
date1.toLocalDate().isEqual(date2.toLocalDate())
This will get rid of the Time part of the DateTime.
There is another approach, but it does not account for the case where the two dates have a different timezone, so it's less reliable:
date1.withTimeAtStartOfDay().isEqual(date2.withTimeAtStartOfDay())
return DateTimeComparator.getDateOnlyInstance().compare(first, second);
Via How to compare two Dates without the time portion?
If you want to ignore time components (i.e. you want to compare only dates) you can use DateMidnight class instead of Date Time. So your example will look something like this:
Date ds = new Date();
DateMidnight d = new DateMidnight(ds);
DateMidnight e = new DateMidnight(2012, 12, 7);
System.out.println(d.isEqual(e));
But beware, it will print "true" only today :)
Also note that by default JDK Date and all Joda-Time instant classes (DateTime and DateMidnight included) are constructed using default timezone. So if you create one date to compare in code, but retrieve another one from the DB which probably stores dates in UTC you may encounter inconsistencies assuming you are not in UTC time zone.
As they're DateTime objects, their time parts are also taken into consideration when you're comparing them. Try setting the time parts of the first date to 0, like:
d = d.withTime(0, 0, 0, 0);
I stumbled into this question while looking for a comparison with today. Here's how you can compare a date to today :
date1.toLocalDate().isBeforeNow() // works also with isAfterNow
This is a static method which works for me.
public static boolean isSameDay(DateTime date1, DateTime date2){
return date1.withTimeAtStartOfDay().isEqual(date2.withTimeAtStartOfDay());
}
DateTimeComparator.getDateOnlyInstance().compare(obj1, obj2);
obj1 and obj2 can be a String, Long, Date(java.util)... For the details see
http://www.joda.org/joda-time/apidocs/index.html?org/joda/time/DateTimeComparator.html
Write your own method
public boolean checkEqual(DateTime first,DateTime second){
if(first.<getterforyear> == second.<getterforyear> && first.<getterformonth> == second.<getterformonth> && first.<getterforday> == second.<getterforday>){
return true;
}
return false;
}
Calculating the difference between two dates (java.util.Date) in terms of no. of days look like very simple and we can find different ways to do that. I used the following code to calculate the date difference:
public static long daysBetween(Calendar startDate, Calendar endDate) {
Calendar date = (Calendar) startDate.clone();
long daysBetween = 0;
while (date.before(endDate)) {
date.add(Calendar.DAY_OF_MONTH, 1);
daysBetween++;
}
return daysBetween;
}
In main(), I used the following two dates :
Calendar c1 = Calendar.getInstance();
c1.set(2011, 1, 1);
Calendar c2 = Calendar.getInstance();
c2.set(2011, 1, 31);
long difference = daysBetween(c1, c2); //
But the value of the variable difference is not consistent. It is sometimes 30 and sometimes 31. So, why that might have happened.
Is there any solution to use the method results a consistent output ?
You're setting the date part of the calendars, but not the time part.
Sometimes the clock will tick between the calls to getInstance() and sometimes it won't, hence the inconsistency.
Options:
Set the time as well as the date, e.g. to midnight
Use a better date/time library - Joda Time - which has a more suitable representation (LocalDate). An important moral here is that if you can find a type which represents the exact information you have, and nothing else, that's likely to be a good fit and cause fewer complications.
Using LocalDate, you wouldn't even have to do the loop as Joda Time has good support for computing the differences between two values anyway.
LocalDate date1 = new LocalDate(2011, 1, 1);
LocalDate date2 = new LocalDate(2011, 1, 31);
Days period = Days.daysBetween(days1, days2);
int days = period.getDays();
You are only setting the year, month and day. The hours, minutes, seconds and milli-seconds are the current time (and thus different every time you run it)
I suggest you use Joda Time's LocalDate instead as it appears to does exactly what you want.
Please your opinion on the following code.
I need to calculate the diff in days between 2 Date objects. It is assured that both Date objects are within the same TimeZone.
public class DateUtils {
public final static long DAY_TIME_IN_MILLIS = 24 * 60 * 60 * 1000;
/**
* Compare between 2 dates in day resolution.
*
* #return positive integer if date1 > date2, negative if date1 < date2. 0 if they are equal.
*/
public static int datesDiffInDays(final Date date1, final Date date2){
long date1DaysMS = date1.getTime() - (date1.getTime() % DAY_TIME_IN_MILLIS);
long date2DaysMS = date2.getTime() - (date2.getTime() % DAY_TIME_IN_MILLIS);
long timeInMillisDiff = (date1DaysMS - date2DaysMS);
int ret = (int) (timeInMillisDiff / DAY_TIME_IN_MILLIS);
return ret;
}
Can you point to a problem that I might have missed ?
EDIT: #mmyers asked if pass my unit test. Well - Yes. But I have no real experience with dates and I know that is a big subject. Posted below the unit test that I'm using.
public class TestMLDateUtils {
#Test
public final void testDatesDiffInDays() {
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
// 00:00:00.000 1.1.1970
Calendar cal1970 = Calendar.getInstance();
cal1970.setTimeInMillis(0);
Calendar tested = Calendar.getInstance();
tested.setTimeInMillis(0);
// Add 1 millisecond, date = 00:00:00.001 1.1.1970
tested.add(Calendar.MILLISECOND, 1);
assertTrue(DateUtils.datesDiffInDays(cal1970.getTime(), tested.getTime()) == 0);
// Add 1 second, date = 00:00:01.001 1.1.1970
tested.add(Calendar.SECOND, 1);
assertTrue(DateUtils.datesDiffInDays(cal1970.getTime(), tested.getTime()) == 0);
// Add 1 minute, date = 00:01:01.001 1.1.1970
tested.add(Calendar.MINUTE, 1);
assertTrue(DateUtils.datesDiffInDays(cal1970.getTime(), tested.getTime()) == 0);
// Add 1 hour, date = 01:01:01.001 1.1.1970
tested.add(Calendar.HOUR_OF_DAY, 1);
assertTrue(DateUtils.datesDiffInDays(cal1970.getTime(), tested.getTime()) == 0);
// date = 23:59:59.999 1.1.1970
tested.setTimeInMillis(0);
tested.add(Calendar.MILLISECOND, 999);
tested.add(Calendar.SECOND, 59);
tested.add(Calendar.MINUTE, 59);
tested.add(Calendar.HOUR_OF_DAY, 23);
//System.out.println("D: " + tested.getTime());
assertTrue(DateUtils.datesDiffInDays(cal1970.getTime(), tested.getTime()) == 0);
// date = 00:00:00.000 2.1.1970
tested.setTimeInMillis(0);
tested.add(Calendar.DAY_OF_MONTH, 1);
assertTrue(DateUtils.datesDiffInDays(cal1970.getTime(), tested.getTime()) == -1);
assertTrue(DateUtils.datesDiffInDays(tested.getTime(), cal1970.getTime()) == 1);
// date = 00:00:00.001 2.1.1970
tested.add(Calendar.MILLISECOND, 1);
assertTrue(DateUtils.datesDiffInDays(cal1970.getTime(), tested.getTime()) == -1);
assertTrue(DateUtils.datesDiffInDays(tested.getTime(), cal1970.getTime()) == 1);
// date = 00:00:01.001 2.1.1970
tested.add(Calendar.SECOND, 1);
assertTrue(DateUtils.datesDiffInDays(cal1970.getTime(), tested.getTime()) == -1);
assertTrue(DateUtils.datesDiffInDays(tested.getTime(), cal1970.getTime()) == 1);
// date = 00:01:01.001 2.1.1970
tested.add(Calendar.MINUTE, 1);
assertTrue(DateUtils.datesDiffInDays(cal1970.getTime(), tested.getTime()) == -1);
assertTrue(DateUtils.datesDiffInDays(tested.getTime(), cal1970.getTime()) == 1);
// date = 01:01:01.001 2.1.1970
tested.add(Calendar.HOUR_OF_DAY, 1);
assertTrue(DateUtils.datesDiffInDays(cal1970.getTime(), tested.getTime()) == -1);
assertTrue(DateUtils.datesDiffInDays(tested.getTime(), cal1970.getTime()) == 1);
// date = 13:01:01.001 2.1.1970
tested.add(Calendar.HOUR_OF_DAY, 12);
assertTrue(DateUtils.datesDiffInDays(cal1970.getTime(), tested.getTime()) == -1);
assertTrue(DateUtils.datesDiffInDays(tested.getTime(), cal1970.getTime()) == 1);
}
}
Immediate problem: days can have less than or more than 24 hours due to daylight saving time changes.
Secondary problem: normally when people think in days, they really mean "human days" rather than "periods of 24 hours". In other words, many people would say that 7pm-7am the next day is a difference of a day, whereas 7am-7pm the same day is a difference of zero days. Both are 12 hours. At that point, you really need to know the calendar that is being considered.
Of course, this may not matter for your situation, but we don't really know what that is.
Third problem: you're using the built-in calendar API instead of Joda Time. That's almost never a good idea - it's horrible and riddled with gotchas and problems. And yes, the regulars here will tell you that's always part of my answer when it comes to Java dates and times - and for good reason. It's really that important.
EDIT: Your test sets the default time zone to be UTC. That's not really a good idea (especially without resetting it in a finally statement). Time zones are tricky, but you should really think about what values you've got, what they mean, and what time zones are involved.
The time zone, if any, within the Date object is irrelevant, since you're using getTime(); that "[r]eturns the number of milliseconds since January 1, 1970, 00:00:00 GMT represented by this Date object."
However, you aren't accounting for leap seconds, which some implementations may return. Thus, if the day range you give has one or more leap seconds in it, and your times are near enough to the same time of day, your calculation may be wrong by a day. That said, there appears to be no way to see if any particular implementation accounts for leap seconds or not (I expect that most don't), and the difference is pretty darn trivial anyway.
There are many dimensions to a code review; rather than correctness, addressed by others, let me focus a little on style. This will of course be somewhat more subjective than a review concentrating on correctness.
I would inline the "ret" variable. It increases the size of the method without enhancing readability.
I would consider separating the conversion between milliseconds and days into a separate function. Your full class probably performs that division in multiple places. Even if not, it's helpful in that it's easier to name functions that do only one thing.
Speaking of naming, I would rename the function, perhaps to "dayDifference" - abbreviations cause many problems, not least of which is the difficulty of remember which abbreviation was used in which circumstance. If you use none, ever, that particular source of confusion is eliminated. Similarly, I would rename the constant to MILLISECONDS_PER_DAY.
Another problem which hasn't been mentioned yet is leap seconds. Days may have more or less than 24 * 60 * 60 seconds due to adjustments in UTC time to keep it more or less in synch with the mean solar year. Probably not a big deal for your usage, but you should at least be aware of the possibility.
A good API is what you need if you have non-trivial requirements for dealing with dates and times. The link to Joda Time in Jon Skeet's answer appears to be broken, so here is a link that does work.
Date already has this method, look up Date.compareTo(Date) in Javadoc.