【问题标题】:Output RFC 3339 Timestamp in Java在 Java 中输出 RFC 3339 时间戳
【发布时间】:2010-09-22 07:18:14
【问题描述】:

我想输出带有 PST 偏移量的时间戳(例如,2008-11-13T13:23:30-08:00)。 java.util.SimpleDateFormat 似乎没有以 hour:minute 格式输出时区偏移量,它不包括冒号。有没有一种简单的方法可以在 Java 中获取该时间戳?

// I want 2008-11-13T12:23:30-08:00
String timestamp = new SimpleDateFormat("yyyy-MM-dd'T'h:m:ssZ").format(new Date());
System.out.println(timestamp); 
// prints "2008-11-13T12:23:30-0800" See the difference?

另外,SimpleDateFormat 无法正确解析上面的示例。它会抛出一个ParseException

// Throws a ParseException
new SimpleDateFormat("yyyy-MM-dd'T'h:m:ssZ").parse("2008-11-13T13:23:30-08:00")

【问题讨论】:

  • 对于这个问题的新读者,我建议你不要使用SimpleDateFormatDate。这些类设计不佳且早已过时,尤其是前者,尤其是出了名的麻烦。使用现代 Java 日期和时间 API java.time 中的 OffsetDateTimeZonedDateTime。见the answer by Arvind Kumar Avinash

标签: java timestamp rfc3339


【解决方案1】:

我用这个测试了很多,对我来说效果很好......特别是在解析(以及格式化)方面,它是迄今为止我发现的最接近的

DateTimeFormatter rfc3339Formatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME;

DateTimeFormatter rfc3339Parser = new DateTimeFormatterBuilder()
    .parseCaseInsensitive()
    .appendValue(ChronoField.YEAR, 4)
    .appendLiteral('-')
    .appendValue(ChronoField.MONTH_OF_YEAR, 2)
    .appendLiteral('-')
    .appendValue(ChronoField.DAY_OF_MONTH, 2)
    .appendLiteral('T')
    .appendValue(ChronoField.HOUR_OF_DAY, 2)
    .appendLiteral(':')
    .appendValue(ChronoField.MINUTE_OF_HOUR, 2)
    .appendLiteral(':')
    .appendValue(ChronoField.SECOND_OF_MINUTE, 2)
    .optionalStart()
    .appendFraction(ChronoField.NANO_OF_SECOND, 2, 9, true) //2nd parameter: 2 for JRE (8, 11 LTS), 1 for JRE (17 LTS)
    .optionalEnd()
    .appendOffset("+HH:MM","Z")
    .toFormatter()
    .withResolverStyle(ResolverStyle.STRICT)
    .withChronology(IsoChronology.INSTANCE);

测试用例https://github.com/guyplusplus/RFC3339-DateTimeFormatter

【讨论】:

  • 是正确的,它使用了现代 Java 日期和时间 API java.time,这很好。我们不需要这种复杂性。查看 Vijay Upadhyay 和 Arvind Kumar Avinash 的答案。
  • 感谢您的反馈。实际上重新检查后 DateTimeFormatter.ISO_OFFSET_DATE_TIME 是最好的。
  • 这并不完全正确,即它不会解析在小数秒中具有 1 位数字的有效 RFC3339 值。实际上,RFC3339 允许分数中有任意数量的数字,但 Java 时间最多只支持 9(即使在宽松模式下)。因此我认为应该是appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true)
  • 实际上它确实在小数秒内解析了 1 位数字,在第 95 行进行了测试。JDK 中存在一个错误,直到 11,它需要在 @987654324 中为 1 或 2(参见第 38 行) @ 取决于 JDK 版本。如果将 appendFraction 的第二个参数设置为 0,则日期时间如“1985-04-12T23:20:50.Z”(第 140 行)被认为是有效的,但它不符合 RFC3339:如果有句点, 至少需要 1 位数字。好的,那里变得超级详细!至于最多 9 位数字,是的,这是 JAVA 限制。
【解决方案2】:

java.time

java.util 日期时间 API 及其格式化 API SimpleDateFormat 已过时且容易出错。建议完全停止使用,改用modern Date-Time API*

另外,下面引用的是来自home page of Joda-Time的通知:

