Ошибка округления, или currentTimeMills + nanoTime ошибаются?
Есть задача - собирать таймштампы с точностью хотябы до микросекунд (беру нано, так как из-коробки можно использовать System.nanoTime()), не потому что важна точность, а потому что важно чёткое понимание последовательности. Почему взято время? Потому что оно в любом случае используется, и настроено на всех машинах, где выполняется этот код, значит от него можно относительно точно отталкиваться без потерь в производительности.
Извлекать время решил таким образом:
public static Timestamp getCurrentTimestamp() {
Timestamp timestamp = new Timestamp(System.currentTimeMillis());
timestamp.setNanos((int) (System.nanoTime() % 1000000000));
return timestamp;
}
Почему currentTimeMillis? - Потому что java.sql.Timestamp не построить иначе, только через миллисекунды. А так как нужна хоть какая то точность (мне важно чтобы две операции были последовательны, не важно, например, если конечное время будет с погрешностью в 1 милисекунду, важно что если "б" выполнялось после "а", чтобы по времени это чётко было видно), затем добавляю наносекунды.
Далее всё попадает в БД. Где, о чудо, я вижу что единичные операции залогированны в другой последовательности!
Несколько дней ломал голову что происходит, пока не написал юниттест, который всё прояснил, вот пример происходящего:
public static void main(String[] args) {
final int COUNT = 10000000;
List<Timestamp> times = new ArrayList<>(COUNT);
for(int i = 0; i < COUNT; i++) {
times.add(getCurrentTimestamp());
}
Timestamp prev = times.get(0);
for(int i = 1; i< COUNT; i++) {
Timestamp current = times.get(i);
if(current.before(prev)) {
System.out.println("iteration " + i + ", curr: " + current.toInstant() + ", prev: " + prev.toInstant());
}
prev = current;
}
}
public static Timestamp getCurrentTimestamp() {
Timestamp timestamp = new Timestamp(System.currentTimeMillis());
timestamp.setNanos((int) (System.nanoTime() % 1000000000));
return timestamp;
}
Кратко - тест показывает что в определённый момент времени, очередное логирование таймштампа, ВНЕЗАПНО, показывает время в прошлом. Вот результат вывода в консоль:
iteration 6169721, curr: 2023-07-18T12:28:00.103141700Z, prev: 2023-07-18T12:28:00.997348300Z
В идеальном примере - консоль обязана быть чистой. Та же ситуация происходит и в юниттестах, они не проходят на достаточно больших выборках.
Подскажите, как более точно считать время, при этом, не допускать считываний и расчёта времени, после которых очередное извлечение штампа приводят в прошлое?