获取Java Date或Calendar的时间组件

16
有没有一种简单或优雅的方法可以仅获取Java Date(或Calendar)的日期时间部分(小时/分钟/秒/毫秒)?我想要以一种好的方式分别考虑日期(年/月/日)和时间部分,但据我所知,我只能访问每个字段。
我知道我可以编写自己的方法来单独提取我感兴趣的字段,但我会将其作为静态实用程序方法编写,这很丑陋。此外,我知道Date和Calendar对象具有毫秒精度,但我看不到在任何情况下都可以访问毫秒分量的方法。
编辑:我没有表明清楚:使用Date :: getTime()或Calendar :: getTimeInMillis之一对我来说并不是非常有用,因为它们返回自纪元以来的毫秒数(由该日期或日历表示),这不会真正将“时间”与其他信息分开。
Jherico的答案是我认为最接近的东西,但肯定还是需要将其卷入我自己编写的方法中。它不完全是我想要的,因为它仍然包括返回的毫秒值中的小时、分钟和秒 - 尽管我可能可以使其适合我的目的。
我仍然认为每个组成部分是“单独”的,尽管当然它们不是。你可以将一个时间写成自一个任意参考日期以来的毫秒数,或者你可以将完全相同的时间写成“年/月/日 小时:分钟:秒.毫秒”的形式。
这不是为了显示。我知道如何使用DateFormat制作漂亮的日期字符串。
编辑2:我的原始问题来源于一小组实用程序函数,我发现自己正在编写这些函数,例如:
- 检查两个Date是否表示同一天的日期时间; - 检查日期是否在由另外两个日期指定的范围内,但有时根据时间组件检查是否包含和不包含。

Joda Time有这种功能吗?


编辑3:关于@Jon提出的我的第二个要求,澄清一下:第二个要求是使用我的日期表示整个天数时产生的 - 其中时间组件根本不重要 - 有时候又要表示日期时间(在我看来,这是包含年/月/日小时:分钟:秒:...最准确的词)。

当日期表示整个天数时,其时间部分为零(例如,日期的“时间组件”为午夜),但语义要求对结束日期进行包括范围检查。因为我把这个检查留给了Date::before和Date::after处理,所以必须向结束日期添加1天 - 所以当日期的时间部分为零时需要特殊处理。

希望这样没有让事情变得更加复杂。


2
这是用于展示的吗? - Nick Holt
Joda肯定能轻松实现第一个需求。我不确定我理解第二个需求。 - Jon Skeet
是的,听起来我现在只能自己解决问题,并尽快(尖叫着)转向Joda。 - Matt Ball
2
如果所有日期和时间都在一个共同的基础上(最好是2、10或16进制),并且整个世界只有一个时区,那么工作将会变得多么容易啊,但这只是我的梦想。 :P - Matt Ball
使用这种简单方法进行计算,您得出的结果将会偏离真实值约26或27秒。要了解原因,请查看我对@Jherico答案的评论。 - Richard Gomes
显示剩余3条评论
10个回答

10

好的,我知道这是一个可预测的答案,但是...... 使用Joda Time。它有单独的表示方法,例如“日期”,“即时”,“一天中的时间”等等。它是一个更丰富的API,并且在我看来通常比内置类更合理。

如果这是您感兴趣的唯一日期/时间操作,则可能过度了......但是如果您在使用内置日期/时间API进行任何重要操作,则强烈建议您尽快迁移到Joda。

另外,您应该考虑您感兴趣的时区。 Calendar具有关联的时区,但是Date没有(它只表示从Unix纪元以毫秒为单位测量的特定瞬间)。


