【问题标题】:Java - Unexpected result from Calendar.set(HOUR_OF_DAY) [duplicate]Java - Calendar.set(HOUR_OF_DAY)的意外结果[重复]
【发布时间】:2018-05-31 18:11:35
【问题描述】:

JVM 版本是 1.7。时区为 GMT+3,偏移 180 分钟。 1500411600000 对应于7/19/2017, 12:00:00 AM(我已经验证了这个online)。

我正在执行以下代码来调整Date 实例的时间:

final Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
Date date = new Date(1500411600000L);
calendar.setTime(date);
calendar.set(Calendar.HOUR_OF_DAY, 23);
calendar.set(Calendar.MINUTE, 59);
calendar.set(Calendar.SECOND, 59);
calendar.set(Calendar.MILLISECOND, 999);
date = calendar.getTime();

我希望date 变成7/19/2017, 11:59:59 PM,但我得到的是7/19/2017, 2:59:59 AM,而不是这个。 3 小时的差异 - 与我的时区与 UTC/GMT 的差异一样多,所以我想这里发生了一些未被注意到的转换。

您能帮我找到与时区无关的代码来调整日期时间吗?

【问题讨论】:

  • “验证”与您链接的网站,我得到一个完全不同的日期/时间:7/18/2017, 11:00:00 PM
  • 试试jodatime joda.org/joda-time
  • 您为什么要为早已过时的Calendar 类苦苦挣扎? Joda-Time 更好 (@ThomasEdwin),但在维护模式下,java.time, the modern Java date and time API 仍然更好,更易于使用。
  • 它可能与Calendar 没有任何关系。更过时的Date 类的toString 方法会抓取你的JVM 的时区并将其时间呈现在这个时区,非常混乱。在Date 中,您拥有正确且所需的时间点。见All about java.util.Date
  • 毫秒值 1500411600000 对应 2017-07-18T21:00:00Z 或 2017-07-19T00:00+03:00。

标签: java date calendar timezone-offset


【解决方案1】:

您使用的是Calendar.getInstance(TimeZone.getTimeZone("UTC")),但您必须使用您所在的时区。正如您所说GMT+3

【讨论】:

  • 我在开发过程中不知道服务器的时区,所以我不想硬编码 GMT+3
【解决方案2】:

请参阅此处的此线程,该线程解释了有关日期和时区的问题。

How to set time zone of a java.util.Date?

Date 对象将具有正确的调整时间,但在显示时,输出将使用您的本地时区。您可以使用以下代码强制设置 JVM 的时区,但这可能会对代码的其他部分产生意想不到的后果。

TimeZone.setDefault(TimeZone.getTimeZone("UTC"));

在理想情况下,您会使用 Java 8 日期类或 Joda 时间库类,它们都提供了一些简单的日期操作方法。

Java 8 date classes

