【问题标题】:Converting ISO 8601-compliant String to java.util.Date将符合 ISO 8601 的字符串转换为 java.util.Date
【发布时间】:2011-01-13 04:50:25
【问题描述】:

我正在尝试将ISO 8601 格式化字符串转换为java.util.Date

我发现yyyy-MM-dd'T'HH:mm:ssZ 模式与区域设置一起使用时符合 ISO8601(比较示例)。

但是,使用java.text.SimpleDateFormat,我无法转换格式正确的字符串2010-01-01T12:00:00+01:00。我必须先将其转换为2010-01-01T12:00:00+0100,不带冒号。

所以,目前的解决方案是

SimpleDateFormat ISO8601DATEFORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.GERMANY);
String date = "2010-01-01T12:00:00+01:00".replaceAll("\\+0([0-9]){1}\\:00", "+0$100");
System.out.println(ISO8601DATEFORMAT.parse(date));

这显然不是那么好。我错过了什么还是有更好的解决方案?


回答

感谢娟泽的评论,我找到了Joda-Time的魔法,它也是described here

所以,解决办法是

DateTimeFormatter parser2 = ISODateTimeFormat.dateTimeNoMillis();
String jtdate = "2010-01-01T12:00:00+01:00";
System.out.println(parser2.parseDateTime(jtdate));

或者更简单地说,通过构造函数使用默认解析器:

DateTime dt = new DateTime( "2010-01-01T12:00:00+01:00" ) ;

对我来说,这很好。

【问题讨论】:

  • 准备好接收大量“使用 JodaTime”的答案...
  • @Ice09:如果 DateTimeFormat 的 API 文档是正确的(但 JoDa 文档可能具有误导性、错误或不完整),那么您在自己的“答案”中使用的模式与 ISO8601 不兼容.
  • 我不确定这是什么时候添加的,但“X”似乎解决了 SimpleDateFormat 中的这个问题。模式“yyyy-MM-dd'T'HH:mm:ssX”成功解析了问题中的示例。
  • “X”从 Java 7 开始可用。
  • Java 8 让它变得简单!亚当在下面的答案中有一个隐藏的宝石:stackoverflow.com/a/27479533/1262901

标签: java date iso8601


【解决方案1】:

很遗憾,SimpleDateFormat(Java 6 及更早版本)可用的时区格式不符合ISO 8601。 SimpleDateFormat 理解时区字符串,如“GMT+01:00”或“+0100”,后者根据RFC # 822

即使 Java 7 根据 ISO 8601 添加了对时区描​​述符的支持,SimpleDateFormat 仍然无法正确解析完整的日期字符串,因为它不支持可选部分。

使用正则表达式重新格式化您的输入字符串当然是一种可能性,但替换规则并不像您的问题那么简单:

  • 某些时区并非全天休息UTC,因此字符串不一定以“:00”结尾。
  • ISO8601 只允许时区包含小时数,因此“+01”等同于“+01:00”
  • ISO8601 允许使用“Z”来表示 UTC,而不是“+00:00”。

更简单的解决方案可能是使用 JAXB 中的数据类型转换器,因为 JAXB 必须能够根据 XML Schema 规范解析 ISO8601 日期字符串。 javax.xml.bind.DatatypeConverter.parseDateTime("2010-01-01T12:00:00Z") 会给你一个 Calendar 对象,如果你需要一个 Date 对象,你可以简单地使用 getTime() 。

你也可以使用Joda-Time,但我不知道你为什么要这么做。

【讨论】:

  • JAXB 解决方案是一种非常有创意的方法!它也很有效,我已经用我的样本对其进行了测试。但是,对于遇到问题并被允许使用 JodaTime 的人,我建议使用它,因为它感觉更自然。但是您的解决方案不需要额外的库(至少对于 Java 6)。
  • 反过来:日历 c = GregorianCalendar.getInstance();c.setTime(aDate);return javax.xml.bind.DatatypeConverter.printDateTime(c);
  • 哇,放这么有用的东西真是不明显的地方。找了几天。
  • 其实b/c没那么简单,你必须初始化jaxb datatypeConverter。我最终自己使用 DatatypeFactory,就像 DataTypeConverterImpl 在内部做的那样。真是头疼。
  • 如果这一切都是真的,你应该更新维基百科:en.wikipedia.org/wiki/ISO_8601#Time_zone_designators,因为它表明你(至少部分)不正确。
【解决方案2】:

那是blessed by Java 7 documentation

DateFormat df1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
String string1 = "2001-07-04T12:08:56.235-0700";
Date result1 = df1.parse(string1);

DateFormat df2 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
String string2 = "2001-07-04T12:08:56.235-07:00";
Date result2 = df2.parse(string2);

您可以在SimpleDateFormat javadocSimpleDateFormat javadoc示例部分中找到更多示例。

UPD 02/13/2020:在 Java 8 中有一个 completely new way 可以做到这一点

【讨论】:

  • 您的回答帮助我将 MongoDB 的 ISODate 转换为本地日期。问候。
  • @b.long Java 为这种符合 ISO 8601 的格式添加了多个常量。 Java 为日期时间工作提供了一个全新的框架,其中包括对此类格式的内置默认支持。请参阅 Java 8 中的新 java.time framework,其灵感来自 Joda-Time,取代了麻烦的 java.util.Date、.Calendar 和 SimpleDateFormat 类。
  • 不是说需要提前知道日期格式吗?如果你必须接受string1string2 但不知道你会得到哪个。
  • 'Z' 需要用引号引起来
  • @kervin 如果 Z 用引号引起来,格式化程序不会专门寻找字符 Z,而不是它可以表示的所有偏移字符串吗?如果您的日期字符串恰好是 UTC,那么引用 Z 似乎只是巧合。
【解决方案3】:

