JavaRush /Java блог /Java Developer /Как не потеряться во времени — DateTime и Calendar
Автор
Milan Vucic
Репетитор по программированию в Codementor.io

Как не потеряться во времени — DateTime и Calendar

Статья из группы Java Developer
Привет! Сегодня мы начнем работать с новым типом данных, с которым до этого не сталкивались, а именно — с датами. Как не потеряться во времени — DateTime и Calendar - 1Что такое дата, думаю, объяснять не нужно:) В принципе, записать текущую дату и время в Java вполне возможно в обычную строку.

public class Main { 
   public static void main(String[] args) { 

       String date = "11 июня 2018 года"; 
       System.out.println(date); 
   } 
}
Но у такого подхода много недостатков. Класс String создан для работы с текстом, и методы у него соответствующие. Если нам нужно будет как-то управлять датой (прибавить к ней 2 часа, например) — String тут не справится. Или, например, — вывести в консоль текущую дату и время на момент компиляции программы. Тут String тоже не поможет: пока ты напишешь код и запустишь его - время изменится и в консоль будет выведено неактуальное. Поэтому в Java его создателями были предусмотрены несколько классов для работы с датами и временем. Первый из них - это класс java.util.Date

Класс Date Java

Мы указали для него полное название, поскольку в другом пакете в Java есть еще класс java.sql.Date. Не перепутай! Первое что нужно о нем знать — он хранит дату в миллисекундах, которые прошли с 1 января 1970 года. Для этой даты есть даже отдельное название — “Unix-время” Довольно интересный способ, согласен? :) Второе, что стоит запомнить: если создать объекта Date с пустым конструктором — результатом будет текущая дата и время на момент создания объекта. Помнишь, мы писали, что для даты в формате String такая задача будет проблематичной? Класс Date ее легко решает.

public class Main { 
   public static void main(String[] args) { 

       Date date = new Date(); 
       System.out.println(date); 
   } 
} 
Запусти этот код несколько раз, и увидишь, как время каждый раз будет меняться:) Это возможно именно благодаря хранению в миллисекундах: они являются самой маленькой единицей времени, поэтому результаты настолько точные. Существует и другой конструктор для Date: можно указать точное количество миллисекунд, которое прошло с 00:00 1 января 1970 года до требуемой даты, и она будет создана:

public class Main { 
   public static void main(String[] args) { 
 
       Date date = new Date(1212121212121L); 
       System.out.println(date); 
   }
}
Вывод в консоль:

Fri May 30 08:20:12 MSD 2008
У нас получилось 30 мая 2008 года. “Fri” означает день недели — “Friday” (пятница), а MSD — “Moscow Daylight Saving” (московское летнее время). Миллисекунды передаются в формате long, поскольку их количество чаще всего не влезает в int. Итак, какие операции с датами нам могут понадобиться в работе? Ну, самое очевидное, конечно — сравнение. Определить была ли одна дата позже или раньше другой. Это можно сделать по-разному. Например, можно вызвать метод Date.getTime().Он вернет количество миллисекунд, прошедших с полуночи 1 января 1970 года. Просто вызовем его у двух объектов Date и сравним между собой:

public class Main { 
   public static void main(String[] args) { 

       Date date1 = new Date(); 

       Date date2 = new Date(); 

       System.out.println((date1.getTime() > date2.getTime())? 
               "date1 позже date2" : "date1 раньше date2"); 
   } 
}
Вывод:

date1 раньше date2
Но есть и более удобный способ, а именно — использовать специальные методы класса Date: before(), after() и equals(). Все они возвращают результат в формате boolean. Метод before() проверяет, была ли наша дата раньше той, которую мы передаем в качестве аргумента:

public class Main { 
   public static void main(String[] args) throws InterruptedException { 

       Date date1 = new Date(); 

       Thread.sleep(2000);//приостановим работу программы на 2 секунды 
       Date date2 = new Date(); 

       System.out.println(date1.before(date2)); 
   } 
}
Вывод в консоль:

true
Похожим образом работает и метод after(), он проверяет была ли ли наша дата позже той, которую мы передаем в качестве аргумента:

public class Main { 
   public static void main(String[] args) throws InterruptedException { 

       Date date1 = new Date(); 

       Thread.sleep(2000);//приостановим работу программы на 2 секунды 
       Date date2 = new Date(); 

       System.out.println(date1.after(date2)); 
   }
} 
Вывод в консоль:

