Java Calendar: Getting Difference Between Two Dates/Times - Off by One

…衆ロ難τιáo~ 提交于 2019-12-08 17:36:19

问题


I have seen many questions and answers on this topic, but none addressed my particular problem. I extended the java Calendar class (standard--no third party libraries), and needed to find the difference in days between two arbitrary dates.

Method:

  1. Change the time of both dates to midnight.
  2. Convert the dates to milliseconds.
  3. Find the difference between the two dates.
  4. Divide the result by the number of milliseconds in a day (24 * 60 * 60 * 1000).
  5. The result should be the difference in days.

And it sometimes is, and it sometimes isn't. Even tests on the same date can be off by one. What's going on?


回答1:


As suggested by other users, you need to also set the millisecond value of the calendars to zero to compare only the dates. This can be achieved with the following code snippet:

someCalendar.set(Calendar.MILLISECOND, 0)

Also, note that timezone changes (e.g. moving from winter time to summer time) may mean there is more or less than 86,400,000 ms in a day.




回答2:


The Joda Time Library has very good support for such problems:

LocalDate d1 = new LocalDate(calendar1.getTimeInMillis());
LocalDate d2 = new LocalDate(calendar2.getTimeInMillis());
int days = Days.daysBetween(d1, d2).getDays();

UPDATE (feedback from @basil-bourque):

As of Java 8 the new time library java.time has been introduced, now a similar option without external dependencies is available:

int days = Duration.between(calendar1.toInstant(), calendar2.toInstant()).toDays();



回答3:


tl;dr

ChronoUnit.DAYS.between( then , now )

Naïve calculations

You assume in your code that every day is exactly twenty four hours long. Not true. Anomalies such as Daylight Saving Time (DST) mean that days may vary such as 23 hours long, 25 hours, or other numbers. The very meaning of a time zone is to track a history of these anomalies.

Also, you assume the day starts at midnight. Not true. Because of anomalies, some days start at other time-of-day such as 01:00.

Avoid legacy date-time classes

You are using the troublesome old date-time classes such as java.util.Date, java.util.Calendar, and java.text.SimpleTextFormat are now legacy, supplanted by the java.time classes.

Time zone

Specify a proper time zone name in the format of continent/region, such as America/Montreal, Africa/Casablanca, or Pacific/Auckland. Never use the 3-4 letter abbreviation such as EST or IST as they are not true time zones, not standardized, and not even unique(!).

Do not extend java.time

Do not extend (subclass) the java.time classes (they are marked final).

And do not generalize to their interfaces for your business logic; stick to the concrete classes of this framework. While generalizing makes sense in other frameworks such as Java Collections, not so in java.time.

Using java.time

This work is much easier with the java.time classes.

ZonedDateTime represents a moment on the timeline with an assigned tim zone (ZoneId).

ZoneId z = ZoneId.of( "America/Montreal" );

// of(int year, int month, int dayOfMonth, int hour, int minute, int second, int nanoOfSecond, ZoneId zone)
ZonedDateTime then = ZonedDateTime.of( 2017 , 1 , 23 , 12 , 34 , 56 , 123456789 , z );

ZonedDateTime now = ZonedDateTime.now( z );

The ChronoUnit enum can calculate elapsed time between a pair of moments.

long days = ChronoUnit.DAYS.between( then , now );

See this code run live at IdeOne.com.

then.toString(): 2017-01-23T12:34:56.123456789-05:00[America/Montreal]

now.toString(): 2017-03-01T21:26:04.884-05:00[America/Montreal]

days: 37

Converting legacy instances to java.time

If you are given GregorianCalendar objects, convert to java.time using new methods added to the old classes.

ZonedDateTime zdt = myGregCal.toZonedDateTime() ;

If you know a Calendar instance is actually a GregorianCalendar, cast it.

GregorianCalendar myGregCal = (GregorianCalendar) myCal ;

Half-Open

The java.time classes define spans of time by the Half-Open approach where the beginning is inclusive while the ending is exclusive.


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 and 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 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.




回答4:


I have written a much more simpler way to tackle this problem, even i faced the same problem of having one day extra for some dates.

This is my approach currently,

    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);

    //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);

    long remainingDays = Math.round((float) (endDateCalendar.getTimeInMillis() - currentDayCalendar.getTimeInMillis()) / (24 * 60 * 60 * 1000));

    return remainingDays;
}

previously I was using this piece of line, rest is same :-

long remainingDays = TimeUnit.MILLISECONDS.toDays(
            Math.abs(expiryCalendar.getTimeInMillis() - currentDayCalendar.getTimeInMillis()));

But seems to be not working for me.




回答5:


You recognized the issue in step one by setting the time to midnight: that made sure that all the hours, minutes, and seconds were at zero. But you didn't go far enough! You also have to make sure that the milliseconds are zeroed out, too.

Here's some code to do exactly that.

protected long truncate_to_seconds (long date_in_millis) {
    long l = date_in_millis / 1000L;
    l *= 1000L;
    return l;
} 



回答6:


I believe you need to truncate the time part from the date before computing the difference as below:

   DateFormat formatter= new SimpleDateFormat("MM/dd/yyyy");
   String truncatedDateString1 = formatter.format(date1);
   Date truncatedDate1 = formatter.parse(truncatedDateString1);

   String truncatedDateString2 = formatter.format(date2);
   Date truncatedDate2 = formatter.parse(truncatedDateString2);

   long timeDifference = truncatedDate2.getTime()- truncatedDate1.getTime();

   int daysInBetween = timeDifference / (24*60*60*1000);

Hope this works.




回答7:


I have my function as purpose at your request. I was using calendar, i set all the time part to 0 before compare.

    int countDays(Date dateBegin, Date dateEnd) {
    if (dateBegin == null || dateEnd == null)
        return 0;
    Calendar from = asCalendar(dateBegin); // asCalendar() Initialise a Calendar from a Date
    Calendar to = asCalendar(dateEnd);
    // Set the time part to 0
    from.set(Calendar.MILLISECOND, 0);
    from.set(Calendar.SECOND, 0);
    from.set(Calendar.MINUTE, 0);
    from.set(Calendar.HOUR_OF_DAY, 0);
    to.set(Calendar.MILLISECOND, 0);
    to.set(Calendar.SECOND, 0);
    to.set(Calendar.MINUTE, 0);
    to.set(Calendar.HOUR_OF_DAY, 0);
    int nbJours = 0;
    for (Calendar c = from ; c.before(to) ; c.add(Calendar.DATE, +1))
    {
        nbJours++;
    }
    for (Calendar c = from ; c.after(to) ; c.add(Calendar.DATE, -1))
    {
        nbJours--;
    }
    return nbJours;
    }


来源:https://stackoverflow.com/questions/13198609/java-calendar-getting-difference-between-two-dates-times-off-by-one

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!