好的,这个问题已经回答了,但无论如何我都会放弃我的答案。它可能对某人有所帮助。

我一直在寻找Android 解决方案(API 7)。

  • Joda 是不可能的 - 它很大并且初始化缓慢。对于该特定目的,这似乎也是一种严重的过度杀伤。
  • 涉及javax.xml 的答案不适用于 Android API 7。

最终实现了这个简单的类。它仅涵盖了 ISO 8601 字符串的最常见形式,但在某些情况下这应该足够了(当您非常确定输入将采用 this 格式时) .

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;

/**
 * Helper class for handling a most common subset of ISO 8601 strings
 * (in the following format: "2008-03-01T13:00:00+01:00"). It supports
 * parsing the "Z" timezone, but many other less-used features are
 * missing.
 */
public final class ISO8601 {
    /** Transform Calendar to ISO 8601 string. */
    public static String fromCalendar(final Calendar calendar) {
        Date date = calendar.getTime();
        String formatted = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
            .format(date);
        return formatted.substring(0, 22) + ":" + formatted.substring(22);
    }

    /** Get current date and time formatted as ISO 8601 string. */
    public static String now() {
        return fromCalendar(GregorianCalendar.getInstance());
    }

    /** Transform ISO 8601 string to Calendar. */
    public static Calendar toCalendar(final String iso8601string)
            throws ParseException {
        Calendar calendar = GregorianCalendar.getInstance();
        String s = iso8601string.replace("Z", "+00:00");
        try {
            s = s.substring(0, 22) + s.substring(23);  // to get rid of the ":"
        } catch (IndexOutOfBoundsException e) {
            throw new ParseException("Invalid length", 0);
        }
        Date date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").parse(s);
        calendar.setTime(date);
        return calendar;
    }
}

性能说明:我每次都实例化新的 SimpleDateFormat,以避免在 Android 2.1 中出现a bug。如果您和我一样惊讶,请参阅this riddle。对于其他 Java 引擎,您可以将实例缓存在私有静态字段中(使用 ThreadLocal,以保证线程安全)。

【讨论】:

  • 也许这应该成为一个自己的问题,有自己的答案?
  • 这是我在寻找答案时遇到的第一页,所以看起来很合适。对于大多数 Java 开发人员来说,Android 并不完全是 Java。但是,在大多数情况下,一个与另一个工作方式相同,因此许多 Android 开发人员在查找此内容时会搜索“java”。
  • 我必须在小数秒内添加 .SSS,但效果很好,谢谢。你为什么要s = s.substring(0, 22) + s.substring(23); - 我看不出这有什么意义
【解决方案4】:

java.time

java.time API(内置于 Java 8 及更高版本)使这更容易一些。

如果你知道输入在UTC中,比如末尾的Z(代表祖鲁语),Instant类可以解析。

java.util.Date date = Date.from( Instant.parse( "2014-12-12T10:39:40Z" ));

如果您的输入可能是另一个offset-from-UTC 值,而不是末尾的Z (Zulu) 指示的UTC,请使用OffsetDateTime 类进行解析。

OffsetDateTime odt = OffsetDateTime.parse( "2010-01-01T12:00:00+01:00" );

然后提取Instant,并通过调用from 转换为java.util.Date

Instant instant = odt.toInstant();  // Instant is always in UTC.
java.util.Date date = java.util.Date.from( instant );

【讨论】:

  • 这个答案太辛苦了。根据定义,java.util.Date 没有时区。所以不需要所有与时区相关的代码:LocalDateTimeZoneIdatZone。这个简单的单线就可以了:java.util.Date date = Date.from( ZonedDateTime.parse( "2014-12-12T10:39:40Z" ).toInstant() );
  • @BasilBourque 这太复杂了:Date.from(Instant.parse("2014-12-12T10:39:40Z" )); 就够了。
  • @assylias 你是对的,但这仅在日期字符串为 UTC 时区时有效,ISO8601 允许任何时区...
  • @Adam 我的错 - 我没有意识到这个问题比你的例子更笼统。作为旁注,OffsetDateTime 足以解析 ISO8601(不包含时区信息,而仅包含偏移量)。
  • @assylias 感谢您对让Instant 进行解析的评论。虽然对于这个特定问题还不够,但这是一个值得指出的重要区别。所以我添加了第二个代码示例。糟糕,刚刚注意到这原本不是我的答案;我希望亚当批准。
【解决方案5】:

tl;博士

OffsetDateTime.parse ( "2010-01-01T12:00:00+01:00" )

使用 java.time

Java 8 及更高版本中的新 java.time 包受到 Joda-Time 的启发。

OffsetDateTime 类表示时间轴上带有offset-from-UTC 但不是时区的时刻。

OffsetDateTime odt = OffsetDateTime.parse ( "2010-01-01T12:00:00+01:00" );

调用 toString 会生成标准 ISO 8601 格式的字符串:

2010-01-01T12:00+01:00

要通过 UTC 的镜头查看相同的值,请提取 Instant 或将偏移量从 +01:00 调整为 00:00

Instant instant = odt.toInstant();  

……或者……

OffsetDateTime odtUtc = odt.withOffsetSameInstant( ZoneOffset.UTC );

根据需要调整到时区。 time zone 是一个区域的 offset-from-UTC 值的历史记录,其中包含一组处理异常情况的规则,例如夏令时 (DST)。因此,尽可能应用时区,而不是仅仅使用偏移量。

ZonedDateTime zonedDateTimeMontréal = odt.atZoneSameInstant( ZoneId.of( "America/Montreal" ) );

对于仅日期值,请使用 LocalDate

LocalDate ld = LocalDate.of( 2010 , Month.JANUARY , 1 ) ;

或者:

LocalDate ld = LocalDate.parse( "2010-01-01" ) ;

关于java.time

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

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

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

