Java 8之前在早期Android中将ZonedDateTime转换为Date

10

我想替换ZonedDateTime.toInstant方法,因为它只在Android API 26及以上版本中可用。
但是我的应用程序需要支持API 19。
我想将ZonedDateTime转换为Date,以便我可以像这样做些事情:

final Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date());
final long millis = calendar.getTimeInMillis();

我想要实现以下内容:
我想要计算当前日期和另一个日期之间的差距,以秒,分钟,小时等为单位,最高可能的单位胜出,所以我会得到例如5天前的结果。


你是如何构建你的ZonedDateTime实例的?为什么不能直接用你问题中的代码替换它? - TheWanderer
1
可能是Java中两个日期之间的天数差异?的重复问题。 - Tomin B Azhakathu
1
@TheWanderer 我从一个 JSON 中获取了一个字符串,然后将其传递给 ZonedDateTime.parse()。我将尝试使用 ThreeTen Backport,并会更新情况。 - manuelwaldner
1
日期字符串为"2019-02-23T16:50:21Z",因此它是UTC时间。 - manuelwaldner
我添加了一个解决方案,使用String而不是ZonedDateTime。在此之前,我不得不调整我的API库,以返回原始的String而不是已解析的ZonedDateTime,但它正在工作!谢谢。 - manuelwaldner
显示剩余2条评论
5个回答

9

简要概述

在Android 26之前,请使用ThreeTen-ABP库。

避免使用传统的日期时间类

旧的日期时间类,如CalendarDate,非常糟糕。它们充斥着糟糕的设计决策和技巧,是由不了解日期时间处理的人构建的。请避免使用它们。随着JSR 310的采用,它们被完全取代,原因有很多。

请避免使用这些传统的类。只使用java.time类。

ThreeTen-Backport

对于Java 6和Java 7,大部分java.time功能都被移植到ThreeTen-Backport项目中。

ThreeTen-ABP

该后移端口在ThreeTen-ABP项目中进一步适用于早期的Android (<26)。

我建议您将此库添加到您的项目中,以便您可以避免使用悲惨的遗留类。

转换

在需要与尚未更新为java.time的旧代码进行接口时,必须在传统和现代之间进行转换。

在Java 8及更高版本中,通过调用旧类上找到的新to…from…方法进行转换。

在后移端口中,使用org.threeten.bp.DateTimeUtils类上找到的to…转换方法进行转换。

经过的时间

您的问题涉及计算经过的时间。

要计算年、月和日,请使用Period

要计算天数(与日历无关的24小时时间块)、小时、分钟、秒和分数秒,请使用Duration
搜索 Stack Overflow 以获取更多信息。这些类已经被讨论过很多次了。

关于java.time

java.time框架内置于Java 8及更高版本中。这些类取代了老旧的遗留日期时间类,如java.util.DateCalendarSimpleDateFormat

要了解更多信息,请参阅Oracle教程。并在Stack Overflow上搜索许多示例和解释。规范是JSR 310

Joda-Time项目现在处于维护模式,建议迁移到java.time类。

您可以直接与数据库交换java.time对象。使用符合JDBC 4.2或更高版本的JDBC驱动程序。无需字符串,也无需java.sql.*类。

如何获取java.time类?

enter image description here


9

您可以通过以下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

本文介绍了安卓系统支持的 Java 语言 API 的版本更新情况,包括在 Android 11 中添加的新功能,如 Lambda 表达式和 Streams API。对于使用较旧的 Android 版本的开发人员,建议考虑使用第三方库或兼容库以获得更多的 Java 功能支持。

测试过了,它能正常工作!我在我的项目中使用ZonedDateTime,通常只能在api>=26上运行,所以我下载了一个带有api 24的模拟器,项目可以正常运行而不会崩溃! - Amr

3

已经有Basil Bourque提供了很好的答案。根据你的评论,我认为我可以再具体一点:

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不支持比天更长的时间单位,如果需要返回周、月或年,只需使用OffsetDateTime代替Instant即可。

问题:我可以在我的Android API级别上使用java.time吗?

是的,在旧版和新版Android设备上,java.time都可以正常使用。只需至少具有Java 6

  • 在Java 8及更高版本和更新的Android设备(从API级别26开始),现代API已内置。此时,请从java.time及其子包中导入。
  • 在Java 6和7中,获取ThreeTen Backport,即现代类的后备(ThreeTen用于JSR 310;请参见底部的链接)。
  • 在(旧版)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()))

链接


有趣的方法,循环所有 ChronoUnit 枚举值。我想知道是否可以将其压缩成一个流,也许是一行代码? - Basil Bourque
它可以转换为流,@BasilBourque(在我去掉代码中多余的try-catch之后更好)。请查看我的编辑。一开始我避免使用它,因为我发现倒序流数组的代码更难阅读(灵感来自Java 8 stream reverse order)。 - Ole V.V.
1
感谢您做到这一点,@ManuelWa。您可以考虑将其发布为自己的答案,而不是在问题中。我想您可能只是不想考虑所有时间单位。我看到您已经找到了解决方案。 - Ole V.V.

1

解决方案(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);
}

1

Roman Droppa的答案很好,但是今天(2023年)您必须更改库的版本:

 compileOptions {
        coreLibraryDesugaringEnabled true
        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.9'

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接