129
TimeUnit.DAYS.convert(
   Math.abs(
      new SimpleDateFormat("dd-MM-yyyy HH:mm:ss").parse("30-03-2020 00:00:00").getTime() - 
      new SimpleDateFormat("dd-MM-yyyy HH:mm:ss").parse("1-03-2020 00:00:00").getTime()
   ),
   TimeUnit.MILLISECONDS)

The result is 28, while it should be 29.

Could the time zone/location be the problem?

coder
  • 5,200
  • 3
  • 20
  • 45
Joe
  • 7,749
  • 19
  • 60
  • 110
  • 17
    Note: Please don't use `SimpleDateFormat` any longer, for it's obsolete. Use packages from `java.time` instead. In `SimpleDateFormat`'s case, use `DateTimeFormatter`. In case of Java 7, see Andy Turner's comment below. – MC Emperor Feb 05 '20 at 08:24
  • 28
    *Don't* do maths on times. Use a proper time library (`java.time` [although I note you are on Java 7], [ThreeTenBp](https://github.com/ThreeTen/threetenbp), Joda). – Andy Turner Feb 05 '20 at 08:24
  • 14
    I would love to have been in the meeting where someone goes "Okay, now that we've got timezones kinda figured out let's go 100% arse-mode and implement this thing called daylight savings which came to me in a dream after my acid trip last night." – MonkeyZeus Feb 05 '20 at 18:00
  • @Andreas I wonder how does TimeUnit (pretends to) know when is DST shift in any given Time Zone, since it's not a fixed date and can even change from one year to the another. – gmauch Feb 05 '20 at 19:43
  • 5
    @gmauch [`TimeUnit`](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/TimeUnit.html) doesn't pretend to know anything about DST. As the javadoc says: *A nanosecond is defined as one thousandth of a microsecond, a microsecond as one thousandth of a millisecond, a millisecond as one thousandth of a second, a minute as sixty seconds, an hour as sixty minutes, and **a day as twenty four hours**.* --- Since DST causes 2 days of the year to not be exactly 24 hours, `TimeUnit` gets it wrong when DST is involved. – Andreas Feb 05 '20 at 20:16
  • 1
    This problem occurs only on computers that are in time zones with daylight savings. It gives the correct number of days (29) in time zones that do not have daylight savings! – Gopinath Feb 10 '20 at 23:45
  • Every possible question about programming with time or dates that was ever or will ever be asked was already answered by Tom Scott: https://www.youtube.com/watch?v=-5wpm-gesOY – Fabian Röling Feb 16 '20 at 01:22
  • @MonkeyZeus I'm guessing that meeting was held in the summer, when they realized they'd rather enjoy that hour of daylight at the end of the day than waste it at the beginning of the day while they were at work. – Ryan Lundy Feb 25 '20 at 08:06

2 Answers2

211

The problem is that because of Daylight Saving Time shift (on Sunday, March 8, 2020), there are 28 days and 23 hours between those dates. TimeUnit.DAYS.convert(...) truncates the result to 28 days.

