Java 日期比较差一天
Java date comparison off by a day
我有一个 Java
方法可以比较两个 Dates
和 returns 它们之间的天数,但相差一天。
即使我将小时、分钟和秒设为 0,计算仍然关闭。
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;
}
输出
Today = 1453939200030 Expiration = 1454544000000
1/28 到 2/4 之间有 7 天,但是这个 returns 6.
好吧,如您所见,您没有清除毫秒数,1454544000000 - 1453939200030 = 604799970
除以 86400000
得到 6.99999965277777...
,这意味着 6
当截断为 int
.
现在,如果你也清除毫秒,今天就变成了1453939200000
,这将导致你回答7
。
注意:这并不意味着你已经完成了,因为夏令时。使用 DST,其中一个时间戳可能与另一个时间戳相差 ±1 小时,因此您仍然可能遇到截断问题。
这是对您的特定问题的回答。尝试搜索如何在 Java.
中正确查找日期之间的天数
Today = 1453939200030
时间以毫秒为单位,看起来你输入的 Date
多了 30 毫秒。
当我减去 30 毫秒,然后在计算器上计算时,我得到 7 天。按照你的数字,我得到 6.9999996527777777777777777777778
,在 long
数学中,小数被截断为 6
。
也将毫秒归零。
expDate.set(Calendar.MILLISECOND, 0);
todayDate.set(Calendar.MILLISECOND, 0);
java.time
问题和其他答案使用过时的 classes。旧的日期时间 classes,例如 java.util.Date/.Calendar 与最早版本的 Java 捆绑在一起,已被证明是相当麻烦的。那些旧的 classes 已被 Java 8 及更高版本中的 java.time 框架所取代。
正如其他答案正确指出的那样,问题是开始 long
在右侧有 30
,排除了全天计算。
天数定义
此外,您必须定义天数的含义。您的意思是按日期计数,所以 1 月 3 日的任何时间到 4 日的任何时间都是一天,即使时间是午夜前后一分钟?还是您指的是通用 24 小时时间块的计数,而忽略了由于夏令时 (DST) 和其他异常情况,特定时区的特定日期并不总是 24 小时的事实?
按日期计算天数
如果你想要前者,按日期计算,然后使用 LocalDate
class(没有时间和时区的仅日期)和 Period
class(定义为年、月、日计数的时间跨度)在 java.time.
中找到
定义您的输入。使用 long
而不是 int
。这些数字显然表示自 1970 年第一刻以来的毫秒数(UTC)。
long startMilli = 1_453_939_200_030L;
long stopMilli = 1_454_544_000_000L;
将那些 long
个数字转换为 Instant
个对象,UTC 时间轴上的一个时刻。
Instant startInstant = Instant.ofEpochMilli ( startMilli );
Instant stopInstant = Instant.ofEpochMilli ( stopMilli );
定义要考虑日历日期的时区。请注意,时区对于定义日期至关重要。全球各地的日期并不相同。日期因时区而异。
ZoneId zoneId = ZoneId.of ( "America/Montreal" );
将该时区应用于每个 Instant
以生成 ZonedDateTime
。
ZonedDateTime startZdt = ZonedDateTime.ofInstant ( startInstant , zoneId );
ZonedDateTime stopZdt = ZonedDateTime.ofInstant ( stopInstant , zoneId );
要获得 Period
,我们需要“本地”日期。 “本地”是指任何特定地点,通用日期值。 LocalDate
class 不包含时区,但在确定 LocalDate
.
时应用 ZonedDateTime
中包含的时区
LocalDate startLocalDate = startZdt.toLocalDate ();;
LocalDate stopLocalDate = stopZdt.toLocalDate ();
将我们的时间跨度定义为通用天数,单位为 Period
。
Period period = Period.between ( startLocalDate , stopLocalDate );
询问 Period
以询问其中包含的通用天数。
int days = period.getDays ();
转储到控制台。
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
天数
如果你想要一整天的计数,使用Days
class from ThreeTen-Extra。请注意,在下面的输出中,我们得到 六 (6) 天的计数,而不是上面看到的七 (7)。
三十加
ThreeTen-Extra 项目扩展了 java.time。 运行 由构建 java.time.
的同一批人
between
方法的行为没有明确记录。实验表明,它似乎是基于 24 小时制的时间块,而不是日期。将 030
替换为 000
,并尝试将 stopMilli 中的最后一个 000
替换为 030
,以查看您自己的行为。
Days daysObject = Days.between ( startZdt , stopZdt );
int daysObjectCount = daysObject.getAmount ();
转储到控制台。您在输出中看到的 P6D
字符串是根据 ISO 8601 标准中定义的格式生成的。 java.time 默认使用此标准来解析和生成日期时间值的文本表示。这些标准格式非常明智和有用,所以请浏览链接的维基百科页面。
System.out.println ( "daysObject: " + daysObject + " | daysObjectCount: " + daysObjectCount );
daysObject: P6D | daysObjectCount: 6
为了解决我的问题,我已将毫秒数归零,并将长数转换为双数,以在必要时保持准确性和舍入。
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);
我有一个 Java
方法可以比较两个 Dates
和 returns 它们之间的天数,但相差一天。
即使我将小时、分钟和秒设为 0,计算仍然关闭。
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;
}
输出
Today = 1453939200030 Expiration = 1454544000000
1/28 到 2/4 之间有 7 天,但是这个 returns 6.
好吧,如您所见,您没有清除毫秒数,1454544000000 - 1453939200030 = 604799970
除以 86400000
得到 6.99999965277777...
,这意味着 6
当截断为 int
.
现在,如果你也清除毫秒,今天就变成了1453939200000
,这将导致你回答7
。
注意:这并不意味着你已经完成了,因为夏令时。使用 DST,其中一个时间戳可能与另一个时间戳相差 ±1 小时,因此您仍然可能遇到截断问题。
这是对您的特定问题的回答。尝试搜索如何在 Java.
中正确查找日期之间的天数Today = 1453939200030
时间以毫秒为单位,看起来你输入的 Date
多了 30 毫秒。
当我减去 30 毫秒,然后在计算器上计算时,我得到 7 天。按照你的数字,我得到 6.9999996527777777777777777777778
,在 long
数学中,小数被截断为 6
。
也将毫秒归零。
expDate.set(Calendar.MILLISECOND, 0);
todayDate.set(Calendar.MILLISECOND, 0);
java.time
问题和其他答案使用过时的 classes。旧的日期时间 classes,例如 java.util.Date/.Calendar 与最早版本的 Java 捆绑在一起,已被证明是相当麻烦的。那些旧的 classes 已被 Java 8 及更高版本中的 java.time 框架所取代。
正如其他答案正确指出的那样,问题是开始 long
在右侧有 30
,排除了全天计算。
天数定义
此外,您必须定义天数的含义。您的意思是按日期计数,所以 1 月 3 日的任何时间到 4 日的任何时间都是一天,即使时间是午夜前后一分钟?还是您指的是通用 24 小时时间块的计数,而忽略了由于夏令时 (DST) 和其他异常情况,特定时区的特定日期并不总是 24 小时的事实?
按日期计算天数
如果你想要前者,按日期计算,然后使用 LocalDate
class(没有时间和时区的仅日期)和 Period
class(定义为年、月、日计数的时间跨度)在 java.time.
定义您的输入。使用 long
而不是 int
。这些数字显然表示自 1970 年第一刻以来的毫秒数(UTC)。
long startMilli = 1_453_939_200_030L;
long stopMilli = 1_454_544_000_000L;
将那些 long
个数字转换为 Instant
个对象,UTC 时间轴上的一个时刻。
Instant startInstant = Instant.ofEpochMilli ( startMilli );
Instant stopInstant = Instant.ofEpochMilli ( stopMilli );
定义要考虑日历日期的时区。请注意,时区对于定义日期至关重要。全球各地的日期并不相同。日期因时区而异。
ZoneId zoneId = ZoneId.of ( "America/Montreal" );
将该时区应用于每个 Instant
以生成 ZonedDateTime
。
ZonedDateTime startZdt = ZonedDateTime.ofInstant ( startInstant , zoneId );
ZonedDateTime stopZdt = ZonedDateTime.ofInstant ( stopInstant , zoneId );
要获得 Period
,我们需要“本地”日期。 “本地”是指任何特定地点,通用日期值。 LocalDate
class 不包含时区,但在确定 LocalDate
.
ZonedDateTime
中包含的时区
LocalDate startLocalDate = startZdt.toLocalDate ();;
LocalDate stopLocalDate = stopZdt.toLocalDate ();
将我们的时间跨度定义为通用天数,单位为 Period
。
Period period = Period.between ( startLocalDate , stopLocalDate );
询问 Period
以询问其中包含的通用天数。
int days = period.getDays ();
转储到控制台。
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
天数
如果你想要一整天的计数,使用Days
class from ThreeTen-Extra。请注意,在下面的输出中,我们得到 六 (6) 天的计数,而不是上面看到的七 (7)。
三十加
ThreeTen-Extra 项目扩展了 java.time。 运行 由构建 java.time.
的同一批人between
方法的行为没有明确记录。实验表明,它似乎是基于 24 小时制的时间块,而不是日期。将 030
替换为 000
,并尝试将 stopMilli 中的最后一个 000
替换为 030
,以查看您自己的行为。
Days daysObject = Days.between ( startZdt , stopZdt );
int daysObjectCount = daysObject.getAmount ();
转储到控制台。您在输出中看到的 P6D
字符串是根据 ISO 8601 标准中定义的格式生成的。 java.time 默认使用此标准来解析和生成日期时间值的文本表示。这些标准格式非常明智和有用,所以请浏览链接的维基百科页面。
System.out.println ( "daysObject: " + daysObject + " | daysObjectCount: " + daysObjectCount );
daysObject: P6D | daysObjectCount: 6
为了解决我的问题,我已将毫秒数归零,并将长数转换为双数,以在必要时保持准确性和舍入。
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);