您可以直接与您的数据库交换 java.time 对象。使用符合JDBC 4.2 或更高版本的JDBC driver。不需要字符串,不需要java.sql.* 类。 Hibernate 5 & JPA 2.2 支持 java.time

从哪里获取 java.time 类?


【讨论】:

  • 我不认为这有帮助,因为它不解析 ISO 8601 日期,只解析一个非常具体的子集。例如,java.time.OffsetDateTime.parse ( "2010-01-01" ) 失败
  • @phil294 所有的 java.time 类都会根据每个类解析 ISO 8601 标准格式输入。您的"2010-01-01" 示例仅是日期,因此将由仅日期类LocalDate 解析。例如:LocalDate.parse( "2010-01-01" )。您的 OffsetDateTime 示例类是一个日期,有一个时间,有一个偏移量(UTC 前/后的小时-分钟-秒数)。所以OffsetDateTime 使用日期、时间和偏移量解析 ISO 8601 格式的输入。例如:OffsetDateTime.parse( "2010-01-01T12:30:00-08:00" )Run code live at IdeOne.com.
【解决方案6】:

Jackson-databind library 也有 ISO8601DateFormat class 可以做到这一点(ISO8601Utils 中的实际实现。

ISO8601DateFormat df = new ISO8601DateFormat();
Date d = df.parse("2010-07-28T22:25:51Z");

【讨论】:

  • 无法解析此日期:2015-08-11T13:10:00。我得到String index out of range: 19。查看代码似乎需要指定毫秒和时区。这些应该是可选的。
  • 引用文档,解析格式为:[yyyy-MM-dd|yyyyMMdd][T(hh:mm[:ss[.sss]]|hhmm[ss[.sss]])]?[Z|[+-]hh:mm]]。换句话说,毫秒是可选的,但时区是强制性的。
  • 啊,是的,看起来你是对的。不过,我很确定 ISO8601 允许您省略时区,所以它仍然是错误的。 JodaTime 有效:new DateTime("2015-08-11T13:10:00").toDate()
  • 该类现已弃用,新的​​类是 StdDateFormat。否则它的工作原理相同。
  • ISO8601DateFormat 已弃用,请改用 StdDateFormat
【解决方案7】:

从 Java 8 开始,有一种全新的官方支持方式来执行此操作:

    String s = "2020-02-13T18:51:09.840Z";
    TemporalAccessor ta = DateTimeFormatter.ISO_INSTANT.parse(s);
    Instant i = Instant.from(ta);
    Date d = Date.from(i);

【讨论】:

  • 如果字符串是即时格式,以Z结尾作为偏移量,我们不需要明确指定。只需Instant i = Instant.parse(s);。问题中的字符串有+01:00,在这种情况下DateTimeFormatter.ISO_INSTANT 不起作用(至少在我的Java 11 上不起作用)。
  • @OleV.V.您可以使用ISO_OFFSET_DATE_TIME 来格式化带有偏移量的日期,例如+01:00 (docs.oracle.com/javase/8/docs/api/java/time/format/…)
  • 这是真的,@LucasBasquerotto。虽然没有明确提及格式化程序,但答案 by Adamby Basil Bourque 已经做了类似的事情。
  • 这无法解析“2020-06-01T14:34:00-05:00”,这是由 Javascript 的 toISOString() 方法生成的字符串。
【解决方案8】:

对于 Java 版本 7

您可以关注 Oracle 文档: http://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html

X - 用于 ISO 8601 时区

TimeZone tz = TimeZone.getTimeZone("UTC");
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX");
df.setTimeZone(tz);
String nowAsISO = df.format(new Date());

System.out.println(nowAsISO);

DateFormat df1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX");
//nowAsISO = "2013-05-31T00:00:00Z";
Date finalResult = df1.parse(nowAsISO);

System.out.println(finalResult);

【讨论】:

  • 这意味着时区是必需的。根据 ISO 8601,它是可选的。秒数等也是如此。所以这只解析 ISO 8601 的特定子集。
  • 适用于 Java 1.8
【解决方案9】:

DatatypeConverter 解决方案并非适用于所有虚拟机。以下对我有用:

javax.xml.datatype.DatatypeFactory.newInstance().newXMLGregorianCalendar("2011-01-01Z").toGregorianCalendar().getTime()

我发现 joda 不能开箱即用(特别是对于我上面给出的示例,其中的时区应该是有效的)

