【问题标题】:Time offset between two dates两个日期之间的时间偏移
【发布时间】:2021-10-02 14:23:00
【问题描述】:

我有两个时间戳和一个本地化日期,用于查找时区偏移并将其添加到这些日期。如何更简单地计算日期之间的时间偏移?我的方法不适用于负值(if (tsOffset.toSecondOfDay() > 0 总是true)。

fun parseDateTime(startTs: Long, endTs: Long, localizedDateTime: String): Pair<String, String> {
    val dateUtcStart = LocalDateTime.ofInstant(Instant.ofEpochMilli(startTs), ZoneOffset.UTC)
    val dateUtcEnd = LocalDateTime.ofInstant(Instant.ofEpochMilli(endTs), ZoneOffset.UTC)

    val formatter = DateTimeFormatterBuilder()
        .parseCaseInsensitive()
        .parseDefaulting(ChronoField.YEAR_OF_ERA, dateUtcStart.year.toLong())
        .append(DateTimeFormatter.ofPattern("E dd MMM hh:mm a"))
        .toFormatter(Locale.ENGLISH)

    val localDateTime = LocalDateTime.parse(localizedDateTime, formatter)

    val localTs = Timestamp.valueOf(localDateTime).time
    val tsOffset = LocalTime.ofInstant(Instant.ofEpochMilli(localTs - startTs), ZoneOffset.systemDefault())
    val tzString = if (tsOffset.toSecondOfDay() > 0) "+$tsOffset" else tsOffset.toString()

    val startDate = dateUtcStart.toString() + tzString
    val endDate = dateUtcEnd.toString() + tzString

    return Pair(startDate, endDate)
}

@Test
fun parseDateTime() {
    val pair1 = parseDateTime(1626998400000, 1627005600000, "Fri 23 Jul 10:30 am")
    val pair2 = parseDateTime(1626998400000, 1627005600000, "Thu 22 Jul 11:30 pm")

    // pass
    assertEquals("2021-07-23T00:00+10:30", pair1.first)
    assertEquals("2021-07-23T02:00+10:30", pair1.second)
    // fails
    assertEquals("2021-07-23T00:00-00:30", pair2.first)
    assertEquals("2021-07-23T02:00-00:30", pair2.second)
}

我也试过了

val dur = Duration.between(dateUtcStart, localDateTime)

但不确定如何在字符串中转换或正确添加到日期。

这里startTs - 事件时间戳的开始。 endTs - 此事件时间戳的结束。 localizedDateTime 用于显示实际时区(真实城市)的开始时间,而时间戳则以 UTC 显示时间。我需要从 localizedDateTime 中提取这个时区,并将其添加到开始和结束字符串 dateTimes(start = "2021-07-23T00:00+10:30", end = "2021-07-23T02:00+10:30" 对应于 startTs = 1626998400000endTs = 1627005600000)。

【问题讨论】:

  • 我对您的localizedDateTime: String 输入的角色感到困惑。该字符串只是一个日期和一天中的时间,没有时区的上下文或与 UTC 的偏移量。然而,您似乎认为它代表某个时区的某个时刻。您的代码中可能存在一些严重的缺陷。特别是,您对 LocalDateTimeTimestamp 类的使用令人不安。我建议您编辑您的问题,以更清楚地解释您的原始问题/目标,而不是关注您的代码。仔细而准确地指定输入的每个元素代表什么,以及您想要输出什么。
  • localizedDateTime 用于显示实际时区(真实城市)的时间,而时间戳以 UTC 显示时间。所有这些都是第 3 方输入,我无法更改它们。我的问题是:我只有这 3 个日期,并且需要像在测试中一样使用时区偏移来创建开始和结束日期(例如 2021-07-23T00:00+10:30
  • 将澄清作为对您问题的编辑而不是作为评论发布。
  • @Psijic 更新您的问题以描述所有这些混乱在做什么 - 因为您的代码没有帮助,因为它到处都是并且滥用 java.time 类型。你想做什么-? “开始”和“结束”分别代表什么?那个时间代表什么?示例更新:“输入 A 和 B 是 epoch-millis。输入 C 是一个字符串,表示与 epoch-milli A 在同一时刻的时间,在某个未知时区。此方法应派生该时区,然后渲染 epoch-milli A 和 B 在该时区具有硬编码格式。如果不可能,则抛出异常 X。
  • 我认为startTs(等于2021-07-23T00:00:00Z UTC)和localizedDateTime应该表示相同的时间点,我们需要计算UTC抵消这些信息?但是要使结果开始日期和时间相等,它肯定需要是2021-07-23T10:30+10:30,而不是2021-07-23T00:00+10:30。我错过了什么?

标签: java date kotlin timezone


【解决方案1】:

我在Kotlin forum得到了答复:

    val duration = Duration.between(dateUtcStart, localDateTime)
    val offset = ZoneOffset.ofTotalSeconds(duration.seconds.toInt())
    val startDate = dateUtcStart.atOffset(offset).toString()
    val endDate = dateUtcEnd.atOffset(offset).toString()

【讨论】:

    【解决方案2】:

    相反,它并不比您的代码简单,但它解决了您遇到的几个问题。

    免责声明:我没有你的 Pair 课程,我无法编写或运行 Kotlin。因此,我正在从方法内部打印结果字符串,您必须根据自己的目的进行更改。你将不得不手动翻译我的 Java。

    private static void parseDateTime(long startTs, long endTs, String localizedDateTime) {
        // startTs and localizedDateTime are both representations of the event start time.
        // Use this information to obtain the UTC offset of the local time.
        
        Instant startInstant = Instant.ofEpochMilli(startTs);
        OffsetDateTime startUtc = startInstant.atOffset(ZoneOffset.UTC);
        
        // Corner case: the local year may be different from the UTC year if the event is close to New Year.
        // Check whether this is the case. First get the UTC month and the local month.
        Month utcMonth = startUtc.getMonth();
    
        DateTimeFormatter baseSyntaxFormatter = new DateTimeFormatterBuilder()
                .parseCaseInsensitive()
                .append(DateTimeFormatter.ofPattern("E dd MMM hh:mm a"))
                .toFormatter(Locale.ENGLISH);
        Month localMonth = baseSyntaxFormatter.parse(localizedDateTime, Month::from);
        
        int utcYear = startUtc.getYear();
        int localYear;
        if (utcMonth.equals(Month.DECEMBER) && localMonth.equals(Month.JANUARY)) {
            // Local date is in the following year
            localYear = utcYear + 1;
        } else if (utcMonth.equals(Month.JANUARY) && localMonth.equals(Month.DECEMBER)) {
            localYear = utcYear - 1;
        } else {
            localYear = utcYear;
        }
        
        DateTimeFormatter finalFormatter = new DateTimeFormatterBuilder()
                .append(baseSyntaxFormatter)
                .parseDefaulting(ChronoField.YEAR_OF_ERA, localYear)
                .toFormatter(Locale.ENGLISH);
    
        LocalDateTime startLocal = LocalDateTime.parse(localizedDateTime, finalFormatter);
        // Now calculate offset
        Duration durationOfOffset = Duration.between(startUtc.toLocalDateTime(), startLocal);
        ZoneOffset offset = ZoneOffset.ofTotalSeconds(Math.toIntExact(durationOfOffset.getSeconds()));
    
        String startString = startLocal.atOffset(offset).toString();
        
        // Calculate end date and time
        String endString = Instant.ofEpochMilli(endTs)
                .atOffset(offset)
                .toString();
    
        System.out.format("%s - %s%n", startString, endString);
    }
    

    让我们用你的示例数据来试试吧:

        parseDateTime(1_626_998_400_000L, 1_627_005_600_000L, "Fri 23 Jul 10:30 am");
        parseDateTime(1_626_998_400_000L, 1_627_005_600_000L, "Thu 22 Jul 11:30 pm");
    

    输出:

    2021-07-23T10:30+10:30 - 2021-07-23T12:30+10:30
    2021-07-22T23:30-00:30 - 2021-07-23T01:30-00:30
    

    您注意到本地开始时间现在是 10:30 和 23:30,与您的输入字符串一样,我相信这可以纠正您遇到的错误。

    让我们也尝试一个连接新年的例子:

        Instant start = Instant.parse("2021-01-01T00:00:00Z");
        parseDateTime(start.toEpochMilli(),
                start.plus(2, ChronoUnit.HOURS).toEpochMilli(),
                "Thu 31 Dec 10:30 pm");
    
    2020-12-31T22:30-01:30 - 2021-01-01T00:30-01:30
    

    我使用了与您在自己的答案中提供的基本相同的计算偏移量的方法。

    您的代码存在问题

    首先,正如我所说,您的单元测试断言的时间与您输入的时间不一致。对于所需的输出,您采用了一天中的 UTC 时间并结合了本地偏移量,这给出了不同的时间点。 UTC 偏移量始终与该偏移量处的时间一起使用(对于一天中的 UTC 时间,将使用偏移量 Z+00:00)。

    考虑让您的方法返回Pair&lt;OffsetDateTime, OffsetDateTime&gt;,而不是Pair&lt;String, String&gt;。字符串用于呈现给用户,有时用于数据交换。在您的程序中,您应该使用正确的日期时间对象,而不是字符串。

    正如我在 cmets 中所说,新年不会在所有时区同时发生。因此,您从dateUtcStart.year.toLong() 获得的年份不必是来自不同时区的字符串中假定的年份。我的代码考虑到了这一点。

    不要涉及Timestamp 类。它的设计很糟糕而且已经过时了。它给你的只是额外的转换,因此更加复杂。

    您的基本问题是使用LocalTime 的持续时间可能是正数或负数。 LocalTime 是一天中的某个时间,而不是一段时间。您自己的解决方案已经解决了这个问题。

    这是错误的:

        val startDate = dateUtcStart.toString() + tzString
    

    首先,您不应该对日期和时间数学使用字符串操作。您正在使用的 java.time 中的类可以更轻松地生成您需要的字符串,而且出错的风险也更小。其次,您确实在这里遇到了一个错误:您将计算出的偏移量附加到 UTC 格式的字符串中,即假定偏移量为 0(或 Z)。这就是为什么您实际上能够产生单元测试预期的错误结果的原因。

    【讨论】:

      猜你喜欢
      • 2011-05-18
      • 2013-11-02
      • 2013-09-13
      • 1970-01-01
      • 2012-11-29
      • 1970-01-01
      • 1970-01-01
      • 2015-08-26
      • 2016-10-29
      相关资源
      最近更新 更多