在 Joda-Time 中使用两种时区格式解析日期的模式

Pattern for parsing date with two timezone format in Joda-Time

我有一个场景,我正在获取各种不同模式的日期字符串(来自第三方电子邮件服务器)(例如):

这意味着,只有时区正在更改。我可以使用 Java 的 SimpleDateFormat 轻松解析它,例如:

String pattern = "EEE, dd MMM yyyy HH:mm:ss Z '('z')'"
SimpleDateFormat df = new SimpleDateFormat(pattern);
df.parse("Fri, 31 Mar 2017 13:31:14 +0530 (IST)");

但是当使用 Joda-Time 库中的 DateTimeFormat 时,我无法使用相同的模式。

String pattern = "EEE, dd MMM yyyy HH:mm:ss Z '('z')'"
DateTimeFormat parser = DateTimeFormat.forPattern(pattern)
parser.parseDateTime("Fri, 31 Mar 2017 13:31:14 +0530 (IST)")

我在这里缺少什么?

tl;博士

String input = "Mon, 13 Mar 2017 19:00:10 +0530 (IST)";
int index = input.indexOf ( " (" ); // Searching for SPACE + LEFT PARENTHESIS.
String inputModified = input.substring ( 0 , index ); // "Mon, 13 Mar 2017 19:00:10 +0530"

Instant instant = 
    OffsetDateTime.parse ( 
        inputModified , 
        DateTimeFormatter.ofPattern( "EEE, d MMM uuuu HH:mm:ss Z" ) 
    ).toInstant() 
;

查看类似内容 code run live at IdeOne.com

使用java.time

仅供参考:Joda-Time project, now in maintenance mode, advises migration to the java.time 类。

two timezone format in Joda-Time

Mon, 13 Mar 2017 19:00:10 +0530 (IST)

不,那是 time zone 格式。

+0530 是一个 offset-from-UTC, a number of hours and minutes away from UTC

指定 proper time zone name in the format of continent/region, such as America/Montreal, Africa/CasablancaPacific/Auckland。切勿使用 3-4 个字母的缩写,例如 ESTIST,因为它们 不是 真正的时区,不是标准化的,甚至不是唯一的(!)。

由于无法可靠地解析 3-4 个字母的缩写,Joda-Time 有拒绝尝试的策略(如上面 Hugo 的评论所述)。考虑到接下来我们将看到的情况,我怀疑这是一项明智的政策。

java.time类尝试猜测解析此类伪时区名称,但可能不会您的预期价值。事实上,它不恰当地解释了你的第一个例子,在包括 印度标准时间 在内的选择中,将 IST 显然解释为 以色列标准时间 爱尔兰标准时间,可能更多。

String input = "Mon, 13 Mar 2017 19:00:10 +0530 (IST)";
DateTimeFormatter f = DateTimeFormatter.ofPattern( "EEE, d MMM uuuu HH:mm:ss Z '('z')'") ;
ZonedDateTime zdt = ZonedDateTime.parse ( input , f );

zdt.toString(): 2017-03-13T19:00:10+02:00[Asia/Jerusalem]

所以我建议你去掉最后的假缩写块。将剩余的文本解析为 OffsetDateTime,它至少可以在时间轴上为您提供准确的时刻。调整为 Instant 的 UTC,因为您的大部分工作通常应该在 UTC 中完成,包括您的日志记录。

使用 String::substring 删除缩写。请注意,我们在子字符串搜索中将左括号前的 SPACE 包括在内,因为我们要删除这两个字符以及之后的所有内容。

String input = "Mon, 13 Mar 2017 19:00:10 +0530 (IST)";
int index = input.indexOf ( " (" ); // Searching for SPACE + LEFT PARENTHESIS.
String inputModified = input.substring ( 0 , index );

inputModified: Mon, 13 Mar 2017 19:00:10 +0530

解析为 OffsetDateTime 对象,使用末尾的数字偏移量指导我们确定该值的确切时刻。

DateTimeFormatter f = DateTimeFormatter.ofPattern( "EEE, d MMM uuuu HH:mm:ss Z" );
OffsetDateTime odt = OffsetDateTime.parse ( inputModified , f );

odt.toString(): 2017-03-13T19:00:10+05:30

提取一个 Instant 对象以在 UTC 中为我们提供相同的时刻。

Instant instant = odt.toInstant ();

instant.toString(): 2017-03-13T13:30:10Z

如果你坚持的话,你可以调整到你自己的特定时区。但我建议在佩戴 Programmer hat 时学会用 UTC 思考。将 UTC 视为“唯一真实时间”,所有其他时区都只是该主题的变体。

ZoneId z = ZoneId.of( "America/Montreal" );
ZonedDateTime zdt = instant.atZone( z );

ISO 8601

您示例中显示的那种模式在过去的协议中很常见,例如 RFC 1123 / RFC 822。

如今,方法是始终使用 ISO 8601。在这个现代标准中,格式易于跨各种人类文化阅读,对英语的依赖程度较低,易于机器解析,并且设计明确。

java.time 类 在 generating/parsing 字符串时默认使用 ISO 8601。您可以在我上面的示例中看到它们生成的输出。请注意,ZonedDateTime 通过在方括号中附加时区名称来扩展标准。

顺便说一句,如果您有完全符合 RFC 1123 的类似输入,请知道 java.time 提供了一个预定义的格式化程序对象,DateTimeFormatter.RFC_1123_DATE_TIME