Instant toString 前置加

Instant toString prepends plus

我们的数据存储中有记录具有有效和到期时间。此信息使用 Instant.

的字符串表示形式存储

有些记录永远不会过期。但是由于到期日期的值是强制性的,我们决定存储 Instant.MAX.

的字符串表示形式

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

现在的问题是 + 被添加到 Instant.MAX 的字符串表示形式之前。由于 ASCII('+') < ASCII(digit).

,这导致条件 e < Ei 失败

我写了一段代码来了解 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

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

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

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

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


但是,不要为开放式日期范围设置 "max" 值,而是使用空值,即 没有 "max" 值.

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

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

此外,在这种情况下,您可能缺少 =。在 Java 中,范围通常是低包含、高排除(例如参见 [​​=21=] 等),因此使用:

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

你的问题 'why?' 的答案隐藏在 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

大多数日期格式化程序的默认行为是在年份超过 4 位时在其前面加上一个加号。这适用于解析和格式化。参见示例 Year.parse() and the Year section here。该格式几乎融入了 DateTimeFormatter.ISO_INSTANT(请参阅@AndrewLygin 的回答),因此如果您想更改它,则必须将您的瞬间转换为 ZonedDateTime(因为瞬间没有自然字段)并使用自定义格式化程序:

DateTimeFormatter formatter = new DateTimeFormatterBuilder()
        .parseCaseInsensitive()
        .appendValue(ChronoField.YEAR, 4, 10, SignStyle.NORMAL)
        .appendLiteral('-')
        .appendValue(ChronoField.MONTH_OF_YEAR, 2)
        .appendLiteral('-')
        .appendValue(ChronoField.DAY_OF_MONTH, 2)
        .appendLiteral('T')
        .append(DateTimeFormatter.ISO_OFFSET_TIME)
        .toFormatter();

String instant = formatter
        .format(Instant.ofEpochMilli(e).atOffset(ZoneOffset.UTC));

这里的关键是 SignStyle.NORMAL,而不是默认的 SignStyle.EXCEEDS_PAD,如果年份超过 4 位填充,默认的 SignStyle.EXCEEDS_PAD 会在前面加上 +