【问题标题】:ZonedDateTime to Date before Java 8 in early AndroidZonedDateTime 到早期 Android 中 Java 8 之前的日期
【发布时间】: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 实例的?为什么不能用问题中的代码替换它?
  • 你不能使用 Joda Time 或 ThreeTen Backport 吗?
  • @TheWanderer 我从 json 获取字符串,然后将其传递给 ZonedDateTime.parse()。我将尝试 ThreeTen Backport 并提供更新。
  • 日期字符串例如“2019-02-23T16:50:21Z”,所以它是 UTC。

标签: java android date zoneddatetime


【解决方案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 类?

【讨论】:

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

      链接

      【讨论】:

      • 有趣的方法,循环所有 ChronoUnit 枚举值。我想知道这是否可以被压缩成一条小溪,也许是一条单线?
      • 它可以变成一个流,@BasilBourque(在我去掉代码中多余的try-catch之后更好)。请参阅我的编辑。起初我避免使用它,因为我发现向后流式传输数组的代码更难阅读(灵感来自 Java 8 stream reverse order)。
      • 感谢您这样做,@ManuelWa。您可以考虑将其发布为您自己的答案,而不是在问题中。我确实认为您可能不想考虑 all 计时单位。我看到您找到了解决方案。
      猜你喜欢
      • 2020-09-20
      • 2017-10-20
      • 2016-09-12
      • 2015-05-22
      • 1970-01-01
      • 2019-01-13
      • 1970-01-01
      • 2018-11-03
      • 1970-01-01
      相关资源
      最近更新 更多