使用小时调整而不是区域设置打印 DateTime 的最简单方法是什么?

What is the easiest way to print a DateTime using an hour adjustment instead of a locale?

我正在开发一个系统,在该系统中,每个用户都可以根据 UTC 以小时为单位指定他们的调整设置。

因此,当此用户输入 date/time 时,该值会根据他们的设置进行调整并保存为 UTC。同样,在出路时,date/time 从数据库中检索,根据其设置进行调整并显示。

我可能想太多了,但这是否意味着要为每个人显示正确的 date/time,我必须有效地调整时间并告诉我的 SimpleDateFormat 实例这是 "UTC"?现在我在英国,当前时区是 UTC+1,如果我不指定以 UTC 打印,那么时间会延迟一小时!

DateTime dateWithOffset = statusDate.plusMinutes(currentTimezoneOffsetInMinutes);

SimpleDateFormat sdf = new SimpleDateFormat("dd MMM yyyy HH:mm");
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
return sdf.format(dateTime.toDate());

我的想法是否正确?或者有没有更简单的方法来打印日期格式,因为我只想从 UTC 调整小时数?

你太辛苦了。切勿手动调整偏移量和时区,切勿为此目的向日期时间值添加或减去分钟数。让体面的日期时间库来完成这项工作。

java.time

Joda-Time 团队建议我们迁移到 Java 8 及更高版本中内置的 java.time 框架。

ZoneOffset class represents an offset-from-UTC。请记住,在某些地区,偏移可能不仅涉及小时数,还涉及分钟甚至秒。

OffsetDateTime class 表示时间轴中具有指定偏移量的时刻。

int hours = 3;  // input by user

ZoneOffset offset = ZoneOffset.ofHours( hours );
OffsetDateTime odt = OffsetDateTime.now( offset );

java.time 中的 toString 方法使用标准 ISO 8601 格式。

String output = odt.toString();

一般而言,最佳做法是在 UTC 中执行业务逻辑和数据存储。转换 to/from 偏移量或分区值仅用于与用户交互。

java.time UTC is represented by the Instant class 时间线上的一个时刻。您可以从 OffsetDateTime.

中提取一个 Instant 对象
Instant instant = odt.toString();

这个Instant和这个OffsetDateTime都代表了时间轴上的同一时刻。他们呈现出不同的wall-clock times.

跳过OffsetDateTime.now便捷方法的使用,从Instant开始可能更清晰。

Instant instant = Instant.now();  // Always in UTC, by definition.
ZoneOffset offset = ZoneOffset.ofHours( hours );
OffsetDateTime odt = OffsetDateTime.ofInstant( instant , offset );  // Same moment but presenting alternate wall-clock time.

处理输入

如果用户将日期时间值作为字符串输入,我们需要进行解析。在 java.time 中表示 DateTimeFormatter class。格式代码与过时的 java.text.SimpleDateFormat 相似但不完全相同,因此请务必研究文档。

DateTimeFormatter formatter = DateTimeFormatter.ofPattern( "dd MMM uuuu HH:mm";

由于与 UTC 的偏移量是单独给出的,我们将此输入字符串解析为 LocalDateTime 缺少时区信息。

LocalDateTime ldt = LocalDateTime.parse( inputString , formatter );

要查看,请通过调用 ldt.toString() 创建一个 ISO 8601 格式的字符串对象。

2016-01-02T12:34:45

应用预先确定的 ZoneOffset 对象生成 OffsetDateTime 对象。

OffsetDateTime odt = ldt.atOffset( offset );

2016-01-12T12:34:45+03:00

以 UTC 思考

处理日期时间值很头疼。 UTC 是你的阿司匹林。

当程序员到达办公室时,她应该摘下“英国公民/伦敦居民”的帽子,戴上“UTC”的帽子。忘记您自己的本地时区。学会用 UTC(和 24 小时制)思考。在您的办公桌或计算机上添加另一个时钟,设置为 UTC(或 Reykjavík Iceland), or at least bookmark a page like time.is/UTC。在 UTC 中执行所有日志记录、业务逻辑、数据序列化、数据交换和调试。

Instant 成为您的第一个想法,您的首选 class。根据定义,它的值始终采用 UTC。

Instant instant = Instant.now();

查看从我们在上面看到的 OffsetDateTime 值中提取的 Instant,其字符串表示形式为 2016-01-12T12:34:45+03:00。在 UTC 中意味着上午 9 点而不是中午,同一时刻但挂钟时间相差三个小时。 ZZulu 的缩写,表示 UTC。

String output = odt.toInstant().toString();

2016-01-12T09:34:45Z

仅在用户或数据接收器期望时根据需要调整偏移量或时区。仅供参考,时区是与 UTC 的偏移量 加上 一组用于处理夏令时 (DST) 等异常的规则。 尽可能使用时区而不是单纯的偏移量

Europe/London时区夏天和UTC一样,但是冬天用夏令时废话,比UTC早一小时。因此,使用上面看到的相同 Instant,伦敦挂钟时间是上午 10 点,而不是 UTC 中的上午 9 点,并且与我们看到的偏移 +03:00.[=68= 的中午不同]

ZoneId zoneId = ZoneId.of( "Europe/London" );
ZonedDateTime zdt = ZonedDateTime.ofInstant( instant , zoneId );

2016-01-12T10:34:45+01:00[Europe/London]

始终指定 desired/required 偏移量或时区;永远不要通过省略此可选参数来依赖隐式当前默认值。 (Locale by the way.) Note how in all the code of this answer the fact that your JVM has a current default time zone (ZoneId.systemDefault) of Europe/London and the fact that my JVM has a current default time zone of America/Los_Angeles 的同上 完全无关 。代码运行相同,得到相同的结果,无论您使用什么机器进行开发、测试和部署。

语言环境

在生成日期时间值的文本表示时指定一个 Locale 对象,该日期时间值涉及月份或日期、逗号或句点等名称。 Locale 决定 (a) 翻译此类名称时使用的人类语言,以及 (b) 在决定标点符号等问题时遵循的文化规范。

Locale 与时区和与 UTC 的偏移量无关。例如,您可以在印度使用 Locale.CANADA_FRENCH with a date-time zoned for Asia/Kolkata if you had a Québécois 用户。