我正在尝试将 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" ) ;
对我来说,这很好。
遗憾的是,可用于 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
部分)。
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 javadoc 的 示例 部分找到更多示例。
UPD 02/13/2020:在 Java 8 中有一个 completely new way 可以做到这一点
java.time
framework,它取代了麻烦的 java.util.Date、.Calendar 和 SimpleDateFormat 类。
string1
和 string2
但不知道会得到哪一个,该怎么办。
好的,这个问题已经回答了,但无论如何我都会放弃我的答案。它可能会帮助某人。
我一直在寻找适用于 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 中出现 a bug。如果您和我一样感到惊讶,请参阅this riddle。对于其他 Java 引擎,您可以将实例缓存在私有静态字段中(使用 ThreadLocal,以保证线程安全)。
s = s.substring(0, 22) + s.substring(23);
- 我不明白这一点
java.time
java.time API(内置于 Java 8 及更高版本)使这更容易一些。
如果您知道输入在 UTC 中,例如末尾的 Z
(用于 Zulu),则 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 );
LocalDateTime
和 ZoneId
和 atZone
。这个简单的单行将做:java.util.Date date = Date.from( ZonedDateTime.parse( "2014-12-12T10:39:40Z" ).toInstant() );
Date.from(Instant.parse("2014-12-12T10:39:40Z" ));
就足够了。
OffsetDateTime
足以解析 ISO8601(不包含时区信息,而仅包含偏移量)。
Instant
进行解析的评论。虽然对于这个特定问题还不够,但这是一个值得指出的重要区别。所以我添加了第二个代码示例。糟糕,刚刚注意到这原本不是我的答案;我希望亚当同意。
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.Date
、Calendar
和 & SimpleDateFormat
。
要了解更多信息,请参阅 Oracle Tutorial。并在 Stack Overflow 上搜索许多示例和解释。规格为JSR 310。
Joda-Time 项目现在位于 maintenance mode 中,建议迁移到 java.time 类。
您可以直接与您的数据库交换 java.time 对象。使用符合 JDBC 4.2 或更高版本的 JDBC driver。不需要字符串,不需要 java.sql.*
类。休眠 5 和JPA 2.2 支持 java.time。
从哪里获得 java.time 类?
Java SE 8、Java SE 9、Java SE 10、Java SE 11 及更高版本 - 标准 Java API 的一部分,具有捆绑的实现。 Java 9 带来了一些小功能和修复。
Java 9 带来了一些小功能和修复。
Java SE 6 和 Java SE 7 大多数 java.time 功能在 ThreeTen-Backport 中向后移植到 Java 6 和 7。
大多数 java.time 功能在 ThreeTen-Backport 中向后移植到 Java 6 和 7。
Android 更高版本的 Android (26+) 捆绑了 java.time 类的实现。对于早期的 Android (<26),API 脱糖过程带来了最初未内置于 Android 中的 java.time 功能的子集。如果脱糖不能提供您所需要的,ThreeTenABP 项目会将 ThreeTen-Backport(上面提到的)适配到 Android。请参阅如何使用 ThreeTenABP……。
更高版本的 Android (26+) 捆绑了 java.time 类的实现。
对于早期的 Android (<26),API 脱糖过程带来了最初未内置于 Android 中的 java.time 功能的子集。如果脱糖不能提供您所需要的,ThreeTenABP 项目会将 ThreeTen-Backport(上面提到的)适配到 Android。请参阅如何使用 ThreeTenABP……。
如果脱糖不能提供您所需要的,ThreeTenABP 项目会将 ThreeTen-Backport(上面提到的)适配到 Android。请参阅如何使用 ThreeTenABP……。
java.time.OffsetDateTime.parse ( "2010-01-01" )
失败
"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。
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]]
。换句话说,毫秒是可选的,但时区是强制性的。
new DateTime("2015-08-11T13:10:00").toDate()
从 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 上不起作用)。
ISO_OFFSET_DATE_TIME
格式化带有偏移量的日期,例如 +01:00
(docs.oracle.com/javase/8/docs/api/java/time/format/…)
对于 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 语句)。
Date
和 SimpleDateFormat
这样麻烦的旧日期时间类设计不佳、令人困惑且存在缺陷。它们现在是遗留的,被 Java 8 及更高版本中内置的 java.time 类所取代。对于 Java 6 和 Java 7,大部分 java.time 功能都在 ThreeTen-Backport 项目中向后移植。将该库添加到您的应用程序比使用那些遗留类要好得多。 java.time 中的单行解决方案:OffsetDateTime.parse( "2010-01-01T12:00:00+01:00" )
在我搜索了很多以将 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());
日期不包含时区,它将以 UTC 存储,但即使在使用 System.out.println(date)
进行简单输出时也会正确转换为您的 JVM 时区。
Date
则更好,因为该类设计不良且早已过时。例如,最好使用 ZonedDateTime.withZoneSameInstant()
转换为自己的时区。仅当您必须使用 Date
来获取您现在无法升级到 java.time 的旧版 API 时才使用此答案。
java.time
请注意,在 Java 8 中,您可以使用 java.time.ZonedDateTime 类及其静态 parse(CharSequence text)
方法。
Instant
和 ZonedDateTime
在这里是合适的,而不是 ZonedDateTime
。
我遇到了同样的问题并通过以下代码解决了它。
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 类可以处理所有这些情况。
OffsetDateTime
会在概念上更好地匹配带有偏移量的日期时间。
OffsetDateTime
处理您使用 ZonedDateTime
处理的示例。我相信它不能处理 ZonedDateTime
不能处理的任何示例。从这个意义上说,它没有任何改进(但也没有更糟)。对不起,我不是很清楚。
您也可以使用以下课程 -
org.springframework.extensions.surf.util.ISO8601DateFormat
Date date = ISO8601DateFormat.parse("date in iso8601");
链接到 Java 文档 - Hierarchy For Package org.springframework.extensions.surf.maven.plugin.util
正如其他人所提到的,Android 没有很好的方法来支持使用 SDK 中包含的类解析/格式化 ISO 8601 日期。我已经多次编写此代码,因此我最终创建了一个 Gist,其中包含一个支持格式化和解析 ISO 8601 和 RFC 1123 日期的 DateUtils 类。 Gist 还包括一个测试用例,显示它支持的内容。
https://gist.github.com/mraccola/702330625fad8eebe7d3
SimpleDateFormat for JAVA 1.7 有一个很酷的 ISO 8601 格式模式。
这是我所做的:
Date d = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss.SSSZ",
Locale.ENGLISH).format(System.currentTimeMillis());
Z
不是 ISO 8601 时区,如果您想要 ISO 8601 时区,则应使用 X
(或 XX
或 XXX
)
使用像 LocalDate.parse(((String) data.get("d_iso8601")),DateTimeFormatter.ISO_DATE)
这样的字符串
令我惊讶的是,甚至没有一个 java 库支持 https://en.wikipedia.org/wiki/ISO_8601 中的所有 ISO 8601 日期格式。 Joda DateTime 支持其中的大多数,但不是全部,因此我添加了自定义逻辑来处理所有这些。这是我的实现。
导入 java.text.ParseException;导入 java.util.Date;导入 org.apache.commons.lang3.time.DateUtils;导入 org.joda.time.DateTime; public class ISO8601DateUtils { /** * 它解析来自 https://en.wikipedia.org/wiki/ISO_8601 的所有日期时间格式并返回 Joda DateTime。 * Zoda DateTime 不支持格式为 20190531T160233Z 的日期,因此添加了自定义逻辑来使用 SimpleDateFormat 处理此问题。 * @param dateTimeString ISO 8601 日期时间字符串 * @return */ public static DateTime parse(String dateTimeString) { try { return new DateTime( dateTimeString ); } catch(Exception e) { 尝试 { 日期 dateTime = DateUtils.parseDate(dateTimeString, JODA_NOT_SUPPORTED_ISO_DATES);返回新的日期时间(dateTime.getTime()); } catch (ParseException e1) { throw new RuntimeException(String.format("日期 %s 无法解析为 ISO 日期", 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", // 最多秒 "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", // 最多分钟 "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", //Joda DateTime 已经支持最多小时 }; }
Either basic or extended formats may be used, but both date and time must use the same format
像这样做:
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());
}
java.util.Date
、java.util.Calendar
和 java.text.SimpleDateFormat
)现在是 legacy,被 Java 8 及更高版本中内置的 java.time 类所取代。见Tutorial by Oracle。
LocalDateTime
不处理夏令时 (DST),因为它根本不处理时区。为此,我们需要 ZonedDateTime
。提出 Date
和 SimpleDateFormat
是 — 恕我直言不好。
当想要从 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 偏移的重要信息,而没有任何收获。
我也有类似的需求:我需要能够在事先不知道确切格式的情况下解析任何符合 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 项目中有一个 DateFormat
类 JacksonThymeleafISO8601DateFormat
插入缺少的冒号:
我将它用于 ECMAScript 日期格式兼容性。
我无法使用 Java 8 功能,因此只有 java.util.Date
可用。我已经依赖于 gson 库,但不想直接使用 ISO8601Utils
。 ISO8601Utils
是一个内部 API,gson's authors warns not to use it。
我使用 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
.parseISO8601DateToLocalTimeOrNull
测量了 Pixel 3a 上的执行时间,解析日期需要大约 0.5 毫秒。
java.util.Date
。
基本功能礼貌:@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;
}
}
我认为很多人想要做的是解析 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.org见http://en.wikipedia.org/wiki/ISO_8601见http://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
Instant.parse( "2013-12-14T01:55:33.412Z" )