将任何 ISO-8601 格式转换为 Android 中可读的格式

huangapple go评论68阅读模式
英文:

Convert any ISO-8601 format to a readable format in Android

问题

所以,我知道这是一个广泛讨论的问题,有很多关于它的问题和答案(主要涉及到Joda),还有新的DateTimeFormatter类和支持API级别高于26的所有内容。但我的问题是这样的:

  1. 我的Android应用程序支持21及以上版本。
  2. 我从不同的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:

  1. My android application supports 21 and above
  2. 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,有两种方式:

  1. 通过 desugaring。
  2. 通过后移方式;甚至有一个 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 位的存在或缺失都被内置在内,甚至包括秒本身的存在或缺失。InstantOffsetDateTime 都可以按照代码中所示的方式使用。

注意! 如果由于某种原因选择使用一个或多个格式化程序,永远不要将 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

链接

英文:

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:

  1. Through desugaring.
  2. 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

huangapple
  • 本文由 发表于 2020年9月4日 01:53:57
  • 转载请务必保留本文链接:https://go.coder-hub.com/63729201.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定