请注意,从 Java SE 8 开始,用户被要求迁移到 java.time (JSR-310) - JDK 的核心部分,它取代了这个项目。

使用现代日期时间 API java.time 的解决方案:Pacific Time Zone 中最大的城市是洛杉矶,其时区名称为 America/Los_Angeles。使用ZoneId.of("America/Los_Angeles"),您可以创建ZonedDateTime 的实例,该实例旨在自动调整DST 转换时的时区偏移。

如果您需要时区偏移量但不需要时区名称,您可以使用ZonedDateTime#toOffsetDateTimeZonedDateTime 转换为OffsetDateTimeOffsetDateTime 的其他一些用途是创建具有固定时区偏移量的日期时间实例(例如 Instant.now().atOffset(ZoneOffset.of("+05:30")),并解析具有时区偏移量的日期时间字符串。

演示:

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;

public class Main {
    public static void main(String[] args) {
        ZoneId zoneIdLosAngeles = ZoneId.of("America/Los_Angeles");
        ZonedDateTime zdtNowLosAngeles = ZonedDateTime.now(zoneIdLosAngeles);
        System.out.println(zdtNowLosAngeles);

        // With zone offset but without time zone name
        OffsetDateTime odtNowLosAngeles = zdtNowLosAngeles.toOffsetDateTime();
        System.out.println(odtNowLosAngeles);

        // Truncated up to seconds
        odtNowLosAngeles = odtNowLosAngeles.truncatedTo(ChronoUnit.SECONDS);
        System.out.println(odtNowLosAngeles);

        // ################ A winter date-time ################
        ZonedDateTime zdtLosAngelesWinter = ZonedDateTime
                .of(LocalDateTime.of(LocalDate.of(2021, 11, 20), LocalTime.of(10, 20)), zoneIdLosAngeles);
        System.out.println(zdtLosAngelesWinter); // 2021-11-20T10:20-08:00[America/Los_Angeles]
        System.out.println(zdtLosAngelesWinter.toOffsetDateTime()); // 2021-11-20T10:20-08:00

        // ################ Parsing a date-time string with zone offset ################
        String strDateTime = "2008-11-13T13:23:30-08:00";
        OffsetDateTime odt = OffsetDateTime.parse(strDateTime);
        System.out.println(odt); // 2008-11-13T13:23:30-08:00
    }
}

样本运行的输出:

2021-07-18T03:27:15.578028-07:00[America/Los_Angeles]
2021-07-18T03:27:15.578028-07:00
2021-07-18T03:27:15-07:00
2021-11-20T10:20-08:00[America/Los_Angeles]
2021-11-20T10:20-08:00
2008-11-13T13:23:30-08:00

ONLINE DEMO

您一定注意到我没有使用DateTimeFormatter 来解析您问题的日期时间字符串。这是因为您的日期时间字符串符合 ISO-8601 标准。现代日期时间 API 基于 ISO 8601,只要日期时间字符串符合 ISO 8601 标准,就不需要明确使用 DateTimeFormatter 对象。

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


* 出于任何原因,如果您必须坚持使用 Java 6 或 Java 7,您可以使用 ThreeTen-Backport,它将大部分 java.time 功能向后移植到 Java 6 和 7 . 如果您正在为一个 Android 项目工作并且您的 Android API 级别仍然不符合 Java-8,请检查 Java 8+ APIs available through desugaringHow to use ThreeTenABP in Android Project

