Java中SimpleDateFormat在不同操作系统下解释 'z' 的方式不同

8

以下是简化版代码,用SimpleDateFormat模式打印时区信息。

你知道为什么在不同的机器上z会被处理得不同吗?如果有一种方法可以告诉Java在所有机器上都统一地处理它吗?

这个类被用在JavaMail中,这导致我们的电子邮件头部包含的时间不符合RFC 2822标准。

import java.text.SimpleDateFormat;
import java.util.Calendar;

public class DateFormatTest {
    String PATTERN = "z";
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat(this.PATTERN);

    public static void main(final String[] args) {
        new DateFormatTest().printTimezone();
    }

    public void printTimezone() {
        System.out.println(this.simpleDateFormat.format(Calendar.getInstance().getTime()));
    }

}

输出结果:Windows / Mac
PDT

输出:Linux(CentOS Linux 7.5.1804(核心))/ Ubuntu 14 / 18

GMT-07:00

2
你能具体说明违反了哪个RFC(和哪个部分)吗? - Thierry
1
@Gopinath RFC 2822 - Internet Message Format - IETF Datatracker 可能是 RFC 822,其中大写字母 Z 而不是小写字母 z - user85421
它是RFC2822,如Java Mail API(https://javaee.github.io/javaee-spec/javadocs/javax/mail/internet/MailDateFormat.html)中所记录的。无论如何,令人困惑的问题是什么影响格式字符串中小写`z`的输出?在某些环境中,`z`的输出具有带偏移量的时区,而在其他环境中,它只是时区。 - Gopinath
2
请注意,像java.util.Datejava.util.Calendarjava.text.SimpleDateFormat这样的旧日期时间类现在已经成为遗留系统,被内置于Java 8及更高版本中的java.time类所取代。请参阅Oracle的教程 - Basil Bourque
为什么只允许使用5个标签?我想添加 [timezone] 和/或 [timezoneoffset],可能还有 [datetime-formatting]。您可以考虑用其中一个来替换您已经有的标签(我不想为您做出选择)。 - Ole V.V.
显示剩余2条评论
1个回答

10

简短总结

不要使用Calendar,而应该使用java.time类。

对于RFC 1123 / RFC 822格式的字符串:

OffsetDateTime
.now( ZoneOffset.UTC )
.format( DateTimeFormatter.RFC_1123_DATE_TIME )

获取特定时区的当前UTC偏移量的方法是:
ZoneId
.systemDefault()
.getRules()
.getOffset(
    Instant.now() 
)
.toString()

避免使用Calendar 您正在使用可怕的旧日期时间类,这些类早在多年前就被java.time替代了。永远不要使用那些传统的类;它们是一个可怕的糟糕混乱。
您关于Calendar行为的特定问题已经没有意义了,因为再也没有必要再次使用该类。即使与尚未更新为java.time的旧代码进行交互,您也可以通过添加到旧类中的新方法轻松转换传统和现代类之间的差异。
ZonedDateTime zdt = myGregorianCalendar.toZonedDateTime() ;

…and…

GregorianCalendar gc = GregorianCalendar.from( zdt ) ; 

java.time

显然,您想要当前默认时区的UTC偏移量。

获取当前默认时区,一个ZoneId

ZoneId z = ZoneId.systemDefault() ;  // Or specify ZoneId.of( "Pacific/Auckland" ) or so on.

在该时区内查询规则

ZoneRules rules = z.getRules() ;

获取特定时区在某个时刻的UTC偏移量。我们将使用当前时刻,即Instant

Instant now = Instant.now() ;
ZoneOffset offset = rules.getOffset( now ) ;

生成一个表示UTC偏移量的文本。

String output = "At " + now + " in zone " + z + " the offset is " + offset;

在美国洛杉矶时区,日期时间为2018年9月24日23:38:44.192642Z,偏移量为-07:00。
RFC 1123 / RFC 822
您提到了一个RFC但没有指定。也许是RFC 1123 / 822? java.time内置了该格式的格式化程序。
OffsetDateTime nowInUtc = OffsetDateTime.now( ZoneOffset.UTC ) ;
String output = nowInUtc.format( DateTimeFormatter.RFC_1123_DATE_TIME ) ;

2018年9月24日23:45:21 GMT
FYI,RFC 1123 / RFC 822格式很糟糕。它假定使用英语。对于机器来解析和人类来阅读都很困难。但我知道你可能需要它来处理过时的旧协议。
现代协议使用ISO 8601标准格式。方便的是,在解析/生成字符串时,java.time类默认使用这些格式。

关于java.time

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

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

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

您可以直接使用与JDBC 4.2或更高版本兼容的JDBC驱动程序,与数据库交换java.time对象。无需使用字符串,也不需要使用java.sql.*类。

在哪里获取java.time类?

ThreeTen-Extra项目通过额外的类扩展了java.time。该项目是java.time可能未来添加类的试验场。您可能会在这里找到一些有用的类,如Interval, YearWeek, YearQuartermore

1
完整性值得赞赏。 - Jerry Andrews
1
顺便说一下,OffsetDateTime.now(ZoneId.of("America/Los_Angeles")).format(DateTimeFormatter.RFC_1123_DATE_TIME) 生成了 Mon, 24 Sep 2018 22:16:36 -0700。如果提问者喜欢的话,这也符合RFC标准,不是吗? - Ole V.V.
1
@OleV.V. 当然可以。 (a) 这个问题并没有明确说明目标是哪个时区或偏移量。(b) 我倾向于使用UTC,除非有明确的理由不使用。 - Basil Bourque

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