繁体   English   中英

将符合 ISO 8601 的字符串转换为 java.util.Date

[英]Converting ISO 8601-compliant String to java.util.Date

我正在尝试将ISO 8601格式的 String 转换为java.util.Date

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

但是,使用java.text.SimpleDateFormat ,我无法转换格式正确的 String 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));

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


回答

感谢 JuanZe 的评论,我找到了Joda-Time 的魔法, 这里也有描述

所以,解决方案是

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" ) ;

对我来说,这很好。

不幸的是, 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”而不是“+00:00”来表示 UTC。

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

您也可以使用Joda-Time ,但我不知道您为什么要为此烦恼(2022 年更新;可能是因为 Android 的javax.xml包中缺少整个javax.xml.bind部分)。

Java 7 文档所祝福的方式:

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 javadoc示例部分中找到更多示例。

UPD 2020 年 2 月 13 日: Java 8 中有一种全新的方法可以做到这一点

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

我一直在寻找适用于 Android (API 7) 的解决方案

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

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

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 中的错误 如果你和我一样惊讶,看看这个谜语 对于其他 Java 引擎,您可以将实例缓存在私有静态字段中(使用 ThreadLocal,以保证线程安全)。

java.time

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

如果您知道输入是UTC 格式,例如末尾的Z (代表 Zulu),则Instant类可以解析。

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

如果您的输入可能是另一个从 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 );

tl;博士

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

使用 java.time

Java 8 及更高版本中新的java.time包的灵感来自 Joda-Time。

OffsetDateTime类表示时间轴上具有与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 );

如果需要,调整到时区。 时区是一个区域与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 及更高版本中。 这些类取代了麻烦的日期时间类,例如java.util.DateCalendarSimpleDateFormat

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

现在处于维护模式Joda-Time项目建议迁移到java.time类。

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

从哪里获得 java.time 类?


Jackson-databind 库还具有执行此操作的ISO8601DateFormat 类(在ISO8601Utils中的实际实现。

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

从 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);

对于 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);

DatatypeConverter 解决方案不适用于所有 VM。 以下对我有用:

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

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

我认为我们应该使用

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

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

解析 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());
  }
}

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 语句)。

在我搜索了很多以将 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))

Java 8+

我在答案中没有找到的简单的一个衬里:

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

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

java.time

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

我遇到了同样的问题并通过以下代码解决了它。

 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());

它对我来说很好。

正如这里的优秀答案所示,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 类。

您也可以使用以下课程 -

org.springframework.extensions.surf.util.ISO8601DateFormat


Date date = ISO8601DateFormat.parse("date in iso8601");

链接到 Java Doc - Hierarchy For Package org.springframework.extensions.surf.maven.plugin.util

我正在尝试将ISO 8601格式的String转换为java.util.Date

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

但是,使用java.text.SimpleDateFormat不能转换格式正确的String 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));

显然不是那么好。 我是否缺少某些东西或有更好的解决方案?


回答

感谢JuanZe的评论,我找到了Joda-Time魔术, 这里也对此进行了描述

因此,解决方案是

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" ) ;

对我来说,这很好。

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

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

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

类 SimpleDateFormat

这是我所做的:

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

使用像LocalDate.parse(((String) data.get("d_iso8601")),DateTimeFormatter.ISO_DATE)这样的字符串

我很惊讶,甚至没有一个 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 }; }

像这样做:

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

一个小测试展示了如何解析 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());

    }

当想要从 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)
}

我也有类似的需求:我需要能够在事先不知道确切格式的情况下解析任何符合 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");

希望它有所帮助。

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

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

我将它用于 ECMAScript 日期格式兼容性。

这是使用Java 7的基于Kotlin的方法。您可以传递自定义模式,也可以仅使用默认的ISO8601模式。 但是,您可能需要处理解析错误。

object DateUtil {

    private const val ISO_8601_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSX"

    fun convertDateToTimestamp(date: Date, pattern: String = ISO_8601_PATTERN): String {
        val df = SimpleDateFormat(pattern, Locale.getDefault()).apply {
            timeZone = TimeZone.getTimeZone("UTC")
        }
        return df.format(date)
    }

    fun convertTimestampToDate(timestamp: String, pattern: String = ISO_8601_PATTERN): Date {
        val formatter = SimpleDateFormat(pattern, Locale.getDefault()).apply {
            timeZone = TimeZone.getTimeZone("UTC")
        }
        return formatter.parse(timestamp) ?: Date(0)
    }
}

我不能使用 Java 8 的特性,所以只有java.util.Date可用。 我已经依赖于 gson 库,但不想直接使用ISO8601Utils ISO8601Utils是一个内部 API, gson 的作者警告不要使用它

我使用 gson 的公共 API 解析了 ISO8601 日期:

fun parseISO8601DateToLocalTimeOrNull(date: String): Date? {
    return try {
        GsonBuilder()
            .create()
            .getAdapter(Date::class.java)
            .fromJson("\"$date\"")
     } catch (t: Throwable) {
        null
     }
}

在引擎盖下,适配器仍然使用ISO8601Utils 但是,如果您使用的是适配器,您可以确定不同的兼容版本的 gson 不会破坏您的项目。

我担心适配器的创建可能会很慢,所以我用debuggable=false测量了 Pixel 3a 上的执行时间。 parseISO8601DateToLocalTimeOrNull需要大约 0.5 毫秒来解析日期。

基本功能礼貌:@wrygiel。

该函数可以将 ISO8601 格式转换为可以处理偏移值的 Java 日期。 根据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;
}

}

我正在尝试将ISO 8601格式的String转换为java.util.Date

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

但是,使用java.text.SimpleDateFormat不能转换格式正确的String 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));

显然不是那么好。 我是否缺少某些东西或有更好的解决方案?


回答

感谢JuanZe的评论,我找到了Joda-Time魔术, 这里也对此进行了描述

因此,解决方案是

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" ) ;

对我来说,这很好。

我认为很多人想要做的是解析 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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM