两个日期之间的时间偏移
Time offset between two dates
我有两个时间戳和一个本地化日期,用于查找时区偏移并将其添加到这些日期。如何更简单地计算日期之间的时间偏移量?我的方法不适用于负值(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 = 1626998400000
和 endTs = 1627005600000
)。
我在 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()
相反,它并不比您的代码简单,但它修复了您遇到的几个问题。
免责声明:我没有你的 Pair
class,我也不会写 运行 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
)。
考虑让您的方法 return 成为 Pair<OffsetDateTime, OffsetDateTime>
,而不是 Pair<String, String>
。字符串用于呈现给用户,有时用于数据交换。在您的程序中,您应该使用正确的日期时间对象,而不是字符串。
正如我在评论中所说,新年不会在所有时区同时发生。因此,您从 dateUtcStart.year.toLong()
获得的年份不必是来自不同时区的字符串中假定的年份。我的代码考虑到了这一点。
不涉及Timestamp
class。它设计不佳且早已过时。它给你的只是一个额外的转换,因此更加复杂。
您的基本问题是使用 LocalTime
持续时间可能是正数或负数。 LocalTime
是一天中的某个时间,而不是一段时间。您自己的解决方案已经解决了这个问题。
这是错误的:
val startDate = dateUtcStart.toString() + tzString
首先,您不应该对日期和时间数学运算使用字符串操作。您正在使用的来自 java.time 的 classes 可以更轻松地生成您需要的字符串,并且出错的风险更小。其次,您确实在此处遇到错误:您将计算出的偏移量附加到 UTC 格式的字符串,即假定偏移量为 0(或 Z
)。这就是为什么您实际上能够产生单元测试预期的错误结果的原因。
我有两个时间戳和一个本地化日期,用于查找时区偏移并将其添加到这些日期。如何更简单地计算日期之间的时间偏移量?我的方法不适用于负值(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 = 1626998400000
和 endTs = 1627005600000
)。
我在 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()
相反,它并不比您的代码简单,但它修复了您遇到的几个问题。
免责声明:我没有你的 Pair
class,我也不会写 运行 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
)。
考虑让您的方法 return 成为 Pair<OffsetDateTime, OffsetDateTime>
,而不是 Pair<String, String>
。字符串用于呈现给用户,有时用于数据交换。在您的程序中,您应该使用正确的日期时间对象,而不是字符串。
正如我在评论中所说,新年不会在所有时区同时发生。因此,您从 dateUtcStart.year.toLong()
获得的年份不必是来自不同时区的字符串中假定的年份。我的代码考虑到了这一点。
不涉及Timestamp
class。它设计不佳且早已过时。它给你的只是一个额外的转换,因此更加复杂。
您的基本问题是使用 LocalTime
持续时间可能是正数或负数。 LocalTime
是一天中的某个时间,而不是一段时间。您自己的解决方案已经解决了这个问题。
这是错误的:
val startDate = dateUtcStart.toString() + tzString
首先,您不应该对日期和时间数学运算使用字符串操作。您正在使用的来自 java.time 的 classes 可以更轻松地生成您需要的字符串,并且出错的风险更小。其次,您确实在此处遇到错误:您将计算出的偏移量附加到 UTC 格式的字符串,即假定偏移量为 0(或 Z
)。这就是为什么您实际上能够产生单元测试预期的错误结果的原因。