java.time.Duration implementation vs ISO8601 standard - java

IIUC java.time should end multiple date libraries and related issues, and offer something good and standardized. So it was kinda shock to me to learn, that java.time.Duration violates iso8601 standard. While it should be possible to parse: P2Y1DT1S, as that is valid by standard, java.time.Duration will parse only up to days (so you have to workaround via P731DT1S and java.time.Period can't do that at all, since it does not allow temporal part.
Questions:
Why? Is there a valid reason not to have interval parsing according to specification? What am I overlooking?
Or is there some readily available class, which will parse ISO8601?

Well, parsing such a string means it yields an object which supports both concepts to be present at the same time – a sort of PeriodDuration. But there is none in Java, and its rationale is explained here by JodaStephen:
Note that a Duration contains an amount of seconds, not separate amounts of seconds, minutes and hours. The amount of seconds can exceed 24 hours, thus a Duration can represent a "day". But it is a fixed 24 hours day. By contrast, the representation of a "day in Period is descriptive and takes into account DST. The state of a Period is formed from three separate fields - days, months and years.
Bear in mind that "The customer occupied the hotel room for 2 days and seventeen and a half hours, P2DT17H30M" has the possibility to be complicated by DST cutovers. Using Period and Duration separately things are clear - Period is affected by DST cutovers and Duration is not.
(Emphasis mine.)
So in short, the two concepts handle DST differently, and combining the two complicates things.
Regarding your second question, this answer suggests the org.threeten.extra.PeriodDuration class offered by the ThreeTen-Extra library.

tl;dr
Use the org.threeten.extra.PeriodDuration class.
PeriodDuration
.between(
LocalDate
.of( 2023 , Month.JANUARY , 23 )
.atTime( LocalTime.NOON )
.atZone( ZoneId.of( "America/New_York" ) ) ,
LocalDate
.of( 2023 , Month.JANUARY , 23 )
.atTime( LocalTime.NOON )
.atZone( ZoneId.of( "America/New_York" ) )
.plusDays( 2 )
.plusHours( 17 )
.plusMinutes( 30 )
)
.normalizedStandardDays()
.toString()
P2DT17H30M
Details
The Answer by MC Emperor is correct and wise. Always think twice before making use of a time span combining years-months-days with hours-minutes-seconds.
PeriodDuration
But if you insist on that combination, there is a class for that. You will find it in the ThreeTen-Extra library. This library is led by the same man, Stephen Colebourne, who leads the java.time (JSR 310) framework and its predecessor, Joda-Time.
The org.threeten.extra.PeriodDuration class represents an amount of time in the ISO 8601 calendar system that combines a period and a duration.
Here is some example code.
ZoneId z = ZoneId.of( "America/New_York" );
LocalDate ld = LocalDate.of( 2023 , Month.JANUARY , 23 );
LocalTime lt = LocalTime.NOON;
ZonedDateTime start = ZonedDateTime.of( ld , lt , z );
ZonedDateTime end = start.plusDays( 2 ).plusHours( 17 ).plusMinutes( 30 );
PeriodDuration pd = PeriodDuration.between( start , end );
Dump to console.
System.out.println( start + "/" + end + " = " + pd );
When run:
2023-01-23T12:00-05:00[America/New_York]/2023-01-26T05:30-05:00[America/New_York] = P3DT-6H-30M
Notice in that result we have negative amounts for hours and minutes. This is because the two-phase logic:
First, examine dates, LocalDate objects. Use Period.between to count days where the beginning is inclusive while the ending is exclusive. From the 23rd to the 26th is 3 days.
Second, examine time-of-day values, LocalTime objects. Use Duration.between to calculate elapsed time. We are calculating a duration between noon and 5:30 AM, so we must go back in time six and a half hours.
Therefore our result is 3 days minus 6.5 hours.
You can see this logic in the open source code.
If you want to smooth out this result to flip the negative hours-minutes, add call to PeriodDuration#normalizedStandardDays().
PeriodDuration pd = PeriodDuration.between( start , end ).normalizedStandardDays();
2023-01-23T12:00-05:00[America/New_York]/2023-01-26T05:30-05:00[America/New_York] = P2DT17H30M
P2DT17H30M = P3DT-6H-30M
We now get 2 days and 17.5 hours, P2DT17H30M. This is the same amount of time as 3 days minus 6.5 hours, P3DT-6H-30M.

Related

Java Time & Rest API

I’ve red multiple articles and discussions and still I have some uncertainty: I’m not sure if I should use Instant or any other type to store Booking – in the sense of “Online booking” (so participants that are from different countries/time zones needs meet in the same moment on the timeline). I tend to use LocalDateTime, since DB and Backend is set to UTC and since incoming “create booking” json message contains ISO 8601 (with offset) startDateTime & endDateTime fields.
Let’s take this setup: 1. Database (UTC, Oracle, MSSQL), Java 11 Backend (UTC, SpringBoot, Spring JDBC template), Angular 13 Frontend.
Sources:
https://apiux.com/2013/03/20/5-laws-api-dates-and-times
https://medium.com/decisionbrain/dates-time-in-modern-java-4ed9d5848a3e
What's the difference between Instant and LocalDateTime?
java.time.LocalDate vs Instant for a 'business date'
https://mattgreencroft.blogspot.com/2014/12/java-8-time-choosing-right-object.html
What is clear for now:
Use ISO 8601 in REST API
Accept any time zone
Store it in UTC
Return it in UTC
Don’t use time if you don’t need it
Use "date-time values as count-from-epoch" as less as possible, since debugging and logging becomes very difficult. Use it just for technical stuff (e.g. ping).
Next:
And e.g. article 2) is telling: Many applications can be written only using LocalDate, LocalTime, and Instant, with the time zone added at the UI layer.
And the 3) is telling: So business app developers use Instant and ZonedDateTime classes most commonly. Nearly all of your backend, database, business logic, data persistence, data exchange should all be in UTC. But for presentation to users you need to adjust into a time zone expected by the user. This is the purpose of the ZonedDateTime class.
Let’s summarize & verify examples:
LocalTime
Company has a policy that lunchtime starts at 12:30 PM at each of its factories worldwide.
LocalDate
LocalDate + time zone (in separate db column)
Birthday - if it were crucial to know someone’s age to the very day, then a date-only value is not enough. With only a date, a person appearing to turn 18 years old in Tokyo Japan would still be 17 in Toledo Ohio US.
Due date in a contract. If stated as only a date, a stakeholder in Japan will see a task as overdue while another stakeholder in Toledo sees the task as on-time.
LocalDateTime
Christmas starts at midnight on the 25th of December 2015.
Booking – in the sense of “local” dentist appointment
Instant
Some critical function must start every day at 1:30am (LocalDateTime & ZonedDateTime are wrong in this case, because e.g. during switching from/to standard time to summer time, function runs twice/not at all).
store Booking – in the sense of “Online booking” (so participants that are from different countries/time zones needs meet in the same moment on the timeline). I tend to use LocalDateTime, since DB and Backend is set to UTC and since incoming “create booking” json message contains ISO 8601 (with offset) startDateTime & endDateTime fields.
You did not really define what you mean by "booking".
If what you meant is to pinpoint a moment, a specific point on the timeline, then LocalDateTime is exactly the wrong class to use.
For example, suppose a missile launching company has offices in Japan, Germany, and Houston Texas US. Staff at all offices want to be in a group conference call during a launch. We would specify the moment of launch with an offset of zero from UTC. For that we use Instant class. The Z in the string below means an offset of zero, +00:00, and is pronounced “Zulu”.
Instant launch = Instant.parse( "2022-05-23T05:31:23.000Z" ) ;
Let's tell each office their deadline to join the call.
ZonedDateTime tokyo = launch.atZone( ZoneId.of( "Asia/Tokyo" ) ) ;
ZonedDateTime berlin = launch.atZone( ZoneId.of( "Europe/Berlin" ) ) ;
ZonedDateTime chicago = launch.atZone( ZoneId.of( "America/Chicago" ) ) ; // Houston time zone is "America/Chicago".
We have four date-time objects, all representing the very same simultaneous moment.
As for the current default time zone of your database session and of your backend server, those should be irrelevant to your programming. As a programmer, those are out of your control, and are subject to change. So always specify your desired/expected time zone.
As for the rest of your Question, I cannot discern a specific question. So all I can do is comment on your examples.
LocalTime
Company has a policy that lunchtime starts at 12:30 PM at each of its factories worldwide.
Yes, LocalTime is correct for representing a time-of-day for any place in the world.
Let's ask if lunch has started at the factory in Delhi India.
LocalTime lunchStart = LocalTime.of( 12 , 30 ) ;
ZoneId zKolkata = ZoneId.of( "Asia/Kolkata" ) ;
ZonedDateTime nowKolkata = ZonedDateTime.now( zKolkata ) ;
boolean isBeforeLunchKolkata = nowKolkata.toLocalTime().isBefore( lunchStart ) ;
Actually, we have a subtle bug there. On some dates in some zones, the time 12:30 may not exist. For example, suppose that time zone on that date has a Daylight Saving Time (DST) cutover, "Springing ahead" one hour at noon to 1 PM. In that case, there would be no 12:30. The time 13:30 would be the "new 12:30".
To fix this bug, let's adjust from nowKolkata to use a different time of day. During this adjustment, java.time considers any anomalies such as DST, and handles them.
Note that this moving to a new time-of-day results in a new fresh separate ZonedDateTime. The java.time classes use immutable objects.
ZonedDateTime lunchTodayKolkata = nowKolkata.with( lunchStart ) ;
Now we should rewrite that test for whether lunch has started.
boolean isBeforeLunchKolkata = nowKolkata.isBefore( lunchTodayKolkata ) ;
Let's ask how long until lunch starts. Duration class represents a span of time not attached to the timeline, on the scale of hours-minutes-seconds.
Duration duration = Duration.between( nowKolkata , lunchTodayKolkata ) ;
We can use that duration as another way of determining if lunch has started there at that factory. If the duration is negative, we know we passed the start of lunch — meaning we would have to go back in time to see lunch start.
boolean isAfterLunchKolkata = duration.isNegative() ;
boolean isBeforeLunchKolkata = ! duration.isNegative() ;
LocalDate
The LocalDate class represents a date only, without a time-of-day, and without the context of a time zone or offset-from-UTC.
Be aware that for any given moment the date varies around the globe by zone. So right now it can be “tomorrow” in Tokyo Japan while simultaneously “yesterday” in Toledo Ohio US. So a LocalDate is inherently ambiguous with regard to the timeline.
Birthday - if it were crucial to know someone’s age to the very day, then a date-only value is not enough. With only a date, a person appearing to turn 18 years old in Tokyo Japan would still be 17 in Toledo Ohio US.
If you want to be precise about someone's age, you need their time of birth in addition to the date of birth and time zone of birth. Just the date and time zone of their birth place is not enough to narrow down their age to the first moment of their 18th year.
The time zone for Toledo Ohio US is America/New_York.
LocalDate birthDate = LocalDate.of( 2000 , Month.JANUARY , 23 ) ;
LocalTime birthTime = LocalTime.of( 3 , 30 ) ;
ZoneId birthZone = ZoneId.of( "America/New_York" ) ;
ZonedDateTime birthMoment = ZonedDateTime.of( birthDate , birthTime , birthZone ) ;
To determine if the person is 18 years of age precisely, add 18 years to that moment of birth. Then compare to current moment.
ZonedDateTime turning18 = birthMoment.plusYears( 18 ) ;
ZonedDateTime nowInBirthZone = ZonedDateTime.now( birthZone ) ;
boolean is18 = turning18.isBefore( nowInBirthZone ) ;
Due date in a contract. If stated as only a date, a stakeholder in Japan will see a task as overdue while another stakeholder in Toledo sees the task as on-time.
True.
A contract needing moment precision must state a date, a time-of-day, and a time zone.
Let's say a contract expires after the 23rd of May next year as seen in Chicago IL US.
I suggest avoiding the imprecise notion of "midnight". Focus on the first moment of a day on a certain date in a certain zone. So "after the 23rd" means the first moment of the 24th.
Some dates in some zones may start a time other than 00:00. Let java.time determine the first moment of a day.
LocalDate ld = LocalDate.of( 2023 , Month.MAY , 23 ) ;
ZoneId zChicago = ZoneId.of( "America/Chicago" ) ;
ZonedDateTime expiration = ld.plusDays( 1 ).atStartOfDay( zChicago ) ;
boolean isContractInEffect = ZonedDateTime.now( zChicago ).isBefore( expiration ) ;
LocalDateTime
Christmas starts at midnight on the 25th of December 2015.
Use LocalDateTime when you mean a date and time as seen in any locality.
Christmas starts at the first moment of the 25th of December in 2015 in every locality. So yes, use LocalDateTime.
LocalDateTime xmas2015 = LocalDateTime.of( 2015 , Month.DECEMBER , 25 , 0 , 0 , 0 , 0 ) ;
That class purposely lacks the context of a time zone or offset-from-UTC. So an object of this class is inherently ambiguous with regard to the timeline. This class cannot represent a moment.
In other words, Santa arrives in Kiribati (the most advanced time zone, at 14 hours ahead of UTC) before arriving in Japan, and arrives still later in India, still later in Europe, and so on. The red sleigh chases each new day dawning successively in zone after zone around the world all night long.
Let's determine the moment Christmas started then in Jordan.
ZoneId zAmman = ZoneId.of( "Asia/Amman" ) ;
ZonedDateTime xmasAmman = xmas2015.atZone( zAmman) ;
Adjust that moment to the time zone for Denver Colorado US.
ZoneId zDenver = ZoneId.of( "America/Denver" ) ;
ZonedDateTime xmasStartingInAmmanAsSeenInDenver = xmasAmman.withZoneSameInstant( zDenver ) ;
If you interrogate that xmasStartingInAmmanAsSeenInDenver object, you will see that when Christmas was starting in Jordan, the date in Denver was still the 24th, the day before Christmas. Christmas would not reach Colorado for several more hours.
Booking – in the sense of “local” dentist appointment
Appointments in the future that are intended to stick with their assigned time-of-day without regard to any changes to the time zone's rules must be tracked as three separate pieces:
Date with time-of-day
Time zone by which to interpret that date & time.
Duration, for how long the appointment will last.
Let's book an appointment for next January 23rd at 3 PM in New Zealand.
LocalDateTime ldt = LocalDateTime.of( 2023 , 1 , 23 , 15 , 0 , 0 , 0 ) ;
ZoneId z = ZoneId.of( "Pacific/Auckland" ) ;
Duration d = Duration.ofHours( 1 ) ;
Each of those three items must be stored in your database.
The first can be stored in a column of a type akin to the SQL standard type TIMESTAMP WITHOUT TIME ZONE. Notice the "WITHOUT", not "WITH".
The time zone can be stored as text, by its standardized name in format of Continent/Region. Never use 2-4 letter pseudo-zones such as IST, CST, etc.
I would store the duration in standard ISO 8601 format, PnYnMnDTnHnMnS. The P marks the beginning, which the T separates the two portions. So 1 hour is PT1H.
When you need to create a schedule, you must determine a moment. To do that, combine your parts.
LocalDateTime ldt = myResultSet.getObject( … , LocalDateTime.class ) ;
ZoneId z = ZoneId.of( myResultSet.getString( … ) ) ;
Duration d = Duration.parse( myResultSet.getString( … ) ) ;
ZonedDateTime start = ldt.atZone( z ) ;
ZonedDateTime end = start.plus( d ) ;
I and others have explained this multiple times, so search to learn more. The key point is that politicians around the world have shown a penchant for frequently changing the rules of the time zones in their jurisdictions. And they do so with surprisingly little forewarning. So the moment of that 3PM appointment might come an hour earlier that you expect now, or a half-hour later, or who knows what. Expecting your time zones of interest to remain stable is to doom your software to an eventual fail. You may think me paranoid. Let's check back in some years… when all the customers in your booking app are arriving at the wrong time.
Instant
Some critical function must start every day at 1:30am (LocalDateTime & ZonedDateTime are wrong in this case, because e.g. during switching from/to standard time to summer time, function runs twice/not at all).
No, incorrect, not Instant. If you want to run a task at 1:30 AM daily, you need to use LocalTime combined with a ZoneId. This is similar in concept to the appointments discussion directly above.
If we want the server in a Chicago office to run that task in the wee hours, we would define the date-time and zone.
ZoneId z = ZoneId.of( "America/Chicago" ) ;
LocalTime targetTime = LocalTime.of( 1 , 30 ) ;
To determine the next run, capture the current moment.
ZonedDateTime now = ZonedDateTime.now( z ) ;
See if we are before the target time, then calculate time to wait. We use "not after" as a shorter way of saying "if before or equal to". Think about if the current moment happens to be exactly 1:30:00.000000000 AM.
if( ! now.toLocalTime().isAfter( targetTime ) ) { // If before or equal to the target time.
Duration d = Duration.between( now , now.with( targetTime ) ) ;
…
}
If the target time has passed for today, add a day. Then calculate time to elapse until the next run.
else { // Else, after target time. Move to next day.
LocalDate tomorrow = now.toLocalDate().plusDays( 1 ) ;
ZonedDateTime nextRun = ZonedDateTime.of( tomorrow , targetTime , z ) ;
Duration d = Duration.between( now , nextRun ) ;
…
}
The ZonedDateTime.of method automatically adjusts if your specified time-of-day does not exist on that date in that zone.
If you are using an executor service to run your task at that same local time, then you cannot use ScheduledExecutorService with the methods scheduleAtFixedRate or scheduleWithFixedDelay. Use the schedule method with a single delay.
Pass a reference to that executor service to the constructor of your task, your Runnable/Callable. Then write your task in such as way that the last thing it does in submit itself to the executor service with a fresh newly-calculated delay. On most days that delay will be 24 hours. But on anomalous days such as Daylight Saving Time (DST) cutover, the delay might be something like 23 hours or 25 hours. So each task run schedules the next task run.
Neither. For appointments at a given location (be it for a video call meeting or simply a barber's appointment), you want ZonedDateTime.
Story time!
Let's say you're planning to be in Amsterdam somewhere in summer 2024, and for some strange reason you wanna make sure you look great, so you go a little nuts and decide to make an appointment at a barber's, for 14:00, May 2nd, 2024 (that'll be a wednesday), at some specific barbershop in Amsterdam.
We could calculate precisely how many seconds need to pass before your barbershop appointment is up: There will be some moment in time, and you can tell anybody on the planet to wait those exact number of seconds and then clap their hands. When they clap their hands? That exact time you have your appointment. It will be very early in the morning in California if someone were to clap in California, of course, but you could do that. That's the point - you're talking about a specific moment in time, which means it'll be a different time of day depending on where on the planet.
But, and this is not even hypothetical: The EU has decided a while ago that the EU should stop with the daylight savings time. The EU is busy with a few other things at the moment so enforcement of this directive is still being hashed out, but it's been decided - so now the debate is about when the EU stops with summer time and which time zone(s) the what used to be known as 'central european time', used by the vast majority of EU countries (it's a very large timezone), needs to become.
Notably, the EU has not decided on that, yet. In fact, each country is as of yet free to pick whatever they please. It is therefore quite plausible that e.g. The Netherlands, which is on the western edge of this very large zone, and where your barbershop appointment it, will decide to stick with what is currently 'winter time', because germany is likely to do that and citizens of both countries have grown rather used to it being the same time in both countries. But, sticking with summer time is also quite plausible. We simply don't know yet.
One day, in parliament, a hammer will come down. When that hammer comes down, that is the very moment in time when the dutch timezone definition has officially changed, and let's say they choose to stick with wintertime, at that exact moment that hammer comes down, your barber appointment just moved by 1 hour. It's still at 14:00, May 2nd, 2024, in Amsterdam - but the number of seconds until your appointment shifts by 3600 seconds the very moment that hammer lands in the dutch parliament in The Hague. Had you stored this moment in time in UTC, it would now be wrong. Had you stored this moment in time in terms of seconds left to go, or seconds from UTC epoch midnight newyears 1970, it would now be wrong. So don't store appointments in those ways.
That explains the pragmatic difference between Instant+timezone, and ZonedDateTime objects.
A ZonedDateTime object represents the notion of 'I have an appointment, in Europe/Amsterdam, at 14:00, on May 2nd, in 2024.
Given that that is exactly what it represents, if your tzdata files are updated to reflect that hammer coming down, that's okay. It's still 14:00, May 2nd, in 2024. In terms of 'how many seconds until the appointment', the moment in time represented by that object would shift by 1 hour the moment your JVM is updated with the new tz info.
Instant is different. Instant represents an exact moment in time in terms of 'how many seconds until that moment'. The act of storing that appointment as an Instant would involve calculating the amount of seconds until it happens and then storing that. And, as we just determined, that is incorrect and will mean you're going to miss your appointment. It's not how an appointment works (which works in terms of a year, month, day, hour, minute, and second, and a possibly implied timezone - no agenda any human uses is based on the idea of 'what does your day look like 134823958150154 seconds from now?') - and you should always use the data type (and store data) in terms of how it works, and not some conversion of it, because if you store conversions, you 'encode' your reliance on it into your storage and stuff goes wrong if your conversion no longer works. Which is why storing it as an Instant is the mistake here.
The story also explains why the only non-stupid way to store timezone info is as something like Europe/Amsterdam - because something like "CET" (Central European Time) may not even exist 2 years hence. There is absolutely no guarantee every single last one of the many many countries that are in CET today will pick the same zone when the EU directive grows teeth, whenever that may be. So if you store the appointment as 'in the CET zone', then all you know, come 2024, is that you now have no idea when your barbershop's appointment actually is. Not all that useful.
Coordinating a time to meet, without the context of place
Every so often you want to coordinate a time to meet, but not a place to meet - a virtual meeting, for example. Usually these are still 'localized' (somebody is deemed the host, they plan the meeting for, say, 17:30 their time, all participants store that ZonedDateTime construct, and their agenda systems will tell them when to log in for that meeting at the appropriate time, regardless of where on the planet they are). So I'd just do that.
But given that the meeting is virtual, 'a timezone is implied' no longer has to neccessarily be true. In which case it is fine to store things as an Instant (and a ZonedDateTime where the zone is UTC is so close to the concept of Instant, there is basically no difference at all), in which case e.g. someone in The Netherlands would see their appointment move by an hour the moment that hammer comes down. Assuming they did it right and indicated to their calendaring tool that the appointment is in the UTC zone.
It's possible you want this instead. But it's more common to go with the 'host zone' route. (there isn't that much of a difference; the calendaring tool needs to support the concept of saving an appointment with the timezone (and not convert it to the right time in the local zone, as that will break if either the host zone or the target zone decides to make a change to their tz data, which happens all the time - once a calendaring tool supports this, you could choose to use UTC as host zone for such a virtual meeting).
DBs
You've more or less correctly concluded that there are 3 fundamentally different, and therefore utterly incompatible notions of date and time; you cannot convert one to the other without losing something on the way. These are:
LocalDate (and LocalTime, and LocalDateTime): If you want to wake up 08:00 and you want that alarm to continue to wake you up at 08:00 locally even if you fly to different zones, this is the thing you want.
ZonedDateTime: For appointments.
Instant: To log that something happens or will happen X seconds from now or X seconds in the past; a slam dunk for e.g. timestamps on log messages, and obviously the one to use for space stuff (that solar flare will occur on X - when that hammer comes down in dutch parliament, and a newspaper calculated the solar flare out to an exact date, hour, and minute and printed that earlier, that is now wrong, of course, and they'd have to print a correction. Had they printed an instant, unwieldy as that might be to us humans, they would not have had to do that).
Unfortunately, most languages (including java's obsolete java.util.Date and java.util.Calendar approaches) mess this up and didn't realize time is just that complicated, and that the above 3 concepts cannot be converted without losing an important nuance.
As a consequence, DBs sometimes play fast and loose too. More to the point, JDBC which is usually the 'glue' that java apps use to talk to DBs, has an API that involves the types java.sql.Date and java.sql.Timestamp, and both of those are based on j.u.Date (they extend it), and j.u.Date, name notwithstanding, is equivalent to Instant - it stores a moment-in-time, does not store a timezone in the first place, and will break if timezones up and change definition on you.
So, the actual JDBC calls you have to use to get the right data type in and out is tricky. The theoretical correct answer is LDT and ZDT directly:
ResultSet rs = somePreparedStatement.query();
/* WRONG */ {
Date x = rs.getDate(1);
ZonedDateTime y = convertToZDT(x);
}
/* RIGHT */ {
ZonedDateTime y = rs.getObject(1, ZonedDateTime.class);
}
However, many JDBC drivers don't support this. Very painful. Similarly, to replace question marks in prepared statements with dates, the correct move is ps.setObject(1, someZdtInstance), not with a j.u.Date instance or even an java.sql.Date instance. Whether they support that, oof. You'd have to check - many do not.
I suggest you check the DB docs about the type(s) for dates they support and how it actually stores them. If it sounds good (that is, it stores an actual year, month, day, hour, minute, second, and full timezone name, all in a single column), use that. If none of the data types support that, do it yourself and make 7 columns.
Assuming you found a data type that does the job, figure out if .getObject(x, ZDT.class) works, and ps.setObject(x, zdtInstance). Let's hope so. If it does, that's your answer. If it does not, kludge around to figure out what the workaround is: Write code that converts and check what arrives - after all, if the DB is storing actual y/m/d/h/m/s/zone info, and you give it a j.u.Timestamp object which does not have that info, then the DB is going to have to be converting that back to ymdhmsz info again. Assuming that works out fine, then this workaround is not going to break on you. Even if the appointment is in Amsterdam and that hammer comes down between now and then.

How to represent just 'time' with JodaTime

I'm not sure how can I explain just 'time', so I wanna start with an example.
When I write a movie player, I want to represent the start time(00:00:00) in millis as 0, and if current frame position is on 5 minutes(00:05:00), it would be 300,000(5 * 60 * 1000).
I don't care about timezone, calendar or clock information in real world.
However, JodaTime, for example, Instant is related to Unixtime(based on 1970), Interval, Duration and Period are based on Instant, and LocalTime is based on 24-hour(which enforce hour field to 1-24).
Of course, I can parse "00:05:00" into 300,000 manually, but I wonder how can I achieve this goal with JodaTime or other library.
Duration
Your statement about the Duration class is incorrect. You said it is based on Instant, but, no, it is not.
A Duration represents a span of time not attached to the timeline. A Duration in Joda-Time is merely a count of milliseconds. This fact is stated in its Javadoc.
Ditto for Period, which represents a span-of-time unattached to the timeline on a scale of years-months-days. A Duration works on a scale of hours-minutes-seconds.
To represent a span of five minutes:
Duration d = Duration.standardMinutes( 5 ) ;
If you want to get the moment in the future by that amount of time, add that duration to the current moment.
Instant fiveMinutesInFuture = new Instant().plus( d ) ;
ISO 8601
To represent a duration as text, use the standard ISO 8601 format, with a P at the beginning and a T separating the years-months-days portion from the hours-minutes-seconds portion.
So five minutes is PT5M.
String output = d.toString() ;
Parse such ISO 8601 strings.
Duration d = Duration.parse( "PT5M" ) ; // Five minutes.
Clock format
For serializing a duration object as text, always use the ISO 8601 standard format, never clock format.
For user-interface presentation, I suggest you not represent a span of time using clock notation such as 00:05:00. Doing so is confusing and ambiguous to users. Use the ISO 8601 standard formats instead, as they are designed to be practical and unambiguous.
If you insist on representing the duration in clock time format, call the getStandard… methods to get the hours, minutes, and seconds.
String output =
String.format( "%02d", d.getStandardHours() ) +
":" +
String.format( "%02d", d.getStandardMinutes() ) +
":" +
String.format( "%02d", d.getStandardSeconds() )
;
00:05:00
If the duration might be over 24 hours long, you will also need to call getStandardDays. These days are merely chunks of time of 24-hours long. Such days are not calendar days as the Duration is not attached to the timeline.
java.time
Be aware that Joda-Time is now in maintenance mode. It’s successor, java.time, is built into Java 8 and later as well as Android 26+. For Java 6 & 7 use ThreeTen-Backport. For earlier Android, use ThreeTenABP.
The concepts are the same in java.time as discussed here. The java.time.Duration class is quite similar except for the much finer resolution of nanoseconds and a slightly different syntax.
Instant.now().plus(
Duration.ofMinutes( 5 )
)
Or:
Instant.now().plus(
Duration.parse( "PT5M" )
)
To build a clock-reading, call the to…Part methods.
String output =
String.format( "%02d", d.toHoursPart() ) +
":" +
String.format( "%02d", d.toMinutesPart() ) +
":" +
String.format( "%02d", d.toSecondsPart() )
;
00:05:00

Java ZonedDateTime.toInstant() behavior

I'm running the below expressions on December 7th, 2018.
I'm seeing a discrepancy whereby this:
ZonedDateTime.now(ZoneId.of("America/New_York")).minusDays(30)
returns (correctly):
2018-11-07T22:44:11.242576-05:00[America/New_York]
whereas conversion to an instant:
ZonedDateTime.now(ZoneId.of("America/New_York")).minusDays(30).toInstant()
seems to mess up the result by adding an extra day to it:
2018-11-08T03:58:01.724728Z
I need an instant conversion to use its result in the following code as Date:
... = Date.from(t.toInstant())
An equivalent Python code (Django) works correctly:
datetime.datetime.now('America/New_York')+datetime.timedelta(days=-30)
evaluating to: datetime: 2018-11-07 20:13:55.063888-05:00
What's causing the discrepancy?
What should I use so that Java conversion to Date resulted in the November 7th being returned, just like in Python's case? Basically, I'm looking to an equivalent translation of that Python code into Java, or in pseudocode:
`datetime.X = datetime.now(deployment_zone) - (N_days)`,
where `deployment_zone` is configurable (i.e. `America/New_York`)
`N_days` is configurable (i.e. 30)
Update for #Basil Bourque:
When I formulated the original question, I (per SO rules) tried to simplify it to a digestible form which probably destroyed most of the necessary context making it vague. Let me try again.
As I explained in the comments, I'm converting the existing Python code (which is more actively maintained and which client wants to keep intact) to existing Java code (legacy that has not been properly maintained and strayed away from the Python's logic some time back). Both code bases need to be functionally on par with each other. Java needs to do what Python is already doing.
Python code is as follows (I'm lumping all into one place for succinctness, in reality it's distributed across a couple of files):
analytics.time_zone=America/New_York
TIME_ZONE = props.getProperty('analytics.time_zone', 'UTC')
TZ = pytz.timezone(TIME_ZONE)
def days_back(num_days=0):
adjusted_datetime = datetime.datetime.now(TZ)+datetime.timedelta(days=-num_days)
return DateRangeUtil.get_start_of_day(adjusted_datetime)
class DateRangeUtil():
#staticmethod
def get_start_of_day(date):
return date.astimezone(TZ).replace(hour=0, minute=0, second=0, microsecond=0)
which basically takes the configured time zone, in which it obtains the current instant, subtracts a specified number of days from it, converts it to the beginning of that date and thus receives the lower bound of the range to use while querying the DB, something like Start time: datetime: 2018-11-07 20:13:55.063888-05:00
When I started on the Java side, it had:
public final static DateRange parse(String dateRange) {
//.....
int days = ...
return new DateRange(toBeginningOfDay(daysBack(days)), toEndOfDay(daysBack(0)));
private final static Date daysBack(int days) {
return toDate(LocalDateTime.now().minusDays(days));
}
private final static Date toBeginningOfDay(Date d)
{
Calendar c=Calendar.getInstance();
c.setTime(d);
c.set(HOUR_OF_DAY,0);
c.set(MINUTE,0);
c.set(SECOND,0);
c.set(MILLISECOND, 0);
return c.getTime();
}
private final static Date toDate(LocalDateTime t) {
return Date.from(t.atZone(ZoneId.systemDefault()).toInstant());
}
That code didn't work and introduced the discrepancy which I describe in my original question. I started experimenting and introduced ZonedDateTime into the picture. While investigating, I found that it's the call to .toInstant() that seems to be a culprit and wanted to understand what's behind it in more depth.
In his answer, #ernest_k suggested a solution which seemed to have worked, but I still didn't quite understood which is clear from questions in the comments to his response.
The changes I made based on #ernest_k response are as follows:
private final static Date daysBack(int days) {
return toDate(ZonedDateTime.now(ZoneId.of("America/New_York")).minusDays(days).toLocalDateTime());
private final static Date toDate(LocalDateTime t) {
return Date.from(t.toInstant(ZoneOffset.UTC));
}
This seems to produce the desired outcome: However conversion from local to zoned and then back again seemed too much, so I experimented a bit more and found that simply the LocalDateTime does the trick as well:
private final static Date toDate(LocalDateTime t) {
return Date.from(t.toInstant(ZoneOffset.UTC));
}
private final static Date daysBack(int days) {
return toDate(LocalDateTime.now().minusDays(days));
}
I can see that LocalDate (and perhaps LocalDateTime) has a convenient atStartOfDay() which seems to be a fitting candidate for elimination of Dates out of the picture while replacing the legacy toBeginningOfDay(Date d) method above. Not sure it's doing the same thing - I haven't yet experimented with that idea, so the suggestions are most welcome.
So, with all of the tribulations above, my question started around toInstant() behavior, and when it's passed a zone id, whether it converts TO an instant in that zone, or FROM it, or what?
I guess for the situation I'm describing we only care that the lower time bound in the DB query is formed by comparing some consistent marker of current time (its upper bound) to what it was in the same place (time zone?) in the past N days, so comparing it with UTC should server the purpose.
Does that then make passing the zone in unnecessary?
Now, that a solution seems to have been found, the question revolves around the soundness of the approach described above and the solution that's been stumbled upon - is it the most optimal one, best practices around Java timing libs, etc. The code needs to work for any time zone in which the code bases will end up being deployed, that's why the zone is passed in via configuration.
Also, I wonder if things change when/if the DB itself is deployed off-premise from the rest of the codebase and is configured to persist data in some other time zone. But that might be another question.
tl;dr
ZonedDateTime.toInstant() adjusts a moment from a time zone to UTC. You end up with the same moment, different wall-clock time, and possibly a different date for the same simultaneous point on the timeline. What you are seeing is not a problem, not a discrepancy.
Your problem is not with subtracting 30 days. The real problems:
Not understanding that time zone affects the date
Conflating dates with days
Furthermore, your Question is vague. Saying “30 days ago” can mean at least three different things:
30 * 24 hours
A range from 22:44 thirty calendar days ago in New York time zone to 22:44 now in New York time
The entire day today as seen in New York and the entire days going back 30 days on the calendar as seen in New York.
All three possibilities are covered below, with example code, labeled with ➥.
⑦🕥 🇺🇸📞 ↔ 📞🇮🇸 ⑧🕞
On the 7th of December, shortly before midnight (22:44), Alice in her New York apartment decides to call her friend Bob in Reykjavík, Iceland. Bob can't believe his phone is ringing, and looking over at the clock on his bedside table sees the time is almost 4 AM (03:44). And Bob's fancy digital clock shows the date as the 8th of December, not the 7th. Same simultaneous moment, same point on the timeline, different wall-clock time, different date.
The people of Iceland use UTC as their time zone, year-round. New York is five hours behind UTC in December 2018, and so five hours behind Iceland. In New York it is “yesterday” the 7th while in Iceland it is “tomorrow” the 8th. Different dates, same moment.
So forget about subtracting the thirty days. Any time you take a moment in New York that is close to midnight, and then adjust to UTC, you will be moving the date forward.
No discrepancy, no extra day added. For any given moment, the date varies around the globe by time zone. With a range in time zones of about 26-27 hours, it is always “tomorrow” and “yesterday” somewhere.
Another Answer suggests involving LocalDateTime into this problem. That is ill-advised. That class purposely lacks any concept of time zone or offset-from-UTC. That means a LocalDateTime cannot represent a moment. A LocalDateTime represents potential moments along the range of 26-27 hours mentioned above. Makes no sense to involve that class here.
Instead, use OffsetDateTime for a moment viewed with an offset-from-UTC, versus [ZonedDateTime][2] which uses a time zone.
What is the difference between an offset and zone? An offset is merely a number of hours-minutes-seconds, nothing more, nothing less. A zone, in contrast, is much more. A zone is a history of the past, present, and future changes to the offset used by the people of particular region. So a time zone is always preferable to a mere offset, as it brings more information. If you want UTC specifically, you need only an offset, an offset of zero hours-minutes-seconds.
OffsetDateTime odt = zdt.toOffsetDateTime().withOffsetSameInstant( ZoneOffset.UTC ) ; // Adjust from a time zone to UTC.
The zdt and odt seen here both represent the same moment, the same point on the timeline, different wall-clock time, like Alice and Bob example above.
Days != Dates
If you want to query for a range of thirty days ago, you must define what you mean by “days”.
Days
➥ Do you mean 30 chunks of 24-hour long spans of time? If so, work with Instant. This class represents a moment in UTC, always in UTC.
ZoneId z = ZoneId.of( "America/New_York" ) ;
ZonedDateTime zdtNow = ZonedDateTime.now( z ) ;
Instant instantNow = zdt.toInstant() ; // Adjust from time zone to UTC. Same moment, different wall-clock time.
Instant instantThirtyDaysAgo = instantNow.minus( 30 , ChronoUnit.DAYS ) ; // Subtract ( 30 * 24 hours ) without regard for dates.
You may be able to exchange an Instant with your database via your JDBC driver. But Instant is optional, while support for OffsetDateTime is required by JDBC 4.2 and later. If that is the case, let's re-write that code.
ZoneId z = ZoneId.of( "America/New_York" ) ;
ZonedDateTime zdtNow = ZonedDateTime.now( z ) ;
OffsetDateTime odtNow = zdt.toOffsetDateTime().withOffsetSameInstant( ZoneOffset.UTC ) ; // Adjust from time zone to UTC. Same moment, different wall-clock time.
OffsetDateTime odtThirtyDaysAgo = odtNow.minusDays( 30 ) ;
Your SQL might be something like the following.
Note what we are using the Half-Open approach to defining a span-of-time, where the beginning is inclusive while the ending is exclusive. This is generally best practice, as it avoid the problem of finding the infinitely divisible last moment, and it provides for neatly abutting spans without gaps. So we do not use the SQL command BETWEEN, being fully-closed (inclusive on both ends).
SELECT * FROM event_ WHERE when_ >= ? AND when_ < ? ;
Set values for the placeholders in your prepared statement.
myPreparedStatement.setObject( 1 , odtThirtyDaysAgo ) ;
myPreparedStatement.setObject( 2 , odtNow ) ;
Dates
➥ If by “30 days ago” you meant 30 boxes on the calendar hanging on the wall in a New York office, that is a very different problem.
Same time-of-day
And if so, do you mean from the current moment and moving back 30 days to the same time-of-day?
ZoneId z = ZoneId.of( "America/New_York" ) ;
ZonedDateTime zdtNow = ZonedDateTime.now( z ) ;
ZonedDateTime zdtThirtyDaysAgo = zdtNow.minusDays( 30 ) ; // `ZonedDateTime` will try to keep the same time-of-day but will adjust if that time on that date in that zone is not valid.
With the code seen above, the ZonedDateTime class will try to use the same time-of-day on the earlier date. But that time may not be valid on that date in that zone, because of anomalies such as Daylight Saving Time (DST) cutover. In such an anomaly, the ZonedDateTime class adjusts to a valid time. Be sure to study the JavaDoc to understand the algorithm and to see if it suits your business rules.
Pass to your prepared statement.
myPreparedStatement.setObject( 1 , zdtThirtyDaysAgo ) ;
myPreparedStatement.setObject( 2 , zdtNow ) ;
Entire day
➥ Or by “30 days ago” do you mean dates, and by dates you mean all-day-long?
If so, we need to focus on the date-only value, by using LocalDate class, without a time-of-day and without a time zone.
ZoneId z = ZoneId.of( "America/New_York" ) ;
LocalDate today = LocalDate.now( z ) ;
LocalDate tomorrow = today.plusDays( 1 ) ;
LocalDate thirtyDaysAgo = tomorrow.minusDays( 30 ) ;
Now we need to go from the date to a specific moment by assigning a time-of-day and a time zone. We want the time to be the first moment of the day. Do not assume that means 00:00. Because of anomalies such as DST, the day may start at another time such as 01:00. Let java.time determine the first moment of the day on that date in that zone.
ZonedDateTime zdtStart = thirtyDaysAgo.atStartOfDay( z ) ;
ZonedDateTime zdtStop = tomorrow.atStartOfDay( z ) ;
Pass to your prepared statement.
myPreparedStatement.setObject( 1 , zdtStart ) ;
myPreparedStatement.setObject( 2 , zdtStop ) ;
That "extra day" is not really an extra day. 2018-11-07T22:44:11 in New York is equivalent to 2018-11-08T03:58:01 in UTC (it's the same point in time). The difference is just 5 hours, not a day (and when I google this, I see New York is GMT-5).
ZonedDateTime#toInstant returns an Instant instance representing the same point in time (in UTC):
Converts this date-time to an Instant.
This returns an Instant representing the same point on the time-line as this date-time. The calculation combines the local date-time and offset.
If you want to not use the offset when converting to instant, then you should perhaps use LocalDateTime:
ZonedDateTime.now(ZoneId.of("America/New_York"))
.toLocalDateTime()
.toInstant(ZoneOffset.UTC)
This tells it to convert as though it were already UTC time (but a warning is appropriate here: this changes the date/time value)
First, avoid the need for an old-fashioned Date if you can. java.time, the modern Java date and time API, gives you all the functionality you need.
Sometimes we do need a Date for a legacy API that we cannot change or don’t want to upgrade just now. Java is giving you what I think you want. Demonstration:
ZonedDateTime nov7 = ZonedDateTime.of(2018, 11, 7, 22, 44, 0, 0,
ZoneId.of("America/New_York"));
Instant inst = nov7.toInstant();
System.out.println("As Instant: " + inst);
Date oldFashionedDate = Date.from(inst);
System.out.println("As Date: " + oldFashionedDate);
Output from this was:
As Instant: 2018-11-08T03:44:00Z
As Date: Wed Nov 07 22:44:00 EST 2018
Admitted, to get this output I had to change my JVM’s default time zone to America/New_York first.
Date and Instant are roughly equivalent but print differently. Meaning their toString methods behave differently, which may be confusing. Each is a point in time, none of them is a date (despite the name of one of them). It is never the same date in all time zones.
Date.toString picks up your JVM’s time zone setting and uses it for generating the string it returns. Instant.toString on the other hand always uses UTC for this purpose. This is why the same point in time is printed with different date and time. Fortunately they both also print a bit of time zone information so the difference is at least visible. Date prints EST, which, albeit ambiguous, in this case means Eastern Standard Time. Instant prints Z for offset zero from UTC or “Zulu time”.

Number of days between two epoch days in scala

I have two epoch timestamps, I am trying to find the number of days between the two timestamps.
This is what I have now:
dateFrom = inputEntry.getValue(inputFields(0).get).asInstanceOf[String].toLong
dateTo =inputEntry.getValue(inputFields(1).get).asInstanceOf[String].toLong
Example:
dateFrom dateTo result
1501583232 1501641000 1
1501583232 1501986600 5
I am starting with two epoch dates here
tl;dr
ChronoUnit.DAYS.between( … , … )
Details
This has been covered many times already on Stack Overflow. So briefly here…
For date-time values, use date-time objects. Use java.time classes only, avoid the troublesome legacy date-time classes ( Date, Calendar, etc).
Do you mean a difference of dates or a difference of 24-hour chunks of time?
I'll go with dates here.
First, translate what appears to be number of whole seconds since an epoch reference date of 1970-01-01T00:00:00Z into a point in the timeline in UTC.
Note the L on the end of numeric literal to indicate a long rather than int.
Instant instant = Instant.ofEpochSecond( 1_501_583_232L ) ;
Assign time zone for which you want to consider dates.
ZoneId z = ZoneId.of( "America/Montreal" ) ;
ZonedDateTime zdt = instant.atZone( z );
Convert to a date-only.
LocalDate ld = zdt.toLocalDate() ;
Get difference.
long days = ChronoUnit.DAYS.between( ld , ld2 ) ;
To get the results you want, you must define how you're going to calculate the difference.
Taking your first example (difference between 1501583232 and 1501641000 should be 1 day):
The epochs 1501583232 and 1501641000 are the number of seconds since 1970-01-01T00:00Z, so they are equivalent to the following UTC dates:
1501583232: 2017-08-01T10:27:12Z
1501641000: 2017-08-02T02:30:00Z
Note that the difference between them is 16 hours, 2 minutes and 48 seconds (so, less than a day). If you get the difference in days, technically it will be zero.
But if you consider only the dates (2017-08-01 and 2017-08-02) and ignore the time (hour/minute/second), then the difference can be either zero or 1, depending on the timezone you are.
If you consider only the UTC dates (2017-08-01 and 2017-08-02), the difference is 1 day.
But if you take the same UTC dates above in America/Los_Angeles timezone, you'll get:
1501583232: 2017-08-01T03:27:12-07:00
1501641000: 2017-08-01T19:30-07:00
Now the difference is zero days, no matter if you consider only the date (both are 2017-08-01), or the date and time (the difference in hours will be 16, less than a day).
So, you must define how you're going to calculate the difference (consider only the date, or both date and time, and what timezone will be used).
In your case, it seems that you're considering only the date and ignoring the time, but it's not clear what timezone it's using. Anyway, you can use JDK's 8 new java.time API for that (for JDK <= 7 you can use the ThreeTen Backport - The code below works for both. The only difference is the package names (in Java 8 is java.time and in ThreeTen Backport (or Android's ThreeTenABP) is org.threeten.bp), but the classes and methods names are the same).
The code is basically the same of #BasilBourque's answer, because it's very straighforward with the new API (I just wanted to add the insights above).
First you create the Instant's from the epoch values:
Instant instant1 = Instant.ofEpochSecond(1501583232L);
Instant instant2 = Instant.ofEpochSecond(1501641000L);
If you want the difference considering the date and time, you can use:
ChronoUnit.DAYS.between(instant1, instant2);
The result will be zero.
If you want to consider only the dates in UTC (and ignore the time), just do:
// convert to UTC and get just the date (day/month/year)
LocalDate d1 = instant1.atZone(ZoneOffset.UTC).toLocalDate();
LocalDate d2 = instant2.atZone(ZoneOffset.UTC).toLocalDate();
long days = ChronoUnit.DAYS.between(d1, d2);
The result will be 1.
To convert to a different timezone (instead of UTC), use the ZoneId class:
// use a specific timezone
ZoneId zone = ZoneId.of("Asia/Kolkata");
// convert the Instant to a timezone and get only the date
LocalDate d1 = instant1.atZone(zone).toLocalDate();
LocalDate d2 = instant2.atZone(zone).toLocalDate();
long days = ChronoUnit.DAYS.between(d1, d2);
In this case, the difference is 1, but as I said above, different timezones can produce different results (can be either zero or 1 - for example, changing the code above to ZoneId.of("America/Los_Angeles"), the result is zero).
Note that the API uses IANA timezones names (always in the format Region/City, like Asia/Kolkata or Europe/Berlin).
Avoid using the 3-letter abbreviations (like CST or IST) because they are ambiguous and not standard.
You can get a list of available timezones (and choose the one that fits best your system) by calling ZoneId.getAvailableZoneIds().
You can also use the system's default timezone with ZoneId.systemDefault(), but this can be changed without notice, even at runtime, so it's better to explicity use a specific one.

Difference between two dates, in days, varies

Date d = new Date(today.getTimeInMillis());
Date d1 = new Date(dueDate.getTimeInMillis());
int daysUntil = (int) ((d1.getTime() - d.getTime())/ (1000 * 60 * 60 * 24));
Using the above code, where today is a calendar set to 00:00 on the current day, and dueDate is set to 00:00 on the date I am comparing today to, my results from this differ.
There is something in this which varies, making my output either x or x+1 where x is the correct answer.
What is the issue here, and what can I do to make it more stable?
Vague Question
You do not provide actual values, so we cannot determine precisely the problem. We do not know what the today and dueDate variables are.
Outmoded
The question is now outmoded, as the troublesome old date-time classes including java.util.Date/.Calendar have been supplanted by the new java.time framework. See Tutorial. Defined by JSR 310, inspired by Joda-Time, and extended by the ThreeTen-Extra project.
In java.time:
An Instant is a moment on the timeline in UTC.
A ZoneId represents a time zone. Use proper time zone names, never the 3-4 letter codes like "EST" or "IST" as they are neither standardized nor unique.
Conceptually, ZonedDateTime = Instant + ZoneId.
ThreeTen-Extra
Unfortunately, java.time does not include a facility for calculating days elapsed between date-time values. We can use the ThreeTen-Extra project and its Days class with between method to provide that calculation. The ThreeTen-Extra project is a collection of features deemed non-essential for java.time during the JSR process.
ZoneId zoneId = ZoneId.of ( "America/Montreal" );
ZonedDateTime now = ZonedDateTime.now ( zoneId );
ZonedDateTime then = now.minusDays ( 4 );
ZonedDateTime due = now.plusDays ( 3 );
Integer days = org.threeten.extra.Days.between ( then , due ).getAmount ();
Dump to console.
System.out.println ( "From then: " + then + " to due: " + due + " = days: " + days );
From then: 2015-10-31T16:01:13.082-04:00[America/Montreal] to due: 2015-11-07T16:01:13.082-05:00[America/Montreal] = days: 7
Joda-Time
For Android or older versions of Java, use the excellent Joda-Time library.
The Days class is smart and handles anomalies such as Daylight Saving Time (DST).
Note that unlike java.util.Date, a Joda-Time DateTime object knows its own time zone.
// Specify a time zone rather than rely on default.
DateTimeZone timeZone = DateTimeZone.forID( "America/Regina" ); // Or "Europe/London".
DateTime now = new DateTime( timeZone );
DateTime startOfToday = now.withTimeAtStartOfDay();
DateTime fewDaysFromNow = now.plusDays( 3 );
DateTime startOfAnotherDay = fewDaysFromNow.withTimeAtStartOfDay();
Days days = Days.daysBetween( startOfToday, startOfAnotherDay );
Dump to console…
System.out.println( days.getDays() + " days between " + startOfToday + " and " + startOfAnotherDay + "." );
When run…
3 days between 2014-01-21T00:00:00.000-06:00 and 2014-01-24T00:00:00.000-06:00.
There are mainly two reasons why your code is broken:
second parts or millisecond fractions (you might have overlooked)
daylight saving effects
I demonstrate and explain the second reason.
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date d1 = sdf.parse("2016-03-20");
Date d2 = sdf.parse("2016-03-28");
int daysUntil = (int) ((d2.getTime() - d1.getTime()) / (1000 * 60 * 60 * 24));
System.out.println(daysUntil); // 7 (should be 8)
The code was run in timezone "Europe/Berlin". Due to the change from winter time to summer time causing a jump of clocks by one hour forward on 2016-03-27 at 2 am, there is one hour missing. One day has only 23 hours so the division by 24 yields zero resulting in counting one day less.
What can you do else?
Your workaround adding 1000 milliseconds to dueDate sounds as if you have overlooked possible millisecond deltas in your input. This might solve a special case but will usually not be sufficient to solve the daylight saving problem, too. Whatever you choose on base of java.util.Date it is a more or less an evil hack.
The best I have in mind (within the scope of Android-built-in stuff) is to construct an instance of java.util.GregorianCalendar and to add successively one day after one until you have passed the due-date, and then count how many days you have added. Not elegant and errorprone because varying millisecond parts can easily be overlooked here, too.
Otherwise you can try various external libraries for this task. There are four available on Android which can calculate elapsed days in an easy way.
Date4J (main advantage: very small but else limited features)
Threeten-ABP (uses backport of Java-8)
Joda-Time-Android (based on Joda-Time)
Time4A (my own library for Android)

Categories

Resources