使用JAVA解析RFC 2822日期

21

我需要在Java中解析RFC 2822日期的字符串表示。以下是一个示例字符串:

  

Sat, 13 Mar 2010 11:29:05 -0800

这看起来相当复杂,因此我想确保我做的一切都是正确的,并且不会出现日期通过AM-PM / 军事时间问题、UTC时间问题等错误被解释错误的奇怪问题。

谢谢!


3
你尝试过使用java.text.SimpleDateFormat吗? - skaffman
看起来这是正确的使用方式,我也担心正确的格式字符串是什么,因为它看起来很容易出错。不过根据下面的答案,这似乎是 RFC 2822 的正确字符串:"EEE, d MMM yyyy HH:mm:ss Z"。 - Chris Dutrow
太疯狂了,我从官方的SimpleDateFormat页面上拿下来的。虽然在他们的示例中技术上是正确的,但仍然是错误的:http://java.sun.com/j2se/1.4.2/docs/api/java/text/SimpleDateFormat.html感谢您提醒我,这就是我代码中的内容。 - Chris Dutrow
标准来源:互联网消息格式,请向下滚动至“3.3 日期和时间规范”一节。 - Ole V.V.
7个回答

27

这是快速代码,使用SimpleDateFormat实现了你的需求。

String rfcDate = "Sat, 13 Mar 2010 11:29:05 -0800";
String pattern = "EEE, dd MMM yyyy HH:mm:ss Z";
SimpleDateFormat format = new SimpleDateFormat(pattern);
Date javaDate = format.parse(rfcDate);

//Done.

PS. 我在这里没有处理异常和并发(因为SimpleDateFormat在解析日期时不是同步的)。


请使用此链接 http://docs.oracle.com/javase/8/docs/api/java/text/SimpleDateFormat.html 获取最新可接受值。 - rajadilipkolli
1
请注意,此答案已过时。SimpleDateFormatDate 类已经被现代的 JSR 310 中定义的 java.time 类取代多年了。请参阅 pathfinder78 的答案 - Basil Bourque

20

如果你的应用程序使用的语言不是英语,你可能想要通过使用另一个SimpleDateFormat构造函数来强制指定日期解析/格式化的本地化信息:

String pattern = "EEE, dd MMM yyyy HH:mm:ss Z";
SimpleDateFormat format = new SimpleDateFormat(pattern, Locale.ENGLISH);

4
设置本地化(Locale)是必需的。Android 的 Locale javadoc 建议在计算机之间的通信中使用 Locale.US。链接 - jason gilbert
+1 Locale.ROOT 也是一个不错的选择,但并非所有系统都可用。 - Jules
@jasongilbert,您能解释一下为什么您说Locale“绝对是必需的”吗?根据文档,它并不是必需的:http://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html -- 但也许您是指“如果需要”,我对后面的“如果”很感兴趣... - Adam Tuttle
2
@AdamTuttle 如果您希望在默认语言环境不是美式英语的情况下正确解析日期,那么这是必需的。例如,如果用户在德国,则使用其默认语言环境无法正确解析日期。 - jason gilbert

6
请记住,在 RFC-2822 中,“[星期几]”是可选的,因此建议的示例未覆盖所有 RFC-2822 日期格式。另外,RFC-822 日期类型允许许多不同的时区标注(obs-zone),这些标注不被“Z”格式说明符覆盖。我猜除了查找“,”和“-|+”来确定要使用哪个模式之外,没有其他简单的方法。

2
+1 指出星期几是可选的。我正在设置一个瀑布式的 try/catch,使用各种模式来不断尝试变化,直到找到一个可行的为止。 - Adam Tuttle
1
不仅 day-of-week 是可选的,而且 time-of-day 中的秒也被声明为可选项:time-of-day = hour ":" minute [ ":" second ] - Roman Vottner

4

DateTimeFormatter.RFC_1123_DATE_TIME

自Java 8以来,新的日期时间类被实现:java.time.ZonedDateTimejava.time.LocalDateTimeZonedDateTime几乎可以直接支持解析RFC字符串:

