代码之家  ›  专栏  ›  技术社区  ›  nitish712

Instant toString前缀加

  •  16
  • nitish712  · 技术社区  · 8 年前

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

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

    [s,e] 。我们查询数据存储并返回所有此类记录 [Si, Ei] 满足条件的 Si < s && e < Ei 请注意,这里比较的是字符串表示。

    现在的问题是 + 正在添加到的字符串表示形式之前 即时最大值 。这不符合条件 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 回复  |  直到 8 年前
        1
  •  11
  •   Mutantoe Andrew Lygin    8 年前

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

        2
  •  2
  •   Andreas LppEdd    8 年前

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

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

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


    但是,与其为开放式日期范围设置“最大”值,不如使用空值,即 没有“最大”值。

    当你改变你的 Si < s && e < Ei 条件为:

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

    = 在这种情况下。在Java中,范围通常是低包容性的,高排他性的(例如,请参见 substring() , subList() 等),因此使用此选项:

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

        3
  •  2
  •   shmosel    8 年前

    大多数日期格式化程序的默认行为是,如果年份超过4位数字,则在年份前面加一个加号。这适用于解析和格式化。参见示例 Year.parse() 部分 here 。格式已经非常成熟 DateTimeFormatter.ISO_INSTANT 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位数的填充。