To see the problem (I'm in US Eastern time zone):

SimpleDateFormat fmt = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss");
long diff = fmt.parse("30-03-2020 00:00:00").getTime() -
            fmt.parse("1-03-2020 00:00:00").getTime();

System.out.println(diff);
System.out.println("Days: " + TimeUnit.DAYS.convert(Math.abs(diff), TimeUnit.MILLISECONDS));
System.out.println("Hours: " + TimeUnit.HOURS.convert(Math.abs(diff), TimeUnit.MILLISECONDS));
System.out.println("Days: " + TimeUnit.HOURS.convert(Math.abs(diff), TimeUnit.MILLISECONDS) / 24.0);

Output

2502000000
Days: 28
Hours: 695
Days: 28.958333333333332

To fix, use a time zone that doesn't have DST, e.g. UTC:

SimpleDateFormat fmt = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss");
fmt.setTimeZone(TimeZone.getTimeZone("UTC"));
long diff = fmt.parse("30-03-2020 00:00:00").getTime() -
            fmt.parse("1-03-2020 00:00:00").getTime();

Output

2505600000
Days: 29
Hours: 696
Days: 29.0
Roman Odaisky
  • 2,811
  • 22
  • 26
Andreas
  • 154,647
  • 11
  • 152
  • 247
  • 126
    "To fix" don't do maths with times. Use a proper date/time library. – Andy Turner Feb 05 '20 at 08:26
  • 63
    @AndyTurner To fix, using built-in Java 7 APIs. Since it *can* be fixed, showing how is a valid answer. Forcing someone to include a full library (Joda-Time, ThreeTen, etc.) just to do this one calculation would be overkill. Sure, using a library would be recommended, but is not *required*. – Andreas Feb 05 '20 at 08:36
  • 40
    I respectfully disagree. If you want to do a calculation, do it right; pay the cost to do it right. – Andy Turner Feb 05 '20 at 08:46
  • 16
    My € 0.02: The “right” way to do in Java 7 without any external library would be to use a `GregorianCalendar` object and add 1 day at a time until the end date is reached. I’d pay a high price to avoid that. And adding the backport of a library that is already part of Java 8, 9, 10, 11, 12, 13, … is no high price. On the contrary, next time you need to do anything with date or time, it will already be a gain. – Ole V.V. Feb 05 '20 at 13:27
  • 27
    Even in UTC the last day of June is occasionally one second too short, without any real warning or predictability. Always use a date-time library. – Affe Feb 05 '20 at 17:13
  • 5
    @Affe None of the Java time APIs handle second adjustments, so that is not an issue. They all define that there are exactly 86400 seconds per day. See e.g. [How does the (Oracle) Java JVM know a leap second is occurring?](https://stackoverflow.com/a/30989049/5221149). – Andreas Feb 05 '20 at 20:11
  • @Affe : Although negative [leap seconds](https://en.wikipedia.org/wiki/Leap_second) are potentially allowed, none have ever been applied. To date, leap seconds have been inserted in the last second of June and the last second of December, making the last days of those months one second "too" *long*. I recall that there is provision for an additional pair of potential insertions in the last day of each of March and September, but I don't recall where I read this. – Eric Towers Feb 06 '20 at 08:02
  • 1
    @EricTowers The code in the question and in this answer is working with milliseconds-since-epoch. It is **impossible** for a `millis` value to represent leap seconds, positive or negative, by the very *nature* of the value, so the discussion about leap seconds is *irrelevant* to this answer. – Andreas Feb 06 '20 at 08:13
  • @Andreas : Take it up with Affe; I didn't introduce the topic. I merely corrected a sign and month error in his comment. – Eric Towers Feb 06 '20 at 08:14
  • 1
    I'm sure the answer correctly solves the specific problem that was asked for this combination of question, language, and runtime. I certainly did not downvote it. I am aware JSR-310 "solved" the problem I brought up for supported versions of java by literally *creating a new java-specific definition of what "one second" even means*. I will cite the fact they felt the need to do that as compelling evidence to avoid the rabbit hole of mixing "human" date time with "machine" date time :) – Affe Feb 06 '20 at 16:59
  • Library bloat was a rookie concern even before there were tree shaking tools like ProGuard. Bloat is the price we pay for rapid development and ease of maintenance. – StackOverthrow Feb 06 '20 at 22:21
  • 1
    @AndyTurner: Actually, I would argue there's more here: whether this calculation is "right" or not depends on what semantics you are after. If the semantics is that it should be the _physical time interval_ between the two points which, mind you, are specified down _to the second_, then the correct answer really _is_ 28 days and 23 hours when it is taken as being in that time zone, rounded however you prefer. – The_Sympathizer Feb 07 '20 at 09:36
  • 1
    But if the desired semantics is "how many squares on the calendar are the dates [not date/times] away from each other", then yes, it should be 29, and this calculating method is wrong for that purpose. – The_Sympathizer Feb 07 '20 at 09:37
  • @MichaelHampton, um, I don't think using 12 o'clock here helps? The time interval between the two noons will still be 28 days and 23 hours, which still truncates to 28. You could use noon on the other end, and midnight on the other, of course, but then I think you might as well round the difference instead of truncating it. – ilkkachu Feb 07 '20 at 13:31
  • @Eric, I'm sure that Affe is aware that Java pretends there are always 86400 seconds in the day: that's why it sometimes shows that 30th June or 31st December as one second too short (specifically, when the day has 86401 seconds, but Java ignores 23:59:60). – Toby Speight Feb 07 '20 at 14:52
  • 1
    Where is Jon Skeet when you need him? ;-) – Peter - Reinstate Monica Feb 15 '20 at 16:46
41

The cause of this problem is already mentioned in Andreas's answer.

The question is what exactly you want to count. The fact that you state that the actual difference should be 29 instead of 28, and ask whether "location/zone time could be a problem", reveals what you actually want to count. Apparently, you want to get rid of any timezone difference.

I assume you only want to calculate the days, without time and timezone.

Java 8

Below, in the example of how the number of days between could be calculated correctly, I'm using a class that represents exactly that – a date without time and timezone – LocalDate.

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("d-MM-yyyy HH:mm:ss");
LocalDate start = LocalDate.parse("1-03-2020 00:00:00", formatter);
LocalDate end = LocalDate.parse("30-03-2020 00:00:00", formatter);

long daysBetween = ChronoUnit.DAYS.between(start, end);

Note that ChronoUnit, DateTimeFormatter and LocalDate require at least Java 8, which is not available to you, according to the tag. However, it perhaps is to future readers.

As mentioned by Ole V.V., there's also the ThreeTen Backport, which backports Java 8 Date and Time API functionality to Java 6 and 7.

droidev
  • 7,352
  • 11
  • 62
  • 94
MC Emperor
  • 22,334
  • 15
  • 80
  • 130
  • 2
    @OleV.V. I know there is ThreeTen, some users may have mentioned it a few times. (I was about to link to a SEDE query returning all posts and comments of user 5772882 containing the text `ThreeTen` ;-), but unfortunately it is offline at the time of writing.) I will update the post. – MC Emperor Feb 05 '20 at 14:17
  • 2
    @OleVV It's not a bad thing at all. I think most people are simply ignorant about `java.time`, because at school they still use the old classes. But the Java 8 Date and Time API is very well designed – it would be a loss *not* to use it. – MC Emperor Feb 05 '20 at 22:34