【问题标题】:Get number of days since date object, disregard time获取自日期对象以来的天数,忽略时间
【发布时间】:2020-09-04 16:43:28
【问题描述】:

我不确定这是否可以提高效率,但我需要获取自 unix/epoch 时间戳以来经过的天数,其中时间本身不是因素,只有日期比较现在。

Example 1: 
Timestamp is : 3rd September 14:35 
Compared to now which is: 4th September 00:35 
Days difference = 1

Example 2:
Timestamp is: 3rd September 23:55
Compared to now which is: 4th September 00:35
Days difference = 1

Example 3:
Timestamp is: 2nd September 02:23
Compared to now which is: 4th September 00:35
Days difference = 2

为此,我有以下代码:

String epoch = "1599134401" // the unix/epoch timestamp in seconds
Long epochMillis = Long.valueOf(epoch) * 1000;
Date epochDateObj = new Date(epochMillis);
Calendar tsCal = Calendar.getInstance();
tsCal.setTime(epochDateObj);
tsCal.set(Calendar.HOUR_OF_DAY, 0);
tsCal.set(Calendar.MINUTE, 0);
tsCal.set(Calendar.SECOND, 0);
tsCal.set(Calendar.MILLISECOND, 0);

Calendar today = Calendar.getInstance();
today.set(Calendar.HOUR_OF_DAY, 0);
today.set(Calendar.MINUTE, 0);
today.set(Calendar.SECOND, 0);
today.set(Calendar.MILLISECOND, 0);

long diffInMillies = Math.abs(today.getTime().getTime() - tsCal.getTime().getTime());
long diff = TimeUnit.DAYS.convert(diffInMillies, TimeUnit.MILLISECONDS);
if(diff > 1) {
    return diff + " days";
} else {
    return diff + " day";
}

上面的代码有效,但对我来说,对于这样一个相当小的东西来说,它似乎相当复杂。

有什么优化建议吗?也许有一些我不知道的功能。它是一个使用相当旧的 SDK(回到 Android 4.1)的 Android 应用程序。

【问题讨论】:

  • 我建议你不要使用DateCalendar。这些课程设计不佳且早已过时。而是使用InstantLocalDateZoneIdChronoUnit,均来自java.time, the modern Java date and time API
  • 您要计算哪个时区?首先,Unix 纪元在某些时区是 1969 年 12 月 31 日,在其他时区是 1970 年 1 月 1 日。

标签: java android date


【解决方案1】:

从根本上说,日子是一个人类概念。它们涉及政治、观点、混乱、时区、时代、时代和其他非常多毛的概念。 java.util.Date 在 hades 中没有滚雪球的机会来做正确的事。日历也没有。

您唯一的希望是一个合适的 API,例如 java.time

此外,您需要清理您的问题。你问的是不可能的;你把枪比作奶奶。 epoch-time 从根本上说是一个“计算机”概念——它仅指时间上的时刻,它不知道你问的是何时、何地、谁、哪个政党等。这是一个问题,因为没有任何这些信息,不可能知道今天是哪一天。秒或多或少是普遍的,但天不是。一天可以是 23 小时或 25 小时,或 23 小时 59 分 59 秒,或 24 小时 1 秒 - 有时会跳过一整天,等等。在不知道您问谁以及使用哪个时区(有时是政治实体!)作为上下文的情况下,“一天有多长”是无法回答的。

所以,假设您要问亚利桑那州的某个人。然后,答案将很大程度上取决于您在亚利桑那州的哪个地方以及您问谁:您需要(可能)知道您碰巧问的那个人是否在 1970 年以及在“目标时间。这取决于您是否询问您何时在亚利桑那州的美国原住民预订中,和/或您询问的人是否坚持 NAR 区域。所以我为什么提到政治的事情,为什么你想要的完全不可能。

java.time 来拯救它实际上可以代表疯狂的混乱!

