Как правильно получать записи за последние 30 60 90 дней в Java без ошибок даты и времени

Устали от багов с датами в Java? Узнайте, как безопасно получить данные за 90 дней и избежать подводных камней. Простой гайд для разработчиков!

Работа с датой и временем — одна из самых распространенных, но в то же время сложных задач в программировании. Очень часто разработчикам требуется получить данные за определенный период: например, выбрать записи из базы данных за последние 30, 60 или 90 дней. В этой статье мы подробно разберем, как правильно и безопасно решать эту задачу в Java, избегая классических подводных камней.

Почему не стоит использовать вычитание миллисекунд?

На первый взгляд, самый простой способ получить дату, отстоящую от текущей на 30 дней — это взять текущее время в миллисекундах и вычесть из него количество миллисекунд в 30 сутках. Код выглядит примерно так:

Пример (не рекомендованный):

  1. long millis = System.currentTimeMillis; // Текущее время в миллисекундах
  2. long xDaysInMillis = 30 * 24 * 60 * 60 * 1000; // Перевести дни в миллисекунды
  3. Date newDate = new Date(millis ‒ xDaysInMillis); // Результат может быть неточным

Может показаться, что это простое и элегантное решение, однако оно может быть опасным. Такой подход не учитывает особенности високосных секунд, переходы на летнее/зимнее время (Daylight Saving Time) и другие календарные аномалии. В результате вы можете получить дату, которая будет сдвинута на час или даже на целые сутки, что критично при работе с бизнес-логикой и базами данных.

Устаревшие подходы: использование класса Calendar

До выхода Java 8 разработчики активно использовали класс java.util.Calendar для вычисления дат. Вы можете использовать функцию добавления (или вычитания) класса Calendar следующим образом:

Пример с Calendar:

Calendar calendar = Calendar.getInstance;
calendar.add(Calendar.DAY_OF_MONTH, -30);
Date date30DaysAgo = calendar.getTime;

Этот метод работает корректно и учитывает переходы на летнее время, однако классы Date и Calendar имеют множество недостатков: они изменяемы (mutable), не потокобезопасны и имеют запутанный API (например, нумерация месяцев с нуля). В современном коде их использовать не рекомендуется.

Современный подход: Java 8 Date/Time API (java.time)

Начиная с Java 8, был введен новый API для работы с датами и временем — java.time (JSR 310). Он лишен всех недостатков старых классов. Для получения даты, отстоящей на 30, 60 или 90 дней, достаточно использовать метод minusDays.

В зависимости от вашей задачи, вы можете использовать разные классы из этого пакета:

  • LocalDate — если вам нужна только дата (год, месяц, день) без времени и часового пояса.
  • LocalDateTime, если вам нужны и дата, и время.
  • ZonedDateTime, если вы работаете с конкретным часовым поясом.

Пример получения дат за последние 30, 60 и 90 дней:

LocalDate today = LocalDate.now;
LocalDate date30DaysAgo = today.minusDays(30);
LocalDate date60DaysAgo = today.minusDays(60);
LocalDate date90DaysAgo = today.minusDays(90);

Как видите, код стал максимально читаемым, лаконичным и безопасным.

Получение записей из базы данных (JDBC 4.2+)

Чаще всего задача получения дат за период связана с запросами к базе данных. Например, на Stack Overflow часто задают вопрос: «How to get last 30 / 60 / 90 days records from given date in java? I have some records with receivedDate…».

Если вы работаете с базой данных, важно знать, что начиная с JDBC 4.2 и выше, вы можете напрямую обмениваться объектами java.time с вашей базой данных.

Для столбца базы данных типа данных, аналогичного стандарту SQL DATE (значение только для даты, без времени суток и без часового пояса), используйте класс Java LocalDate. Если в базе хранится дата со временем, используйте LocalDateTime.

Пример кода с PreparedStatement:

Допустим, нам нужно получить записи, где поле receivedDate попадает в последние 30 дней. Где последние 30 дней прямо привязаны ко времени выполнения запроса. То есть если запрос выполняется 25 июля, то нужно выбрать даты с 25 июня по 24 июля (или по 25 июля включительно, в зависимости от бизнес-логики).

String sql = "SELECT * FROM records WHERE receivedDate >= ?";
try (PreparedStatement pstmt = connection.prepareStatement(sql)) {
    LocalDate thirtyDaysAgo = LocalDate.now.minusDays(30);
    pstmt.setObject(1, thirtyDaysAgo);
    // Выполнение запроса и обработка результатов
}

Использование setObject вместо устаревшего setDate позволяет передавать в базу данных современные типы java.time.

Вычисление разницы между датами

Иногда задача обратная: у вас есть две даты (например, интервал дат: ⏤ ), и вам нужно высчитать точное количество полных лет, месяцев и дней в этом промежутке. Для этого в Java 8 предусмотрен класс java.time.Period.

Пример:

LocalDate startDate = LocalDate;of(2009, 1, 28);
LocalDate endDate = LocalDate.of(2013, 3, 5);
Period period = Period.between(startDate, endDate);
System.out.printf("Лет: %d, Месяцев: %d, Дней: %d", period.getYears, period.getMonths, period.getDays);

Работа с датами в Java прошла долгий путь эволюции. Несмотря на то, что старые методы вроде вычитания миллисекунд или использования Calendar все еще могут встречаться в legacy-коде, современный стандарт — это java.time. Использование LocalDate.minusDays в связке с современным JDBC делает получение записей за последние 30, 60 или 90 дней простой, надежной и элегантной задачей.