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 库。

避免使用旧的日期时间类

CalendarDate 等旧的日期时间类是可怕的,真的很糟糕的可怜类。它们充满了糟糕的设计决策和黑客,由不了解日期时间处理的人构建。避开他们。它们完全被 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.DateCalendarSimpleDateFormat

要了解更多信息,请参阅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 8Java SE 9Java SE 10Java SE 11 和更高版本 - 具有捆绑实现的标准 Java API 的一部分。 Java 9 添加了一些小功能和修复。 Java SE 6Java 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 -&gt; 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 之间的区别

如何将 Android SDK 工具恢复到早期版本?

巴西的 ZonedDateTime 仍然得到 -2 而不是 -3 偏移量