我正在使用jdk 1.7并使用Calendar类计算两个日期之间的差。我正在使用下面的代码,但结果不一致。意思是有时是正确的,但有时是一天或一天的时间,并且没有任何规律。
public class Test {
public static void main(String ar[]) {
System.out.println(calculateDays());
}
private static long calculateDays() {
long days_past_due;
Calendar cal1 = Calendar.getInstance();
Calendar cal2 = Calendar.getInstance();
cal1.set(2013, 12, 1);
cal2.set(2013, 12, 30);
days_past_due = getDifference(cal1, cal2, TimeUnit.DAYS);
return days_past_due;
}
public static long getDifference(Calendar b, Calendar a, TimeUnit units)
{
return units.convert(b.getTimeInMillis() - a.getTimeInMillis(), TimeUnit.MILLISECONDS);
}
示例:
Example 1: cal1.set(2013, 12, 1);
cal2.set(2013, 12, 1);
Answer returned: 0 (Correct)
Example 2: cal1.set(2013, 12, 1);
al2.set(2013, 12, 2);
Answer returned: -1 (Correct)
Example 3: cal1.set(2013, 11, 30);
cal2.set(2013, 12, 1);
Answer returned: -2 (Incorrect)
Example 4: cal1.set(2013, 8, 31);
cal2.set(2013, 8, 31);
Answer returned: 0 (Correct)
Example 5: cal1.set(2013, 8, 31);
cal2.set(2013, 9, 1);
Answer returned: 0 (Incorrect)
Example 6: cal1.set(2013, 6, 30);
cal2.set(2013, 6, 30);
Answer returned: 0 (Correct)
Example 7: cal1.set(2013, 6, 30);
cal2.set(2013, 7, 1);
Answer returned: -2 (Incorrect)
我在这里做错了什么?
答案 0 :(得分:2)
您应该使用LocalDate
进行合理的计数,而不是麻烦的Calendar
类。
ChronoUnit.DAYS.between(
LocalDate.of( 2013 , 11 , 30 ) ,
LocalDate.of( 2013 , 12 , 1 )
)
请参阅此code run live at IdeOne.com。
1
您似乎没有意识到可怕的Calendar
类中使用的疯狂计数:从1月到12月的0-11个月。
所以cal1.set(2013, 11, 30)
至cal2.set(2013, 12, 1)
的意思是10月30日至11月1日,实际上是两天,占10月31日。您显然误认为这是11月30日至12月1日,但没有。 / p>
这是index-counting, zero-based。不幸的是,索引计数经常在某些程序员中经常出现,他们不恰当地使用原始数组或系统编程或老式C风格编程中的内存跳转的意义。在使用现代语言的常见商业应用程序中,序数计数通常更有意义。例如,将1月视为第一个月(第1个月),将12月视为第十二个月(第12个月)。
幸运的是,我们现在有了 java.time 类。没有理由让您使用可怕的混乱,它们是诸如Date
,Calendar
和SimpleDateFormat
之类的传统日期时间类。
要在Java 7中使用,请参见下面底部的项目符号中链接的 ThreeTen-Backport 项目。
LocalDate
LocalDate
类表示没有日期和时区的仅日期值。
时区对于确定日期至关重要。在任何给定时刻,日期都会在全球范围内变化。例如,Paris France午夜之后的几分钟是新的一天,而Montréal Québec仍然是“昨天”。
如果未指定时区,则JVM隐式应用其当前的默认时区。该默认值可能在运行时(!)期间change at any moment,因此您的结果可能会有所不同。最好将desired/expected time zone明确指定为参数。
以continent/region
的格式指定proper time zone name,例如America/Montreal
,Africa/Casablanca
或Pacific/Auckland
。切勿使用3-4个字母的缩写,例如EST
或IST
,因为它们不是真正的时区,不是标准化的,甚至不是唯一的(!)。
ZoneId z = ZoneId.of( "America/Montreal" ) ;
LocalDate today = LocalDate.now( z ) ;
如果要使用JVM的当前默认时区,请提出要求并作为参数传递。如果省略,则会隐式应用JVM的当前默认值。最好明确一点,因为默认值可能会在运行时的任何时候被JVM中任何应用程序的任何线程中的任何代码更改。
ZoneId z = ZoneId.systemDefault() ; // Get JVM’s current default time zone.
或指定日期。您可以用数字设置月份,一月至十二月的理智编号为1-12。
LocalDate ld = LocalDate.of( 1986 , 2 , 23 ) ; // Years use sane direct numbering (1986 means year 1986). Months use sane numbering, 1-12 for January-December.
或者更好的是,使用预定义的Month
枚举对象,每年的每个月使用一个。提示:在整个代码库中使用这些Month
对象,而不是仅使用整数,可以使您的代码更具自文档性,确保有效值并提供type-safety。
LocalDate ld = LocalDate.of( 1986 , Month.FEBRUARY , 23 ) ;
ChronoUnit.DAYS
要计算经过的天数,请使用ChronoUnit
枚举。
long days = ChronoUnit.DAYS.between( start , stop ) ;
java.time框架已内置在Java 8及更高版本中。这些类取代了麻烦的旧legacy日期时间类,例如java.util.Date
,Calendar
和SimpleDateFormat
。
目前位于Joda-Time的maintenance mode项目建议迁移到java.time类。
要了解更多信息,请参见Oracle Tutorial。并在Stack Overflow中搜索许多示例和说明。规格为JSR 310。
您可以直接与数据库交换 java.time 对象。使用符合JDBC driver或更高版本的JDBC 4.2。不需要字符串,不需要java.sql.*
类。
在哪里获取java.time类?
ThreeTen-Extra项目使用其他类扩展了java.time。该项目为将来可能在java.time中添加内容提供了一个试验场。您可能会在这里找到一些有用的类,例如Interval
,YearWeek
,YearQuarter
和more。
答案 1 :(得分:1)
Calendar#set(int,int,int)
的Javadoc说(我的重点)
参数:
year-用于设置YEAR日历字段的值。
month-用于设置MONTH日历字段的值。 月份值从0开始。例如,一月为0。
date-用于设置DAY_OF_MONTH日历字段的值。
如果您牢记这一点重新检查示例,并记住日期被“规范化”(即,如果您指定第13个月成为下一年的第一个月),则会发现所有计算出的差异实际上是正确的。
答案 2 :(得分:0)
那么什么是最简单的方法呢?使用相同的Calendar类
使用Calendar
没有更简单的方法。有一种更麻烦的广告容易出错的方式。
首先,您需要正确初始化两个Calendar
对象:
Calendar cal1 = Calendar.getInstance();
Calendar cal2 = Calendar.getInstance();
cal1.clear();
cal2.clear();
cal1.set(2013, Calendar.NOVEMBER, 30);
cal2.set(2013, Calendar.DECEMBER, 1);
Calendar
对象,尽管其名称代表日期,一天中的时间,时区和日历系统。当我们只想将其用于某个时区的公历中的某个日期时,我们需要首先清除字段以摆脱一天中的时间,否则它将干扰后续的计算。接下来,将Calendar
类的命名常量用于您希望设置的日期月份,以减少混乱。
第二,让Calendar
考虑到夏令时(DST)和其他异常之间的过渡,除了增加天数并查看我们何时到达别无其他方法,这是相反的做法:
if (cal1.before(cal2)) {
int daysPastDue = 0;
while (! cal1.after(cal2)) {
daysPastDue++;
cal1.add(Calendar.DATE, 1);
}
// Now cal1 is after cal2, so we’ve counted 1 day too many. Compensate:
daysPastDue--;
System.out.println("Answer returned: " + daysPastDue);
}
此打印:
答案返回:1
我当然不鼓励按照我所展示的方式进行。这是相对多的代码行,而且很容易忘记一个细节并得到错误的结果或犯错。如果可以,请升级到Java 8或9或10或11。如果不能,Basil Bourque’s answer也可以与ThreeTen Backport库一起很好地工作,请参阅他关于Java SE 6和Java SE 7的项目符号。
PS:尽管TimeUnit
枚举非常适合微秒到小时之间的转换,但请不要使用它几天,至少在这种情况下不要使用。 TimeUnit.DAYS
可以算24小时,而日历中的天数可能是23、24或25小时,有时还有其他时长。