考虑日期时的时差

Hours difference while considering the date

我需要从数据库中提取日期字段并将其存储在 VO 中。我如何比较两个日期的时差。

例如:
假设 date1 = 01-SEP-17 10:00:00date2 = 05-SEP-17 12:00:00。我需要比较两个日期并执行一些操作,例如:

if(hours>10){
//do something
}
if(hours<10){
//do something else
}  

我只能将小时数 (date2-date1) 计算为 2,但如何在计算小时数时也考虑日期?

我现在的密码:

Date dateA = someVO.getDate();  
long date = System.currentTimeMillis();  
SimpleDateFormat df = new SimpleDateFormat("dd-MM-YY HH:mm:ss"); 
Date date1 = new Date(date); 
Date date2 = df.parse(dateA.toString());  
long date1Hours = date1.getHours();
long date2Hours = date2.getHours();  

long dateDiff = date1Hours-date2Hours;  
if(dateDiff>10){  
//something
}  
else if(dateDiff<10){  
//something else
}  

使用 Java 8:

中添加的新 Java-Time API 很容易做到
DateTimeFormatter fmt = new DateTimeFormatterBuilder()
                        .parseCaseInsensitive()
                        .appendPattern("dd-MMM-yy HH:mm:ss")
                        .toFormatter(Locale.US);
LocalDateTime date1 = LocalDateTime.parse("01-SEP-17 10:00:00", fmt);
LocalDateTime date2 = LocalDateTime.parse("05-SEP-17 12:00:00", fmt);
long hours = ChronoUnit.HOURS.between(date1, date2);
System.out.println(hours);

输出

98

你基本上已经得到了以毫秒为单位的时间。您总是可以直接比较毫秒数。

long tenHoursInMillis = 36000000;
long dateVOMillis = someVO.getDate().getTime();
long dateSysMillis = System.currentTimeMillis();
if(dateSysMillis - dateAMillis > tenHoursInMillis) {
    // do something
}
else if(dateSysMillis - dateAMillis < tenHoursInMillis) {
    // do something else
}
// do something when they're equal

首先你需要更改SimpleDateFormat中使用的模式,并使用java.util.Locale指定月份名称为英文(否则它使用系统默认语言环境,而不是保证永远是英文)。

然后你得到每个 Date 的相应毫秒值,计算它们之间的差异并将其转换为小时,使用 java.util.concurrent.TimeUnit:

SimpleDateFormat df = new SimpleDateFormat("dd-MMM-yy HH:mm:ss", Locale.ENGLISH);
Date date1 = df.parse("01-SEP-17 10:00:00");
Date date2 = df.parse("05-SEP-17 12:00:00");

// get the difference in hours
long dateDiff = TimeUnit.MILLISECONDS.toHours(date2.getTime() - date1.getTime());

dateDiff 将是 98

如果要与当前日期进行比较,只需使用new Date()


夏令时问题

这种方法有一个问题。尽管在一年中的大部分时间里没有什么不同,但可能会因 Daylight Saving Time 变化而有所不同。

默认情况下,SimpleDateFormat 使用 JVM 默认时区。如果在这两个日期之间有夏令时转换(或只是偏移量更改),结果可能会有所不同。

示例:在 Africa/Windhoek timezone,2017 年 9 月 3 日rd,凌晨 2 点,时钟向前移动了 1 小时,从凌晨 2 点到凌晨 3 点(和偏移从 +01:00 更改为 +02:00)。这意味着,在那一天,该时区不存在凌晨 2 点到 2:59 AM 之间的所有当地时间(就像他们“跳过”了这个小时)。

所以,如果 JVM 默认时区是 Africa/Windhoek,那么使用上面的代码时差将是 97 小时(而不是 98)。

即使您的 JVM 默认时区不是 Africa/Windhoek,这仍然可能发生,具体取决于所涉及的时区和日期。 不仅如此,还有默认时区 。最好指定您正在使用的时区,而不是仅仅依赖默认时区。

您无法避免 DST 影响(除非您使用 UTC),但至少您可以选择要使用的时区,而不是依赖于系统默认值(可以在不通知的情况下更改)。