【讨论】:

    【解决方案3】:

    我尝试了这种格式并为我工作yyyy-MM-dd'T'HH:mm:ss'Z'

    【讨论】:

      【解决方案4】:

      我们可以简单地使用ZonedDateTime classDateTimeFormatter class

      DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssxxx");
      ZonedDateTime z2 = ZonedDateTime.now(ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS);
      System.out.println("format =======> " + z2.format(format));
      

      输出:格式 =======> 30-03-2020T05:57:37+00:00

      【讨论】:

        【解决方案5】:

        查看Joda Time 包。它们使 RFC 3339 日期格式更容易。

        乔达示例:

        DateTime dt = new DateTime(2011,1,2,12,45,0,0, DateTimeZone.UTC);
        DateTimeFormatter fmt = ISODateTimeFormat.dateTime();
        String outRfc = fmt.print(dt);
        

        【讨论】:

        • 这个答案实际上对一个例子很有用。
        • @Biff 您可以简化您的代码示例。不需要格式化程序。 Joda-Time 自动默认为 ISO 8601 / RFC 3339 格式。只需显式或隐式调用toString 方法。像这样,String output = dt.toString();
        【解决方案6】:

        我花了很多时间寻找同一问题的答案,我在这里找到了一些东西:http://developer.android.com/reference/java/text/SimpleDateFormat.html

        建议答案:

        String timestamp = new SimpleDateFormat("yyyy-MM-dd'T'h:m:ssZZZZZ").format(new Date());

        如果您注意到我使用的是 5 'Z' 而不是 1。这会在偏移量中给出带有冒号的输出,如下所示:“2008-11-13T12:23:30-08:00”。希望对您有所帮助。

        【讨论】:

        • 谢谢 - 你的回答被低估了 - 这就是最终允许我输出 RFC 3339 的原因。使用 X 给了我Caused by: java.lang.IllegalArgumentException: Illegal pattern component: XXX at org.apache.commons.lang.time.FastDateFormat.parsePattern(FastDateFormat.java:691) at org.apache.commons.lang.time.FastDateFormat.init(FastDateFormat.java:558)
        • 乐于助人:)
        【解决方案7】:

        从“完成部门”开始,一种解决方案是在 SimpleDateFormat 完成后使用正则表达式来修复字符串。 Perl 中的类似 s/(\d{2})(\d{2})$/$1:$2/ 的东西。

        如果您甚至对此感兴趣,我将使用有效的 Java 代码编辑此响应。

        但是,是的。我也遇到了这个问题。 RFC3339,我在看着你!

        编辑:

        这对我有用

        // As a private class member
        private SimpleDateFormat rfc3339 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
        
        String toRFC3339(Date d)
        {
           return rfc3339.format(d).replaceAll("(\\d\\d)(\\d\\d)$", "$1:$2");
        }
        

        【讨论】:

          【解决方案8】:

          从 Java 7 开始,有用于 ISO8601 时区的 X 模式字符串。对于您描述的格式的字符串,请使用XXXSee the documentation.

          示例:

          System.out.println(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX")
                  .format(new Date()));
          

          结果:

          2014-03-31T14:11:29+02:00
          

          【讨论】:

          • 我在解析从 Google Cloud API 获得的“2017-12-12T02:01:43.924-08:00”时遇到 ParseException
          • @pujanjain 这有毫秒,这个模式不包括。
          • 我认为任何声称符合 RFC3339 的答案都必须按照 xml2rfc.tools.ietf.org/public/rfc/html/rfc3339.html#anchor14 处理可选的 1 位毫秒数
          【解决方案9】:

          我为 RFC3339 创建了一个 InternetDateFormat 类。

          但是源码注释是日文的。

          PS:我自己做了英文版,做了一点重构。

          【讨论】:

            【解决方案10】:

            我发现了一个帮助我解决问题的流浪 PasteBin:http://pastebin.com/y3TCAikc

            以防万一它的内容以后被删除:

            // I want 2008-11-13T12:23:30-08:00
            String timestamp = new SimpleDateFormat("yyyy-MM-dd'T'h:m:ssZ").format(new Date());
            System.out.println(timestamp); 
            // prints "2008-11-13T12:23:30-0800" See the difference?
            
            // Throws a ParseException
            new SimpleDateFormat("yyyy-MM-dd'T'h:m:ssZ").parse("2008-11-13T13:23:30-08:00")
            
            SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'h:m:ss.SZ");
            

            【讨论】:

              【解决方案11】:

              问题在于 Z 产生的时区偏移量没有冒号 (:) 作为分隔符。

              【讨论】:

              • 绝对正确,但没有告诉我们如何解决它。 jjohn 的解决方案为我解决了这个问题。
              【解决方案12】:
              SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'h:m:ss.SZ");
              

              这不是你真正需要的吗?

              【讨论】:

              • 没有。如果你尝试解析上面给出的时间戳,它会抛出 ParseException。
              猜你喜欢
              • 2013-02-09
              • 2012-01-23
              • 1970-01-01
              • 1970-01-01
              • 2016-04-14
              • 1970-01-01
              • 1970-01-01
              • 2011-11-06
              • 2011-08-27
              相关资源
              最近更新 更多