2016-08-23 56 views
15

我们在我们的数据存储中有有效的&到期时间的记录。该信息使用Instant的字符串表示形式存储。Instant toString prepends plus

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

到目前为止这么好。我们有搜索用例来返回在输入时间范围[s,e]内有效的所有记录。我们查询数据存储并返回满足条件的所有这些记录[Si, Ei]Si < s && e < 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 

我已经截断+的选项在坚持数据存储之前。我更感兴趣的是为什么会发生这种情况,我们如何明确地避免它?

+6

领先的加号是超出了9999年的所有有效的ISO-8601格式恕我直言,这是一个有点可疑的使用有限值(Instant.MAX)实现无限边界还是要靠字符串比较。基于即时对象比较,您能否仅从数据库中检索实例并在您的应用服务器层中查询?或者你可以在数据库/商店中存储经过的秒数(如数字)。 –

+0

谢谢你指出。比较应该在数据库本身进行,唯一允许的比较是整数和字符串。我已经使用了字符串表示而不是纪元秒,以便在手动查询数据时它看起来更具可读性。 – nitish712

+0

这是可悲的是,ISO委员会选择+作为前缀年超越9999的ISO格式的一个很好的特性是标准的ASCII/Unicode字符串排序,时间排序相吻合,这是由符号+侵犯。任何一封信都会保留在9999之后。再加上一些调整,将它延长到0000之前的日期看起来也是可能的。 – chi

回答

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

+0

感谢您指出确切的代码! – nitish712

2

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

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

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


然而,而不是设置一个开放式的日期范围内的“最大”值,使用空值,即有没有“最大”值。

你会当更改Si < s && e < Ei条件:

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

此外,你可能缺少这一条件的=。在Java中,范围通常较低,包容,上独占(例如见substring()subList()等),所以用这个:

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

+0

有问题的数据存储区是DynamoDB,需要有一个非空值才能使范围查询正常工作。 – nitish712

2

大多数日期格式化的默认行为是前面加上一个加号到一年,如果是超过4个位数更长。这适用于解析和格式。例如见Year.parse()部分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位填充这会在前面加上+

+0

一个很大的错误,这段代码不适用于'Instant.MAX'。我自己测试过,也尝试过。 Reason对于支持'Instant'的值范围来说是奇怪的选择,超过了''LocalDate''类支持的范围。否则一个好主意,选择一个适当的标志风格。 –