我在Scala中使用Java的java.util.Date
类,想要比较一个Date
对象和当前时间。我知道可以通过使用getTime()
计算时间差:
(new java.util.Date()).getTime() - oldDate.getTime()
然而,这只给我留下了一个表示毫秒的long
。是否有更简单、更好的方法来获得时间差?
我在Scala中使用Java的java.util.Date
类,想要比较一个Date
对象和当前时间。我知道可以通过使用getTime()
计算时间差:
(new java.util.Date()).getTime() - oldDate.getTime()
然而,这只给我留下了一个表示毫秒的long
。是否有更简单、更好的方法来获得时间差?
/**
* Get a diff between two dates
* @param date1 the oldest date
* @param date2 the newest date
* @param timeUnit the unit in which you want the diff
* @return the diff value, in the provided unit
*/
public static long getDateDiff(Date date1, Date date2, TimeUnit timeUnit) {
long diffInMillies = date2.getTime() - date1.getTime();
return timeUnit.convert(diffInMillies,TimeUnit.MILLISECONDS);
}
然后你可以调用:
getDateDiff(date1,date2,TimeUnit.MINUTES);
获取两个日期之间的差异,以分钟为单位。
TimeUnit
是 java.util.concurrent.TimeUnit
,它是一个标准的 Java 枚举类型,从纳秒到天数都有。
public static Map<TimeUnit,Long> computeDiff(Date date1, Date date2) {
long diffInMillies = date2.getTime() - date1.getTime();
//create the list
List<TimeUnit> units = new ArrayList<TimeUnit>(EnumSet.allOf(TimeUnit.class));
Collections.reverse(units);
//create the result map of TimeUnit and difference
Map<TimeUnit,Long> result = new LinkedHashMap<TimeUnit,Long>();
long milliesRest = diffInMillies;
for ( TimeUnit unit : units ) {
//calculate difference in millisecond
long diff = unit.convert(milliesRest,TimeUnit.MILLISECONDS);
long diffInMilliesForUnit = unit.toMillis(diff);
milliesRest = milliesRest - diffInMilliesForUnit;
//put the result in the map
result.put(unit,diff);
}
return result;
}
输出结果类似于Map:{DAYS=1, HOURS=3, MINUTES=46, SECONDS=40, MILLISECONDS=0, MICROSECONDS=0, NANOSECONDS=0}
,并按单位排序。
您只需将该映射转换为用户友好的字符串即可。
上述代码片段计算了两个时间点之间的简单差异。在夏令时转换期间可能会出现问题,如this post所述。这意味着如果您计算没有时间的日期之间的差异,可能会缺少一天/小时。
在我看来,日期差异有点主观,特别是在天数方面。您可以:
计算经过的24小时时间数量:day+1 - day = 1 day = 24h
计算经过的时间数量,注意夏令时:day+1 - day = 1 = 24h(但使用午夜时间和夏令时可能为0天23小时)
计算“日切换”的数量,这意味着day+1 1pm - day 11am = 1 day,即使经过的时间只有2小时(如果有夏令时,则为1小时:p)
如果您对天数的日期差异定义与第一种情况相匹配,则我的答案是有效的。
Interval interval = new Interval(oldInstant, new Instant());
但是您也可以获取本地日期/时间的差异:
// returns 4 because of the leap year of 366 days
new Period(LocalDate.now(), LocalDate.now().plusDays(365*5), PeriodType.years()).getYears()
// this time it returns 5
new Period(LocalDate.now(), LocalDate.now().plusDays(365*5+1), PeriodType.years()).getYears()
// And you can also use these static methods
Years.yearsBetween(LocalDate.now(), LocalDate.now().plusDays(365*5)).getYears()
JDK中的Date
API十分糟糕。建议使用Joda Time库.
Joda Time有一个时间间隔概念Interval:
Interval interval = new Interval(oldTime, new Instant());
编辑:顺便说一下,Joda有两个概念:Interval
用于表示两个时间瞬间之间的时间间隔(表示上午8点到10点之间的时间),而Duration
则表示没有实际时间边界的时间长度(例如表示两个小时!)
如果只关心时间比较,则大多数Date
实现(包括JDK)都实现了Comparable
接口,这允许您使用Comparable.compareTo()
方法。
Comparable.compareTo()
而不是 Comparable.compare()
。 - Scott Morrisonjava.time
包受 Joda-Time 的启发,但并非可以直接替换。它们各有优点和缺点。幸运的是,你不必在它们之间做出选择。只要小心使用 import
语句,就可以根据其强项使用它们。请参见这个答案以了解 java.time 的示例。 - Basil Bourqueint diffInDays = (int)( (newerDate.getTime() - olderDate.getTime())
/ (1000 * 60 * 60 * 24) )
请注意,这适用于UTC日期,因此如果您查看本地日期,则差异可能会偏离一天。要使其在本地日期上正常工作需要完全不同的方法,因为涉及到夏令时。
(endDate.getTime() - startDate.getTime())
被强制转换为int
而不是最终结果,从而导致整数溢出。 - Michael Borgwardtdouble diffInSeconds = ((after.getTime() - b4.getTime()) / 1000d);
。 - Joey Baruch使用Java 8+内置的java.time框架:
ZonedDateTime now = ZonedDateTime.now();
ZonedDateTime oldDate = now.minusDays(1).minusMinutes(10);
Duration duration = Duration.between(oldDate, now);
System.out.println("ISO-8601: " + duration);
System.out.println("Minutes: " + duration.toMinutes());
输出:
ISO-8601: PT24H10M
分钟:1450
将过时的java.util.Date
对象转换为其替代品java.time.Instant
。然后使用Duration
计算经过的时间。
Duration d =
Duration.between( // Calculate the span of time between two moments as a number of hours, minutes, and seconds.
myJavaUtilDate.toInstant() , // Convert legacy class to modern class by calling new method added to the old class.
Instant.now() // Capture the current moment in UTC. About two and a half hours later in this example.
)
;
PnYnMnDTnHnMnS
明智的标准ISO 8601将时间段定义为一定数量的年、月、日、小时等,并将这样的时间段称为持续时间。格式为PnYnMnDTnHnMnS
,其中P
表示“周期”,T
分隔日期部分和时间部分,中间是数字后面跟着一个字母。
示例:
P3Y6M4DT12H30M5S
三年零六个月零四天十二小时三十分钟五秒钟PT4H30M
四小时半java.util.Date
/java.util.Calendar
类。新类受到广受欢迎的Joda-Time框架的启发,是其后继者,在概念上相似但重新架构。由JSR 310定义。由ThreeTen-Extra项目扩展。请参见Tutorial。
Instant类表示时间线上以UTC为基准的一个瞬间,精度为纳秒(最多九位十进制小数)。Instant
类。
Instant instant = Instant.now() ; // Capture current moment in UTC.
Date
/Calendar
。但如果必须与尚未更新为java.time的旧代码进行互操作,则需要进行相互转换。调用添加到旧类中的新转换方法。要将java.util.Date
转换为Instant
,请调用Date::toInstant
。Instant instant = myJavaUtilDate.toInstant() ; // Convert from legacy `java.util.Date` class to modern `java.time.Instant` class.
ZoneId zoneId = ZoneId.of ( "America/Montreal" );
ZonedDateTime now = ZonedDateTime.now ( zoneId );
ZonedDateTime future = now.plusMinutes ( 63 );
Duration duration = Duration.between ( now , future );
在控制台中输出。
Period
和 Duration
都使用 ISO 8601 标准来生成它们的值的字符串表示形式。
System.out.println ( "now: " + now + " to future: " + now + " = " + duration );
long totalHours = duration.toHours();
Duration
类新增了一些方法,用于返回天、小时、分钟、秒、毫秒/纳秒等不同部分。调用to...Part
方法:如toDaysPart()
、toHoursPart()
等。
ChronoUnit
ChronoUnit
枚举。long daysElapsed = ChronoUnit.DAYS.between( earlier , later );
Instant now = Instant.now();
Instant later = now.plus( Duration.ofHours( 2 ) );
…
long minutesElapsed = ChronoUnit.MINUTES.between( now , later );
120
java.time框架内置于Java 8及其后续版本。这些类替代了老旧的传统日期时间类,如java.util.Date
、Calendar
和SimpleDateFormat
。
Joda-Time项目现已进入维护模式,建议迁移到java.time。
要了解更多,请查看Oracle教程。并在Stack Overflow上搜索许多示例和解释。规范是JSR 310。Interval
、YearWeek
、YearQuarter
和更多。
更新:Joda-Time 项目现已进入 维护模式,团队建议迁移到 java.time 类。为了历史记录,我保留本节内容。
Joda-Time 库使用 ISO 8601 作为默认设置。它的 Period
类解析和生成这些 PnYnMnDTnHnMnS 字符串。
DateTime now = DateTime.now(); // Caveat: Ignoring the important issue of time zones.
Period period = new Period( now, now.plusHours( 4 ).plusMinutes( 30));
System.out.println( "period: " + period );
period: PT4H30M
Period
、Duration
和ChronoUnit
三个类,它们的目的是确定时间点之间的差距。请指出我“冗长的文字”中哪些部分是不相关的。而且您说问题要求“日期之间的差异”是不正确的:问题要求在Date
对象之间计算时间差,这些对象是日期时间时刻,而不是“日期”,尽管该类的命名不幸地具有这种含义。 - Basil Bourquejava.util.Date
转换为Instant
(只需调用.toInstant
)。我还添加了“Moment”部分来解释。您提到了一对Date
对象,但实际上问题仅使用单个现有的Date
对象,然后捕获当前时刻,所以我也这样做了。感谢反馈!提示:放弃使用Date
- 这些遗留类真的是一团糟。 - Basil Bourque你需要更明确地定义你的问题。例如,你可以获取两个Date
对象之间的毫秒数并将其除以24小时的毫秒数,但是:
Date
对象始终使用协调世界时(UTC)。Days d = Days.daysBetween(startDate, endDate);
int days = d.getDays();
使用毫秒级别的方法可能会在某些语言环境下导致问题。
例如,假设有两个日期:03/24/2007 和 03/25/2007,它们之间相差1天;
然而,在英国运行时,如果使用毫秒级别的方法,你将得到0天。
/** Manual Method - YIELDS INCORRECT RESULTS - DO NOT USE**/
/* This method is used to find the no of days between the given dates */
public long calculateDays(Date dateEarly, Date dateLater) {
return (dateLater.getTime() - dateEarly.getTime()) / (24 * 60 * 60 * 1000);
}
更好的实现方式是使用java.util.Calendar
/** Using Calendar - THE CORRECT WAY**/
public static long daysBetween(Calendar startDate, Calendar endDate) {
Calendar date = (Calendar) startDate.clone();
long daysBetween = 0;
while (date.before(endDate)) {
date.add(Calendar.DAY_OF_MONTH, 1);
daysBetween++;
}
return daysBetween;
}
一个稍微简单一点的替代方案:
System.currentTimeMillis() - oldDate.getTime()
"更好看"的问题是:你需要什么样的效果?将时间段表示为小时、天数等可能会导致误差和错误的预期,因为日期的复杂性(例如,夏令时可能导致一天有23或25个小时)。
Date
、Calendar
和SimpleDateFormat
就太不应该了”。 - Basil Bourquejava.time.Period
和/或Duration
。请查看下面的Basil Bourque的答案。 - Ole V.V.