false
В наших примерах мы “усыпляем” программу на 2 секунды, чтобы две даты гарантированно отличались. На быстрых компьютерах время между созданием date1 и date2 может быть меньше одной миллисекунды, и в таком случае оба метода — и before(), и after() — будут возвращать false. А вот метод equals() в такой ситуации вернет true! Ведь он сравнивает именно количество миллисекунд, прошедших с 00:00 1 января 1970 для каждой даты. Объекты будут считаться равными только в том случае, если совпадают вплоть до миллисекунды:

public static void main(String[] args) { 

   Date date1 = new Date(); 
   Date date2 = new Date(); 

   System.out.println(date1.getTime()); 
   System.out.println(date2.getTime()); 

   System.out.println(date1.equals(date2)); 
} 
Вот еще на что нужно обратить внимание. Если ты откроешь документацию класса Date на сайте Oracle, то увидишь, что многие его методы и конструкторы были обозначены словом Deprecated (“нерекомендуемый‘). Вот, посмотри: Class Date Вот что сами создатели Java говорят про те части классов, которые стали deprecated: “Программный элемент, аннотированный @Deprecated, является тем, что программистам не рекомендуется использовать, как правило, потому, что это опасно, или потому, что существует лучшая альтернатива.” Это не означает, что этими методами вообще нельзя пользоваться. Более того, если ты сам попробуешь запустить код с их использованием в IDEA — он, скорее всего будет работать Возьмем для примера deprecated метод Date.getHours(), который возвращает количество часов из объекта Date.

public static void main(String[] args) { 

   Date date1 = new Date(); 

   System.out.println(date1.getHours()); 

} 
Если на момент запуска кода у вас, например, время 14:21, он выведет число 14. Как видите, deprecated-метод зачеркнут, но он вполне себе работает. Это методы не стали убирать совсем, чтобы не сломать кучу уже написанного с их использованием кода. То есть эти методы не “сломаны” и не “удалены”, просто их не рекомендуют использовать по причине наличия более удобной альтернативы. О ней, кстати, написано прямо в документации: Как не потеряться во времени — DateTime и Calendar - 2Большинство методов класса Date было перенесено в его улучшенную, расширенную версию — класс Calendar. С ним мы и познакомимся дальше:)

Java Calendar

В версии Java 1.1 появился новый класс — Calendar. Он сделал работу с датам в Java несколько проще, чем она выглядела раньше. Единственной реализацией класса Calendar, с которой мы и будем работать, является класс GregorianCalendar (он реализует Григорианский календарь, по которому живет большинство стран мира). Его основное удобство заключается в том, что он умеет работать с датами в более удобном формате. Например, он может:
  • Прибавить к текущей дате месяц или день
  • Проверить, является ли год високосным;
  • Получить отдельные компоненты даты (например, получить из целой даты номер месяца)
  • А также — внутри него разработана очень удобная система констант (многие из них мы увидим ниже).
Еще одним важным отличием класса Calendar является то, что в нем реализована константа Calendar.Era: ты можешь установить для даты эру BC (“Before Christ” - до рождества Христова, т.е. “до нашей эры”) или AC (“After Christ” - “наша эра”). Давай рассмотрим все это на примерах. Создадим календарь с датой 25 января 2017 года:

public static void main(String[] args) { 

  Calendar calendar = new GregorianCalendar(2017, 0 , 25); 
} 
Месяцы в классе Calendar (как и в Date, кстати) начинаются с нуля, поэтому мы передали число 0 в качестве второго аргумента. Главное при работе с классом Calendar — понимать, что это именно календарь, а не отдельная дата. Как не потеряться во времени — DateTime и Calendar - 3Дата — это просто несколько чисел, обозначающих конкретный промежуток времени. А календарь - это целое устройство, с помощью которого можно много чего с датами делать:) Это достаточно хорошо видно, если попробовать вывести объект Calendar в консоль: Вывод:

