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));