Почему вычитание миллисекунд эпохи (1927 год) даёт странный результат?
Проблема: исторические изменения часовых поясов
Вы столкнулись с классическим эффектом, связанным с историческими корректировками часовых поясов. Дело не в баге Java, а в реальном историческом событии: в 1927 году в Шанхае (и вообще в Китае) произошёл переход на другое время.
Что произошло в 1927 году?
До 1 января 1928 года в Шанхае использовалось местное среднее солнечное время (LMT). 31 декабря 1927 года в 23:54:08 по местному времени был произведён перевод часов на 5 минут 52 секунды назад, чтобы синхронизировать время с поясом UTC+8. Это означает, что между 23:54:07 и 23:54:08 фактически прошло не 1 секунда, а 353 миллисекунды (потому что часы "откатились" на 5 минут 52 секунды, и момент 23:54:08 наступил почти сразу после 23:54:07).
Вот как это выглядит на временной шкале (UTC):
23:54:07 LMT (UTC+8:05:52) → 15:48:15 UTC
23:54:08 CST (UTC+8:00) → 15:54:08 UTC
Разница: 15:54:08 - 15:48:15 = 5 минут 53 секунды = 353 секунды? Нет!
На самом деле разница в UTC составляет 5 минут 53 секунды, но в местном времени между этими двумя моментами прошло всего 1 секунда. Java правильно вычисляет разницу в миллисекундах эпохи (UTC), поэтому date2.getTime() - date1.getTime() даёт реальную разницу по UTC — 353 000 миллисекунд (353 секунды).
Почему вы ожидали 1000 мс?
Вы ожидали, что разница в местном времени (1 секунда) будет равна разнице в UTC (1000 мс). Но из-за сдвига часового пояса это не так. Ваш код преобразует строки в Date, используя часовой пояс Asia/Shanghai, который учитывает исторические изменения.
Как исправить?
Если вам нужна разница именно в местном времени (без учёта исторических сдвигов), используйте java.time пакет из Java 8:
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
public class FixedDate {
public static void main(String[] args) {
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime ldt1 = LocalDateTime.parse("1927-12-31 23:54:07", dtf);
LocalDateTime ldt2 = LocalDateTime.parse("1927-12-31 23:54:08", dtf);
long diff = ldt2.toEpochSecond(ZoneOffset.UTC) - ldt1.toEpochSecond(ZoneOffset.UTC);
System.out.println("Разница (в UTC): " + diff + " секунд");
// Теперь будет 1 секунда, но это уже не реальное историческое время
}
}
Но помните: исторические данные важны. Если вы работаете с реальными историческими событиями, то правильный ответ — 353 секунды. Если вам нужна "наивная" разница — используйте LocalDateTime без часового пояса.
Вывод
- Не все дни длятся 24 часа — из-за перевода часов некоторые дни короче или длиннее.
- Java правильно обрабатывает исторические данные — база данных часовых поясов (IANA TZDB) содержит все известные изменения.
- Используйте java.time — старые классы
DateиSimpleDateFormatустарели и могут давать неожиданные результаты.
Альтернативный подход: использование Instant и Duration
Если вам нужно получить именно разницу в миллисекундах между двумя моментами времени с учётом часового пояса, но без сюрпризов от исторических данных, можно использовать java.time.Instant и java.time.Duration:
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
public class Alternative {
public static void main(String[] args) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
ZoneId shanghai = ZoneId.of("Asia/Shanghai");
ZonedDateTime zdt1 = ZonedDateTime.of(
LocalDateTime.parse("1927-12-31 23:54:07", formatter), shanghai);
ZonedDateTime zdt2 = ZonedDateTime.of(
LocalDateTime.parse("1927-12-31 23:54:08", formatter), shanghai);
Instant instant1 = zdt1.toInstant();
Instant instant2 = zdt2.toInstant();
long diffMillis = Duration.between(instant1, instant2).toMillis();
System.out.println("Реальная разница: " + diffMillis + " мс");
// Выведет 353000 (из-за исторического сдвига)
}
}
Этот подход явно показывает, что разница во времени между двумя "соседними" секундами в 1927 году в Шанхае составляет 353 секунды (5 минут 53 секунды) по UTC. Если вы хотите игнорировать исторические данные, просто используйте LocalDateTime с фиксированным смещением, как показано в первом ответе.
Ответов пока нет.
Войдите чтобы оставить ответ
Блоги