Related
I'm using openjdk version 1.8.0_112-release for development but will need to support previous JDK versions too (pre-Java-8) - so can't use java.time.
I am writing a utitily class to calculate the date to see if a saved date is before the current date which means its expired.
However, I am not sure I have done this the correct way. I am using LocalDate class to calculate the days. The expiration is counted starting from the date and time the user clicked save. That date will be saved and a check will be done against this saved date and time and the current date and time i.e. when the user logs in.
Is this the best way to do it? I would like to keep to the LocalDate class.
import org.threeten.bp.LocalDate;
public final class Utilities {
private Utilities() {}
public static boolean hasDateExpired(int days, LocalDate savedDate, LocalDate currentDate) {
boolean hasExpired = false;
if(savedDate != null && currentDate != null) {
/* has expired if the saved date plus the specified days is still before the current date (today) */
if(savedDate.plusDays(days).isBefore(currentDate)) {
hasExpired = true;
}
}
return hasExpired;
}
}
I'm using the class like this:
private void showDialogToIndicateLicenseHasExpired() {
final LocalDate currentDate = LocalDate.now();
final int DAYS_TO_EXPIRE = 3;
final LocalDate savedDate = user.getSavedDate();
if(hasDateExpired(DAYS_TO_EXPIRE, savedDate, currentDate)) {
/* License has expired, warn the user */
}
}
I am looking a solution that will take in account time zones. If a license was set to expire in 3 days, and the user was to travel to a different time zone. i.e. they could be ahead or behind based on hours. The license should still expire.
Your code is basically fine. I would do it basically the same way, just with a detail or two being different.
As Hugo has already noted, I would use java.time.LocalDate and drop the use of ThreeTen Backport (unless it is a specific requirement that your code can run on Java 6 or 7 too).
Time Zone
You should decide in which time zone you count your days. Also I would prefer if you make the time zone explicit in yout code. If your system will be used in your own time zone only, the choice is easy, just make it explicit. For example:
final LocalDate currentDate = LocalDate.now(ZoneId.of("Asia/Hong_Kong"));
Please fill in the relevant zone ID. This will also make sure the program works correctly even if one day it happens to run on a computer with an incorrect time zone setting. If your system is global, you may want to use UTC, for example:
final LocalDate currentDate = LocalDate.now(ZoneOffset.UTC);
You will want to do similarly when saving the date when the user clicked Save so your data are consistent.
72 hours
Edit: I understand from your comment that you want to measure 3 days, that is, 72 hours, from the save time to determine whether the license has expired. For this a LocalDate does not give you enough information. It is only a date without a clock time, like May 26 2017 AD. There are some other options:
Instant is a point in time (with nanosecond precision, even). This is the simple solution to make sure the expiration happens after 72 hours no matter if the user moves to another time zone.
ZonedDateTime represents both a date and a time and a time zone, like 29 May 2017 AD 19:21:33.783 at offset GMT+08:00[Asia/Hong_Kong]. If you want to remind the user when the saved time was, a ZonedDateTime will you allow you to present that information with the time zone in which the save date was calculated.
Finally OffsetDateTime would work too, but it doesn’t seem to give you much of the advantages of the two others, so I will not eloborate on this option.
Since an instant is the same in all time zones, you don’t specify a time zone when getting the current instant:
final Instant currentDate = Instant.now();
Adding 3 days to an Instant is a little different LocalDate, but the rest of the logic is the same:
public static boolean hasDateExpired(int days, Instant savedDate, Instant currentDate) {
boolean hasExpired = false;
if(savedDate != null && currentDate != null) {
/* has expired if the saved date plus the specified days is still before the current date (today) */
if (savedDate.plus(days, ChronoUnit.DAYS).isBefore(currentDate)) {
hasExpired = true;
}
}
return hasExpired;
}
The use of ZonedDateTime, on the other hand, goes exactly like LocalDate in the code:
final ZonedDateTime currentDate = ZonedDateTime.now(ZoneId.of("Asia/Hong_Kong"));
If you want the current time zone setting from the JVM where the program runs:
final ZonedDateTime currentDate = ZonedDateTime.now(ZoneId.systemDefault());
Now if you declare public static boolean hasDateExpired(int days, ZonedDateTime savedDate, ZonedDateTime currentDate), you may do as before:
/* has expired if the saved date plus the specified days is still before the current date (today) */
if (savedDate.plusDays(days).isBefore(currentDate)) {
hasExpired = true;
}
This will perform the correct comparison even if the two ZonedDateTime objects are in two different time zones. So no matter if the user travels to a different time zone, s/he will not get fewer nor more hours before the license expires.
You can use ChronoUnit.DAYS (in org.threeten.bp.temporal package, or in java.time.temporal if you use java 8 native classes) to calculate the number of days between the 2 LocalDate objects:
if (savedDate != null && currentDate != null) {
if (ChronoUnit.DAYS.between(savedDate, currentDate) > days) {
hasExpired = true;
}
}
Edit (after bounty explanation)
For this test, I'm using threetenbp version 1.3.4
As you want a solution that works even if the user is in a different timezone, you shouldn't use LocalDate, because this class doesn't handle timezone issues.
I think the best solution is to use the Instant class. It represents a single point in time, no matter in what timezone you are (at this moment, everybody in the world are in the same instant, although the local date and time might be different depending on where you are).
Actually Instant is always in UTC Time - a standard indepedent of timezone, so very suitable to your case (as you want a calculation independent of what timezone the user is in).
So both your savedDate and currentDate must be Instant's, and you should calculate the difference between them.
Now, a subtle detail. You want the expiration to happen after 3 days. For the code I did, I'm making the following assumptions:
3 days = 72 hours
1 fraction of a second after 72 hours, it's expired
The second assumption is important for the way I implemented the solution. I'm considering the following cases:
currentDate is less than 72 hours after savedDate - not expired
currentDate is exactly 72 hours after savedDate - not expired (or expired? see comments below)
currentDate is more than 72 hours after savedDate (even by a fraction of a second) - expired
The Instant class has nanosecond precision, so in case 3 I'm considering that it's expired even if it's 1 nanosecond after 72 hours:
import org.threeten.bp.Instant;
import org.threeten.bp.temporal.ChronoUnit;
public static boolean hasDateExpired(int days, Instant savedDate, Instant currentDate) {
boolean hasExpired = false;
if (savedDate != null && currentDate != null) {
// nanoseconds between savedDate and currentDate > number of nanoseconds in the specified number of days
if (ChronoUnit.NANOS.between(savedDate, currentDate) > days * ChronoUnit.DAYS.getDuration().toNanos()) {
hasExpired = true;
}
}
return hasExpired;
}
Note that I used ChronoUnit.DAYS.getDuration().toNanos() to get the number of nanoseconds in a day. It's better to rely on the API instead of having hardcoded big error-prone numbers.
I've made some tests, using dates in the same timezone and in different ones.
I used ZonedDateTime.toInstant() method to convert the dates to Instant:
import org.threeten.bp.ZoneId;
import org.threeten.bp.ZonedDateTime;
// testing in the same timezone
ZoneId sp = ZoneId.of("America/Sao_Paulo");
// savedDate: 22/05/2017 10:00 in Sao Paulo timezone
Instant savedDate = ZonedDateTime.of(2017, 5, 22, 10, 0, 0, 0, sp).toInstant();
// 1 nanosecond before expires (returns false - not expired)
System.out.println(hasDateExpired(3, savedDate, ZonedDateTime.of(2017, 5, 25, 9, 59, 59, 999999999, sp).toInstant()));
// exactly 3 days (72 hours) after saved date (returns false - not expired)
System.out.println(hasDateExpired(3, savedDate, ZonedDateTime.of(2017, 5, 25, 10, 0, 0, 0, sp).toInstant()));
// 1 nanosecond after 3 days (72 hours) (returns true - expired)
System.out.println(hasDateExpired(3, savedDate, ZonedDateTime.of(2017, 5, 25, 10, 0, 0, 1, sp).toInstant()));
// testing in different timezones (savedDate in Sao Paulo, currentDate in London)
ZoneId london = ZoneId.of("Europe/London");
// In 22/05/2017, London will be in summer time, so 10h in Sao Paulo = 14h in London
// 1 nanosecond before expires (returns false - not expired)
System.out.println(hasDateExpired(3, savedDate, ZonedDateTime.of(2017, 5, 25, 13, 59, 59, 999999999, london).toInstant()));
// exactly 3 days (72 hours) after saved date (returns false - not expired)
System.out.println(hasDateExpired(3, savedDate, ZonedDateTime.of(2017, 5, 25, 14, 0, 0, 0, london).toInstant()));
// 1 nanosecond after 3 days (72 hours) (returns true - expired)
System.out.println(hasDateExpired(3, savedDate, ZonedDateTime.of(2017, 5, 25, 14, 0, 0, 1, london).toInstant()));
PS: for case 2 (currentDate is exactly 72 hours after savedDate - not expired) - if you want this to be expired, just change the if above to use >= instead of >:
if (ChronoUnit.NANOS.between(savedDate, currentDate) >= days * ChronoUnit.DAYS.getDuration().toNanos()) {
... // it returns "true" for case 2
}
If you don't want nanosecond precision and just want to compare the days between the dates, you can do as in #Ole V.V's answer. I believe our answers are very similar (and I suspect that the codes are equivalent, although I'm not sure), but I haven't tested enough cases to check if they differ in any particular situation.
The Answer by Hugo and the Answer by Ole V.V. Are both correct, and the one by Ole V.V. is most important, as time zone is crucial to determine the current date.
Period
Another useful class for this work is the Period class. This class represents a span of time unattached to the timeline as a number of years, months, and days.
Note that this class is not appropriate to representing the elapsed time needed for this Question because this representation is "chunked" as years, then months, and then any remaining days. So if LocalDate.between( start , stop ) were used for an amount of several weeks, the result might be something like "two months and three days". Notice that this class does not implement the Comparable interface for this reason, as one pair of months cannot be said to be bigger or smaller than another pair unless we know which specific months are involved.
We can use this class to represent the two-day grace-period mentioned in the Question. Doing so makes our code more self-documenting. Better to pass around an object of this type than passing a mere integer.
Period grace = Period.ofDays( 2 ) ;
LocalDate start = LocalDate.of( 2017 , Month.JANUARY , 23 ).plusDays( grace ) ;
LocalDate stop = LocalDate.of( 2017 , Month.MARCH , 7 ) ;
We use ChronoUnit to calculate elapsed days.
int days = ChronoUnit.DAYS.between( start , stop ) ;
Duration
By the way, the Duration class is similar to Period in that it represents a span of time not attached to the timeline. But Duration represents a total of whole seconds plus a fractional second resolved in nanoseconds. From this you can calculate a number of generic 24-hour days (not date-based days), hours, minutes, seconds, and fractional second. Keep in mind that days are not always 24-hours long; here in the United States they currently may be 23, 24, or 25 hours long because of Daylight Saving Time.
This Question is about date-based days, not lumps of 24-hours. So the Duration class is not appropriate here.
About java.time
The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
Where to obtain the java.time classes?
Java SE 8, Java SE 9, and later
Built-in.
Part of the standard Java API with a bundled implementation.
Java 9 adds some minor features and fixes.
Java SE 6 and Java SE 7
Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
Android
The ThreeTenABP project adapts ThreeTen-Backport (mentioned above) for Android specifically.
See How to use ThreeTenABP….
The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.
I think much better to use this:
Duration.between(currentDate.atStartOfDay(), savedDate.atStartOfDay()).toDays() > days;
Duration class placed in java.time package.
As this question is not getting "enough responses", I have added another answer:
I have used "SimpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));" to set the timezone to UTC. So there is no longer a timezone (all Date / time will be set to UTC).
savedDate is set to UTC.
dateTimeNow is also set to UTC, with the number of expired "days" (negative number) added to dateTimeNow.
A new Date expiresDate uses the long milliseconds from dateTimeNow
Check if savedDate.before(expiresDate)
package com.chocksaway;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
public class ExpiredDate {
private static final long DAY_IN_MS = 1000 * 60 * 60 * 24;
private static boolean hasDateExpired(int days, java.util.Date savedDate) throws ParseException {
SimpleDateFormat dateFormatUtc = new SimpleDateFormat("yyyy-MMM-dd HH:mm:ss");
dateFormatUtc.setTimeZone(TimeZone.getTimeZone("UTC"));
// Local Date / time zone
SimpleDateFormat dateFormatLocal = new SimpleDateFormat("yyyy-MMM-dd HH:mm:ss");
// Date / time in UTC
savedDate = dateFormatLocal.parse( dateFormatUtc.format(savedDate));
Date dateTimeNow = dateFormatLocal.parse( dateFormatUtc.format(new Date()));
long expires = dateTimeNow.getTime() + (DAY_IN_MS * days);
Date expiresDate = new Date(expires);
System.out.println("savedDate \t\t" + savedDate + "\nexpiresDate \t" + expiresDate);
return savedDate.before(expiresDate);
}
public static void main(String[] args) throws ParseException {
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DATE, 0);
if (ExpiredDate.hasDateExpired(-2, cal.getTime())) {
System.out.println("expired");
} else {
System.out.println("not expired");
}
System.out.print("\n");
cal.add(Calendar.DATE, -3);
if (ExpiredDate.hasDateExpired(-2, cal.getTime())) {
System.out.println("expired");
} else {
System.out.println("not expired");
}
}
}
Running this code gives the following output:
savedDate Mon Jun 05 15:03:24 BST 2017
expiresDate Sat Jun 03 15:03:24 BST 2017
not expired
savedDate Fri Jun 02 15:03:24 BST 2017
expiresDate Sat Jun 03 15:03:24 BST 2017
expired
All dates / times are UTC. First is not expired. Second is expired (savedDate is before expiresDate).
KISS
public static boolean hasDateExpired(int days, java.util.Date savedDate) {
long expires = savedDate().getTime() + (86_400_000L * days);
return System.currentTimeMillis() > expires;
}
Works on old JRE's just fine. Date.getTime() gives milliseconds UTC, so timezone isn't even a factor. The magic 86'400'000 is the number of milliseconds in a day.
Instead of using java.util.Date you can simplify this further if you just use a long for savedTime.
I have built a simple utility class ExpiredDate, with a TimeZone (such as CET), expiredDate, expireDays, and differenceInHoursMillis.
I use java.util.Date, and Date.before(expiredDate):
To see if Date() multiplied by expiryDays plus (timezone difference multiplied by expiryDays) is before expiredDate.
Any date older than the expiredDate is "expired".
A new Date is created by adding (i) + (ii):
(i). I use the number of milliseconds in a day to (DAY_IN_MS = 1000 * 60 * 60 * 24) which is multiplied with the (number of) expireDays.
+
(ii). To deal with a different TimeZone, I find the number of milliseconds between the Default timezone (for me BST), and the TimeZone (for example CET) passed into ExpiredDate. For CET, the difference is one hour, which is 3600000 milliseconds. This is multiplied by the (number of) expireDays.
The new Date is returned from parseDate().
If the new Date is before the expiredDate -> set expired to True.
dateTimeWithExpire.before(expiredDate);
I have created 3 tests:
Set the expiry date 7 days, and expireDays = 3
Not expired (7 days is greater than 3 days)
Set the expiry date / time, and expireDays to 2 days
Not expired - because the CET timezone adds two hours (one hour per day) to the dateTimeWithExpire
Set the expiry date 1 days, and expireDays = 2 (1 day is less than 2 days)
expired is true
package com.chocksaway;
import java.text.ParseException;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
public class ExpiredDate {
/**
* milliseconds in a day
*/
private static final long DAY_IN_MS = 1000 * 60 * 60 * 24;
private String timeZone;
private Date expiredDate;
private int expireDays;
private int differenceInHoursMillis;
/**
*
* #param timeZone - valid timezone
* #param expiredDate - the fixed date for expiry
* #param expireDays - the number of days to expire
*/
private ExpiredDate(String timeZone, Date expiredDate, int expireDays) {
this.expiredDate = expiredDate;
this.expireDays = expireDays;
this.timeZone = timeZone;
long currentTime = System.currentTimeMillis();
int zoneOffset = TimeZone.getTimeZone(timeZone).getOffset(currentTime);
int defaultOffset = TimeZone.getDefault().getOffset(currentTime);
/**
* Example:
* TimeZone.getTimeZone(timeZone) is BST
* timeZone is CET
*
* There is one hours difference, which is 3600000 milliseconds
*
*/
this.differenceInHoursMillis = (zoneOffset - defaultOffset);
}
/**
*
* Subtract a number of expire days from the date
*
* #param dateTimeNow - the date and time now
* #return - the date and time minus the number of expired days
* + (difference in hours for timezone * expiryDays)
*
*/
private Date parseDate(Date dateTimeNow) {
return new Date(dateTimeNow.getTime() - (expireDays * DAY_IN_MS) + (this.differenceInHoursMillis * expireDays));
}
private boolean hasDateExpired(Date currentDate) {
Date dateTimeWithExpire = parseDate(currentDate);
return dateTimeWithExpire.before(expiredDate);
}
public static void main(String[] args) throws ParseException {
/* Set the expiry date 7 days, and expireDays = 3
*
* Not expired
*/
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DATE, -7);
ExpiredDate expired = new ExpiredDate("CET", cal.getTime(), 3);
Date dateTimeNow = new Date();
if (expired.hasDateExpired(dateTimeNow)) {
System.out.println("expired");
} else {
System.out.println("NOT expired");
}
/* Set the expiry date / time, and expireDays to 2 days
* Not expired - because the CET timezone adds two hours to the dateTimeWithExpire
*/
cal = Calendar.getInstance();
cal.add(Calendar.DATE, -2);
expired = new ExpiredDate("CET", cal.getTime(), 2);
dateTimeNow = new Date();
if (expired.hasDateExpired(dateTimeNow)) {
System.out.println("expired");
} else {
System.out.println("NOT expired");
}
/* Set the expiry date 1 days, and expireDays = 2
*
* expired
*/
cal = Calendar.getInstance();
cal.add(Calendar.DATE, -1);
expired = new ExpiredDate("CET", cal.getTime(), 2);
dateTimeNow = new Date();
if (expired.hasDateExpired(dateTimeNow)) {
System.out.println("expired");
} else {
System.out.println("NOT expired");
}
}
}
In Java, how would I go about constructing a utility that would take a range of dates (start and end date) and then would see how many times a given partial date ( the month and day-of-month) appears in that range, and will add an entry to a list for each match.
In my instance, I want to give it a range of say 5 years - starting Jan 1st 2014 and going to Dec 31st 2019. My check date is the 2nd August. I want the method to return the full information about each match of any August 2 of any year in the range. So for 2014 is will return Saturday 2nd August 2014, then Sunday 2nd August 2015 etc and so on.
I've been trying to get something working so far with Joda Time and the default date/calendar classes in Java and I'm just getting myself in a mess.
Thanks,
S
Edit: How silly of me, apologies for not adding my code :(
public static List<Date> getDaysInRange(Date startdate,
Date enddate,
Date checkDate) {
SimpleDateFormat sdf = new SimpleDateFormat("MMdd");
List<Date> dates = new ArrayList<>();
Calendar cal = new GregorianCalendar();
cal.setTime(startdate);
while (cal.getTime().before(enddate)) {
if (sdf.format(cal.getTime()).equals(sdf.format(checkDate))) {
Date result = cal.getTime();
dates.add(result);
}
cal.add(Calendar.DATE, 1);
}
return dates;
}
Date-Only
Since you want only a date without time-of-day and without time zone, use a date-only class. The old java.util.Date/.Calendar classes lack such a class. And those old classes are notoriously troublesome and flawed.
Instead use either:
Joda-Time
java.time, built into Java 8, inspired by Joda-Time.
Joda-Time
Here is some untested code using Joda-Time 2.6.
The main idea is to focus on the small set of possible year numbers rather than test every day of year. In the example below, that means six date-time values to compare rather than thousands. Besides efficiency, the purpose of the code becomes more apparent.
The arguments to your routine should be a month number and a day-of-month number, a pair of ints or Integers, rather than a Date. As seen in this examples two int variables, month and day.
LocalDate start = new LocalDate( 2011, 2, 3 );
LocalDate stop = new LocalDate( 2016, 4, 5 );
int yearStart = start.getYear();
int yearStop = stop.getYear();
int month = 11;
int day = 22;
for ( i = yearStart, i <= yearStop, i++ )
{
LocalDate x = new LocalDate( i, month, day );
boolean matchStart = ( x.isEqual( start ) || x.isAfter( start ) );
boolean matchStop = x.isBefore( stop ); // Half-Open approach where beginning of range is inclusive while ending is exclusive.
if ( matchStart && matchStop )
{
// Add to collection of LocalDate objects.
// Later you can ask each LocalDate object for its day-of-week.
{
}
java.time
The java.time package also offers a LocalDate class. The code would be similar to the above Joda-Time example.
I think using SimpleDateFormat is a bad idea. Use Calendar for comparison directly, like this
cal1.get(Calendar.MONTH) == cal2.get(Calendar.MONTH) && cal1.get(Calendar.DAY_OF_MONTH) == cal2.get(Calendar.DAY_OF_MONTH)
I have a requirement where I need to find out number of months between two dates including extra days.
example:
start date:01/01/2014
end date:21/02/2014
LocalDate startDate = new LocalDate(startDate1);
LocalDate endDate = new LocalDate(endDate1);
PeriodType monthDay =PeriodType.yearMonthDay().withYearsRemoved();
Period difference = new Period(startDate, endDate, monthDay);
int months = difference.getMonths();
int days = difference.getDays()
the out put I will get is:
months:1 days:20
but my requirement is I want get total months including that extra day.
like:1.66 months.
How to get this one in java?
In order to be able to say 1.66 months you need to define how long a month is. It's not always the same. If you assume that a month is 30 days long then you can solve this by using:
Date startDate = new SimpleDateFormat("dd/MM/yyyy").parse("01/01/2014");
Date endDate = new SimpleDateFormat("dd/MM/yyyy").parse("21/02/2014");
double result = (endDate.getTime() - startDate.getTime()) / (1000D*60*60*24*30);
This gives us 1.7 and if you divide with 31 you get 1.6451612903225807.
If you want a better (but not perfect) approximation of how long a month is you can try 365/12 which will give you 1.6767123287671233 but still this is not perfect because leap years have 366 days.
The problem though is not with the formula, but with the problem definition. Nobody in real life says "I'll be there in exactly 1.66 months" and nobody will ever ask you to convert 1.66 months in days.
This is my own answer, a variation on cherouvim's
final Date startDate = new GregorianCalendar (2014, 0, 1).getTime ();
final Date endDate = new GregorianCalendar (2014, 1, 21).getTime ();
System.out.println ((endDate.getTime () - startDate.getTime ()) / (float) (1000L * 60 * 60 * 24 * 30));
How can I convert the number of days to number of months and days using Joda-Time. For example, when I have 33 days, it should display 1 month and 2 days.
public static void main(String[]args)
{
Calendar calendar = new GregorianCalendar();
int years = calendar.get(Calendar.YEAR);
int month = (calendar.get(Calendar.MONTH))+1;
int day = calendar.get(Calendar.DATE);
DateTime startDate = new DateTime(years, month, day, 0, 0, 0, 0);
DateTime endDate = new DateTime(2014, 7, 3, 0, 0, 0, 0);
Days d = Days.daysBetween(startDate, endDate);
int days = d.getDays();
int t = 1000 * 60 * 60 *24;
int days = days/t;
System.out.println(days);
}
You can use class org.joda.time.Period for this.
Example:
Period p = new Period(startDate, endDate, PeriodType.yearMonthDayTime());
System.out.println(p.getMonths());
System.out.println(p.getDays());
Trying to create a decimal fraction number to represent months makes no sense to me, as months have different numbers of days (28, 29, 30, 31).
ISO 8601
The sensible ISO 8601 standard defines a textual way to represent a span of time in terms of months and days, called Durations. Joda-Time has a class for this purpose called Period. Forgive the mismatch in terms, as there is no standard definition of date-time terminology yet.
For an example of using the Period class, see this other answer by Ilya on this question.
ISO Duration
The textual format is PnYnMnDTnHnMnS where P means "Period" and the T separates the date portion from time portion. The other parts are optional. One month and two days would be P1M2D. The Joda-Time Period class both parses and generates such strings.
I want to find the difference between two Calendar objects in number of days if there is date change like If clock ticked from 23:59-0:00 there should be a day difference.
i wrote this
public static int daysBetween(Calendar startDate, Calendar endDate) {
return Math.abs(startDate.get(Calendar.DAY_OF_MONTH)-endDate.get(Calendar.DAY_OF_MONTH));
}
but its not working as it only gives difference between days if there is month difference its worthless.
Try the following approach:
public static long daysBetween(Calendar startDate, Calendar endDate) {
long end = endDate.getTimeInMillis();
long start = startDate.getTimeInMillis();
return TimeUnit.MILLISECONDS.toDays(Math.abs(end - start));
}
In Java 8 and later, we could simply use the java.time classes.
hoursBetween = ChronoUnit.HOURS.between(calendarObj.toInstant(), calendarObj.toInstant());
daysBetween = ChronoUnit.DAYS.between(calendarObj.toInstant(), calendarObj.toInstant());
This function computes the number of days between two Calendars as the number of calendar days of the month that are between them, which is what the OP wanted. The calculation is performed by counting how many multiples of 86,400,000 milliseconds are between the calendars after both have been set to midnight of their respective days.
For example, my function will compute 1 day's difference between a Calendar on January 1, 11:59PM and January 2, 12:01AM.
import java.util.concurrent.TimeUnit;
/**
* Compute the number of calendar days between two Calendar objects.
* The desired value is the number of days of the month between the
* two Calendars, not the number of milliseconds' worth of days.
* #param startCal The earlier calendar
* #param endCal The later calendar
* #return the number of calendar days of the month between startCal and endCal
*/
public static long calendarDaysBetween(Calendar startCal, Calendar endCal) {
// Create copies so we don't update the original calendars.
Calendar start = Calendar.getInstance();
start.setTimeZone(startCal.getTimeZone());
start.setTimeInMillis(startCal.getTimeInMillis());
Calendar end = Calendar.getInstance();
end.setTimeZone(endCal.getTimeZone());
end.setTimeInMillis(endCal.getTimeInMillis());
// Set the copies to be at midnight, but keep the day information.
start.set(Calendar.HOUR_OF_DAY, 0);
start.set(Calendar.MINUTE, 0);
start.set(Calendar.SECOND, 0);
start.set(Calendar.MILLISECOND, 0);
end.set(Calendar.HOUR_OF_DAY, 0);
end.set(Calendar.MINUTE, 0);
end.set(Calendar.SECOND, 0);
end.set(Calendar.MILLISECOND, 0);
// At this point, each calendar is set to midnight on
// their respective days. Now use TimeUnit.MILLISECONDS to
// compute the number of full days between the two of them.
return TimeUnit.MILLISECONDS.toDays(
Math.abs(end.getTimeInMillis() - start.getTimeInMillis()));
}
Extension to #JK1 great answer :
public static long daysBetween(Calendar startDate, Calendar endDate) {
//Make sure we don't change the parameter passed
Calendar newStart = Calendar.getInstance();
newStart.setTimeInMillis(startDate.getTimeInMillis());
newStart.set(Calendar.HOUR_OF_DAY, 0);
newStart.set(Calendar.MINUTE, 0);
newStart.set(Calendar.SECOND, 0);
newStart.set(Calendar.MILLISECOND, 0);
Calendar newEnd = Calendar.getInstance();
newEnd.setTimeInMillis(endDate.getTimeInMillis());
newEnd.set(Calendar.HOUR_OF_DAY, 0);
newEnd.set(Calendar.MINUTE, 0);
newEnd.set(Calendar.SECOND, 0);
newEnd.set(Calendar.MILLISECOND, 0);
long end = newEnd.getTimeInMillis();
long start = newStart.getTimeInMillis();
return TimeUnit.MILLISECONDS.toDays(Math.abs(end - start));
}
UPDATE The Joda-Time project, now in maintenance mode, advises migration to the java.time classes. See the Answer by Anees A for the calculation of elapsed hours, and see my new Answer for using java.time to calculate elapsed days with respect for the calendar.
Joda-Time
The old java.util.Date/.Calendar classes are notoriously troublesome and should be avoided.
Instead use the Joda-Time library. Unless you have Java 8 technology in which case use its successor, the built-in java.time framework (not in Android as of 2015).
Since you only care about "days" defined as dates (not 24-hour periods), let's focus on dates. Joda-Time offers the class LocalDate to represent a date-only value without time-of-day nor time zone.
While lacking a time zone, note that time zone is crucial in determining a date such as "today". A new day dawns earlier to the east than to the west. So the date is not the same around the world at one moment, the date depends on your time zone.
DateTimeZone zone = DateTimeZone.forID ( "America/Montreal" );
LocalDate today = LocalDate.now ( zone );
Let's count the number of days until next week, which should of course be seven.
LocalDate weekLater = today.plusWeeks ( 1 );
int elapsed = Days.daysBetween ( today , weekLater ).getDays ();
The getDays on the end extracts a plain int number from the Days object returned by daysBetween.
Dump to console.
System.out.println ( "today: " + today + " to weekLater: " + weekLater + " is days: " + days );
today: 2015-12-22 to weekLater: 2015-12-29 is days: 7
You have Calendar objects. We need to convert them to Joda-Time objects. Internally the Calendar objects have a long integer tracking the number of milliseconds since the epoch of first moment of 1970 in UTC. We can extract that number, and feed it to Joda-Time. We also need to assign the desired time zone by which we intend to determine a date.
long startMillis = myStartCalendar.getTimeInMillis();
DateTime startDateTime = new DateTime( startMillis , zone );
long stopMillis = myStopCalendar.getTimeInMillis();
DateTime stopDateTime = new DateTime( stopMillis , zone );
Convert from DateTime objects to LocalDate.
LocalDate start = startDateTime.toLocalDate();
LocalDate stop = stopDateTime.toLocalDate();
Now do the same elapsed calculation we saw earlier.
int elapsed = Days.daysBetween ( start , stop ).getDays ();
Here's my solution using good old Calendar objects:
public static int daysApart(Calendar d0,Calendar d1)
{
int days=d0.get(Calendar.DAY_OF_YEAR)-d1.get(Calendar.DAY_OF_YEAR);
Calendar d1p=Calendar.getInstance();
d1p.setTime(d1.getTime());
for (;d1p.get(Calendar.YEAR)<d0.get(Calendar.YEAR);d1p.add(Calendar.YEAR,1))
{
days+=d1p.getActualMaximum(Calendar.DAY_OF_YEAR);
}
return days;
}
This assumes d0 is later than d1. If that's not guaranteed, you could always test and swap them.
Basic principle is to take the difference between the day of the year of each. If they're in the same year, that would be it.
But they might be different years. So I loop through all the years between them, adding the number of days in a year. Note that getActualMaximum returns 366 in leap years and 365 in non-leap years. That's why we need a loop, you can't just multiply the difference between the years by 365 because there might be a leap year in there. (My first draft used getMaximum, but that doesn't work because it returns 366 regardless of the year. getMaximum is the maximum for ANY year, not this particular year.)
As this code makes no assumptions about the number of hours in a day, it is not fooled by daylight savings time.
tl;dr
java.time.temporal.ChronoUnit // The java.time classes are built into Java 8+ and Android 26+. For earlier Android, get must of the functionality by using the latest tooling with "API desugaring".
.DAYS // A pre-defined enum object.
.between(
( (GregorianCalendar) startCal ) // Cast from the more abstract `Calendar` to the more concrete `GregorianCalendar`.
.toZonedDateTime() // Convert from legacy class to modern class. Returns a `ZonedDateTime` object.
.toLocalDate() // Extract just the date, to get the Question's desired whole-days count, ignoring fractional days. Returns a `LocalDate` object.
,
( (GregorianCalendar) endCal )
.toZonedDateTime()
.toLocalDate()
) // Returns a number of days elapsed between our pair of `LocalDate` objects.
java.time
The Answer by Mohamed Anees A is correct for hours but wrong for days. Counting days requires a time zone. That other Answer uses the Instant which is a moment in UTC, always in UTC. So you are not getting the correct number of calendar days elapsed.
To count days by the calendar, convert your legacy Calendar to a ZonedDateTime, then feed to ChronoUnit.DAYS.between.
Time zone
A time zone is crucial in determining a date. For any given moment, the date varies around the globe by zone. For example, a few minutes after midnight in Paris France is a new day while still “yesterday” in Montréal Québec.
If no time zone is specified, the JVM implicitly applies its current default time zone. That default may change at any moment during runtime(!), so your results may vary. Better to specify your desired/expected time zone explicitly as an argument. If critical, confirm the zone with your user.
Specify a proper time zone name in the format of Continent/Region, such as America/Montreal, Africa/Casablanca, or Pacific/Auckland. Never use the 2-4 letter abbreviation such as EST or IST as they are not true time zones, not standardized, and not even unique(!).
ZoneId z = ZoneId.of( "America/Montreal" ) ;
LocalDate today = LocalDate.now( z ) ; // Capture the current date as seen through the wall-clock time used by the people of a certain region (a time zone).
If you want to use the JVM’s current default time zone, ask for it and pass as an argument. If omitted, the code becomes ambiguous to read in that we do not know for certain if you intended to use the default or if you, like so many programmers, were unaware of the issue.
ZoneId z = ZoneId.systemDefault() ; // Get JVM’s current default time zone.
Convert from GregorianCalendar to ZonedDateTime
The terrible GregorianCalendar is likely the concrete class behind your Calendar. If so, convert from that legacy class to the modern class, ZonedDateTime.
GregorianCalendar gc = null ; // Legacy class representing a moment in a time zone. Avoid this class as it is terribly designed.
if( myCal instanceof GregorianCalendar ) { // See if your `Calendar` is backed by a `GregorianCalendar` class.
gc = (GregorianCalendar) myCal ; // Cast from the more general class to the concrete class.
ZonedDateTime zdt = gc.toZonedDateTime() ; // Convert from legacy class to modern class.
}
The resulting ZonedDateTime object carries a ZoneId object for the time zone. With that zone in place, you can then calculate elapsed calendar days.
Calculate elapsed days
To calculate the elapsed time in terms of years-months-days, use Period class.
Period p = Period.between( zdtStart , zdtStop ) ;
If you want total number of days as the elapsed time, use ChronoUnit.
long days = ChronoUnit.DAYS.between( zdtStart , zdtStop ) ;
About java.time
The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.* classes.
Where to obtain the java.time classes?
Java SE 8, Java SE 9, Java SE 10, Java SE 11, and later - Part of the standard Java API with a bundled implementation.
Java 9 adds some minor features and fixes.
Java SE 6 and Java SE 7
Most of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
Android
Later versions of Android bundle implementations of the java.time classes.
For earlier Android (<26), the ThreeTenABP project adapts ThreeTen-Backport (mentioned above). See How to use ThreeTenABP….
The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.
I have the similar (not exact same) approach given above by https://stackoverflow.com/a/31800947/3845798.
And have written test cases around the api, for me it failed if I passed
8th march 2017 - as the start date and 8th apr 2017 as the end date.
There are few dates where you will see the difference by 1day.
Therefore, I have kind of made some small changes to my api and my current api now looks something like this
public long getDays(long currentTime, long endDateTime) {
Calendar endDateCalendar;
Calendar currentDayCalendar;
//expiration day
endDateCalendar = Calendar.getInstance(TimeZone.getTimeZone("EST"));
endDateCalendar.setTimeInMillis(endDateTime);
endDateCalendar.set(Calendar.MILLISECOND, 0);
endDateCalendar.set(Calendar.MINUTE, 0);
endDateCalendar.set(Calendar.HOUR, 0);
endDateCalendar.set(Calendar.HOUR_OF_DAY, 0);
//current day
currentDayCalendar = Calendar.getInstance(TimeZone.getTimeZone("EST"));
currentDayCalendar.setTimeInMillis(currentTime);
currentDayCalendar.set(Calendar.MILLISECOND, 0);
currentDayCalendar.set(Calendar.MINUTE, 0);
currentDayCalendar.set(Calendar.HOUR,0);
currentDayCalendar.set(Calendar.HOUR_OF_DAY, 0);
long remainingDays = (long)Math.ceil((float) (endDateCalendar.getTimeInMillis() - currentDayCalendar.getTimeInMillis()) / (24 * 60 * 60 * 1000));
return remainingDays;}
I am not using TimeUnit.MILLISECONDS.toDays that were causing me some issues.
Kotlin solution, purely relies on Calendar. At the end gives exact number of days difference.
Inspired by #Jk1
private fun daysBetween(startDate: Calendar, endDate: Calendar): Long {
val start = Calendar.getInstance().apply {
timeInMillis = 0
set(Calendar.DAY_OF_YEAR, startDate.get(Calendar.DAY_OF_YEAR))
set(Calendar.YEAR, startDate.get(Calendar.YEAR))
}.timeInMillis
val end = Calendar.getInstance().apply {
timeInMillis = 0
set(Calendar.DAY_OF_YEAR, endDate.get(Calendar.DAY_OF_YEAR))
set(Calendar.YEAR, endDate.get(Calendar.YEAR))
}.timeInMillis
val differenceMillis = end - start
return TimeUnit.MILLISECONDS.toDays(differenceMillis)
}
If your project doesn't support new Java 8 classes (as selected answer), you can add this method to calculate the days without being influenced by timezones or other facts.
It is not as fast (greater time complexity) as other methods but it's reliable, anyways date comparisons are rarely larger than hundreds or thousands of years.
(Kotlin)
/**
* Returns the number of DAYS between two dates. Days are counted as calendar days
* so that tomorrow (from today date reference) will be 1 , the day after 2 and so on
* independent on the hour of the day.
*
* #param date - reference date, normally Today
* #param selectedDate - date on the future
*/
fun getDaysBetween(date: Date, selectedDate: Date): Int {
val d = initCalendar(date)
val s = initCalendar(selectedDate)
val yd = d.get(Calendar.YEAR)
val ys = s.get(Calendar.YEAR)
if (ys == yd) {
return s.get(Calendar.DAY_OF_YEAR) - d.get(Calendar.DAY_OF_YEAR)
}
//greater year
if (ys > yd) {
val endOfYear = Calendar.getInstance()
endOfYear.set(yd, Calendar.DECEMBER, 31)
var daysToFinish = endOfYear.get(Calendar.DAY_OF_YEAR) - d.get(Calendar.DAY_OF_YEAR)
while (endOfYear.get(Calendar.YEAR) < s.get(Calendar.YEAR)-1) {
endOfYear.add(Calendar.YEAR, 1)
daysToFinish += endOfYear.get(Calendar.DAY_OF_YEAR)
}
return daysToFinish + s.get(Calendar.DAY_OF_YEAR)
}
//past year
else {
val endOfYear = Calendar.getInstance()
endOfYear.set(ys, Calendar.DECEMBER, 31)
var daysToFinish = endOfYear.get(Calendar.DAY_OF_YEAR) - s.get(Calendar.DAY_OF_YEAR)
while (endOfYear.get(Calendar.YEAR) < d.get(Calendar.YEAR)-1) {
endOfYear.add(Calendar.YEAR, 1)
daysToFinish += endOfYear.get(Calendar.DAY_OF_YEAR)
}
return daysToFinish + d.get(Calendar.DAY_OF_YEAR)
}
}
Unit Tests, you can improve them I didn't need the negative days so I didn't test that as much:
#Test
fun `Test days between on today and following days`() {
val future = Calendar.getInstance()
calendar.set(2019, Calendar.AUGUST, 26)
future.set(2019, Calendar.AUGUST, 26)
Assert.assertEquals(0, manager.getDaysBetween(calendar.time, future.time))
future.set(2019, Calendar.AUGUST, 27)
Assert.assertEquals(1, manager.getDaysBetween(calendar.time, future.time))
future.set(2019, Calendar.SEPTEMBER, 1)
Assert.assertEquals(6, manager.getDaysBetween(calendar.time, future.time))
future.set(2020, Calendar.AUGUST, 26)
Assert.assertEquals(366, manager.getDaysBetween(calendar.time, future.time)) //leap year
future.set(2022, Calendar.AUGUST, 26)
Assert.assertEquals(1096, manager.getDaysBetween(calendar.time, future.time))
calendar.set(2019, Calendar.DECEMBER, 31)
future.set(2020, Calendar.JANUARY, 1)
Assert.assertEquals(1, manager.getDaysBetween(calendar.time, future.time))
}
#Test
fun `Test days between on previous days`() {
val future = Calendar.getInstance()
calendar.set(2019, Calendar.AUGUST, 26)
future.set(2019,Calendar.AUGUST,25)
Assert.assertEquals(-1, manager.getDaysBetween(calendar.time, future.time))
}
#Test
fun `Test days between hour doesn't matter`() {
val future = Calendar.getInstance()
calendar.set(2019, Calendar.AUGUST, 26,9,31,15)
future.set(2019,Calendar.AUGUST,28, 7,0,0)
Assert.assertEquals(2, manager.getDaysBetween(calendar.time, future.time))
future.set(2019,Calendar.AUGUST,28, 9,31,15)
Assert.assertEquals(2, manager.getDaysBetween(calendar.time, future.time))
future.set(2019,Calendar.AUGUST,28, 23,59,59)
Assert.assertEquals(2, manager.getDaysBetween(calendar.time, future.time))
}
#Test
fun `Test days between with time saving change`() {
val future = Calendar.getInstance()
calendar.set(2019, Calendar.OCTOBER, 28)
future.set(2019, Calendar.OCTOBER,29)
Assert.assertEquals(1, manager.getDaysBetween(calendar.time, future.time))
future.set(2019, Calendar.OCTOBER,30)
Assert.assertEquals(2, manager.getDaysBetween(calendar.time, future.time))
}
public int getIntervalDays(Calendar c1,Calendar c2){
Calendar first = cleanTimePart(c1);
Calendar second = cleanTimePart(c2);
Long intervalDays = (first.getTimeInMillis() - second.getTimeInMillis())/(1000*3600*24);
return intervalDays.intValue();
}
private Calendar cleanTimePart(Calendar dateTime){
Calendar newDateTime = (Calendar)dateTime.clone();
newDateTime.set(Calendar.HOUR_OF_DAY,0);
newDateTime.set(Calendar.MINUTE,0);
newDateTime.set(Calendar.SECOND,0);
newDateTime.set(Calendar.MILLISECOND,0);
return newDateTime;
}
Calendar day1 = Calendar.getInstance(); Calendar day2 = Calendar.getInstance(); int diff = day1.get(Calendar.DAY_OF_YEAR)
- day2.get(Calendar.DAY_OF_YEAR);