java.util.GregorianCalendar[time=?,areFieldsSet=false,areAllFieldsSet=false,lenient=true,zone=sun.util.calendar.ZoneInfo[id="Europe/Moscow",offset=10800000,dstSavings=0,useDaylight=false,transitions=79,lastRule=null],firstDayOfWeek=2,minimalDaysInFirstWeek=1,ERA=?,YEAR=2017,MONTH=0,WEEK_OF_YEAR=?,WEEK_OF_MONTH=?,DAY_OF_MONTH=25,DAY_OF_YEAR=?,DAY_OF_WEEK=?,DAY_OF_WEEK_IN_MONTH=?,AM_PM=0,HOUR=0,HOUR_OF_DAY=0,MINUTE=0,SECOND=0,MILLISECOND=?,ZONE_OFFSET=?,DST_OFFSET=?]
Видишь сколько информации! У календаря есть куча свойств, которыми не обладает обычная дата, и все они выводятся в консоль (так работает метод toString() в классе Calendar). Если при работе тебе нужно просто получить из календаря простую дату, т.е. объект Date — это делается при помощи метода Calendar.getTime() (название не самое логичное, но тут уж ничего не поделаешь):

public static void main(String[] args) { 

   Calendar calendar = new GregorianCalendar(2017, 0 , 25); 
   Date date = calendar.getTime(); 
   System.out.println(date); 
}
Вывод:

Wed Jan 25 00:00:00 MSK 2017
Вот теперь мы “упростили” календарь до обычной даты. Поехали дальше. Помимо цифровых обозначений месяцев, в классе Calendar можно использовать константы. Константы — это статические поля класса Calendar с уже установленным значением, которое нельзя изменить. Этот вариант на самом деле даже лучше, поскольку такое написание улучшает читаемость кода.

public static void main(String[] args) { 
   GregorianCalendar calendar = new GregorianCalendar(2017, Calendar.JANUARY , 25); 
}
Calendar.JANUARY — одна из констант для обозначения месяца. При таком варианте именования никто не забудет, например, что цифра “3” обозначает апрель, а не привычный нам третий по счету месяц - март. Просто пишешь Calendar.APRIL - и все:) Все поля календаря (число, месяц, минуты, секунды и т.д.) можно устанавливать по отдельности с помощью метода set(). Он очень удобен, поскольку в классе Calendar для каждого поля выделена своя константа, и итоговый код будет выглядеть максимально просто. Например, в прошлом примере мы создали дату, но не установили для нее текущее время. Давай установим время 19:42:12

public static void main(String[] args) { 
   Calendar calendar = new GregorianCalendar(); 
   calendar.set(Calendar.YEAR, 2017); 
   calendar.set(Calendar.MONTH, 0); 
   calendar.set(Calendar.DAY_OF_MONTH, 25); 
   calendar.set(Calendar.HOUR_OF_DAY, 19); 
   calendar.set(Calendar.MINUTE, 42); 
   calendar.set(Calendar.SECOND, 12); 

   System.out.println(calendar.getTime()); 
}
Вывод:

Wed Jan 25 19:42:12 MSK 2017
Мы вызываем метод set(), передаем в него константу (в зависимости от того поля, которое хотим изменить) и новое значение для этого поля. Получается, что метод set() — эдакий “супер-сеттер”, который умеет устанавливать значение не для одного поля, а для множества полей:) Прибавление и вычитание значений в классе Calendar осуществляется с помощью метода add(). В него необходимо передать то поле, которое ты хочешь изменить, и число - сколько именно ты хочешь прибавить/убавить от текущего значения. Например, вернем дату, которую мы создали, на 2 месяца назад:

public static void main(String[] args) { 
   Calendar calendar = new GregorianCalendar(2017, Calendar.JANUARY , 25); 
   calendar.set(Calendar.HOUR, 19); 
   calendar.set(Calendar.MINUTE, 42); 
   calendar.set(Calendar.SECOND, 12); 

   calendar.add(Calendar.MONTH, -2);//чтобы отнять значение - в метод нужно передать отрицательное число 
   System.out.println(calendar.getTime()); 
}
Вывод:

Fri Nov 25 19:42:12 MSK 2016
Отлично! Мы вернули дату на 2 месяца назад. В результате изменился не только месяц, но и год, с 2017 на 2016. Подсчет текущего года при переносе дат, конечно, выполняется автоматически и его не надо контролировать вручную. Но если для каких-то целей тебе нужно отключить это поведение — можно и так. Специальный метод roll() может прибавлять и убавлять значения, не затрагивая при этом остальные значения. К примеру, вот так:

public static void main(String[] args) { 
   Calendar calendar = new GregorianCalendar(2017, Calendar.JANUARY , 25); 
   calendar.set(Calendar.HOUR, 10); 
   calendar.set(Calendar.MINUTE, 42); 
   calendar.set(Calendar.SECOND, 12); 

   calendar.roll(Calendar.MONTH, -2); 
   System.out.println(calendar.getTime()); 
} 
Мы сделали ровно то же самое, что и в предыдущем примере — отняли 2 месяца от текущей даты. Но теперь код сработал по-другому: месяц поменялся с января на ноябрь, но год как был 2017-ым, так и остался! Вывод:

Sat Nov 25 10:42:12 MSK 2017
Далее. Как мы и говорили выше, все поля объекта Calendar можно получить по отдельности. За это отвечает метод get():

public static void main(String[] args) { 
   GregorianCalendar calendar = new GregorianCalendar(2017, Calendar.JANUARY , 25); 
   calendar.set(Calendar.HOUR, 10); 
   calendar.set(Calendar.MINUTE, 42); 
   calendar.set(Calendar.SECOND, 12); 
 
   System.out.println("Год: " + calendar.get(Calendar.YEAR)); 
   System.out.println("Месяц: " + calendar.get(Calendar.MONTH)); 
   System.out.println("Порядковый номер недели в месяце: " + calendar.get(Calendar.WEEK_OF_MONTH));//порядковый номер недели в месяце

   System.out.println("Число: " + calendar.get(Calendar.DAY_OF_MONTH)); 

   System.out.println("Часы: " + calendar.get(Calendar.HOUR)); 
   System.out.println("Минуты: " + calendar.get(Calendar.MINUTE)); 
   System.out.println("Секунды: " + calendar.get(Calendar.SECOND)); 
   System.out.println("Миллисекунды: " + calendar.get(Calendar.MILLISECOND)); 

} 
Вывод:

Год: 2017 
Месяц: 0 
Порядковый номер недели в месяце: 4 
Число: 25 
Часы: 10 
Минуты: 42 
Секунды: 12 
Миллисекунды: 0
То есть помимо “супер-сеттера” в классе Calendar есть еще и “супер-геттер” :) Еще один интересный момент — это, конечно, работа с эрами. Для создания даты “до нашей эры” нужно использовать поле Calendar.Era Например, создадим дату, обозначающую битву при Каннах, в которой Ганнибал победил войско Рима. Это произошло 2 августа 216 г. до н. э.:

public static void main(String[] args) { 
   GregorianCalendar cannes = new GregorianCalendar(216, Calendar.AUGUST, 2); 
   cannes.set(Calendar.ERA, GregorianCalendar.BC); 

   DateFormat df = new SimpleDateFormat("dd MMM yyy GG"); 
   System.out.println(df.format(cannes.getTime())); 
} 
Здесь мы использовали класс SimpleDateFormat, чтобы вывести дату в более понятном нам формате (буквы “GG” отвечают как раз за вывод эры). Вывод:

02 авг 216 до н.э.
В классе Calendar есть еще много методов и констант, почитай про них в документации:

Перевод строки в Date

Для перевода String в Date можно воспользоваться вспомогательным классом Java — SimpleDateFormat. Это класс, который нужен для приведения даты в определяемый вами формат. Как не потеряться во времени — DateTime и Calendar - 5В свою очередь, он очень похож на DateFormat. Единственное заметное различие между ними заключается в том, что SimpleDateFormat можно использовать для форматирования (преобразования даты в строку) и для парсинга строки в дату с поддержкой языкового стандарта, тогда как DateFormat не поддерживает языкового стандарта. Кроме того, DateFormat — это абстрактный класс, который обеспечивает базовую поддержку для форматирования и анализа дат, а SimpleDateFormat — это конкретный класс, расширяющий класс DateFormat. Вот так выглядит пример создания объекта SimpleDateFormat и форматирования Date:

SimpleDateFormat formater = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = new Date(1212121212121L);

System.out.println(formatter.format(date));
В приведенном выше примере мы использовали шаблон "yyyy-MM-dd HH:mm:ss", который означает:
  • 4 цифры для года (yyyy);
  • 2 цифры для месяца (ММ);
  • 2 цифры для дня (dd);
  • 2 цифры для часов в 24-часовом формате (HH);
  • 2 цифры для минут (mm);
  • 2 цифры для секунд (ss).
