Joda:将系统日期和时间转换为另一个区域中的 date/time

Joda: convert the system date and time to date/time in another zone

我在 SO 阅读了很多帖子并测试了其中的大部分。 None 为我工作。这是我的代码:

DateTimeZone fromTimeZone = DateTimeZone.forID("America/New_York");
DateTimeZone toTimeZone = DateTimeZone.forID("US/Central");  
Date now = new Date();        
DateTime dateTime = new DateTime(now, fromTimeZone); 
DateTime newDateTime = dateTime.withZone(toTimeZone);
System.out.println(dateTime.toDate() + "--" + newDateTime.toDate());

这是我得到的印刷品:

Tue Aug 22 13:08:13 EDT 2017--Tue Aug 22 13:08:13 EDT 2017

我希望显示"Tue Aug 22 12:08:13 CDT 2017"第二时区。

A java.util.Date doesn't have timezone information. Joda's DateTime has, but it's wrapped into a Chronology to translate this instant to "human readable" date/time fields.

但最终,两个对象都只是 represent points (instants) in the time-line

只需检查 dateTime.getMillis()newDateTime.getMillis()dateTime.toDate().getTime()newDateTime.toDate().getTime() 的值。它们都将完全相同,这个值表示自纪元(1970-01-01T00:00Z).

以来的毫秒数

传递给 DateTime 对象的时区只影响 toString() 的输出(当此毫秒值是 "translated" 到本地日期和时间时),但它不会更改毫秒值本身。所以如果你这样做:

DateTime dateTime = new DateTime(now, fromTimeZone);
System.out.println(dateTime);

它将打印相当于毫秒值的日期和时间,但转换为 fromTimeZone (America/New_York):

2017-08-22T13:33:08.345-04:00

withZone方法只是设置了不同的时区,但是keeps the same milliseconds value:

DateTime newDateTime = dateTime.withZone(toTimeZone);
System.out.println(newDateTime);

上面的代码保留了即时(毫秒值),但在 toTimeZone (US/Central):

中打印了等效的日期和时间

2017-08-22T12:33:08.345-05:00

.toDate()方法returns一个java.util.Date,它只包含相同的毫秒值,没有时区信息。然后,System.out.println 隐式调用 Date::toString() method, and this converts the milliseconds value to the JVM's default timezone。在这种情况下,两者都是:

Tue Aug 22 13:33:08 EDT 2017

因为两个日期代表相同的时刻(自纪元以来的相同毫秒数)。


如果你想获得包含特定格式日期的 String,你可以使用 org.joda.time.format.DateTimeFormatter:

DateTimeFormatter fmt = DateTimeFormat.forPattern("EEE MMM dd HH:mm:ss z yyyy").withLocale(Locale.ENGLISH);
System.out.println(fmt.print(new DateTime(DateTimeZone.forID("US/Central"))));

不需要转换日期对象,因为实际上没有真正发生转换:以上所有方法都不会更改毫秒值。

另请注意,我使用 java.util.Locale 来确保月份和星期几是英文的。如果您不指定语言环境,将使用 JVM 默认值,并且不能保证始终为英语(并且它也可以更改,即使在运行时也是如此,因此最好始终指定它)。

然后我获取当前日期并设置打印时要使用的时区。请注意,您可以直接获得 DateTime,无需创建 java.util.Date

输出将是:

Tue Aug 22 12:33:08 CDT 2017

要获得完全相同的输出(包括两个日期),您可以:

DateTimeFormatter fmt = DateTimeFormat.forPattern("EEE MMM dd HH:mm:ss z yyyy").withLocale(Locale.ENGLISH);
DateTime nowNy = new DateTime(DateTimeZone.forID("America/New_York"));
DateTime nowCentral = nowNy.withZone(DateTimeZone.forID("US/Central"));
System.out.println(fmt.print(nowNy) + "--" + fmt.print(nowCentral));

输出将是:

Tue Aug 22 13:33:08 EDT 2017--Tue Aug 22 12:33:08 CDT 2017


Java新Date/TimeAPI

Joda-Time 处于维护模式并被新的 APIs 取代,所以我不建议用它开始一个新项目。即使在 joda's website 它说:"Note that Joda-Time is considered to be a largely “finished” project. No major enhancements are planned. If using Java SE 8, please migrate to java.time (JSR-310)." (如果你不想或不能从 Joda 迁移到另一个 API, 你可以不考虑这个部分).

如果您正在使用 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),但是 类和方法名称相同。

相关的类是DateTimeFormatter(将日期格式化为特定格式的String),ZonedDateTime(表示日期和时间特定时区)和 ZoneId(代表时区):

// formatter - use English locale for month and day of week
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss z yyyy", Locale.ENGLISH);

// current date/time in New York timezone
ZonedDateTime nowNy = ZonedDateTime.now(ZoneId.of("America/New_York"));
// convert to another timezone (US/Central)
ZonedDateTime nowCentral = nowNy.withZoneSameInstant(ZoneId.of("US/Central"));

// format dates
System.out.println(fmt.format(nowNy) + "--" + fmt.format(nowCentral));

输出同上