【问题标题】:JSR 310: Converting between time zonesJSR 310:时区之间的转换
【发布时间】:2013-04-07 20:37:36
【问题描述】:

尝试使用 JSR 310 在时区之间转换毫秒日期时间值。需要处理毫秒值才能使用旧版 API。就我而言,它介于本地和 UTC/GMT 之间,但我希望它是完全相同的代码,而与所涉及的源时区和目标时区无关。这是我的小测试程序。它被配置为在最后一次本地 DST 更改前后的几个小时内进行迭代。输出是错误的,所以我认为 UTCtoLocalMillis 是错误的,但我的测试方法也可能存在问题。我所说的错误输出的意思是,对于我的时区,应该减去小时以获得 UTC,但代码实际上增加了小时。其次,夏令时的时间点也相差一小时。

我首先获取本地时区,然后将其重置为 UTC,这样 Date().toString() 在创建输出时不会执行任何转换。

我想要的是使用新的 JSR 310 API 创建一个以毫秒为单位的方法,以及一个源 ZoneID 和一个目标 ZoneID,并返回转换后的毫秒数。

public class Test {
static int DAYS_OFFSET_TO_BE_BEFORE_DST_CHANGE = -16;

static TimeZone UTC_TZ = TimeZone.getTimeZone("UTC");

static TimeZone LOCAL_TZ = TimeZone.getDefault();

static ZoneId UTC_ID = ZoneId.of(UTC_TZ.getID());

static ZoneId LOCAL_ZONE_ID = ZoneId.of(LOCAL_TZ.getID());

static long UTCtoLocalMillis(final long utcMillis) {
    final Instant instant = Instant.ofEpochMilli(utcMillis);
    final ZonedDateTime before
        = ZonedDateTime.ofInstant(instant, UTC_ID);
    final ZonedDateTime after
        = before.withZoneSameLocal(LOCAL_ZONE_ID);
    return after.toInstant().toEpochMilli();
}

public static void main(final String[] args) {
    TimeZone.setDefault(UTC_TZ);
    System.out.println("LOCAL_TZ: " + LOCAL_TZ.getDisplayName());
    final Calendar cal = Calendar.getInstance(LOCAL_TZ);
    cal.add(Calendar.DATE, DAYS_OFFSET_TO_BE_BEFORE_DST_CHANGE);
    final Date start = cal.getTime();
    // DST Change: Sunday, 31 March 2013 01:59:59 (local time)
    final long oneHour = 3600000L;
    for (int i = 0; i < 12; i++) {
        final Date date = new Date(start.getTime() + i * oneHour);
        System.out.println("UTC: " + date + "   toLocal: "
                + new Date(UTCtoLocalMillis(date.getTime())));
    }
}
}

