Вероятное объяснение того, как это может произойти, заключается в том, что настройка часового пояса сервера не учитывает летнее время (DST). В качестве примера я беру часовой пояс Америки / Асунсьона. Асунсьон является крупнейшим городом и столицей Парагвая. Парагвай по смещению -04: 00 в стандартное время, но в -03: 00 летом. Летнее время продолжало действовать 8 марта и будет действовать до 24 марта.
Сначала демонстрация того, что идет не так:
System.setProperty("user.timezone", ZoneId.ofOffset("GMT", ZoneOffset.ofHours(-4)).getId());
// Send a timestamp from America/Asuncion time zone
ZoneId zone = ZoneId.of("America/Asuncion");
// Create the timestamp as March 8 at 00:00:00
Instant testTime
= ZonedDateTime.of(2019, 3, 8, 0, 0, 0, 0, zone).toInstant();
Timestamp ts = Timestamp.from(testTime);
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(baos)) {
objectOutputStream.writeObject(ts);
}
byte[] ba = baos.toByteArray();
try (ObjectInputStream is = new ObjectInputStream(new ByteArrayInputStream(ba))) {
java.util.Date obj = (java.util.Date) is.readObject();
System.out.println(obj);
}
}
Для этой простой демонстрации нам не нужен отдельный клиентский и серверный код. Я создаю Timestamp
объект с известной датой и временем в рассматриваемом часовом поясе, записываю его в ObjectOutputStream
и считываю его обратно из ObjectInputStream
, используя те же байты. Я получаю Timestamp
, представляющий в тот же момент времени . Чтобы продемонстрировать, что происходит, когда приемник печатает его, я установил часовой пояс по умолчанию прямо вверху. Я использую постоянное смещение -04:00
, чтобы продемонстрировать, что происходит, когда часовой пояс сервера не учитывает летнее время. Это печатает:
2019-03-07 23: 00: 00.0
Так что вы получили тот же результат. Это связано с тем, что - до смешного - Timestamp.toString
использует настройку часового пояса JVM для генерации строки.
Как исправить?
Когда мы знаем, что Timestamp
был отправлен из часового пояса Америки / Асунсьона, мы можем явно использовать этот часовой пояс на сервере:
java.util.Date obj = (java.util.Date) is.readObject();
ZonedDateTime timeAsSent = obj.toInstant().atZone(zone);
System.out.println(timeAsSent);
2019-03-08T00: 00-03: 00 [Америка / Асунсьон]
Теперь время согласуется с тем, что было записано в поток вывода объекта.
Лучше исправить
Если есть возможность, полностью избегайте старых классов Date
и Timestamp
. Эти классы плохо разработаны и играют с вами такие шутки. Они также давно устарели. Вместо этого запишите ZonedDateTime
или Instant
в поток объектов на стороне клиента и, конечно, прочитайте тот же тип на стороне сервера. Тогда не будет места для сюрпризов.