Instant 代表一个时刻。它在内部存储为 epoch-millis,不能 告诉您那个时刻的日、月、年、时代、小时等。那是因为……嗯,那是因为现实就是这样运作的。如果我现在打响指,然后我问某人“现在几点了”,这取决于我在哪里,我要问的人在哪里,以及他们归属于哪个政党,所以这是不可能的。但是,您将 ZoneInstant 结合起来,现在我们正在取得进展。

LocalDateTime 表示人类所说的时间:年/月/日 + 小时/分钟/秒。出于同样的原因,不可能反过来将其转换为纪元。出于同样的原因,如果您将此与Zone 结合使用,门开始打开。

ZonedDateTime 试图弥合差距:它代表了人类所说的时间,但我们在说出它的人的位置(和政治背景)中编码。您可以将其存储为 LocalDateTime + TimeZone 或 Instant+TimeZone(当然,您不需要知道它是如何实现的)。当然,您可以从 ZDT 转移到 Instant 或 LocalDateTime,这可以回答很多问题。

让我们尝试解决您的问题:

String epoch = "1599134401"; // the unix/epoch timestamp in seconds
String where = "Europe/Berlin"; // what you want is impossible without this!!

Instant instant = Instant.ofEpochSecond(Long.valueOf(epoch));
ZonedDateTime target = instant.atZone(ZoneId.of(where));
ZonedDateTime today = ZonedDateTime.now(where);

long days = ChronoUnit.DAYS.between(target, today);
System.out.println(days);

作为一般规则,如果您开始认真计算约会日期,那您就搞砸了,而且不会奏效。当然,并不是说您的测试会抓住它;当时钟倒退或前进或某个政党在恰好结束夏令时前 5 天决定时,它会变成猿,或者客户端在一个地方而你的服务器在另一个地方等等 - 所有测试很少捕获的东西.

正确使用 java.time 通常意味着您不需要进行太多计算,所以它在这里,幸运的是。

