英文:
Convert any ISO-8601 format to a readable format in Android
问题
所以,我知道这是一个广泛讨论的问题,有很多关于它的问题和答案(主要涉及到Joda),还有新的DateTimeFormatter类和支持API级别高于26的所有内容。但我的问题是这样的:
- 我的Android应用程序支持21及以上版本。
- 我从不同的API获取多种ISO-8601日期/时间格式的变化:
例如:a) “2020-09-03T17:03:11.719566Z” b) “2021-03-05T18:30:00Z”
所以,我需要找到今天和那个日期之间的天数。当我编写代码来解析其中一个日期时,另一个会触发异常,反之亦然,所以我编写了嵌套的try/catch块来处理这两种情况...就像这样:
fun getFormattedDate(stringDate: String?): Long {
if (dueDateString.isNullOrEmpty())
return 0
val today = Calendar.getInstance().time
try {
val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ENGLISH)
val date = inputFormat.parse(dueDateString)
return if (date != null) {
val dueCalendar = Calendar.getInstance()
dueCalendar.time = date
getNoOfDays(today.time, dueCalendar.timeInMillis)
} else
0
} catch (ex: Exception) {
ex.showLog()
try {
val inputFormat1 = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH)
val date = inputFormat1.parse(dueDateString)
return if (date != null) {
val dueCalendar = Calendar.getInstance()
dueCalendar.time = date
getNoOfDays(today.time, dueCalendar.timeInMillis)
} else
0
} catch (exc: Exception) {
exc.showLog()
return 0
}
}
}
我使用这个函数来找到两个日期之间的天数:
fun getDueDateAfterParsing(dueDateString: String?): Long {
val today = ZonedDateTime.ofInstant(now(), ZoneId.systemDefault())
val dueDate = ZonedDateTime.parse(
dueDateString,
DateTimeFormatter.ISO_OFFSET_DATE_TIME.withZone(ZoneId.systemDefault())
)
return ChronoUnit.DAYS.between(today, dueDate)
}
我非常确定解决这个问题不可能这么复杂。ISO-8601有这么多格式,所以我不能编写适用于所有情况的try/catch块,对吗?所以有谁能帮助我找到最简单的方法来解决这个问题?
我也考虑过正则表达式,大多数情况下都会提到Joda,但至少我想确保什么是最优解或最佳解决方法。
提前感谢任何帮助。
英文:
So, I know this is a problem well discussed upon and a lot of questions and answers (mostly concerning Joda) and also with the new class DateTimeFormatter and all that supports api levels above 26. But my concern is this:
- My android application supports 21 and above
- I get multiple variations of date/time formats of ISO-8601 from different APIs:
for eg: a) “2020-09-03T17:03:11.719566Z” b) “2021-03-05T18:30:00Z”
So, I require to find #days between today and that date. When I write the code to parse one of them the other hits my catch block and vice versa, so I write a nested try/catch block to handle both the cases...something like this:
fun getFormattedDate(stringDate: String?): Long {
if (dueDateString.isNullOrEmpty())
return 0
val today = Calendar.getInstance().time
try {
val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ENGLISH)
val date = inputFormat.parse(dueDateString)
return if (date != null) {
val dueCalendar = Calendar.getInstance()
dueCalendar.time = date
getNoOfDays(today.time, dueCalendar.timeInMillis)
} else
0
} catch (ex: Exception) {
ex.showLog()
try {
val inputFormat1 = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH)
val date = inputFormat1.parse(dueDateString)
return if (date != null) {
val dueCalendar = Calendar.getInstance()
dueCalendar.time = date
getNoOfDays(today.time, dueCalendar.timeInMillis)
} else
0
} catch (exc: Exception) {
exc.showLog()
return 0
}
}
}
I am using this function to find #days between two dates:
fun getDueDateAfterParsing(dueDateString: String?): Long {
val today = ZonedDateTime.ofInstant(now(), ZoneId.systemDefault())
val dueDate = ZonedDateTime.parse(
dueDateString,
DateTimeFormatter.ISO_OFFSET_DATE_TIME.withZone(ZoneId.systemDefault())
)
return ChronoUnit.DAYS.between(today, dueDate)
}
I am pretty sure that the solution to this cannot be this complex. There are so many formats for ISO-8601 so i cant be writing try/catch blocks that fits for all cases right? So can anybody help me with the most simplest way I can get my way through this?
I have thought about regex too and most of them will end up saying Joda I am guessing, but at-least I want to be sure about what is the most optimal way or the best way of doing this.
Thanks in advance for any help.
答案1
得分: 1
java.time
和 desugaring 或 ThreeTenABP
你可以在 Android API 等级 21 及更高版本上使用 DateTimeFormatter
和其他来自 java.time
的类,这是现代 Java 日期和时间 API,有两种方式:
- 通过 desugaring。
- 通过后移方式;甚至有一个 Android 版本的适配库,称为 ThreeTenABP,已经被 chrylis 提到过。它是 JSR-310 的 ThreeTen,其中首次描述了
java.time
,以及 Android 后移。
关于这两种方式,详见底部的链接。
代码
你甚至不需要指定格式化程序。
String stringWeGot = "2020-09-03T17:03:11.719566Z";
Instant parsed = Instant.parse(stringWeGot);
System.out.println(parsed);
输出:
> 2020-09-03T17:03:11.719566Z
或者使用你的另一个示例字符串:
String stringWeGot = "2021-03-05T18:30:00Z";
> 2021-03-05T18:30:00Z
java.time
的类会解析最常见的 ISO 8601 变体作为默认值,也就是说,不需要显式格式化程序。秒的小数部分从 0 到 9 位的存在或缺失都被内置在内,甚至包括秒本身的存在或缺失。Instant
和 OffsetDateTime
都可以按照代码中所示的方式使用。
注意! 如果由于某种原因选择使用一个或多个格式化程序,永远不要将 Z
固定为格式模式字符串中的字面值。Z
是与 UTC 的偏移(零偏移)并且必须被解析为这样,否则你会在绝大多数 Android 设备上得到错误的结果。
此外,当涉及到计算天数时,java.time
在计数方面远远优于旧的且设计不良的类如 Calendar
。编辑:你的方法用于在设备时区中计算到截止日期的整天数是正确的。只是作为参考,我会这样做:
ZoneId zone = ZoneId.systemDefault();
ZonedDateTime today = ZonedDateTime.now(zone);
ZonedDateTime dueDate = parsed.atZone(zone);
long daysUntilDue = ChronoUnit.DAYS.between(today, dueDate);
System.out.println(daysUntilDue);
使用你提供的示例字符串 2021-03-05T18:30:00Z
,并在欧洲/哥本哈根时区中运行,结果为:
> 181
链接
- Oracle 教程:日期时间,解释了如何使用
java.time
。 - Java 规范请求(JSR)310,其中首次描述了
java.time
。 - ThreeTen Backport 项目,将
java.time
后移至 Java 6 和 7(JSR-310 的 ThreeTen)。 - 通过 desugaring 可用的 Java 8+ API
- ThreeTenABP,ThreeTen Backport 的 Android 版本
- 问题:如何在 Android 项目中使用 ThreeTenABP,附有非常详尽的解释。
英文:
java.time and desugaring or ThreeTenABP
You can use DateTimeFormatter
and the other classes from java.time, the modern Java date and time API, on Android API level 21 and up in two ways:
- Through desugaring.
- Through the backport; there is even an Android adaptation of it, ThreeTenABP, already mentioned by chrylis. It’s ThreeTen for JSR-310, where java.time was first described, and ABP for Android backport.
For both ways see the links at the bottom.
Code
You don’t even need to specify a formatter.
String stringWeGot = "2020-09-03T17:03:11.719566Z";
Instant parsed = Instant.parse(stringWeGot);
System.out.println(parsed);
Output:
> 2020-09-03T17:03:11.719566Z
Or with your other example string:
String stringWeGot = "2021-03-05T18:30:00Z";
> 2021-03-05T18:30:00Z
The classes of java.time parse the most common ISO 8601 variants as their default, that is, without an explicit formatter. Presence or absence of from 0 through 9 decimals on the seconds is built in, even the presence or absence of the seconds themselves. Both Instant
and OffsetDateTime
can be used in the manner shown in the code.
Warning! If you do opt for one or more formatters for one reason or another, never hardcode Z
as a literal in the format pattern string. Z
is an offset (of zero) from UTC and must be parsed as such, or you get incorrect results on the vast majority of Android devices.
Also when it comes to counting days, java.time is far superior to the old and poorly designed classes like Calendar
. EDIT: Your method for counting whole days until the due date in the device time zone is correct. Just for reference, my way of doing it would be:
ZoneId zone = ZoneId.systemDefault();
ZonedDateTime today = ZonedDateTime.now(zone);
ZonedDateTime dueDate = parsed.atZone(zone);
long daysUntilDue = ChronoUnit.DAYS.between(today, dueDate);
System.out.println(daysUntilDue);
Using your example string of 2021-03-05T18:30:00Z
and running in Europe/Copenhagen time zone just now the result was:
> 181
Links
- Oracle tutorial: Date Time explaining how to use java.time.
- Java Specification Request (JSR) 310, where
java.time
was first described. - ThreeTen Backport project, the backport of
java.time
to Java 6 and 7 (ThreeTen for JSR-310). - Java 8+ APIs available through desugaring
- ThreeTenABP, Android edition of ThreeTen Backport
- Question: How to use ThreeTenABP in Android Project, with a very thorough explanation.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论