Мгновенный 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-разрядное дополнение.