【讨论】:

    【解决方案2】:

    rzwitserloot 已经提供了一个非常棒且有见地的答案,我强烈推荐它。作为一个小补充,我想给你我的代码。当然,仍然使用 java.time,现代 Java 日期和时间 API。

        ZoneId zone = ZoneId.of("Europe/Tirane");
        DateTimeFormatter epochSecondFormatter = new DateTimeFormatterBuilder()
                .appendValue(ChronoField.INSTANT_SECONDS)
                .toFormatter();
        
        String epoch = "1599134401"; // the unix/epoch timestamp in seconds
        Instant then = epochSecondFormatter.parse(epoch, Instant::from);
        LocalDate thatDay = then.atZone(zone).toLocalDate();
        
        LocalDate today = LocalDate.now(zone);
        
        long diff = ChronoUnit.DAYS.between(thatDay, today);
        diff = Math.abs(diff);
        if (diff == 1) {
            System.out.println("" + diff + " day");
        } else {
            System.out.println("" + diff + " days");
        }
    

    刚才运行代码的时候,输出是:

    1 天

    由于您想忽略一天中的时间,LocalDate 是用于日期的正确类。 LocalDate 是一个日期,有时间,没有时区。

    在英语中(虽然不是以英语为母语),我更喜欢说“0 天”,而不是“0 天”。所以我改变了你在单数和复数之间选择的条件。

    您的代码有效吗?

    您的代码在极端情况下给出了不准确的结果。 TimeUnit 通常是时间单位转换的一个很好的枚举,但它假设一天总是 24 小时,但情况并非总是如此,正如 rzwitserloot 解释的那样。该答案和这个答案的 java.time 代码正确地考虑了夏令时 (DST) 和其他时间异常的转换。

    问题:java.time 可以在 Android 4.1 上运行吗?

    java.time 在较旧和较新的 Android 设备上都能很好地工作。它只需要至少 Java 6

    • 在 Java 8 及更高版本以及更新的 Android 设备(从 API 级别 26 起)中,现代 API 是内置的。
    • 在非 Android Java 6 和 7 中,获取 ThreeTen Backport,这是现代类的后向端口(对于 JSR 310,ThreeTen;请参阅底部的链接)。
    • 在较旧的 Android 上,请使用脱糖或 Android 版本的 ThreeTen Backport。它被称为 ThreeTenABP。在后一种情况下,请确保从 org.threeten.bp 导入日期和时间类以及子包。

    链接

    【讨论】:

      【解决方案3】:

      如果我们不想将 ThreeTenABP 库添加到我们的项目中,我们需要在 UTC 中标准化为一个没有时间的日期,以防止像夏令时这样的事情扭曲结果。

      为此,一个辅助方法是合适的:

      static long toDateUtcMillis(Date time) {
          // Get year/month/day according to default time zone
          Calendar cal = Calendar.getInstance();
          cal.setTime(time);
          int year = cal.get(Calendar.YEAR);
          int month = cal.get(Calendar.MONTH);
          int day = cal.get(Calendar.DAY_OF_MONTH);
          
          // Set year/month/day in UTC
          cal.setTimeZone(TimeZone.getTimeZone("UTC"));
          cal.clear();
          cal.set(year, month, day);
          return cal.getTimeInMillis();
      }
      

      我们现在可以轻松计算天数。在下面,如果日期是相反的,我们将返回负值。如果不需要,请添加对 Math.abs() 的调用。

      static int daysBetween(Date date1, Date date2) {
          long dateMillis1 = toDateUtcMillis(date1);
          long dateMillis2 = toDateUtcMillis(date2);
          return (int) TimeUnit.MILLISECONDS.toDays(dateMillis2 - dateMillis1);
      }
      

      测试

      public static void main(String[] args) throws Exception {
          test("3 September 2020 14:35", "4 September 2020 00:35");
          test("3 September 2020 23:55", "4 September 2020 00:35");
          test("2 September 2020 02:23", "4 September 2020 00:35");
      }
      
      static void test(String date1, String date2) throws ParseException {
          // Parse the date strings in default time zone
          SimpleDateFormat format = new SimpleDateFormat("d MMMM yyyy HH:mm", Locale.US);
          int days = daysBetween(format.parse(date1), format.parse(date2));
          System.out.println("Timestamp is: " + date1);
          System.out.println("Compared to: " + date2);
          System.out.println("Days difference = " + days);
          System.out.println();
      }
      

      输出

      Timestamp is: 3 September 2020 14:35
      Compared to: 4 September 2020 00:35
      Days difference = 1
      
      Timestamp is: 3 September 2020 23:55
      Compared to: 4 September 2020 00:35
      Days difference = 1
      
      Timestamp is: 2 September 2020 02:23
      Compared to: 4 September 2020 00:35
      Days difference = 2
      
      

      【讨论】:

      • 我们需要标准化为 UTC 中的无时间日期 — 是的,如果我们想以 UTC 计算天数。
      • @OleV.V.天就是天,除了有时 DST 使天长为 23 或 25 小时,而不是常见的 24 小时,所以我们需要一个没有 DST 的时区,以便毫秒计算不会出错。 UTC就是这样一个时区。它与 UTC 无关,都是关于避免 DST 的。如果您查看代码,它会获取本地日期并从中制作 UTC 日期。它没有进行时区转换,因此我们不是“以 UTC 计算天数”,我们正在计算无 DST 时区的 24 小时周期数。
      • 在我看来,您正在调整对代码的要求,而我们应该以相反的方式来做……
      • @OleV.V.我为这个问题编写了代码,它是关于比较两个“时间戳,其中时间本身不是一个因素,只比较日期”。例如。我在东部时区,2020-10-31T00:00 和 2020-11-01T23:59 之间的天数是 1 天,因为 10 月 31 日和 11 月之间有一天1,即使两个时间戳之间有 48 小时 59 分钟,即超过 2 天(以毫秒计),因为 DST 于 11 月 1 日星期日凌晨 2:00 结束。
      • Daniel,在 Andreas 上次评论中提到的示例中,您希望得到哪个结果,1 天还是 2 天?
      猜你喜欢
      • 2018-01-11
      • 2019-10-30
      • 1970-01-01
      • 1970-01-01
      • 2022-07-08
      • 1970-01-01
      • 2011-04-30
      • 2011-09-03
      • 2017-12-07
      相关资源
      最近更新 更多