Мгновенный toString добавляет плюс

У нас есть записи в нашем хранилище данных, которые имеют эффективный и срок действия. Эта информация хранится с использованием строкового представления 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

У меня есть возможность обрезать + перед сохранением в хранилище данных. Меня больше интересует, почему это происходит и как мы можем избежать этого?

Ответы

Ответ 1

Ответ на ваш вопрос "почему?" скрыт в реализации 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

Если вы хотите поддерживать сортировку строкового значения, вам необходимо убедиться, что вы никогда не превышаете диапазон года от 0000 до 9999.

Это означает замену Instant.MAX на Instant.parse("9999-12-31T23:59:59Z"), которая также является максимальной датой, которую могут обрабатывать большинство RDBMS.

Чтобы пропустить шаг синтаксического анализа, используйте Instant.ofEpochSecond(253402300799L).


Однако вместо того, чтобы устанавливать значение "max" для открытого диапазона дат, используйте нулевое значение, т.е. нет значения "max".

Вы бы изменили условие Si < s && e < Ei на:

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

Кроме того, в этом состоянии, вероятно, отсутствует =. В Java диапазоны обычно имеют более низкий уровень, верхний эксклюзив (например, см. substring(), subList() и т.д.), поэтому используйте это:

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

Ответ 3

Поведение по умолчанию для большинства форматов даты - это добавить знак плюса в год, если он длиннее 4 цифр. Это касается как синтаксического анализа, так и форматирования. См. Например Year.parse() и раздел Год . Формат довольно сильно запечен в 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-разрядное дополнение.