【问题讨论】:

    标签: java timezone java-8 jsr310


    【解决方案1】:

    我想要的是使用新的 JSR 310 API 创建一个以毫秒为单位的方法,以及一个源 ZoneID 和一个目标 ZoneID,并返回转换后的毫秒数。

    您的要求似乎很混乱。时间 APIs 中一般提到的“毫秒”值是 epoch-millis,即从 1970-01-01T00:00Z 开始的毫秒数。毫秒值始终与 UTC 相关。虽然有“本地毫秒”的概念,但它从未真正使用过。特别是,java.util.Date 构造函数接受 epoch-millis。

    因此,new Date(UTCtoLocalMillis(date.getTime()))这个代码基本没有意义。

    【讨论】:

    • 谢谢!所以看来我没有“得到”时间 API。我必须承认,到目前为止,我不得不处理很多日期,但既不是时间,也不是时区。但我认为一个问题可以解决所有问题:如果世界上所有(正确配置的)计算机同时调用 System.currentTimeMillis(),那么它们会得到相同的(近似)值吗? javadoc 说“以毫秒为单位返回当前时间”,它既不告诉我们它是“本地”还是“UTC”,因为“当前”并不具体暗示其中一个,IMO。
    • 好的。我自己回答了这个问题,要求地球另一端的人同时运行这个单线: public class Test { public static void main(String[] args) { System.out.println(System .currentTimeMillis()); } } 并且同时出来了。基本上, System.currentTimeMillis() (和日期)始终采用 UTC。我希望这在 Javadoc 中更清楚。 :(
    【解决方案2】:

    tl;博士

    Instant                              // Represent a moment in UTC with a resolution of nanoseconds.
    .ofEpochMilli( utcMillis )           // Parse a count of milliseconds since epoch of 1970-01-01T00:00:00Z.
    .atZone(                             // Adjust from UTC to some time zone.
        ZoneId.of( "Pacific/Auckland" ) 
    )
    

    详情

    Answer by JodaStephen 是正确的。没有“LocalMillis”之类的东西。你似乎有一些概念上的误解。这是可以理解的,因为日期时间工作非常棘手。

    提示:学习在 UTC 中思考和工作。作为程序员或管理员,请忘记您自己的时区。将 UTC 视为 真正的时间。不同的时区只是变化。使用 UTC 进行日志记录、跟踪、数据存储和数据交换。应用时区仅用于向用户展示。在 UTC 和您自己的地方时区之间来回切换会让您发疯。在您的桌子上放置第二个时钟,标记并设置为 UTC;工作时使用它。

    另一个提示:从概念上关注时间线。时间只有一种流动。表示该时间线的主要方式是 UTC。不同的时区只是同一时间线上同一时刻的不同表示。

    这是一些示例 java.time 代码。

    使用新的 JSR 310 API 获取以毫秒为单位的时间、源 ZoneID 和目标 ZoneID,并返回转换后的毫秒数。

    您不理解的部分是 "source ZoneID" 始终为 UTC 的毫秒数-since-epoch。任何在 UTC 中不是经过毫秒计数的人都是没有经验的,并且会自找麻烦。

    如果自 1970 年 UTC 第一时刻的纪元参考 1970-01-01T00:00:00Z 以来的毫秒数,则解析为 InstantInstant 代表 UTC 中的时刻。

    Instant instant = Instant.ofEpochMilli( utcMillis ) ;
    

    String 中生成代表Instant 中的时刻的文本。默认情况下,java.time 类在解析/生成字符串时使用ISO 8601 标准格式。末尾的Z 表示UTC,发音为“Zulu”。

    String output = instant.toString() ;
    

    2018-01-23T01:23:45.123456789Z

    往另一个方向走……

    long millisecondsFromEpoch = instant.toEpochMilli() ;
    

    手头有Instant,您可能希望通过特定地区(时区)的人们使用的挂钟时间的镜头来查看那个时刻。我们所说的“挂钟时间”是指一个人看到挂在墙上的时钟和日历时所看到的时间和日期。

    continent/region 的格式指定proper time zone name,例如America/MontrealAfrica/CasablancaPacific/Auckland。切勿使用 3-4 个字母的缩写,例如 ESTIST,因为它们不是真正的时区,没有标准化,甚至不是唯一的 (!)。

    ZoneId z = ZoneId.of( "Africa/Tunis" ) ;
    ZonedDateTime zdt = instant.atZone( z ) ;  // Same moment, same point on the timeline, different wall-clock time.
    

    我们可以通过提取Instant 来返回UTC。

    Instant instant = zdt.toInstant() ;  // Same moment, different wall-clock time.
    

    最后提示:切勿使用DateCalendarSimpleDateFormat 或其他相关的旧日期时间类。他们无休止地令人困惑和沮丧。仅使用 java.time 类。


    关于java.time

    java.time 框架内置于 Java 8 及更高版本中。这些类取代了麻烦的旧 legacy 日期时间类,例如 java.util.DateCalendarSimpleDateFormat

    Joda-Time 项目现在位于maintenance mode,建议迁移到java.time 类。

    要了解更多信息,请参阅Oracle Tutorial。并在 Stack Overflow 上搜索许多示例和解释。规格为JSR 310

    您可以直接与您的数据库交换 java.time 对象。使用符合JDBC 4.2 或更高版本的JDBC driver。不需要字符串,不需要java.sql.* 类。

    从哪里获得 java.time 类?

    ThreeTen-Extra 项目通过附加类扩展了 java.time。该项目是未来可能添加到 java.time 的试验场。您可以在这里找到一些有用的类,例如IntervalYearWeekYearQuartermore

    【讨论】:

    • 嗨。虽然我很欣赏冗长而详细的答案,但我觉得奇怪的是,在提出以下问题后 5 年,你会认为我现在仍然不明白这是如何工作的:“你似乎有一些概念上的误解。 ..”
    • @SebastienDiot 您似乎也误解了 Stack Overflow 不是电子邮件。当我在这里发帖时,我不是单独和你说话。 今天,我正在与成千上万阅读此页面的其他人交谈。作为对您的回应,我实际上是在解决那些寻找此页面的人的困惑、误解或好奇。如果我只是单独帮助你,我会收取咨询费,而不是免费工作。为后代服务的乐趣是让这项免费工作变得有价值的原因。
    猜你喜欢
    • 2015-02-11
    • 2018-01-09
    • 2018-04-05
    • 1970-01-01
    • 2016-11-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多