其实,我从来没有听说过Joda Time(或者寻找过任何可能提到它的东西)。我不确定是否要使用新的时间库,因为这个项目的很多代码已经基于Java Dates和Calendars。虽然我同意它们都是相当糟糕的API,但也许在下一个项目中会考虑使用... - Matt Ball
4
我同意这个观点,Java的日期/时间API是JDK中最糟糕的API和实现之一。我相信所有的Java开发者都会记得第一次尝试在Java中处理日期/时间时的困惑,然后查看Javadoc... +1(表示赞同)这个观点,Java的日期/时间API是JDK中最糟糕的API和实现之一。我相信所有的Java开发者都会记得第一次尝试在Java中处理日期/时间时的困惑,然后查看Javadoc... - SteveD
1
@Matt org.joda.time.base.AbstractInstant类有一个toDate()方法[(http://joda-time.sourceforge.net/api-release/org/joda/time/base/AbstractInstant.html#toDate%28%29)],您可以使用它将Joda日期转换为java.util.Date对象,以便Java日期API和Joda能够和谐共存。 - Rob Hruska
是的,你可以轻松地从 java.util.Date(或其毫秒值;我忘记了哪个)创建一个 DateTime。Joda(大多数情况下)强制你弄清楚你真正感兴趣的概念,以及更适当地处理诸如时区之类的事情。 - Jon Skeet

9
提取一天中的时间部分应该是通过将毫秒数除以每天的毫秒数并得到余数来实现的。
long MILLIS_PER_DAY = 24 * 60 * 60 * 1000;
Date now = Calendar.getInstance().getTime();
long timePortion = now.getTime() % MILLIS_PER_DAY;

或者,考虑使用joda-time,一个功能更全面的时间库。


3
这取决于你所说的“时间部分”,这非常取决于你感兴趣的时区... - Jon Skeet
2
这个答案假设一天有24 * 60 * 60秒。你会惊讶地发现这并不总是正确的!原因是由于地球的自转周期正在减慢(非常非常缓慢!)随着世纪的推移,天文测量需要对我们的时钟进行调整。例如,在2015年6月30日,该日添加了一秒钟。在1972年至2012年期间,平均每18个月插入一个闰秒。https://en.wikipedia.org/wiki/Leap_second - Richard Gomes
如果这是一种不好的做法,那么应该如何做? - Derrick
这完全取决于目的...如果所有日历对象都在同一时区(例如,日落(现在),日出(现在)和“现在”本身),并且您只想进行比较,那就没问题。 - chksr

3
使用日历API -
解决方案1 -
Calendar c = Calendar.getInstance();
String timeComp = c.get(Calendar.HOUR_OF_DAY)+":"+c.get(Calendar.MINUTE)+":"+c.get(Calendar.SECOND)+":"+c.get(Calendar.MILLISECOND);
System.out.println(timeComp);  

输出 - 13:24:54:212

解决方案2 -

SimpleDateFormat time_format = new SimpleDateFormat("HH:mm:ss.SSS");
String timeComp = time_format.format(Calendar.getInstance().getTime());

输出 - 15:57:25.518


2
为了回答其中的一部分问题,访问毫秒组件的方法如下:
long mill = Calendar.getInstance().getTime();

我不知道您想要做什么具体的事情,但如果是为了文本输出,您可以使用java.text.SimpleDateFormat类。

+1:Calendar.getInstance().get(Calendar.DAY_OF_MONTH) 返回该日期的整数。 - ATorras
这不仅仅涉及毫秒组件。它获取整个日期时间,表示为自纪元以来的毫秒数。 - Matt Ball
@PHeath,你还缺少一个.getTime() - Pacerier

1
你可以在 Calendar 对象上调用 getTimeInMillis() 函数以获取毫秒级时间。你可以在 calendar 对象上调用 get(Calendar.MILLISECOND) 以获取秒的毫秒数。如果你想要显示 Date 或 Calendar 对象的时间,请使用 DateFormat 类。例如:DateFormat.getTimeInstance().format(now)。还有一个 SimpleDateFormat 类可供使用。

1
要仅获取时间,使用Joda-Time的org.joda.time.LocalTime类,如此问题中所述Joda-Time, Time without date
至于仅比较日期而有效地忽略时间,在Joda-Time中,对于每个DateTime实例调用withTimeAtStartOfDay()方法以设置相同的时间值。这是一些示例代码,使用Joda-Time 2.3,类似于我今天在另一个答案中发布的内容。
    // © 2013 Basil Bourque. This source code may be used freely forever by anyone taking full responsibility for doing so.

    // Joda-Time - The popular alternative to Sun/Oracle's notoriously bad date, time, and calendar classes bundled with Java 7 and earlier.
    // http://www.joda.org/joda-time/

    // Joda-Time will become outmoded by the JSR 310 Date and Time API introduced in Java 8.
    // JSR 310 was inspired by Joda-Time but is not directly based on it.
    // http://jcp.org/en/jsr/detail?id=310

    // By default, Joda-Time produces strings in the standard ISO 8601 format.
    // https://en.wikipedia.org/wiki/ISO_8601

    // Capture one moment in time.
    org.joda.time.DateTime now = new org.joda.time.DateTime();
    System.out.println("Now: " + now);

    // Calculate approximately same time yesterday.
    org.joda.time.DateTime yesterday = now.minusDays(1);
    System.out.println("Yesterday: " + yesterday);

    // Compare dates. A DateTime includes time (hence the name).
    // So effectively eliminate the time by setting to start of day.
    Boolean isTodaySameDateAsYesterday = now.withTimeAtStartOfDay().isEqual(yesterday.withTimeAtStartOfDay());
    System.out.println("Is today same date as yesterday: " + isTodaySameDateAsYesterday);

    org.joda.time.DateTime halloweenInUnitedStates = new org.joda.time.DateTime(2013, 10, 31, 0, 0);
    Boolean isFirstMomentSameDateAsHalloween = now.withTimeAtStartOfDay().isEqual(halloweenInUnitedStates.withTimeAtStartOfDay());
    System.out.println("Is now the same date as Halloween in the US: " + isFirstMomentSameDateAsHalloween);

0
以下是一种使用Joda Time并支持时区的解决方案。 因此,您将在JVM中当前配置的时区中获取日期和时间(分别存储在currentDatecurrentTime中)。
请注意,Joda Time不支持闰秒。因此,您可能会偏离真实值26或27秒。这可能只会在未来50年内得到解决,当累积误差接近1分钟且人们开始关注它时。
另请参见:https://en.wikipedia.org/wiki/Leap_second
/**
 * This class splits the current date/time (now!) and an informed date/time into their components:
 * <lu>
 *     <li>schedulable: if the informed date/time is in the present (now!) or in future.</li>
 *     <li>informedDate: the date (only) part of the informed date/time</li>
 *     <li>informedTime: the time (only) part of the informed date/time</li>
 *     <li>currentDate: the date (only) part of the current date/time (now!)</li>
 *     <li>currentTime: the time (only) part of the current date/time (now!)</li>
 * </lu>
 */
public class ScheduleDateTime {
    public final boolean schedulable;
    public final long millis;
    public final java.util.Date informedDate;
    public final java.util.Date informedTime;
    public final java.util.Date currentDate;
    public final java.util.Date currentTime;

    public ScheduleDateTime(long millis) {
        final long now = System.currentTimeMillis();
        this.schedulable = (millis > -1L) && (millis >= now);

        final TimeZoneUtils tz = new TimeZoneUtils();

        final java.util.Date          dmillis   = new java.util.Date( (millis > -1L) ? millis : now );
        final java.time.ZonedDateTime zdtmillis = java.time.ZonedDateTime.ofInstant(dmillis.toInstant(), java.time.ZoneId.systemDefault());
        final java.util.Date          zdmillis  = java.util.Date.from(tz.tzdate(zdtmillis));
        final java.util.Date          ztmillis  = new java.util.Date(tz.tztime(zdtmillis));

        final java.util.Date          dnow   = new java.util.Date(now);
        final java.time.ZonedDateTime zdtnow = java.time.ZonedDateTime.ofInstant(dnow.toInstant(), java.time.ZoneId.systemDefault());
        final java.util.Date          zdnow  = java.util.Date.from(tz.tzdate(zdtnow));
        final java.util.Date          ztnow  = new java.util.Date(tz.tztime(zdtnow));

        this.millis       = millis;
        this.informedDate = zdmillis;
        this.informedTime = ztmillis;
        this.currentDate  = zdnow;
        this.currentTime  = ztnow;
    }
}



public class TimeZoneUtils {

    public java.time.Instant tzdate() {
        final java.time.ZonedDateTime zdtime = java.time.ZonedDateTime.now();
        return tzdate(zdtime);
    }
    public java.time.Instant tzdate(java.time.ZonedDateTime zdtime) {
        final java.time.ZonedDateTime zddate = zdtime.truncatedTo(java.time.temporal.ChronoUnit.DAYS);
        final java.time.Instant instant = zddate.toInstant();
        return instant;
    }

    public long tztime() {
        final java.time.ZonedDateTime zdtime = java.time.ZonedDateTime.now();
        return tztime(zdtime);
     }
    public long tztime(java.time.ZonedDateTime zdtime) {
        final java.time.ZonedDateTime zddate = zdtime.truncatedTo(java.time.temporal.ChronoUnit.DAYS);
        final long millis = zddate.until(zdtime, java.time.temporal.ChronoUnit.MILLIS);
        return millis;
    }
}

你知道java.time类(在Java 8中引入)是否考虑了闰秒吗? - Matt Ball
新的java.time框架提供了在一天的最后一千秒内平衡闰秒的规定。请参阅JavaDoc。但这仅适用于遵守L.S.的JVM实现。目前没有任何常规实现(如OpenJDK或Oracle Java)遵守L.S;我不知道实时Java实现情况,它们可能会遵守。 - Basil Bourque

0

概述

LocalTime lt = myUtilDate.toInstant().atZone( ZoneId.of( "America/Montreal" ) ).toLocalTime() ;

避免使用旧的日期时间类

您正在使用旧的遗留日期时间类。它们很麻烦且容易混淆,建议避免使用。

相反,请使用java.time类。这些类取代了旧类以及Joda-Time库。

转换

将您的java.util.Date转换为Instant

Instant类表示具有UTC时间线上分辨率为纳秒的时刻。

Instant instant = myUtilDate.toInstant();

时区

应用时区非常重要。由于不同地区的时区不同,因此在全球范围内,对于任何给定的时刻,日期都会有所变化。例如,在法国巴黎午夜过后几分钟就是新的一天,而在加拿大魁北克省蒙特利尔则仍然是“昨天”。

使用ZoneId来获取ZonedDateTime对象。

ZoneId z = ZoneId.of( "America/Montreal" );
ZonedDateTime zdt = instant.atZone( z );

本地…类型

LocalDate类表示仅包含日期值而不包含时间和时区的值。同样,LocalTime表示没有日期和时区的时间。您可以将这些视为两个组成部分,再加上ZoneId一起构成ZonedDateTime。您可以从ZonedDateTime中提取这些内容。

LocalDate ld = zdt.toLocalDate();
LocalTime lt = zdt.toLocalTime();

字符串

如果你的目标仅是为了向用户呈现生成字符串,那么不需要使用 Local... 类型。相反,使用 DateTimeFormatter 生成表示日期部分或时间部分的字符串即可。该类足够智能,在生成字符串时自动本地化

指定 Locale 来确定 (a) 用于翻译日、月等名称的人类语言以及 (b) 决定缩略词、大写、标点符号等问题的文化规范。

Locale l = Locale.CANADA_FRENCH ;  // Or Locale.US, Locale.ITALY, etc.

DateTimeFormatter fDate = DateTimeFormatter.ofLocalizedDate( FormatStyle.MEDIUM ).withLocale( locale );
String outputDate = zdt.format( fDate );

DateTimeFormatter fTime = DateTimeFormatter.ofLocalizedTime( FormatStyle.MEDIUM ).withLocale( locale );
String outputTime = zdt.format( fTime );

关于 java.time

java.time 框架是内置于 Java 8 及以上版本的。这些类取代了旧的麻烦日期时间类,如 java.util.Date.Calendarjava.text.SimpleDateFormat

Joda-Time 项目现在处于维护模式,建议迁移到 java.time。

要了解更多信息,请参阅 Oracle教程。并在 Stack Overflow 上搜索许多示例和解释。

许多java.time的功能被移植到Java 6和7中,可以在ThreeTen-Backport中使用,并进一步适应Android平台,在ThreeTenABP中进行了改进(请参见如何使用…)。 ThreeTen-Extra项目通过添加额外的类来扩展java.time。该项目是java.time可能未来添加的可能性的试验场。

0
如果你只是想将它转换成字符串以便于显示或保存,那么只需创建一个 SimpleDateFormat 对象,仅显示时间部分,例如 new SimpleDateFormat("HH:mm:ss")。当然,日期仍然在 Date 对象中,但你不需要关心。
如果你想对其进行算术运算,例如获取两个 Date 对象之间相差的秒数而忽略日期部分,使得 "2009-09-01 11:00:00" 减去 "1941-12-07 09:00:00" 等于 2 小时,那么我认为你需要使用类似 Jherico 的解决方案:获取长整型时间并取模一天。

0

为什么要将它们分开呢?如果你想对时间部分进行任何算术运算,你很快就会遇到麻烦。如果你取出11:59pm并添加一分钟,现在你的时间和日期是分开的,你就会陷入困境——你会得到一个无效的时间和错误的日期。

如果你只想显示它们,那么应用各种简单的日期格式应该可以得到你想要的结果。

如果你想操作日期,我建议你获取长整型值,并以此为基础进行所有操作。在任何时候,你都可以取出这个长整型值并应用格式来轻松地显示分钟/小时/秒。

但我对将日期和时间分开操纵的概念有点担心,似乎是打开了一个潘多拉魔盒。(更不用说时区问题了!)

我相当确定这就是为什么Java没有简单的方法来做到这一点的原因。


幸运的是,我并不试图分别操作日期和时间 - 只是检查它们。 - Matt Ball

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接