String rfcDate = "Tue, 4 Dec 2018 17:37:31 +0100 (CET)";  
if (rfcDate.matches(".*[ ]\\(\\w\\w\\w\\)$")) {
    //Brackets with time zone are added sometimes, for example by JavaMail
    //This must be removed before parsing
    //from: "Tue, 4 Dec 2018 17:37:31 +0100 (CET)"
    //  to: "Tue, 4 Dec 2018 17:37:31 +0100"
    rfcDate = rfcDate.substring(0, rfcDate.length() - 6);
}

//and now parsing... 
DateTimeFormatter dateFormat = DateTimeFormatter.RFC_1123_DATE_TIME;
try {
    ZonedDateTime zoned = ZonedDateTime.parse(rfcDate, dateFormat);
    LocalDateTime local = zoned.toLocalDateTime();        
} catch (DateTimeParseException e) { ... }

1
解析方面的回答很好。但我不建议将ZonedDateTime转换为LocalDateTime。这样做会丢失有价值的信息——时区,而没有获得任何好处。就像没有特定货币背景下的金额一样,LocalDateTime是模糊的,因此无法表示一个时刻。 - Basil Bourque

3

有一个javax.mail类可以执行RFC-2822日期的解析:

javax.mail.internet.MailDateFormat

包括可选和过时的格式。

只需执行以下操作:

new javax.mail.internet.MailDateFormat().parse("Sat, 13 Mar 2010 11:29:00 -0800")
new javax.mail.internet.MailDateFormat().parse("13 Mar 2010 11:29:00 -0800")
new javax.mail.internet.MailDateFormat().parse("13 Mar 2010 11:29 -0800")

它将正确解析这些有效的RFC-2822日期。

至于其他旧的日期格式化程序,MailDateFormat 类不是线程安全的。


1
RFC 2822日期时间字符串包含时区偏移量,例如给定的字符串 Sat, 13 Mar 2010 11:29:05 -0800 具有与UTC相差-08:00小时的时区偏移量,即可以通过将8小时添加到 Sat, 13 Mar 2010 11:29:05 来获取UTC的等效日期时间。
Java 8引入了新的日期时间API,其中包含OffsetDateTime用于表示带有时区偏移量的日期时间。请注意保留HTML标签。
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Locale;

class Main {
    public static void main(String[] args) {
        String strRFC2822DateTimeStr = "Sat, 13 Mar 2010 11:29:05 -0800";

        OffsetDateTime odt = OffsetDateTime.parse(strRFC2822DateTimeStr, DateTimeFormatter.RFC_1123_DATE_TIME);
        System.out.println(odt);

        // Alternatively: using a custom DateTimeFormatter
        DateTimeFormatter parser = DateTimeFormatter.ofPattern("EEE, dd MMM uuuu HH:mm:ss XX", Locale.ENGLISH);
        System.out.println(OffsetDateTime.parse(strRFC2822DateTimeStr, parser));

        // In case you need the equivalent date-time at UTC
        OffsetDateTime odtUtc = odt.withOffsetSameInstant(ZoneOffset.UTC);
        System.out.println(odtUtc);
    }
}

输出:

2010-03-13T11:29:05-08:00
2010-03-13T11:29:05-08:00
2010-03-13T19:29:05Z

了解有关现代日期时间API的更多信息,请参阅Trail: Date Time

一些有用的链接:

  1. 永远不要在没有Locale的情况下使用SimpleDateFormat或DateTimeFormatter
  2. 您可以使用y代替u,但我更喜欢u而不是y
  3. 如何使用JDBC和OffsetDateTime

1

试试这个:

String dateTime = OffsetDateTime.now().format(DateTimeFormatter.RFC_1123_DATE_TIME); 
//RFC_1123 == RFC_2822

1
虽然正确,但这回答了相反的问题。这个问题是关于从字符串解析到OffsetDateTme(或其他日期时间对象)的。感谢您的贡献。 - Ole V.V.
很高兴能够帮忙。这是唯一一个我的电子邮件验证系统接受的正确选项。我尝试了其他选项,最终选择了这个。 - Serhii D

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