即时 toString 预置加

2022-09-03 07:03:30

我们的数据存储中有具有有效和到期时间的记录。此信息使用 的字符串表示形式存储。Instant

有些记录永远不会过期。但是,由于到期日期的值是必需的,因此我们决定存储 的字符串表示形式。Instant.MAX

目前为止,一切都好。我们有搜索用例来返回输入时间范围内处于活动状态的所有记录。我们查询数据存储并返回满足条件的所有此类记录 注意,此处正在比较字符串表示。[s,e][Si, Ei]Si < s && e < Ei

现在的问题是,它被附加到 的字符串表示形式之前。这是由于 不符合条件,因为 。+Instant.MAXe < EiASCII('+') < ASCII(digit)

我已经写了一段代码来知道之后,开始被预先附加:second+

Long e = Instant.now().getEpochSecond()*1000;
for (int i = 0; i < 5; i++) {
    System.out.println(e + "->" + Instant.ofEpochMilli(e));
    e *= 10;
}

哪些打印:

1471925168000->2016-08-23T04:06:08Z
14719251680000->2436-06-07T17:01:20Z
147192516800000->6634-05-07T02:13:20Z
1471925168000000->+48613-06-14T22:13:20Z
14719251680000000->+468404-07-08T06:13:20Z

我可以选择在数据存储中保留之前截断。我更感兴趣的是为什么会发生这种情况,以及我们如何明确地避免它?+


答案 1

你的问题“为什么?”的答案隐藏在实现中(为了简洁起见,我省略了不相关的代码):DateTimeFormatterBuilder.InstantPrinterParser.format()

// use INSTANT_SECONDS, thus this code is not bound by Instant.MAX
Long inSec = context.getValue(INSTANT_SECONDS);
if (inSec >= -SECONDS_0000_TO_1970) {
    // current era
    long zeroSecs = inSec - SECONDS_PER_10000_YEARS + SECONDS_0000_TO_1970;
    long hi = Math.floorDiv(zeroSecs, SECONDS_PER_10000_YEARS) + 1;
    long lo = Math.floorMod(zeroSecs, SECONDS_PER_10000_YEARS);
    LocalDateTime ldt = LocalDateTime.ofEpochSecond(lo - SECONDS_0000_TO_1970, 0, ZoneOffset.UTC);
    if (hi > 0) {
         buf.append('+').append(hi);
    }
    buf.append(ldt);
}

如您所见,它检查10000年周期的边界,如果该值超过其中至少一个,它将添加和此类周期的数量。+

因此,为了防止此类行为,请将最大日期保持在纪元范围内,不要使用 。Instant.MAX


答案 2

如果希望能够支持字符串值的排序,则需要确保永远不会超过 到 的年份范围。00009999

这意味着替换为 ,这也是大多数 RDBMS 可以处理的最大日期。Instant.MAXInstant.parse("9999-12-31T23:59:59Z")

要跳过解析步骤,请使用 。Instant.ofEpochSecond(253402300799L)


但是,不要为开放式日期范围设置“最大值”值,而是使用空值,即没有“最大值”。

当您将条件更改为:Si < s && e < Ei

Si < s && (Ei == null || e < Ei)

此外,在这种情况下,您可能会错过。在Java中,范围通常是下层非独占的,上层独占的(例如,参见substring()subList()等),所以请使用这个:=

Si <= s && (Ei == null || e < Ei)