我正在尝试解析从HTML5的datetime
输入字段返回的日期值。在Opera中尝试一下就可以看到一个例子。返回的日期看起来像这样:2011-05-03T11:58:01Z
。
我想将其解析为Java Date或Calendar对象。
理想情况下,解决方案应具备以下特点:
- 没有外部库(JAR)
- 处理所有可接受的RFC 3339格式
- 可以轻松验证字符串是否是有效的RFC 3339日期
我正在尝试解析从HTML5的datetime
输入字段返回的日期值。在Opera中尝试一下就可以看到一个例子。返回的日期看起来像这样:2011-05-03T11:58:01Z
。
我想将其解析为Java Date或Calendar对象。
理想情况下,解决方案应具备以下特点:
Instant.parse( "2011-05-03T11:58:01Z" )
RFC 3339 仅是对实际标准 ISO 8601 的自我宣称“配置文件”。
与 ISO 8601 不同的是,RFC 故意违反 ISO 8601 以允许零小时的负偏移(-00:00),并赋予其语义含义“偏移未知”。在我看来,这种语义似乎是一个非常糟糕的想法。建议坚持更明智的 ISO 8601 规则。在 ISO 8601 中,没有任何偏移意味着偏移未知 - 这是一个显而易见的含义,而 RFC 规则则是深奥的。
java.time 类现代化,使用解析/生成字符串时默认使用 ISO 8601 格式。
您的输入字符串表示 UTC 中的一个时刻。结尾的 Z
缩写为 Zulu
,表示 UTC。
Instant
(不是 Date
)现代类 Instant
表示 UTC 中的一个时刻。该类替换了 java.util.Date
,并使用纳秒而不是毫秒的更高分辨率。
Instant instant = Instant.parse( "2011-05-03T11:58:01Z" ) ;
ZonedDateTime
(而不是 Calendar
)为了使用某个地区(时区)人们使用的挂钟时间查看相同的时刻,请应用 ZoneId
以获取 ZonedDateTime
。此类 ZonedDateTime
取代了 java.util.Calendar
类。
ZoneId z = ZoneId.of( "Africa/Tunis" ) ;
ZonedDateTime zdt = instant.atZone( z ) ; // Same moment, same point on the timeline, different wall-clock time.
我强烈建议尽可能避免使用旧的日期时间类。但如果你必须与尚未更新到 java.time 的旧代码进行交互,你可以进行来回转换。调用添加到旧类中的新方法。
Instant
替代了 java.util.Date
。
java.util.Date myJUDate = java.util.Date.from( instant ) ; // From modern to legacy.
Instant instant = myJUDate.toInstant() ; // From legacy to modern.
ZonedDateTime
取代了 GregorianCalendar
。
java.util.GregorianCalendar myGregCal = java.util.GregorianCalendar.from( zdt ) ; // From modern to legacy.
ZonedDateTime zdt = myGregCal.toZonedDateTime() ; // From legacy to modern.
GregorianCalendar
的java.util.Calendar
,进行强制类型转换。java.util.GregorianCalendar myGregCal = ( java.util.GregorianCalendar ) myCal ; // Cast to the concrete class.
ZonedDateTime zdt = myGregCal.toZonedDateTime() ; // From legacy to modern.
关于你的问题的特定问题...
java.time类内置于Java 8、9、10及更高版本。实现也包含在后来的Android中。对于早期的Java和早期的Android,请参见本答案的下一节。
各种java.time类处理我所知道的每个ISO 8601格式。它们甚至处理了一些在标准的后续版本中神秘消失的格式。
对于其他格式,请参见各种类的parse
和toString
方法,例如LocalDate
、OffsetDateTime
等。此外,在Stack Overflow上搜索,因为有许多关于这个主题的示例和讨论。
要验证输入字符串,请捕获DateTimeParseException
。
try {
Instant instant = Instant.parse( "2011-05-03T11:58:01Z" ) ;
} catch ( DateTimeParseException e ) {
… handle invalid input
}
java.time框架内置于Java 8及更高版本中。这些类替代了老旧的legacy日期时间类,例如java.util.Date
、Calendar
和SimpleDateFormat
。
Joda-Time项目现在处于maintenance mode维护模式,并建议迁移到java.time类。
要了解更多信息,请参见Oracle教程。并在Stack Overflow上搜索许多示例和解释。规范是JSR 310。
您可以直接使用与JDBC 4.2或更高版本兼容的JDBC驱动程序,无需字符串或java.sql.*
类,即可直接在数据库中交换java.time对象。
如何获取java.time类?
ThreeTen-Extra项目通过添加额外的类来扩展java.time。该项目是java.time可能未来增加内容的试验场。您可能会在这里找到一些有用的类,例如Interval
, YearWeek
, YearQuarter
和more。
OffsetDateTime.parse( "2019-11-30T10:30:00+02:00" )
和 OffsetDateTime.parse( "2019-10-12T07:20:50.52+00:00" )
。所有的 java.time 类默认使用 ISO 8601 格式来解析/生成文本。您只需要选择适合语义的 java.time 类即可。 - Basil Bourquejava.time
类来处理文本的语义有点不真诚。这些语义都是有效的RFC3339,你似乎在说我需要先解析它才能确定要使用哪个类来解析它? - Daniel C. Sobralyyyy
(年份)MM
(月份)dd
(日期)HH
(小时)mm
(分钟)ss
(秒数).SSS
(毫秒,不过不清楚如果有超过或少于3位数字会发生什么)+02:00
这样的格式似乎不被支持,而是支持+0200
、GMT+02:00
和一些使用z
和Z
的命名时区。)'Z'
(不支持其他时区)- 在使用此项之前应该使用format.setTimezone(TimeZone.getTimeZone("UTC"))
。HH:mm:ss
或HH:mm:ss.SSS
。HH:mm:ss'Z'
或HH:mm:ss.SSS'Z'
。yyyy-MM-dd
(完整日期)yyyy-MM-dd'T'HH:mm:ss'Z'
或yyyy-MM-dd'T'HH:mm:ss.SSS'Z'
(完整日期和时间)我们可以看到,这似乎无法解析所有内容。也许从头开始实现一个RFC3339DateFormat
会是一个更好的想法(使用正则表达式,以简便为主,或手动解析,以提高效率)。
y
做了不同的事情? - Paŭlo EbermannDateTimeFormatter.ISO_OFFSET_DATE_TIME
包括这个功能。) - Paŭlo Ebermann刚刚发现谷歌在Google HTTP客户端库中实现了Rfc3339解析器。
经过测试,它可以很好地解析各种子秒时间片段。
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import com.google.api.client.util.DateTime;
DateTimeFormatter formatter = DateTimeFormatter
.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
.withZone(ZoneId.of("UTC"));
@Test
public void test1e9Parse() {
String timeStr = "2018-04-03T11:32:26.553955473Z";
DateTime dateTime = DateTime.parseRfc3339(timeStr);
long millis = dateTime.getValue();
String result = formatter.format(new Date(millis).toInstant());
assert result.equals("2018-04-03T11:32:26.553Z");
}
@Test
public void test1e3Parse() {
String timeStr = "2018-04-03T11:32:26.553Z";
DateTime dateTime = DateTime.parseRfc3339(timeStr);
long millis = dateTime.getValue();
String result = formatter.format(new Date(millis).toInstant());
assert result.equals("2018-04-03T11:32:26.553Z");
}
@Test
public void testEpochSecondsParse() {
String timeStr = "2018-04-03T11:32:26Z";
DateTime dateTime = DateTime.parseRfc3339(timeStr);
long millis = dateTime.getValue();
String result = formatter.format(new Date(millis).toInstant());
assert result.equals("2018-04-03T11:32:26.000Z");
}
DateTime
类。我感到困惑。 - Basil Bourque如果您拥有的格式为2011-05-03T11:58:01Z,那么下面的代码将起作用。但是,最近我在Chrome和Opera中尝试了html5 datetime,它给出的结果是2011-05-03T11:58Z——没有ss部分,这无法被下面的代码处理。
new Timestamp(javax.xml.datatype.DatatypeFactory.newInstance().newXMLGregorianCalendar(date).toGregorianCalendar().getTimeInMillis());
也许不是最优雅的方法,但肯定是我最近制作的一个可行方法:
Calendar cal = Calendar.getInstance();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-HH:mm:ss");
cal.setTime(sdf.parse(dateInString.replace("Z", "").replace("T", "-")));
虽然这个问题很老,但是它可能会帮助想要 Kotlin 版本答案的人。使用这个文件,任何人都可以将 Rfc3339 日期转换为任何日期格式。在这里,我使用一个空文件名 DateUtil
并创建一个名为 getDateString()
的函数,该函数有 3 个参数。
1st argument : Your input date
2nd argument : Your input date pattern
3rd argument : Your wanted date pattern
DateUtil.kt
object DatePattern {
const val DAY_MONTH_YEAR = "dd-MM-yyyy"
const val RFC3339 = "yyyy-MM-dd'T'HH:mm:ss'Z'"
}
fun getDateString(date: String, inputDatePattern: String, outputDatePattern: String): String {
return try {
val inputFormat = SimpleDateFormat(inputDatePattern, getDefault())
val outputFormat = SimpleDateFormat(outputDatePattern, getDefault())
outputFormat.format(inputFormat.parse(date))
} catch (e: Exception) {
""
}
}
现在,在您的活动/函数/数据源映射器中使用此方法以获取字符串格式的日期,如下所示:
getDate("2022-01-18T14:41:52Z", RFC3339, DAY_MONTH_YEAR)
输出将会像这样
18-01-2022
作为备选方案,未来您可以使用ITU[1],它是手写的,用于处理RFC-3339解析并让您轻松处理闰秒。该库无需依赖其他库,仅占用18 kB。
完全公开透明:我是作者
try
{
final OffsetDateTime dateTime = ITU.parseDateTime(dateTimeStr);
}
catch (LeapSecondException exc)
{
// The following helper methods are available let you decide how to progress
//int exc.getSecondsInMinute()
//OffsetDateTime exc.getNearestDateTime()
//boolean exc.isVerifiedValidLeapYearMonth()
}
DateTimeFormatter RFC_3339_DATE_TIME_FORMATTER = new DateTimeFormatterBuilder()
.append(ISO_LOCAL_DATE_TIME)
.optionalStart()
.appendOffset("+HH:MM", "Z")
.optionalEnd()
.toFormatter();
例子:
String dateTimeString = "2007-05-01T15:43:26.3452+07:00";
ZonedDateTime zonedDateTime = ZonedDateTime.from(RFC_3339_DATE_TIME_FORMATTER.parse(dateTimeString));
Instant.parse(string)
即可支持两个偏移选项。 - Oswaldo JuniorDate date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").parse(datetimeInFRC3339format)
java.util.Date
、java.util.Calendar
和java.text.SimpleDateFormat
这样的老旧日期时间类现在已经成为遗留系统,被内置于Java 8及更高版本中的java.time类所取代。请参阅Oracle的教程。 - Basil Bourque