我看到许多有关此主题的问题和答案,但没有人解决我的特定问题。我扩展了java Calendar类(标准 - 没有第三方库),需要找到两个任意日期之间的天数差异。
方法:
- 将两个日期的时间更改为午夜。
- 将日期转换为毫秒。
- 找到两个日期之间的差异。
- 将结果除以一天中的毫秒数(24 * 60 * 60 * 1000)。
- 结果应该是天数的差异。
有时候是这样,有时候不是这样。 即使在同一日期上的测试也可能相差一天。 发生了什么?
Joda Time库对此类问题提供了非常好的支持:
LocalDate d1 = new LocalDate(calendar1.getTimeInMillis());
LocalDate d2 = new LocalDate(calendar2.getTimeInMillis());
int days = Days.daysBetween(d1, d2).getDays();
更新(来自@basil-bourque的反馈):
从Java 8开始,新的时间库java.time
被引入,现在有一种类似的选项可以不依赖外部库来实现:
int days = Duration.between(calendar1.toInstant(), calendar2.toInstant()).toDays();
java.time
版本。 - Arne BurmeisterLocalDateTime
不适合这种情况。该类有意丢失所有偏移和时区信息。因此,您将使用通用的24小时制天数,并忽略表示夏令时(DST)等时区中的异常情况。请参见我的答案获取详细信息。 - Basil Bourquejava.util.Calendar.toInstant()
可在Java 8运行时库中使用。 - Arne Burmeister正如其他用户建议的那样,您还需要将日历的毫秒值设置为零,以仅比较日期。可以使用以下代码片段实现:
someCalendar.set(Calendar.MILLISECOND, 0)
另外,请注意时区的变化(例如从冬令时到夏令时的转换)可能导致一天内的毫秒数少于或多于86,400,000。
ChronoUnit.DAYS.between( then , now )
在你的代码中,你假设每一天都是精确的二十四小时。这是不正确的。例如夏令时(DST)等异常情况意味着一天可能会有23个小时、25个小时或其他数字。时区的意义在于跟踪这些异常的历史记录。
此外,你假设一天从午夜开始。这也是不正确的。由于异常情况,一些日子从其他时间开始,比如01:00
。
java.util.Date
、java.util.Calendar
和 java.text.SimpleTextFormat
,现在已经被 遗弃,被 java.time 类所取代。
请以continent/region
的格式指定正确的时区名称,例如America/Montreal
、Africa/Casablanca
或Pacific/Auckland
。永远不要使用三到四个字母的缩写,比如EST
或IST
,因为它们不是真正的时区,没有标准化,甚至不唯一(!)。
不要扩展(子类化)java.time类(它们被标记为final
)。
也不要将其接口泛化为您的业务逻辑;坚持使用该框架的具体类。虽然在其他框架中泛化是有意义的,比如Java Collections,但在java.time中不是这样。
ZonedDateTime
代表时间线上带有指定时区(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 );
ChronoUnit
枚举可以计算一对时刻之间的经过时间。
long days = ChronoUnit.DAYS.between( then , now );
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
如果您有GregorianCalendar
对象,请使用添加到旧类中的新方法将其转换为java.time。
ZonedDateTime zdt = myGregCal.toZonedDateTime() ;
Calendar
实例实际上是一个 GregorianCalendar
,请将其转换。GregorianCalendar myGregCal = (GregorianCalendar) myCal ;
java.time类采用半开区间的方式定义时间跨度,即起始时间是包含在内的,而结束时间是不包含在内的。
java.time 框架已内置于 Java 8 及以上版本。这些类替代了老旧的 遗留 日期时间类,例如 java.util.Date
、Calendar
和 SimpleDateFormat
。
Joda-Time 项目现在处于维护模式,建议迁移到 java.time 类。
要了解更多信息,请参见Oracle教程。在Stack Overflow上搜索许多示例和解释。规范是JSR 310。
如何获取java.time类?
ThreeTen-Extra项目扩展了java.time的附加类。该项目是java.time可能未来添加的试验场。你可能会在这里找到一些有用的类,例如Interval
, YearWeek
, YearQuarter
和更多。
我已经写了一种更简单的方法来解决这个问题,即使我也遇到了某些日期多出一天的问题。
这是我的当前方法,
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;
}
之前我使用了这段代码,其余部分相同:
long remainingDays = TimeUnit.MILLISECONDS.toDays(
Math.abs(expiryCalendar.getTimeInMillis() - currentDayCalendar.getTimeInMillis()));
在第一步中,您通过将时间设置为午夜来识别问题:这确保了所有小时、分钟和秒都为零。但是您还没有走得远!您还必须确保毫秒也被归零。
下面是一些代码来确保这一点。
protected long truncate_to_seconds (long date_in_millis) {
long l = date_in_millis / 1000L;
l *= 1000L;
return l;
}
set(Calendar.MILLISECOND, 0)
将毫秒数归零。 - Duncan Jones我认为在计算差异之前,您需要从日期中截取时间部分,如下所示:
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);
我已按照您的要求编写了函数。我使用了日历,将所有时间部分设置为0后再进行比较。
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;
}
Calendar
而不是生成一个操作Calendar
实例的辅助库是否是一个好主意呢? - Duncan Jones