Знаки разделения и порядок расстановки символов шаблона сохраняется. Вывод в консоль:

2008-05-30 08:20:12
Шаблонных букв для класса SimpleDateFormat довольно много. Чтобы ты не запутался, мы собрали их в таблицу:
Символ Описание Пример
G эра (в английской локализации — AD и BC) н.э.
y год (4-х значное число) 2020
yy год (последние 2 цифры) 20
yyyy год (4-х значное число) 2020
M номер месяца (без лидирующих нулей) 8
MM номер месяца (с лидирующими нулями, если порядковый номер месяца < 10) 04
MMM трехбуквенное сокращение месяца (в соответствии с локализацией) янв
MMMM полное название месяца Июнь
w неделя в году (без лидирующих нулей) 4
ww неделя в году (с лидирующими нулями) 04
W неделя в месяце (без лидирующих нулей) 3
WW неделя в месяце (с лидирующим нулем) 03
D день в году 67
d день месяца (без лидирующих нулей) 9
dd день месяца (с лидирующими нулями) 09
F день недели в месяце (без лидирующих нулей) 9
FF день недели в месяце (с лидирующими нулями) 09
E день недели (сокращение) Вт
EEEE день недели (полностью) пятница
u номер дня недели (без лидирующих нулей) 5
uu номер дня недели (с лидирующими нулями) 05
a маркер AM/PM AM
H часы в 24-часовом формате без лидирующих нулей 6
HH часы в 24-часовом формате с лидирующим нулем 06
k количество часов в 24-часовом формате 18
K количество часов в 12-часовом формате 6
h время в 12-часовом формате без лидирующих нулей 6
hh время в 12-часовом формате с лидирующим нулем 06
m минуты без лидирующих нулей 32
mm минуты с лидирующим нулем 32
s секунды без лидирующих нулей 11
ss секунды с лидирующим нулем 11
S миллисекунды 297
z часовой пояс EET
Z часовой пояс в формате RFC 822 300
Примеры комбинаций символов шаблонов:
Шаблон Пример
dd-MM-yyyy 01-11-2020
yyyy-MM-dd 2019-10-01
HH:mm:ss.SSS 23:59.59.999
yyyy-MM-dd HH:mm:ss 2018-11-30 03:09:02
yyyy-MM-dd HH:mm:ss.SSS 2016-03-01 01:20:47.999
yyyy-MM-dd HH:mm:ss.SSS Z 2013-13-13 23:59:59.999 +0100
Если же немного ошибиться с форматом, то можно стать обладателем java.text.ParseException, а это не особо приятное достижение. Ну что же, небольшой экскурс по SimpleDateFormat окончен — вернёмся к переводу java string to date. SimpleDateFormat дает нам такие возможности, и мы рассмотрим этот процесс поэтапно.
  1. Создаем строку, с которой нужно задать дату:

    
    String strDate = "Sat, April 4, 2020";
    
  2. Создаем новый объект SimpleDateFormat с шаблоном, который совпадает с тем, что у нас в строке (иначе распарсить не получится):

    
    SimpleDateFormat formatter = new SimpleDateFormat("EEE, MMMM d, yyyy", Locale.ENGLISH);
    

    Как вы видите, у нас тут появился аргумент Locale. Если же мы его опустим, он будет использовать значение Locale по умолчанию, которое не всегда является английским.

    Если языковой стандарт не совпадает с входной строкой, то строковые данные, привязанные к языку, как у нас Mon или April, не будут распознаны и вызовут падение — java.text.ParseException, даже в том случае когда шаблон подходит.

    Тем не менее, можно не указывать формат, если у нас используется шаблон, который не привязан к языку. Как пример — yyyy-MM-dd HH:mm:ss

  3. Создаём дату с помощью форматтера, который в свою очередь парсит её из входной строки:

    
    try {
      Date date = formatter.parse(strDate);
      System.out.println(date);
    }
    catch (ParseException e) {
      e.printStackTrace();
    }
    

    Вывод в консоль:

    
    Sat Apr 04 00:00:00 EEST 2020
    

    Хммм….Но формат-то уже не тот!

    Чтобы сделать тот же формат, вновь используем форматтер:

    
    System.out.println(formatter.format(date));
    

    Вывод в консоль:

    
    Sat, April 4, 2020
    

