即时toString prepends加上

时间:2016-08-23 04:08:53

标签: java java-8 tostring java-time

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

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

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

现在问题是+被添加到Instant.MAX的字符串表示之前。从e < Ei开始,条件ASCII('+') < 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

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

3 个答案:

答案 0 :(得分:10)

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

答案 1 :(得分:2)

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

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

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

然而,不是设置&#34; max&#34;开放日期范围的值,使用空值,即否&#34; max&#34;值。

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

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

此外,您可能在这种情况下错过=。在Java中,范围通常是低级别的,高级别的(例如,请参阅substring()subList()等),因此请使用:

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

答案 2 :(得分:2)

大多数日期格式化程序的默认行为是,如果长度超过4位,则将加号添加到年份。这适用于解析和格式化。例如,请参阅Year.parse()部分here。该格式几乎已融入DateTimeFormatter.ISO_INSTANT(请参阅@ AndrewLygin&#39;答案),因此如果您想要更改它,则必须将您的即时转换为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位数填充,则会+。{/ p>