ZonedDateTime 到早期 Android 中 Java 8 之前的日期
Posted
技术标签:
【中文标题】ZonedDateTime 到早期 Android 中 Java 8 之前的日期【英文标题】:ZonedDateTime to Date before Java 8 in early Android 【发布时间】:2019-07-16 15:37:31 【问题描述】:我正在尝试替换 ZonedDateTime.toInstant
方法,因为它仅在 API 26 for android 之后才可用。
但我的应用应该支持 API 19。
我想将 ZonedDateTime 转换为日期,以便可以执行以下操作:
final Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date());
final long millis = calendar.getTimeInMillis();
我想要达到的目标如下:
我想计算当前日期和另一个日期之间的差异,以秒、分钟、小时为单位……最高可能的单位获胜,所以我会得到例如5 days ago
结果。
【问题讨论】:
你是如何构建你的 ZonedDateTime 实例的?为什么不能用问题中的代码替换它? Difference in days between two dates in Java?的可能重复 你不能使用 Joda Time 或 ThreeTen Backport 吗? @TheWanderer 我从 json 获取字符串,然后将其传递给 ZonedDateTime.parse()。我将尝试 ThreeTen Backport 并提供更新。 日期字符串例如“2019-02-23T16:50:21Z”,所以它是 UTC。 【参考方案1】:您可以简单地通过这个 gradle 配置启用“对较新的 Java 语言 API 的支持”:
<!-- Copyright 2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
android
defaultConfig
//Only required when setting minSdkVersion to 20 or lower
multiDexEnabled true
compileOptions
// Flag to enable support for the new language APIs
coreLibraryDesugaringEnabled true
// Sets Java compatibility to Java 8
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
dependencies
// Dependency with the implementation code for the APIs
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.5'
来源:https://medium.com/androiddevelopers/support-for-newer-java-language-apis-bca79fc8ef65
【讨论】:
经过测试,可以正常工作!我在我的项目中使用 ZonedDateTime,它通常只适用于 api>=26,所以我下载了 api 24 的模拟器,项目运行没有崩溃!【参考方案2】:tl;博士
对于 26 岁之前的 Android,请使用 ThreeTen-ABP 库。
避免使用旧的日期时间类
Calendar
和Date
等旧的日期时间类是可怕的,真的很糟糕的可怜类。它们充满了糟糕的设计决策和黑客,由不了解日期时间处理的人构建。避开他们。它们完全被 java.time 类所取代,采用 JSR 310 是有原因的——实际上,很多原因。
避免使用这些遗留类。仅使用 java.time 类。
ThreeTen-Backport 库
对于 Java 6 和 Java 7,大部分 java.time 功能在 ThreeTen-Backport 项目中向后移植。
ThreeTen-ABP
该反向移植在 ThreeTen-ABP 项目中进一步适应了早期的 Android (
我敦促您将此库添加到您的项目中,这样您就可以避免使用可悲的遗留类。
转化
如果您需要与尚未更新到java.time 的旧代码交互,请在旧代码和现代代码之间来回转换。
在 Java 8 及更高版本中,通过调用旧类上的新 to…
和 from…
方法进行转换。
在后端端口中,使用org.threeten.bp.DateTimeUtils
类中的to…
转换方法进行转换。
经过的时间
您的问题涉及计算经过的时间。
要计算年、月和日,请使用Period
。
要计算天数(与日历无关的 24 小时时间段)、小时、分钟、秒和小数秒,请使用 Duration
。
搜索堆栈溢出以获取更多信息。这些课程已经讲过很多次了。
关于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.*
类。
从哪里获得 java.time 类?
Java SE 8、Java SE 9、Java SE 10、Java SE 11 和更高版本 - 具有捆绑实现的标准 Java API 的一部分。 Java 9 添加了一些小功能和修复。 Java SE 6 和 Java SE 7 大部分 java.time 功能在ThreeTen-Backport 中向后移植到 Java 6 和 7。 Android java.time 类的 Android 捆绑包实现的更高版本。 对于早期的 Android (ThreeTenABP 项目适应 ThreeTen-Backport(如上所述)。见How to use ThreeTenABP…。【讨论】:
【参考方案3】:解决方案(ThreeTen-Backport 库): 效果很好,我已经在 KitKat 模拟器上试用过了。
private static final ChronoUnit[] chronoUnits = ChronoUnit.YEARS, ChronoUnit.MONTHS, ChronoUnit.DAYS, ChronoUnit.HOURS, ChronoUnit.MINUTES, ChronoUnit.SECONDS;
private static final Map<ChronoUnit, Integer> chronoUnitPluralIdMap = new HashMap<ChronoUnit, Integer>()
put(ChronoUnit.YEARS, R.plurals.chrono_unit_years_ago);
put(ChronoUnit.MONTHS, R.plurals.chrono_unit_months_ago);
put(ChronoUnit.DAYS, R.plurals.chrono_unit_days_ago);
put(ChronoUnit.HOURS, R.plurals.chrono_unit_hours_ago);
put(ChronoUnit.MINUTES, R.plurals.chrono_unit_minutes_ago);
put(ChronoUnit.SECONDS, R.plurals.chrono_unit_seconds_ago);
;
public static String getTimeStringUntilNowFromUTC(Context context, String utcDate)
Instant now = Instant.now(Clock.systemUTC());
Instant then = Instant.parse(utcDate);
for (ChronoUnit chronoUnit : chronoUnits)
if (then.isSupported(chronoUnit))
long units = chronoUnit.between(then, now);
if (units > 0)
//noinspection ConstantConditions
return context.getResources().getQuantityString(chronoUnitPluralIdMap.get(chronoUnit), (int)units, (int)units);
return "-";
public static String getTimeBetweenTwoDates(Context context, String date1, String date2)
Instant date1Instant = Instant.parse(date1);
Instant date2Instant = Instant.parse(date2);
final long seconds = ChronoUnit.SECONDS.between(date1Instant, date2Instant);
return getMinutesSecondsString(context, seconds);
【讨论】:
【参考方案4】:Basil Bourque 已经给出了一个很好的答案。在你的 cmets 之后,我想我可以更具体一点:
public static String diff(String thenStr)
Instant now = Instant.now();
Instant then = Instant.parse(thenStr);
ChronoUnit[] units = ChronoUnit.values();
// try longest units first, they are in the far end of the array
for (int i = units.length - 1; i >= 0; i--)
if (then.isSupported(units[i]))
long diffInCurrentUnit = units[i].between(then, now);
if (diffInCurrentUnit != 0)
return "" + diffInCurrentUnit + ' ' + units[i].toString().toLowerCase();
return "0";
让我们试试吧:
System.out.println(diff("2019-02-23T16:50:21Z"));
System.out.println(diff("2019-02-23T20:15:21Z"));
刚刚运行时的输出:
3 hours 36 seconds
我使用的进口产品:
import org.threeten.bp.Instant;
import org.threeten.bp.temporal.ChronoUnit;
import org.threeten.bp.temporal.UnsupportedTemporalTypeException;
Instant
不支持比天更粗的单位(24 小时)。如果您需要能够返回数周、数月或数年,只需使用 OffsetDateTime
而不是 Instant
。
问题:我可以在我的 Android API 级别上使用 java.time 吗?
是的,java.time 在较旧和较新的 Android 设备上运行良好。它只需要至少 Java 6。
在 Java 8 及更高版本以及更新的 Android 设备(从 API 级别 26 开始)中,现代 API 是内置的。在这种情况下,从带有子包的java.time
导入。
在 Java 6 和 7 中获得 ThreeTen Backport,这是现代类的后向端口(JSR 310 的 ThreeTen;请参阅底部的链接)。
在(较旧的)Android 上使用 ThreeTen Backport 的 Android 版本。它被称为 ThreeTenABP。并确保从 org.threeten.bp
导入日期和时间类以及子包。
流?
编辑:@Basil Bourque 在评论中问道:
我想知道这是否可以被压缩成一条溪流,也许是一条单线?
可以,但我认为在这种情况下不会有优势:
return IntStream.range(0, units.length)
.map(i -> units.length - i - 1)
.mapToObj(i -> units[i])
.filter(then::isSupported)
.filter(unit -> unit.between(then, now) != 0)
.map(unit -> "" + unit.between(then, now) + ' ' + unit.toString().toLowerCase())
.findFirst()
.orElse("0");
我发现返回数组元素的代码.map(i -> units.length - i - 1)
有点难以阅读。我们需要计算两次差异,第一次用于过滤,然后用于组装字符串结果。但它有效,如果你愿意,你可以使用它。
使用内部流管道可以避免双重计算,但同样难以阅读:
.flatMap(unit -> LongStream.of(unit.between(then, now))
.filter(diff -> diff != 0)
.mapToObj(diff -> "" + diff + ' ' + unit.toString().toLowerCase()))
链接
Oracle tutorial: Date Time 解释如何使用 java.time。 Java Specification Request (JSR) 310,其中首次描述了java.time
。
ThreeTen Backport project,java.time
到 Java 6 和 7(JSR-310 的 ThreeTen)的反向移植。
ThreeTenABP,Android 版 ThreeTen Backport
Question: How to use ThreeTenABP in Android Project,解释得很透彻。
【讨论】:
有趣的方法,循环所有ChronoUnit
枚举值。我想知道这是否可以被压缩成一条小溪,也许是一条单线?
它可以变成一个流,@BasilBourque(在我去掉代码中多余的try
-catch
之后更好)。请参阅我的编辑。起初我避免使用它,因为我发现向后流式传输数组的代码更难阅读(灵感来自 Java 8 stream reverse order)。
感谢您这样做,@ManuelWa。您可以考虑将其发布为您自己的答案,而不是在问题中。我确实认为您可能不想考虑 all 计时单位。我看到您找到了解决方案。以上是关于ZonedDateTime 到早期 Android 中 Java 8 之前的日期的主要内容,如果未能解决你的问题,请参考以下文章
OffsetDateTime 到 ZonedDateTime - 带有特定的 ZoneId
将 java.sql.Timestamp 转换为 Java 8 ZonedDateTime?
如何将 TIMESTAMP 列映射到 ZonedDateTime JPA 实体属性?
夏令时前后将 Duration.ofDays(1) 和 Period.ofDays(1) 添加到 ZonedDateTime 之间的区别