I'm in an Android project that requires saving a file with TDateTime type (Delphi). I have my date in milliseconds, but I don't know how to convert milliseconds to TDateTime.
I have something like this:
Date dateInMillis = new Date(System.currentTimeMillis());
double dateInDouble = ???;
I'll be glad for any tips that can help me to resolve this.
Delphi's TDateTime measures time in days. Java follows the Unix standard and measures in milliseconds. To convert between the two you need to scale by the number of milliseconds in a day, 86400000.
The other difference is that the two systems use a different epoch. The Unix epoch, as used by Java, is 00:00, 1 Jan 1970. The Delphi epoch is 00:00, 30 December 1899. The Unix epoch, represented as a Delphi TDateTime is 25569.
So, to convert from milliseconds from the Unix epoch, to days from the Delphi epoch you perform the following calculation:
double delphiDateTime = unixMillis/86400000.0 + 25569.0;
More recently post Java 8 with the new date time classes, the following should work:
LocalDateTime localDateTime = LocalDateTime.of(1899, 12, 30, 0, 0);//Delphi date EPOCH time start
double fraction = val % 1;
long intPart = (long) (val - fraction);
localDateTime = localDateTime.plus(intPart, ChronoUnit.DAYS);
localDateTime = localDateTime.plus((long) (24*60*60*1000 * fraction), ChronoUnit.MILLIS); //fraction is a fraction of the time of day
tl;dr
Use modern java.time classes that years ago supplanted Date class.
Duration duration =
Duration.between(
LocalDate.of( 1899 , Month.DECEMBER , 30 ).atStartOfDay( ZoneOffset.UTC ).toInstant() , // Epoch reference moment used by Delphi.
Instant.now() // Current moment as seen in UTC.
);
double temp =
// Get a whole number of days (integer ) + a decimal fraction of partial day = a `double` number = the `TDateTime` type in Delphi.
duration.toDays() +
(
( double ) duration.minusDays( duration.toDays() ).toMillis() // Milliseconds in our partial day.
/
( double ) Duration.ofDays( 1 ).toMillis() // Milliseconds in a full day, a generic 24-hour day.
);
double r = Math.floor( temp * 1000 ) / 1000; // Truncate to third decimal place to represent a resolution of milliseconds, the limit of `TDateTime` type in Delphi.
java.time
Use only the modern java.time classes in Java, never the legacy classes such as java.util.Date or java.sql.Date.
Capture the current moment as seen in UTC.
Instant now = Instant.now() ;
Because of some twisted history, as its epoch reference for its TDateTime class Delphi uses the first moment of 1899-12-30 presumably in UTC. For the date portion, use LocalDate.
LocalDate delphiEpochDate = LocalDate.of( 1899 , Month.DECEMBER , 30 ); // No, not the 31st, the 30th.
epochDate.toString(): 1899-12-30
As a habit, let java.time determine the first moment of the day, as it is not always 00:00 on all dates in all zones.
Instant delphiEpochMoment = delphiEpochDate.atStartOfDay( ZoneOffset.UTC ).toInstant();
odt.toString(): 1899-12-30T00:00Z
Delphi uses a terrible method of tracking time as inherited from spreadsheets: Using a double floating-point fractional number.
The integer portion represents full days, generic 24-hour long days that ignore the anomalies of political time. For such elapsed time, we use Duration.
Duration d = Duration.between( delphiEpochMoment , now ) ;
long days = d.toDays() ; // Generic 24-hour long days.
Next, get the fractional part that represents a portion of a 24-hour day. First we subtract the amount of the full days, to leave us with a partial day.
Duration partialDay = d.minusDays( days ) ;
Then divide the partial amount by the length of a full day. We will use a resolution of milliseconds rather than the nanoseconds capability of Duration, as it seems Delphi is limited to milliseconds.
double millisOfPartialDay = partialDay.toMillis() ;
double millisOfFullDay = Duration.ofDays( 1 ).toMillis() ;
double tempResult = ( millisOfPartialDay / millisOfFullDay ) ;
We should truncate results to milliseconds. For truncating a double, see this Answer. And we should add our whole number of days.
double tempResult = days + ( millisOfPartialDay / millisOfFullDay ) ;
double result = Math.floor( tempResult * 1000 ) / 1000 ;
Putting that all together.
Instant now = Instant.now();
LocalDate delphiEpochDate = LocalDate.of( 1899 , Month.DECEMBER , 30 ); // No, not the 31st, the 30th.
Instant delphiEpochMoment = delphiEpochDate.atStartOfDay( ZoneOffset.UTC ).toInstant();
Duration d = Duration.between( delphiEpochMoment , now );
long days = d.toDays(); // Generic 24-hour long days.
Duration partialDay = d.minusDays( days );
double millisOfPartialDay = partialDay.toMillis();
double millisOfFullDay = Duration.ofDays( 1 ).toMillis();
double tempResult = days + ( millisOfPartialDay / millisOfFullDay );
double result = Math.floor( tempResult * 1000 ) / 1000; // Truncate to third decimal place to represent a resolution of milliseconds.
System.out.println( "delphiEpochMoment = " + delphiEpochMoment );
System.out.println( "d = " + d );
System.out.println( "tempResult = " + tempResult );
System.out.println( "result = " + result );
delphiEpochMoment = 1899-12-30T00:00:00Z
d = PT1056815H56M10.613011S
tempResult = 44033.99734505787
result = 44033.997
Caveat: I have not tested this code. And I do not use Delphi. So buyer beware: this code is worth every penny you paid for it.
Avoid LocalDateTime
Beware: Do not use LocalDateTime to represent a moment. This class represents a date with time-of-day, but lacks the context of a time zone or offset-from-UTC. For example, take the value of Noon on January 23rd of 2021. Would that be noon in Tokyo Japan? Or noon in Toulouse France? Or noon in Toledo Ohio US? Those would be three very different moments, several hours apart. We do not know which is intended if the time zone is lacking.
You could get away with LocalDateTime in this specific problem of Delphi time-tracking because Delphi (presumably) uses UTC, and UTC uses generic 24-hour-long days as does LocalDateTime. But using LocalDateTime is conceptually not fit for this problem, as we need moments for here.
Related
I've been trying to think of a purely Java way to represent a schedule for a whole calendar year. Each schedule is for a 30 minute slot (half hour granularity).
This would be abstracted through a repository methods like findByDateTime()
Essentially I need to model time slots at 30 min granularites for each day.
The way I have hacked it together is like so
public Map<Integer, Map<Integer, Programme>> enumerateMapOfMapSchedules() {
int numberOfSlots = 48; //48 half hours in a day
Map<Integer, Map<Integer, Programme>> dayToTimeScheduleMap = new HashMap<>();
//create a key value map for each day of the year
for (int i = 1; i < 366; i++) {
Map<Integer, Programme> dayProgrammeScheduleMap = new HashMap<>();
for (int y = 1; y < numberOfSlots; y++) {
dayProgrammeScheduleMap.put(y, null);
}
dayToTimeScheduleMap.put(i, dayProgrammeScheduleMap);
}
//creates a map with 365 days and each day having a map of 48 schedule slots
return dayToTimeScheduleMap;
}
I appreciate this solution doesn't handle or have a concept of year, however since these are for mocks/tests then I am ok with this.
Also it doesn't handle a schedule that overlaps, if programme spans two half hour slots.
My query method is quite simple for finding what is in a particular schedule slot.
public Programme findByDateTime(LocalDateTime dateTime) {
int scheduleSlot = dateTime.getHour() * 2;
//if its the after 30 minute schedule slot
if (dateTime.getMinute() > 30) {
scheduleSlot++;
}
return scheduleMap.get(dateTime.getDayOfYear()).get(scheduleSlot);
}
However for iterating through all the data structure to see how many occurences of a particular programme exist.
My question, is there an easier way of doing this?
I tried doing it with a relational DB but it was hard to represent time periods easily without a lot of SQL.
Any suggestions or implementation advice welcomed!
Days vary in length
A calendar schedule only makes sense in the context of a time zone and a year. Politicians frequently change the offset used by the time zone(s) of their jurisdiction. This means days are not always 24-hours long. Anomalies such as Daylight Saving Time (DST) mean a day might be 23 hours long, 25 hours long, or something else such as 23.5 hours long.
Start at the beginning, count by 30-minute increments
So if what you want is to chop up the entire year in 30-minute segments, you must start at the first moment of the first day of a specific year in a specific time zone, and add 30 minutes at a time until reaching the new year.
ZoneId z = ZoneId.of( "America/Montreal" );
Year year = Year.of( 2021 );
LocalDate firstOfYear = year.atDay( 1 );
ZonedDateTime start = firstOfYear.atStartOfDay( z );
List < ZonedDateTime > zdts = new ArrayList <>();
Duration duration = Duration.ofMinutes( 30 );
ZonedDateTime zdt = start;
while ( zdt.getYear() == year.getValue() )
{
zdts.add( zdt );
// Setup the next loop.
zdt = zdt.plus( duration );
}
Return a non-modifiable copy of that list.
List < ZonedDateTime > slots = List.copyOf( zdts );
When run. Notice what happens at 1 or 2 AM on Mar 14, 2021 and Nov 7, 2021.
slots = [2021-01-01T00:00-05:00[America/Montreal], 2021-01-01T00:30-05:00[America/Montreal], 2021-01-01T01:00-05:00[America/Montreal], 2021-01-01T01:30-05:00[America/Montreal], 2021-01-01T02:00-05:00[America/Montreal],
…
2021-03-14T01:00-05:00[America/Montreal], 2021-03-14T01:30-05:00[America/Montreal], 2021-03-14T03:00-04:00[America/Montreal],
…
2021-11-07T00:30-04:00[America/Montreal], 2021-11-07T01:00-04:00[America/Montreal], 2021-11-07T01:30-04:00[America/Montreal], 2021-11-07T01:00-05:00[America/Montreal], 2021-11-07T01:30-05:00[America/Montreal], 2021-11-07T02:00-05:00[America/Montreal],
…
2021-12-31T22:00-05:00[America/Montreal], 2021-12-31T22:30-05:00[America/Montreal], 2021-12-31T23:00-05:00[America/Montreal], 2021-12-31T23:30-05:00[America/Montreal]]
Future projections unreliable!
But beware: politicians frequently change the offset used in a zone! This happens much more often than you likely realize. Politicians have even gotten worse at this reducing their forewarning from years to a few months, or even several weeks as seen recently in Turkey and Morocco, and even no forewarning at all as seen in North Korea.
So you cannot reliably project into the future using the approach seen above.
Slot math
I suppose you could approach the slots-of-year problem in another way. Calculate the number of whole slots during the year this way.
ZoneId z = ZoneId.of( "America/Montreal" );
Year year = Year.of( 2021 );
LocalDate firstOfYear = year.atDay( 1 );
ZonedDateTime start = firstOfYear.atStartOfDay( z );
ZonedDateTime end = start.plusYears( 1 );
Duration slotLength = Duration.ofMinutes( 30 );
long wholeSlotsInYear = Duration.between( start , end ).dividedBy( slotLength );
Then you could jump to a point in the year by multiplying duration, and adding the result to the start of year.
int slotNumber = 22;
Duration jump = slotLength.multipliedBy( slotNumber - 1 ); // Subtract one to change an ordinal number into a zero-based index.
ZonedDateTime slot22 = start.plus( jump );
Appointment book tracking
If you are doing appointments such as at a hair salon or dental clinic, the usual approach is to track a year-month-day with a particular time of day. But track the time zone separately. So use a LocalDateTime with a separate ZoneId in your Java model. In your database table, use a pair of columns, one of type akin to the SQL-standard type TIMESTAMP WITHOUT TIME ZONE and another column of a text type holding the name of the time zone such as America/Montreal or Africa/Tunis.
When building a schedule, apply the zone to determine a moment. In Java, that means applying a ZoneId to a LocalDateTime to get a ZonedDateTime.
You need to be clear on the fundamental idea that a LocalDateTime object does not represent a moment. In our example here, 3 PM on the 23rd of next year could mean 3 PM in Tokyo Japan or 3 PM in Toledo Ohio US, two very different moments several hours apart. A LocalDateTime is inherently ambiguous. Thus the need to store a time zone as well, but kept separate.
LocalDateTime ldt = LocalDateTime.of( 2021 , 1 , 23 , 15 , 0 , 0 , 0 ) ;
ZoneId z = ZoneId.of( "America/Montreal" ) ;
ZonedDateTime zdt = ldt.atZone( z ) ; // Determine a moment.
See that same moment in UTC by extracting a Instant.
Instant instant = zdt.toInstant() ;
NOTE: search Google before marking this question as duplicate. I did search and browse this question and all answers that I found were either for LocalDate, Joda or legacy Java Date.
It took me quite some time to investigate this so I've decided to share this as an answer.
I'd like a way to calculate the (approximate) number of months and days between two Java Instants (objects of java.time.Instant)?
First, what you are asking is not well-defined. For example between the instants 2020-03-01T06:00:00Z and 2020-03-31T05:00:00Z could be:
29 days 23 hours in Australia/Melbourne time zone;
30 days in Europe/Paris time zone;
1 month 1 day in America/Los_Angeles time zone.
Accurate result in a given time zone
ZoneId zone = ZoneId.of("America/Los_Angeles");
Instant start = Instant.parse("2020-03-01T06:00:00Z");
Instant end = Instant.parse("2020-03-31T05:00:00Z");
ZonedDateTime startZdt = start.atZone(zone);
LocalDate startDate = startZdt.toLocalDate();
ZonedDateTime endZdt = end.atZone(zone);
LocalDate endDate = endZdt.toLocalDate();
Period p = Period.between(startDate, endDate);
if (startZdt.plus(p).isAfter(endZdt)) {
// The time of day on the end date is earlier, so don’t count a full date
endDate = endDate.minusDays(1);
p = Period.between(startDate, endDate);
}
System.out.println(p);
Output:
P1M1D
Read as a period of 1 month 1 day.
Approximate result independent of time zone
Prefer to leave as much of the calculation to java.time as possible. This includes the estimate of the length of a month.
Duration diff = Duration.between(start, end);
Duration durationOfAMonth = ChronoUnit.MONTHS.getDuration();
long months = diff.dividedBy(durationOfAMonth);
diff = diff.minus(durationOfAMonth.multipliedBy(months));
long days = diff.toDays();
System.out.println("" + months + " months " + days + " days");
0 months 29 days
I've opted out to approximate solution (it assumes all months have 30.44 days). I've opted out to use something like this:
Duration duration = Duration.between(instant1, instant2).abs(); /* if want negative values remove .abs() */
long hours = duration.toHours();
double daysAndMonthsInDays = hours / 24.0;
int months = daysAndMonthsInDays / 30.44; //average number of days per month
int days = daysAndMonthsInDays - months * 30.44;
Please post another answer if there is a better solution using Duration class or something else. I've decided not to convert Instant to LocalDate and to perform the conversion on that level. That would not use an approximation of 30.44 days in a month, but rather the actual number.
I am bashing my head against Java's Temporal API. I can't find anything in the Java Docs or anywhere else that'll answer what I need to do. I simply need to at 1,000,000,000 seconds to a date and return the date, calculating the moment someone lived a billion seconds (somewhere around 30 years.)
public Temporal getGigasecondDate(Temporal given) {
final long gigasecond = 1000000000;
final long gigaDays = gigasecond / 60 / 60 / 24;
Duration amount = Duration.ofDays(gigaDays);
Temporal date = amount.addTo(given);
return date;
}
I get an error saying Exception in thread "main" java.time.DateTimeException: Unit must be Years, Months or Days, but was Seconds and I can't for the life of me find a way in the JavaDocs, or anywhere else, figure out how to get the unit to years, months, or days.
All I need to do, is return a Temporal object that states the moment someone has lived a billion seconds.
tl;dr
ZonedDateTime.of( // Mom gave birth at 3 AM in year 2000, New Zealand time.
2000 , 1 , 23 , 3 , 45 , 0 , 0 , ZoneId.of( "Pacific/Auckland" )
) // Returns a `ZonedDateTime` object.
.plus(
Duration.ofSeconds( 1_000_000_000L ) // A billion seconds.
) // Returns another `ZonedDateTime` object. Immutable objects in *java.time* means we get a fresh object rather than “mutating” (altering) the original.
.toString() // Generate a `String` representing the value of our `ZonedDateTime` object, using standard ISO 8601 format wisely extended to append the name of the time zone in square brackets.
2031-10-01T05:31:40+13:00[Pacific/Auckland]
Details
The Answer by Andreas is correct. Here are a few more thoughts.
Do not use Temporal
The documentation for java.time explains that generally in an app you should be using the concrete classes rather than the interfaces and superclasses. To quote from Temporal interface:
This interface is a framework-level interface that should not be widely used in application code. Instead, applications should create and pass around instances of concrete types, such as LocalDate. There are many reasons for this, part of which is that implementations of this interface may be in calendar systems other than ISO. See ChronoLocalDate for a fuller discussion of the issues.
Adding a billion seconds to a birth-moment
[add] 1,000,000,000 seconds to a date and return the date,
Determine the moment of the person's birth.
LocalDate ld = LocalDate.of( 2000 , Month.JANUARY , 23 ) ;
LocalTime lt = LocalTime.of( 3 , 45 ) ; // Mom gave birth at 3 AM in New Zealand time.
ZoneId z = ZoneId.of( "Pacific/Auckland" ) ;
ZonedDateTime zdt = ZonedDateTime.of( ld , lt , z ) ;
See that same moment in UTC, if useful.
Instant instant = zdt.toInstant() ;
Define the amount of time to add. The Duration class represents a span of time not attached to the timeline.
Duration d = Duration.ofSeconds( 1_000_000_000L ) ;
Add.
ZonedDateTime zdtLater = zdt.plus( d ) ;
Or, in UTC (same moment, different wall-clock time).
Instant instantLater = instant.plus( d ) ;
See this code run live at IdeOne.com.
zdt.toString(): 2000-01-23T03:45+13:00[Pacific/Auckland]
instant.toString(): 2000-01-22T14:45:00Z
d.toString(): PT277777H46M40S
zdtLater.toString(): 2031-10-01T05:31:40+13:00[Pacific/Auckland]
instantLater.toString(): 2031-09-30T16:31:40Z
If you care for only the date, without the time-of-day and without the time zone, extract a LocalDate.
LocalDate ldLater = zdtLater.toLocalDate() ;
You can call plus(long amountToAdd, TemporalUnit unit), with unit as ChronoUnit.SECONDS:
public static Temporal getGigasecondDate(Temporal given) {
return given.plus(1_000_000_000, ChronoUnit.SECONDS);
}
Test
System.out.println(getGigasecondDate(LocalDateTime.of(2000, 1, 1, 0, 0)));
System.out.println(getGigasecondDate(ZonedDateTime.of(2000, 1, 1, 0, 0, 0, 0, ZoneId.of("America/New_York"))));
Output
2031-09-09T01:46:40
2031-09-09T02:46:40-04:00[America/New_York]
So, someone born at midnight on Jan 1, 2000 will be 1 billion seconds old on Sep 9, 2031 at 1:46:40 AM, assuming no Daylight Savings Time.
If they lived in New York, it would be at 2:46:40 AM EDT.
Using the new Java 8 DateTime API (java.time) how can we check if the "last" capture time is older then a configured set of seconds?
Example...
Last captured time: 13:00:00
Current time time: 13:00:31
if (last captured time is older then 30 seconds) then
do something
tl;dr
Duration.between(
myEarlierInstant ; // Some earlier `Instant`.
Instant.now() ; // Capture the current moment in UTC.
)
.compareTo( // Returns a negative integer, zero, or a positive integer as this object is less than, equal to, or greater than the specified object.
Duration.ofMinutes( 5 ) // A span of time unattached to the timeline.
)
> 0
Details
The Instant class represents a moment on the timeline in UTC with a resolution in nanoseconds.
Instant then = … ;
Instant now = Instant.now();
A Duration represents a span of time in terms of seconds and nanoseconds.
Duration d = Duration.between( then , now );
Extract the number of whole seconds.
long secondsElapsed = d.getSeconds() ;
Compare to your limit. Use TimeUnit enum to convert rather than hard-coding a “magic” number. For example, converting five minutes to a number of seconds.
long limit = TimeUnit.MINUTES.toSeconds( 5 );
Compare.
if( secondsElapsed > limit ) { … }
There's also the getSeconds() method:
Instant start = Instant.now()
// Do something
Instant end = Instant.now()
if (Duration.between(start, end).getSeconds() > 30) {
// do something else
}
Duration it is...
Duration duration = Duration.between(LocalDateTime.now(), LocalDateTime.now().plusSeconds(xx));
System.out.println(duration.getSeconds());
I have a Java method which compares two Dates and returns the number of days between them, but it's off by a day.
Even after I 0 out the hours, min, and sec the calculation is still off.
public long compareDates(Date exp, Date today){
TimeZone tzone = TimeZone.getTimeZone("America/New_York");
Calendar expDate = Calendar.getInstance();
Calendar todayDate = Calendar.getInstance();
expDate.setTime(exp);
todayDate.setTime(today);
expDate.set(Calendar.HOUR_OF_DAY, 0);
expDate.set(Calendar.MINUTE, 0);
expDate.set(Calendar.SECOND, 0);
todayDate.set(Calendar.HOUR_OF_DAY, 0);
todayDate.set(Calendar.MINUTE, 0);
todayDate.set(Calendar.SECOND, 0);
logger.info("Today = " + Long.toString(todayDate.getTimeInMillis()) + " Expiration = " + Long.toString(expDate.getTimeInMillis()));
expDate.setTimeZone(tzone);
todayDate.setTimeZone(tzone);
return (expDate.getTimeInMillis()-todayDate.getTimeInMillis())/86400000;
}
Output
Today = 1453939200030 Expiration = 1454544000000
There's 7 days between 1/28 and 2/4 but this returns 6.
Well, as you can see, you didn't clear the milliseconds, and 1454544000000 - 1453939200030 = 604799970 and dividing by 86400000 gets you 6.99999965277777..., which means 6 when truncated to int.
Now, if you clear the milliseconds too, today becomes 1453939200000, which will lead to you answer 7.
Note: This doesn't mean you're done, because of Daylight Savings Time. With DST, one of the timestamps may be ±1 hour from the other, so you may still get that truncation issue.
This was an answer to your particular issue. Try searching for how to correctly find days between dates in Java.
Today = 1453939200030
The times are given in milliseconds, and it looks like somehow your inputted Date has 30 extra milliseconds on it.
When I subtract the 30 milliseconds, then do the math on a calculator, I get 7 days. With your figures as is, I get 6.9999996527777777777777777777778, and in long math, the decimal figures get truncated to 6.
Zero out the milliseconds also.
expDate.set(Calendar.MILLISECOND, 0);
todayDate.set(Calendar.MILLISECOND, 0);
java.time
The Question and other Answers use outmoded classes. The old date-time classes such as java.util.Date/.Calendar bundled with the earliest versions of Java have proven to be quite troublesome. Those old classes have been supplanted by the java.time framework in Java 8 and later.
As the other Answers point out correctly, the issue is that the start long has 30 on the right side, precluding a whole-day calculation.
Count-Of-Days Definition
Furthermore you must define what you mean by a count-of-days. Do you mean a count by date, so any time on the 3rd of January to any time on the 4th is one day even if the times were a minute before and after midnight? Or do you mean a count of generic 24-hour blocks of time while ignoring the fact that particular days in particular time zones are not always 24-hours long because of Daylight Saving Time (DST) and other anomalies?
Count Days By Date
If you want the former, count by dates, then make use of the LocalDate class (a date-only without time-of-day nor time zone) and the Period class (a span of time defined as a count of years, months, days) found in java.time.
Define your inputs. Use long rather than int. These numbers apparently represent a count of milliseconds since the first moment of 1970 in UTC.
long startMilli = 1_453_939_200_030L;
long stopMilli = 1_454_544_000_000L;
Convert those long numbers into Instant objects, a moment on the timeline in UTC.
Instant startInstant = Instant.ofEpochMilli ( startMilli );
Instant stopInstant = Instant.ofEpochMilli ( stopMilli );
Define the time zone in which you want to consider the calendar dates. Note that time zone is crucial in defining dates. The date is not simultaneously the same around the globe. The date varies by time zone.
ZoneId zoneId = ZoneId.of ( "America/Montreal" );
Apply that time zone to each Instant to produce ZonedDateTime.
ZonedDateTime startZdt = ZonedDateTime.ofInstant ( startInstant , zoneId );
ZonedDateTime stopZdt = ZonedDateTime.ofInstant ( stopInstant , zoneId );
To get a Period, we need “local” dates. By “local” we mean any particular locality, a generic date value. The LocalDate class contains no time zone, but the time zone contained with in the ZonedDateTime is applied when determining a LocalDate.
LocalDate startLocalDate = startZdt.toLocalDate ();;
LocalDate stopLocalDate = stopZdt.toLocalDate ();
Define our span of time as a count of generic days, in Period.
Period period = Period.between ( startLocalDate , stopLocalDate );
Interrogate the Period to ask for the number of generic days contained within.
int days = period.getDays ();
Dump to console.
System.out.println ( "milli: " + startMilli + "/" + stopMilli + " | Instant: " + startInstant + "/" + stopInstant + " | ZonedDateTime: " + startZdt + "/" + stopZdt + " | LocalDate: " + startLocalDate + "/" + stopLocalDate + " | period: " + period + " | days: " + days );
milli: 1453939200030/1454544000000 | Instant: 2016-01-28T00:00:00.030Z/2016-02-04T00:00:00Z | ZonedDateTime: 2016-01-27T19:00:00.030-05:00[America/Montreal]/2016-02-03T19:00-05:00[America/Montreal] | LocalDate: 2016-01-27/2016-02-03 | period: P7D | days: 7
Count Of Whole Days
If you want a count of whole days, use the Days class from ThreeTen-Extra. Notice in the output below that we get a count of six (6) days rather than seven (7) as seen above.
ThreeTen-Extra
The ThreeTen-Extra project extends java.time. Run by the same folks who built java.time.
The behavior of the between method is not documented clearly. Experimenting shows that it seems to based on 24-hour chunks of time, not dates. Replace the 030 with 000, and also try replacing in the stopMilli the last 000 with 030, to see the behavior for yourself.
Days daysObject = Days.between ( startZdt , stopZdt );
int daysObjectCount = daysObject.getAmount ();
Dump to console. The P6D string you see in the output was generated according to the formats defined in the ISO 8601 standard. This standard is used by default in java.time for all parsing and generating of textual representations of date-time values. These standard formats are quite sensible and useful so do glance at that linked Wikipedia page.
System.out.println ( "daysObject: " + daysObject + " | daysObjectCount: " + daysObjectCount );
daysObject: P6D | daysObjectCount: 6
To fix my problems, I have zeroed out the milliseconds as mentioned, as well as casted the longs to doubles in order to maintain accuracy and round when necessary.
expDate.setTime(exp);
todayDate.setTime(today);
expDate.setTimeZone(tzone);
todayDate.setTimeZone(tzone);
expDate.set(Calendar.HOUR_OF_DAY, 0);
expDate.set(Calendar.MINUTE, 0);
expDate.set(Calendar.SECOND, 0);
expDate.set(Calendar.MILLISECOND, 0);
todayDate.set(Calendar.HOUR_OF_DAY, 0);
todayDate.set(Calendar.MINUTE, 0);
todayDate.set(Calendar.SECOND, 0);
todayDate.set(Calendar.MILLISECOND, 0);
double diff = ((double)expDate.getTimeInMillis()-(double)todayDate.getTimeInMillis())/86400000;
return Math.round(diff);