可以在格式化程序中设置时区,因此在分析所有日期时都会考虑到该时区。在下面的示例中,我使用的是 Europe/London,当然您可以更改为最适合您的情况:

// set Europe/London timezone in the SimpleDateFormat
df.setTimeZone(TimeZone.getTimeZone("Europe/London"));

现在所有解析的日期都将被视为伦敦时区(但提醒仍会考虑 DST 影响 - 优点是您知道您正在使用的时区并且 JVM 默认值的任何更改都不会'不要让你的代码突然开始给出不同的和意想不到的结果)。

始终使用 IANA timezones names(始终采用 Continent/City 格式,例如 America/Sao_PauloEurope/Berlin)。 避免使用 3 个字母的缩写(如 CSTPST),因为它们是 ambiguous and not standard.

您可以使用 TimeZone.getAvailableIDs() 获取所有时区的列表 - 然后您可以选择最适合您的情况。

如果不想考虑夏令时的影响,可以使用TimeZone.getTimeZone("UTC")——因为UTC is a standard without DST changes.


Java新Date/TimeAPI

旧的 classes(DateCalendarSimpleDateFormat)有 lots of problems and design issues,它们正在被新的 APIs.

如果您使用 Java 8,请考虑使用 new java.time API. It's easier, less bugged and less error-prone than the old APIs.

如果您使用 Java <= 7,您可以使用 ThreeTen Backport, a great backport for Java 8's new date/time classes. And for Android, there's the ThreeTenABP (more on how to use it ).

下面的代码适用于两者。 唯一的区别是 包名称 (在 Java 8 中是 java.time 而在 ThreeTen Backport(或 Android 的 ThreeTenABP)中是 org.threeten.bp),但是 classes 和方法 names 是相同的。

首先您需要解析输入(使用 DateTimeFormatter)并指定它们所在的时区。由于日期也有时区,我使用 ZonedDateTime,这是这种情况的最佳选择。

然后您可以使用 ChronoUnit 轻松计算小时差。在下面的例子中,我也以伦敦时区为例:

DateTimeFormatter fmt = new DateTimeFormatterBuilder()
    // case insensitive for month name in all caps
    .parseCaseInsensitive()
    // date/time pattern
    .appendPattern("dd-MMM-yy HH:mm:ss")
    // use English locale for month name
    .toFormatter(Locale.ENGLISH)
    // set a timezone
    .withZone(ZoneId.of("Europe/London"));
// parse the dates
ZonedDateTime z1 = ZonedDateTime.parse("01-SEP-17 10:00:00", fmt);
ZonedDateTime z2 = ZonedDateTime.parse("05-SEP-17 12:00:00", fmt);
// calculate the difference in hours
long diffHours = ChronoUnit.HOURS.between(z1, z2);

如果要使用 UTC,只需将 ZoneId 更改为 ZoneOffset.UTC 常量即可。如果要与当前日期进行比较,只需使用:

// use the same ZoneId used in the formatter if you want to consider DST effects
ZonedDateTime.now(ZoneId.of("Europe/London"));

转化次数to/from日期

如果您仍然需要使用 java.util.Date,可以将 from/to 转换为新的 API。在 Java 8 中,您可以使用本地方法,在 Java <=7 中,ThreeTen Backport 具有 org.threeten.bp.DateTimeUtils class.

要将 Date 转换为新的 classes:

Date date = // java.util.Date
// convert to zoneddatetime (java 8)
ZonedDateTime z = date.toInstant().atZone(ZoneId.of("Europe/London"));
// convert to zoneddatetime (java 7 ThreeTen Backport)
ZonedDateTime z = DateTimeUtils.toInstant(date).atZone(ZoneId.of("Europe/London"));

要将 ZonedDateTime 转换回日期:

// convert to zoneddatetime (java 8)
Date date = Date.from(z.toInstant());
// convert to zoneddatetime (java 7 ThreeTen Backport)
Date date = DateTimeUtils.toDate(z.toInstant());