SimpleDateFormat и Calendar

SimpleDateFormat позволит тебе форматировать все создаваемые объекты Date и Calendar для последующего использования. Рассмотрим такой интересный момент как работа с эрами. Для создания даты “до нашей эры” нужно использовать поле Calendar.Era Например, создадим дату, обозначающую битву при Каннах, в которой Ганнибал победил войско Рима. Это произошло 2 августа 216 г. до н. э.:

public static void main(String[] args) {
   GregorianCalendar cannes = new GregorianCalendar(216, Calendar.AUGUST, 2);
   cannes.set(Calendar.ERA, GregorianCalendar.BC);

   DateFormat df = new SimpleDateFormat("dd MMM yyy GG");
   System.out.println(df.format(cannes.getTime()));
}
Здесь мы использовали класс SimpleDateFormat, чтобы вывести дату в более понятном нам формате (как указано выше, буквы “GG” отвечают как раз за вывод эры). Вывод:

02 авг 216 до н.э.

Java Date Format

А вот еще один случай. Предположим, что данный формат даты нас не устраивает:

Sat Nov 25 10:42:12 MSK 2017
Так вот. С помощью наших возможностей в java date format его можно поменять его на свой собственный, без особых сложностей:

public static void main(String[] args) {

   SimpleDateFormat dateFormat = new SimpleDateFormat("EEEE, d MMMM yyyy");
   Calendar calendar = new GregorianCalendar(2017, Calendar.JANUARY , 25);
   calendar.set(Calendar.HOUR, 10);
   calendar.set(Calendar.MINUTE, 42);
   calendar.set(Calendar.SECOND, 12);

   calendar.roll(Calendar.MONTH, -2);
   System.out.println(dateFormat.format(calendar.getTime()));
}
Вывод:

суббота, 25 Ноябрь 2017
Гораздо лучше, да? :)
Комментарии (206)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Максим Li Уровень 36
18 декабря 2023
Ок)
Evgen_Brest Уровень 32
22 августа 2023
Подушню маленечко. Код не скомпилируется)) в главе Перевод строки в Date , есть код с ошибкой)) SimpleDateFormat formater = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//здесь или не хватает t Date date = new Date(1212121212121L); System.out.println(formatter.format(date)); // или здесь лишняя
Степан Шушаков Уровень 26 Expert
10 августа 2023
1. я один дважды прочитал про битву при Каннах, в которой Ганнибал победил войско Рима? 2. разве в последнем примере меняется форма даты не должно не быть

calendar.roll(Calendar.MONTH, -2);
и быть установка Locale ?
Alexander Rozenberg Уровень 32
24 июля 2023
fine
Максим Погодин Уровень 13
4 июля 2023
links: DateTimeFormatter java.time java.time.format
No Name Уровень 32
20 июня 2023
+ статья в копилке
Ислам Уровень 33
5 июня 2023
Nice
Perl Developer Уровень 9
15 марта 2023

    Date date1 = new Date();
    Calendar date2 = Calendar.getInstance();

    System.out.println(date1.getHours());
    System.out.println(date2.get(Calendar.HOUR_OF_DAY));
Murat Уровень 51
10 февраля 2023
Лайк, если тоже проверил быстрый у тебя комп или нет с помощью создания двух объектов класса Date друг за другом путём их сравнения🤓 Мой результат: date1.equals(date2) = true - быстрый получается)
flash_anton Уровень 20
1 февраля 2023
Дьявол кроется в деталях. При использовании описанных в статье set/add/roll/get методов календаря часто забывают про нормализацию при вызове get-ров, про зависящее от режима (lenient/non-lenient) поведение. Пример с get-ром среди set/add/roll: SimpleDateFormat df = new SimpleDateFormat("EEEE, d MMMM yyyy"); Calendar c = new GregorianCalendar(2023, JANUARY, 31); c.set(MONTH, FEBRUARY); //System.out.println(df.format(c.getTime())); c.set(DAY_OF_MONTH, 28); System.out.println(df.format(c.getTime())); Вывод: Tuesday, 28 February 2023 Вывод, если раскомментировать строку: Friday, 3 March 2023 Tuesday, 28 March 2023