【讨论】:

    【解决方案3】:

    使用clear。对我来说,这似乎是一个历史“错误”,一个时区日历,其中 setTime 不会改变区域。

    final Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
    Date date = new Date(1500411600000L);
    calendar.clear(); // To reset _all_ fields, incl. the time zone offset ZONE_OFFSET.
    calendar.setTime(date);
    calendar.set(Calendar.HOUR_OF_DAY, 23);
    calendar.set(Calendar.MINUTE, 59);
    calendar.set(Calendar.SECOND, 59);
    calendar.set(Calendar.MILLISECOND, 999);
    date = calendar.getTime();
    

    当然,这可能是切换到新的 java time API 的正确参数。

    【讨论】:

    • 我同意这是升级的完美理由。但是关于您的解决方案,我认为Calendar.getInstance(TimeZone.getTimeZone("UTC")) + Calendar.clear() 相当于Calendar.getInstance(),不是吗?
    • @naXa getInstance 执行 createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT));clear 重置所有 set-Fields 以及 ZONE_OFFSET。我不确定你是对还是错。
    【解决方案4】:

    问题比我的问题中描述的要大。它源于对用户时区的错误管理日期/时间。在我的应用程序中,时间戳是在用户的时区中发送的,然后在服务器的时区中评估为日期,但没有考虑时区差异。我试图解决这个问题并遇到了问题中描述的问题。

    我听取了@ThomasEdwin 使用 Joda Time 的建议,很高兴分享这个解决方案:

    long userTimezoneOffset = 180;  // it's a parameter submitted by client app
    Date date = new Date(1500411600000L);  // it's another parameter submitted by client app
    
    final DateTimeZone zone = DateTimeZone.forOffsetMillis((int) TimeUnit.MINUTES.toMillis(userTimezoneOffset));
    final DateTimeZone serverZone = DateTimeZone.getDefault();
    
    MutableDateTime dateTime = new MutableDateTime(date, zone);
    dateTime.setHourOfDay(23);
    dateTime.setMinuteOfHour(59);
    dateTime.setSecondOfMinute(59);
    dateTime.setMillisOfSecond(999);
    dateTime.setZoneRetainFields(serverZone);
    date = dateTime.toDate();
    // now date.toString() returns expected result
    

    我还发现-Duser.timezone JVM 参数在调试此问题时非常有用。有关支持的timezone IDs 列表,请参见此处。

    【讨论】:

    • 我再说一遍,Joda-Time 向前迈出了一大步。 Joda-Time 也处于维护模式;预计不会有重大的进一步发展。
    【解决方案5】:

    您是正确的,在偏移 UTC+3 处,您的毫秒值 1500411600000 对应于 2017 年 7 月 19 日午夜(一天的开始)。在其他偏移量处,它对应于 7 月 18 日或 19 日一天中的其他时间。

    java.time

    假设您在自己的时区有午夜并非巧合,该值实际上应该表示日期而不是时间,我建议您使用 java.time 中的 LocalDate 来表示它:

        ZoneId yourTimeZone = ZoneId.of("Europe/Riga");
        LocalDate date = Instant.ofEpochMilli(1500411600000L)
                .atZone(yourTimeZone)
                .toLocalDate();
        System.out.println(date);
    

    这会打印出预期的

    2017-07-19
    

    请替换您的正确时区以防它不是欧洲/里加,或使用ZoneOffset 代替:.atOffset(ZoneOffset.ofHoursMinutes(3, 0))(其他行相同)。

    我怀疑你并不真的想要一天结束,即使在你的问题中你试图设置它。如果这是为了确定某个时间点是否在一天结束之前,请将其与第二天的开始时间进行比较,并要求它严格在之前。这为您省去了看起来奇怪的分钟、秒和秒的小数部分的麻烦。

        ZonedDateTime startOfNextDay = date.plusDays(1).atStartOfDay(yourTimeZone);
    

    java.time 于 2014 年问世,作为 Java 1.0 和 1.1 中设计不佳的日期和时间类以及 Joda-Time 的替代品,从中汲取了很多灵感。我强烈推荐你使用它。

    你在问题​​中尝试了什么

    我相信用java.time 表达时,您的问题代码也更清晰:

        OffsetDateTime endOfDay = Instant.ofEpochMilli(1500411600000L)
                .atOffset(ZoneOffset.UTC)
                .with(LocalTime.MAX);
        System.out.println(endOfDay);
    

    打印出来

    2017-07-18T23:59:59.999999999Z
    

    (UTC 时间为 7 月 18 日结束时;末尾的 Z 表示 UTC)。除了小数位数,这也是你得到的结果。您可能被 Date 实例打印的 Wed Jul 19 02:59:59 EEST 2017 之类的事实所愚弄(时区缩写取决于 JVM 的时区设置)。 Date.toString() 获取 JVM 的时区设置并将日期时间转换为仅生成字符串的时区; Date 实例本身没有被修改,只在时间线上保存一个点,没有时区。

    问题:我可以在我的 Java 版本中使用 java.time 吗?

    是的,你可以。您只需要至少使用 Java 6

    要学习使用java.time,请参阅the Oracle tutorial 或在网上查找其他资源。

    【讨论】:

      猜你喜欢
      • 2013-02-19
      • 1970-01-01
      • 1970-01-01
      • 2011-09-04
      • 1970-01-01
      • 2017-10-04
      • 2014-12-01
      • 1970-01-01
      • 2013-04-03
      相关资源
      最近更新 更多