【讨论】:

    【解决方案10】:

    我认为我们应该使用

    DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'")
    

    日期2010-01-01T12:00:00Z

    【讨论】:

    • 为什么这是一个比其他答案更好的答案,包括 76 票赞成的答案?
    • @ErickRobertson:简单、开箱即用、灵活、无需转换,而且大多数人不关心时区。
    • 如果您不关心时区,那么与时间打交道没有多大意义!
    • 这完全忽略时区。一直在使用它,直到我意识到这正在发生,所以我切换到 JodaTime。
    • 放弃时区只会在某些时候导致错误。
    【解决方案11】:

    解析 ISO8601 时间戳的另一种非常简单的方法是使用org.apache.commons.lang.time.DateUtils

    import static org.junit.Assert.assertEquals;
    
    import java.text.ParseException;
    import java.util.Date;
    import org.apache.commons.lang.time.DateUtils;
    import org.junit.Test;
    
    public class ISO8601TimestampFormatTest {
      @Test
      public void parse() throws ParseException {
        Date date = DateUtils.parseDate("2010-01-01T12:00:00+01:00", new String[]{ "yyyy-MM-dd'T'HH:mm:ssZZ" });
        assertEquals("Fri Jan 01 12:00:00 CET 2010", date.toString());
      }
    }
    

    【讨论】:

      【解决方案12】:

      Java 7+ 的解决方法是使用 SimpleDateFormat:
      DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX", Locale.US);

      此代码可以解析 ISO8601 格式,如:

      • 2017-05-17T06:01:43.785Z
      • 2017-05-13T02:58:21.391+01:00

      但在 Java6 上,SimpleDateFormat 不理解 X 字符并会抛出
      IllegalArgumentException: Unknown pattern character 'X'
      我们需要用SimpleDateFormat将ISO8601日期标准化为Java 6中可读的格式。

      public static Date iso8601Format(String formattedDate) throws ParseException {
          try {
              DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX", Locale.US);
              return df.parse(formattedDate);
          } catch (IllegalArgumentException ex) {
              // error happen in Java 6: Unknown pattern character 'X'
              if (formattedDate.endsWith("Z")) formattedDate = formattedDate.replace("Z", "+0000");
              else formattedDate = formattedDate.replaceAll("([+-]\\d\\d):(\\d\\d)\\s*$", "$1$2");
              DateFormat df1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.US);
              return df1.parse(formattedDate);
          }
      }
      

      当Java 6中发生错误时,上述方法将[Z替换为+0000]或[+01:00替换为+0100](您可以检测Java版本并将try/catch替换为if语句)。

      【讨论】:

      • 不,像 DateSimpleDateFormat 这样麻烦的旧日期时间类设计不佳、令人困惑且存在缺陷。它们现在是遗留的,被 Java 8 及更高版本中内置的 java.time 类所取代。对于 Java 6 和 Java 7,大部分 java.time 功能都在 ThreeTen-Backport 项目中向后移植。将该库添加到您的应用程序比使用那些遗留类要好得多。 java.time中的一行解决方案:OffsetDateTime.parse( "2010-01-01T12:00:00+01:00" )
      【解决方案13】:

      Java 8+

      我在答案中没有找到的简单的一条线:

      Date date = Date.from(ZonedDateTime.parse("2010-01-01T12:00:00+01:00").toInstant());
      

      日期不包含时区,它将以 UTC 存储,但即使在使用 System.out.println(date) 进行简单输出时也会正确转换为您的 JVM 时区。

      【讨论】:

      • 我认为你是对的,这并不完全在其他答案中。如果可以完全避免Date,那就更好了,因为该类设计不佳且早已过时。例如,最好使用ZonedDateTime.withZoneSameInstant() 转换为自己的时区。仅当您必须使用 Date 来获取您现在无法升级到 java.time 的旧版 API 时才使用此答案。
      【解决方案14】:

      经过大量搜索将 ISO8601 转换为最新版本后,我突然发现了一个 java 类,它是 ISO8601Util.java,它是 com.google.gson.internal.bind.util 的一部分。 所以你可以用它来转换日期。

      ISO8601Utils.parse("2010-01-01T12:00:00Z" , ParsePosition(0))
      

      你可以简单地使用这个 kotlin 扩展函数

      fun String.getDateFromString() : Date? = ISO8601Utils.parse(this , 
      ParsePosition(0))
      

      【讨论】:

      【解决方案15】:

      java.time

      请注意,在 Java 8 中,您可以使用 java.time.ZonedDateTime 类及其静态 parse(CharSequence text) 方法。

      【讨论】:

      • 问题中的输入字符串只有一个与 UTC 的偏移量,而不是完整的时区。所以InstantZonedDateTime 在这里是合适的,而不是ZonedDateTime
      【解决方案16】:

      我遇到了同样的问题,用下面的代码解决了。

       public static Calendar getCalendarFromISO(String datestring) {
          Calendar calendar = Calendar.getInstance(TimeZone.getDefault(), Locale.getDefault()) ;
          SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault());
          try {
              Date date = dateformat.parse(datestring);
              date.setHours(date.getHours() - 1);
              calendar.setTime(date);
      
              String test = dateformat.format(calendar.getTime());
              Log.e("TEST_TIME", test);
      
          } catch (ParseException e) {
              e.printStackTrace();
          }
      
          return calendar;
      }
      

      我之前使用过 SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.getDefault());

      但后来我发现异常的主要原因是yyyy-MM-dd'T'HH:mm:ss.SSSZ

      所以我用了

      SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault());

      对我来说效果很好。

      【讨论】:

      • 正是我所需要的,而无需使用 joda-time、XML api 或其他任何东西。只是正确的模式。
      【解决方案17】:

      Java 有十几种不同的方法来解析日期时间,正如这里的优秀答案所示。但有点令人惊讶的是,Java 的时间类都没有完全实现 ISO 8601!

      我建议使用 Java 8:

      ZonedDateTime zp = ZonedDateTime.parse(string);
      Date date = Date.from(zp.toInstant());
      

      这将处理 UTC 和偏移量的示例,例如“2017-09-13T10:36:40Z”或“2017-09-13T10:36:40+01:00”。它适用于大多数用例。

      但它不会处理像“2017-09-13T10:36:40+01”这样的示例,是一个有效的 ISO 8601 日期时间。
      它也不会只处理日期,例如“2017-09-13”。

      如果你必须处理这些,我建议先使用正则表达式来嗅探语法。

      这里有一个很好的 ISO 8601 示例列表,其中包含许多极端情况:https://www.myintervals.com/blog/2009/05/20/iso-8601-date-validation-that-doesnt-suck/ 我不知道有任何 Java 类可以处理所有这些情况。

      【讨论】:

      • OffsetDateTime 会在概念上更好地匹配带有偏移量的日期时间。
      • 嘿@OleV.V.谢谢你的建议。遗憾的是没有: OffsetDateTime.parse() 将为几个有效的 ISO 8601 字符串抛出异常,例如“2017-09-13T10:36:40+01”或“2017-09-13”
      • 我只是想说OffsetDateTime 处理您使用ZonedDateTime 处理的示例。我相信它不能处理ZonedDateTime 不能处理的任何示例。从这个意义上说,它没有任何改进(但也没有更糟)。抱歉,我不是很清楚。
      • 考虑到目前的情况,这应该是 2020 年公认的答案。
      【解决方案18】:

      你也可以使用下面的类 -

      org.springframework.extensions.surf.util.ISO8601DateFormat
      
      
      Date date = ISO8601DateFormat.parse("date in iso8601");
      

      Java 文档链接 - Hierarchy For Package org.springframework.extensions.surf.maven.plugin.util

      【讨论】:

      • 已弃用:改用 com.fasterxml.jackson.databind.util.StdDateFormat
      【解决方案19】:

      正如其他人所提到的,Android 没有很好的方法来支持使用 SDK 中包含的类来解析/格式化 ISO 8601 日期。我已经多次编写此代码,因此我最终创建了一个 Gist,其中包含一个支持格式化和解析 ISO 8601 和 RFC 1123 日期的 DateUtils 类。 Gist 还包括一个测试用例,显示它支持的内容。

      https://gist.github.com/mraccola/702330625fad8eebe7d3

      【讨论】:

        【解决方案20】:

        用于 JAVA 1.7 的 SimpleDateFormat 有一个很酷的 ISO 8601 格式模式。

        Class SimpleDateFormat

        这是我所做的:

        Date d = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss.SSSZ",
                 Locale.ENGLISH).format(System.currentTimeMillis());
        

        【讨论】:

        • Z 格式字符串不是 ISO 8601 时区,如果您需要 ISO 8601 时区,则应使用 X(或 XXXXX
        • d 是字符串类型
        【解决方案21】:

        使用类似的字符串 LocalDate.parse(((String) data.get("d_iso8601")),DateTimeFormatter.ISO_DATE)

        【讨论】:

          【解决方案22】:

          令我惊讶的是,甚至没有一个 java 库支持https://en.wikipedia.org/wiki/ISO_8601 中的所有 ISO 8601 日期格式。 Joda DateTime 支持其中的大多数,但不是全部,因此我添加了自定义逻辑来处理所有这些。这是我的实现。

          import java.text.ParseException;
          import java.util.Date;
          
          import org.apache.commons.lang3.time.DateUtils;
          import org.joda.time.DateTime;
          
          public class ISO8601DateUtils {
          	
          	/**
          	 * It parses all the date time formats from https://en.wikipedia.org/wiki/ISO_8601 and returns Joda DateTime.
          	 * Zoda DateTime does not support dates of format 20190531T160233Z, and hence added custom logic to handle this using SimpleDateFormat.
          	 * @param dateTimeString ISO 8601 date time string
          	 * @return
          	 */
          	public static DateTime parse(String dateTimeString) {
          		try {
          			return new DateTime( dateTimeString );
          		} catch(Exception e) {
          			try {
          				Date dateTime = DateUtils.parseDate(dateTimeString, JODA_NOT_SUPPORTED_ISO_DATES);
          				return new DateTime(dateTime.getTime());
          			} catch (ParseException e1) {
          				throw new RuntimeException(String.format("Date %s could not be parsed to ISO date", dateTimeString));
          			}
          		}
          	}
            
            	private static String[] JODA_NOT_SUPPORTED_ISO_DATES = new String[] {
          			// upto millis
          			"yyyyMMdd'T'HHmmssSSS'Z'",
          			"yyyyMMdd'T'HHmmssSSSZ",
          			"yyyyMMdd'T'HHmmssSSSXXX",
          			
          			"yyyy-MM-dd'T'HHmmssSSS'Z'",
          			"yyyy-MM-dd'T'HHmmssSSSZ",
          			"yyyy-MM-dd'T'HHmmssSSSXXX",
          			
          			// upto seconds
          			"yyyyMMdd'T'HHmmss'Z'",
          			"yyyyMMdd'T'HHmmssZ",
          			"yyyyMMdd'T'HHmmssXXX",
          			
          			"yyyy-MM-dd'T'HHmmss'Z'", 
          			"yyyy-MM-dd'T'HHmmssZ",
          			"yyyy-MM-dd'T'HHmmssXXX",
          			
          			// upto minutes
          			"yyyyMMdd'T'HHmm'Z'",
          			"yyyyMMdd'T'HHmmZ",
          			"yyyyMMdd'T'HHmmXXX",
          
          			"yyyy-MM-dd'T'HHmm'Z'",
          			"yyyy-MM-dd'T'HHmmZ",
          			"yyyy-MM-dd'T'HHmmXXX",
          			
          			//upto hours is already supported by Joda DateTime
          	};
          }

          【讨论】:

          • 其中一半不是有效的 ISO-8601 日期/时间。来自维基百科:Either basic or extended formats may be used, but both date and time must use the same format
          【解决方案23】:

          这样做:

          public static void main(String[] args) throws ParseException {
          
              String dateStr = "2016-10-19T14:15:36+08:00";
              Date date = javax.xml.bind.DatatypeConverter.parseDateTime(dateStr).getTime();
          
              System.out.println(date);
          
          }
          

          这是输出:

          2016 年 10 月 19 日星期三 15:15:36 CST

          【讨论】:

            【解决方案24】:

            显示如何解析 ISO8601 中的日期以及 LocalDateTime 不处理 DST 的小测试。

             @Test
                public void shouldHandleDaylightSavingTimes() throws ParseException {
            
                    //ISO8601 UTC date format
                    SimpleDateFormat utcFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
            
                    // 1 hour of difference between 2 dates in UTC happening at the Daylight Saving Time
                    Date d1 = utcFormat.parse("2019-10-27T00:30:00.000Z");
                    Date d2 = utcFormat.parse("2019-10-27T01:30:00.000Z");
            
                    //Date 2 is before date 2
                    Assert.assertTrue(d1.getTime() < d2.getTime());
                    // And there is 1 hour difference between the 2 dates
                    Assert.assertEquals(1000*60*60, d2.getTime() - d1.getTime());
            
                    //Print the dates in local time
                    SimpleDateFormat localFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm z Z", Locale.forLanguageTag("fr_CH"));
                    localFormat.setTimeZone(TimeZone.getTimeZone("Europe/Zurich"));
            
                    //Both dates are at 02h30 local time (because of DST), but one is CEST +0200 and the other CET +0100 (clock goes backwards)
                    Assert.assertEquals("2019-10-27 02:30 CEST +0200", localFormat.format(d1));
                    Assert.assertEquals("2019-10-27 02:30 CET +0100", localFormat.format(d2));
            
                    //Small test that shows that LocalDateTime does not handle DST (and should not be used for storing timeseries data)
                    LocalDateTime ld1 = LocalDateTime.ofInstant(d1.toInstant(), ZoneId.of("Europe/Zurich"));
                    LocalDateTime ld2 = LocalDateTime.ofInstant(d2.toInstant(), ZoneId.of("Europe/Zurich"));
            
                    //Note that a localdatetime does not handle DST, therefore the 2 dates are the same
                    Assert.assertEquals(ld1, ld2);
            
                    //They both have the following local values
                    Assert.assertEquals(2019, ld1.getYear());
                    Assert.assertEquals(27, ld1.getDayOfMonth());
                    Assert.assertEquals(10, ld1.getMonthValue());
                    Assert.assertEquals(2, ld1.getHour());
                    Assert.assertEquals(30, ld1.getMinute());
                    Assert.assertEquals(0, ld1.getSecond());
            
                }
            

            【讨论】:

            • 仅供参考,java.util.Datejava.util.Calendarjava.text.SimpleDateFormat 等非常麻烦的日期时间类现在是 legacy,被 Java 8 及更高版本中内置的 java.time 类所取代.见Tutorial by Oracle
            • 你说得对,LocalDateTime 不处理夏令时 (DST),因为它根本不处理时区。为此,我们需要ZonedDateTime。提议 DateSimpleDateFormat 是 — 恕我直言不好。
            • 确实 ZonedDateTime 有效。并且 java.time.Instant 也是处理 DST 的一个很好的替代方案。我知道 java.util.Date 已弃用且不应使用,但我只是在回答原始问题:How to convert a String in 8601 to java.util.date ....
            【解决方案25】:

            当想要从 UTC 转换为我们想要的格式时。它会根据我们停留的区域/位置而改变

            //utcDate = "2021-06-05T02:46:29Z"
            fun converterUtcToReadableDateTime(utcDate: String): String {
                val offsetDateTime = OffsetDateTime.ofInstant(Instant.parse(utcDate), ZoneId.systemDefault())
                val patternDate = "dd MMM yyyy h:mm a"
                return DateTimeFormatter.ofPattern(patternDate).format(offsetDateTime)
            }
            
            fun converterUtcToReadableDate(utcDate: String): String {
                val offsetDateTime = OffsetDateTime.ofInstant(Instant.parse(utcDate), ZoneId.systemDefault())
                val patternDate = "d MMM yyyy"
                return DateTimeFormatter.ofPattern(patternDate).format(offsetDateTime)
            }
            
            fun converterUtcToReadableTime(utcDate: String): String {
                val offsetDateTime = OffsetDateTime.ofInstant(Instant.parse(utcDate), ZoneId.systemDefault())
                val patternDate = "h:mm a"
                return DateTimeFormatter.ofPattern(patternDate).format(offsetDateTime)
            }
            

            【讨论】:

            • LocalDateTime 是在这里使用的完全错误的类。您正在丢弃重要信息,与 UTC 有偏差,却一无所获。
            • @OleV.V.是的,谢谢你的更正
            • stackoverflow.com/a/68258560/9346054 我认为最好的自定义,不使用即时UTC
            【解决方案26】:

            我也有类似的需求:我需要能够在事先不知道确切格式的情况下解析任何符合 ISO8601 的日期,并且我想要一个也适用于 Android 的轻量级解决方案。

            当我搜索我的需求时,我偶然发现了这个问题,并注意到 AFAIU,没有答案完全符合我的需求。于是我开发了jISO8601 ,并推送到了maven central。

            加你pom.xml:

            <dependency>
              <groupId>fr.turri</groupId>
              <artifactId>jISO8601</artifactId>
              <version>0.2</version>
            </dependency>
            

            然后你就可以开始了:

            import fr.turri.jiso8601.*;
            ...
            Calendar cal = Iso8601Deserializer.toCalendar("1985-03-04");
            Date date = Iso8601Deserializer.toDate("1985-03-04T12:34:56Z");
            

            希望对您有所帮助。

            【讨论】:

              【解决方案27】:

              要像这样格式化日期,以下在基于 Java 6 的应用程序中对我有用。 thymeleaf 项目中有一个DateFormatJacksonThymeleafISO8601DateFormat 插入缺少的冒号:

              https://github.com/thymeleaf/thymeleaf/blob/40d27f44df7b52eda47d1bc6f1b3012add6098b3/src/main/java/org/thymeleaf/standard/serializer/StandardJavaScriptSerializer.java

              我用它来兼容 ECMAScript 日期格式。

              【讨论】:

                【解决方案28】:

                基本功能礼貌:@wrygiel。

                此函数可以将 ISO8601 格式转换为可以处理偏移值的 Java 日期。根据definition of ISO 8601,偏移量可以以不同的格式提及。

                ±[hh]:[mm]
                ±[hh][mm]
                ±[hh]
                
                Eg:  "18:30Z", "22:30+04", "1130-0700", and "15:00-03:30" all mean the same time. - 06:30PM UTC
                

                这个类有静态方法可以转换

                • ISO8601 字符串到日期(本地时区)对象
                • 日期为 ISO8601 字符串
                • 自动计算夏令时

                示例 ISO8601 字符串

                /*       "2013-06-25T14:00:00Z";
                         "2013-06-25T140000Z";
                         "2013-06-25T14:00:00+04";
                         "2013-06-25T14:00:00+0400";
                         "2013-06-25T140000+0400";
                         "2013-06-25T14:00:00-04";
                         "2013-06-25T14:00:00-0400";
                         "2013-06-25T140000-0400";*/
                
                
                public class ISO8601DateFormatter {
                
                private static final DateFormat DATE_FORMAT_1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
                private static final DateFormat DATE_FORMAT_2 = new SimpleDateFormat("yyyy-MM-dd'T'HHmmssZ");
                private static final String UTC_PLUS = "+";
                private static final String UTC_MINUS = "-";
                
                public static Date toDate(String iso8601string) throws ParseException {
                    iso8601string = iso8601string.trim();
                    if(iso8601string.toUpperCase().indexOf("Z")>0){
                        iso8601string = iso8601string.toUpperCase().replace("Z", "+0000");
                    }else if(((iso8601string.indexOf(UTC_PLUS))>0)){
                        iso8601string = replaceColon(iso8601string, iso8601string.indexOf(UTC_PLUS));
                        iso8601string = appendZeros(iso8601string, iso8601string.indexOf(UTC_PLUS), UTC_PLUS);
                    }else if(((iso8601string.indexOf(UTC_MINUS))>0)){
                        iso8601string = replaceColon(iso8601string, iso8601string.indexOf(UTC_MINUS));
                        iso8601string = appendZeros(iso8601string, iso8601string.indexOf(UTC_MINUS), UTC_MINUS);
                    }
                
                    Date date = null;
                    if(iso8601string.contains(":"))
                        date = DATE_FORMAT_1.parse(iso8601string);
                    else{
                        date = DATE_FORMAT_2.parse(iso8601string);
                    }
                    return date;
                }
                
                public static String toISO8601String(Date date){
                    return DATE_FORMAT_1.format(date);
                }
                
                private static String replaceColon(String sourceStr, int offsetIndex){
                    if(sourceStr.substring(offsetIndex).contains(":"))
                        return sourceStr.substring(0, offsetIndex) + sourceStr.substring(offsetIndex).replace(":", "");
                    return sourceStr;
                }
                
                private static String appendZeros(String sourceStr, int offsetIndex, String offsetChar){
                    if((sourceStr.length()-1)-sourceStr.indexOf(offsetChar,offsetIndex)<=2)
                        return sourceStr + "00";
                    return sourceStr;
                }
                

                }

                【讨论】:

                • 注意——DateFormat 和派生类不是多线程兼容的!使用静态 SimpleDateFormat 对象(例如 DATE_FORMAT_1 和 DATE_FORMAT_2)意味着调用 ISO8601DateFormatter 函数的多个线程将共享同一个 DateFormat 对象。这会导致数据损坏和 DateFormat 调用返回的日期不正确。要解决此问题,您只需将模式字符串设为常量并在需要时创建本地 SimpleDateFormat 变量。这将确保每个对象只被一个线程使用。
                • 线程安全的更好解决方法是使用为线程安全构建的日期时间库。在 Java 中,这个世界要么是 Joda-Time 要么是 java.time。
                【解决方案29】:

                我认为很多人想要做的是解析 JSON 日期字符串。如果您来到此页面,您很有可能希望将 JavaScript JSON 日期转换为 Java 日期。

                显示 JSON 日期字符串的样子:

                    var d=new Date();
                    var s = JSON.stringify(d);
                
                    document.write(s);
                    document.write("<br />"+d);
                
                
                    "2013-12-14T01:55:33.412Z"
                    Fri Dec 13 2013 17:55:33 GMT-0800 (PST)
                

                JSON 日期字符串为 2013-12-14T01:55:33.412Z。

                JSON 规范并未涵盖日期,但上面是一种非常具体的 ISO 8601 格式,而 ISO_8601 则要大得多,这只是一个子集,尽管它非常重要。

                http://www.json.orghttp://en.wikipedia.org/wiki/ISO_8601http://www.w3.org/TR/NOTE-datetime

                碰巧我写了一个 JSON 解析器和一个 PLIST 解析器,它们都使用 ISO-8601,但位不同。

                /*
                    var d=new Date();
                    var s = JSON.stringify(d);
                
                    document.write(s);
                    document.write("<br />"+d);
                
                
                    "2013-12-14T01:55:33.412Z"
                    Fri Dec 13 2013 17:55:33 GMT-0800 (PST)
                
                
                 */
                @Test
                public void jsonJavaScriptDate() {
                    String test =  "2013-12-14T01:55:33.412Z";
                
                    Date date = Dates.fromJsonDate ( test );
                    Date date2 = Dates.fromJsonDate_ ( test );
                
                    assertEquals(date2.toString (), "" + date);
                
                    puts (date);
                }
                

                我为我的项目编写了两种方法来做到这一点。一种标准,一种快速。

                同样,JSON 日期字符串是 ISO 8601 的一个非常具体的实现......

                (我在另一个答案中发布了另一个答案,它应该适用于 PLIST 日期,这是一种不同的 ISO 8601 格式)。

                JSON日期如下:

                public static Date fromJsonDate_( String string ) {
                
                    try {
                
                        return new SimpleDateFormat ( "yyyy-MM-dd'T'HH:mm:ss.SSSXXX").parse ( string );
                    } catch ( ParseException e ) {
                        return Exceptions.handle (Date.class, "Not a valid JSON date", e);
                    }
                
                
                }
                

                PLIST 文件(ASCII 非 GNUNext)也使用 ISO 8601,但没有毫秒,所以……并非所有 ISO-8601 日期都相同。 (至少我还没有找到一个使用 milis 的解析器,而且我看到的解析器完全跳过了时区 OMG)。

                现在是快速版本(您可以在 Boon 中找到它)。

                public static Date fromJsonDate( String string ) {
                
                    return fromJsonDate ( Reflection.toCharArray ( string ), 0, string.length () );
                
                }
                

                请注意,Reflection.toCharArray 如果可用则使用 unsafe,但如果不可用则默认为 string.toCharArray。

                (您可以通过将 Reflection.toCharArray ( string ) 替换为 string.toCharArray() 来将其从示例中删除)。

                public static Date fromJsonDate( char[] charArray, int from, int to ) {
                
                    if (isJsonDate ( charArray, from, to )) {
                        int year = CharScanner.parseIntFromTo ( charArray, from + 0, from + 4 );
                        int month = CharScanner.parseIntFromTo ( charArray,  from +5,  from +7 );
                        int day = CharScanner.parseIntFromTo ( charArray,  from +8,  from +10 );
                        int hour = CharScanner.parseIntFromTo ( charArray,  from +11,  from +13 );
                
                        int minute = CharScanner.parseIntFromTo ( charArray,  from +14,  from +16 );
                
                        int second = CharScanner.parseIntFromTo ( charArray,  from +17,  from +19 );
                
                        int miliseconds = CharScanner.parseIntFromTo ( charArray,  from +20,  from +23 );
                
                        TimeZone tz = TimeZone.getTimeZone ( "GMT" );
                
                
                        return toDate ( tz, year, month, day, hour, minute, second, miliseconds );
                
                    }   else {
                        return null;
                    }
                
                }
                

                isJsonDate 实现如下:

                public static boolean isJsonDate( char[] charArray, int start, int to ) {
                    boolean valid = true;
                    final int length = to -start;
                
                    if (length != JSON_TIME_LENGTH) {
                        return false;
                    }
                
                    valid &=  (charArray [ start + 19 ]  == '.');
                
                    if (!valid) {
                        return false;
                    }
                
                
                    valid &=  (charArray[  start +4 ]  == '-') &&
                            (charArray[  start +7 ]  == '-') &&
                            (charArray[  start +10 ] == 'T') &&
                            (charArray[  start +13 ] == ':') &&
                            (charArray[  start +16 ] == ':');
                
                    return valid;
                }
                

                无论如何...我的猜测是很多人来这里..可能正在寻找 JSON 日期字符串,虽然它是一个 ISO-8601 日期,但它是一个非常具体的日期,需要非常具体的解析.

                public static int parseIntFromTo ( char[] digitChars, int offset, int to ) {
                    int num = digitChars[ offset ] - '0';
                    if ( ++offset < to ) {
                        num = ( num * 10 ) + ( digitChars[ offset ] - '0' );
                        if ( ++offset < to ) {
                            num = ( num * 10 ) + ( digitChars[ offset ] - '0' );
                            if ( ++offset < to ) {
                                num = ( num * 10 ) + ( digitChars[ offset ] - '0' );
                                if ( ++offset < to ) {
                                    num = ( num * 10 ) + ( digitChars[ offset ] - '0' );
                                    if ( ++offset < to ) {
                                        num = ( num * 10 ) + ( digitChars[ offset ] - '0' );
                                        if ( ++offset < to ) {
                                            num = ( num * 10 ) + ( digitChars[ offset ] - '0' );
                                            if ( ++offset < to ) {
                                                num = ( num * 10 ) + ( digitChars[ offset ] - '0' );
                                                if ( ++offset < to ) {
                                                    num = ( num * 10 ) + ( digitChars[ offset ] - '0' );
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                    return num;
                }
                

                https://github.com/RichardHightower/boon Boon 有一个 PLIST 解析器 (ASCII) 和一个 JSON 解析器。

                JSON 解析器是我所知道的最快的 Java JSON 解析器。

                由 Gatling Performance 帅哥独立验证。

                https://github.com/gatling/json-parsers-benchmark

                Benchmark                               Mode Thr     Count  Sec         Mean   Mean error        Units
                BoonCharArrayBenchmark.roundRobin      thrpt  16        10    1   724815,875    54339,825    ops/s
                JacksonObjectBenchmark.roundRobin      thrpt  16        10    1   580014,875   145097,700    ops/s
                JsonSmartBytesBenchmark.roundRobin     thrpt  16        10    1   575548,435    64202,618    ops/s
                JsonSmartStringBenchmark.roundRobin    thrpt  16        10    1   541212,220    45144,815    ops/s
                GSONStringBenchmark.roundRobin         thrpt  16        10    1   522947,175    65572,427    ops/s
                BoonDirectBytesBenchmark.roundRobin    thrpt  16        10    1   521528,912    41366,197    ops/s
                JacksonASTBenchmark.roundRobin         thrpt  16        10    1   512564,205   300704,545    ops/s
                GSONReaderBenchmark.roundRobin         thrpt  16        10    1   446322,220    41327,496    ops/s
                JsonSmartStreamBenchmark.roundRobin    thrpt  16        10    1   276399,298   130055,340    ops/s
                JsonSmartReaderBenchmark.roundRobin    thrpt  16        10    1    86789,825    17690,031    ops/s
                

                它拥有最快的 JSON 解析器,可用于流、读取器、bytes[]、char[]、CharSequence(StringBuilder、CharacterBuffer)和 String。

                查看更多基准:

                https://github.com/RichardHightower/json-parsers-benchmark

                【讨论】:

                • 这个关于 JSON 的答案与问题无关。此外,这个问题是不正确的,因为在极少数JSON data types 中没有“JSON 日期”之类的东西。现在,所有这些代码都可以用对内置 Java 功能的单行调用替换:Instant.parse( "2013-12-14T01:55:33.412Z" )
                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2015-11-16
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2016-12-14
                相关资源
                最近更新 更多