Java Instant toString prepends plus

Java Instant toString prepends plus,java,java-8,tostring,java-time,Java,Java 8,Tostring,Java Time,我们的数据存储中有有效和过期时间的记录。此信息使用Instant的字符串表示形式存储 有些记录永远不会过期。但由于到期日期的值是强制性的,我们决定存储Instant.MAX的字符串表示形式 到目前为止还不错。我们使用搜索用例返回输入时间范围内的所有活动记录[s,e]。我们查询数据存储并返回满足条件Si

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

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

到目前为止还不错。我们使用搜索用例返回输入时间范围内的所有活动记录
[s,e]
。我们查询数据存储并返回满足条件
Si
的所有此类记录
[Si,Ei]
。注意,这里比较的是字符串表示

现在的问题是,
+
Instant.MAX
的字符串表示前面加了前缀。由于
ASCII(“+”)
,因此这是不符合条件的
e

我已经写了一段代码,知道在
秒之后,
+
开始被预先加上:

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

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

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

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

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


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

当您的
Si
条件更改为:

Si

此外,在这种情况下,您可能会缺少一个
=
。在Java中,范围通常是较低的包含范围,较高的独占范围(例如,请参阅等),因此请使用以下选项:


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

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


因此,为了防止这种行为,请将最长日期保持在历元范围内,不要使用
Instant.MAX

大多数日期格式化程序的默认行为是在一年前加上一个加号(如果它超过4位)。这适用于解析和格式化。请参见示例和年份部分。格式基本上是烘焙到
DateTimeFormatter.ISO_INSTANT
(请参阅@AndrewLygin的答案),因此如果您想更改它,您必须将您的INSTANT转换为
ZonedDateTime
(因为INSTANT没有自然字段)并使用自定义格式设置程序:

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.Overses _PAD
,如果年份超过4位填充,它将作为
+
的前缀。

前导加号对于9999年之后的所有年份都是有效的ISO-8601格式。IMHO使用有限值(Instant.MAX)实现无限边界或依赖字符串比较有点可疑。基于即时对象比较,您能否不在应用程序服务器层中从数据库和查询中检索即时对象?或者您可以将经过的秒数(作为数字)存储在db/store中。感谢您指出这一点。比较应该在数据库本身中进行,只允许对整数和字符串进行比较。我使用了字符串表示而不是历元秒,这样在手动查询数据时看起来更可读。令人遗憾的是,国际标准化组织委员会在9999年之后选择+作为前缀。ISO格式的一个很好的特性是,标准ASCII/unicode字符串顺序和时间顺序重合,这被+符号所违反。任何一封信都会把它保存到9999年以后。再做一些调整,也可以将其扩展到0000之前的日期。所讨论的数据存储是DynamoDB,需要有一个非空值才能使范围查询工作。一个大缺陷是,此代码对于
Instant.MAX
不起作用。我自己也试过了,也试一下。原因是,
Instant
支持的值范围的选择很奇怪,它在年份上超过了像
LocalDate
这样的类的支持范围。否则,最好选择合适的标志样式。
// 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);
}
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));