Привет! Сегодня мы начнем работать с новым типом данных, с которым до этого не сталкивались, а именно — с датами.
Что такое дата, думаю, объяснять не нужно:) В принципе, записать текущую дату и время в 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.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-метод зачеркнут, но он вполне себе работает. Это методы не стали убирать совсем, чтобы не сломать кучу уже написанного с их использованием кода. То есть эти методы не “сломаны” и не “удалены”, просто их не рекомендуют использовать по причине наличия более удобной альтернативы. О ней, кстати, написано прямо в документации:
Большинство методов класса Date было перенесено в его улучшенную, расширенную версию — класс Calendar. С ним мы и познакомимся дальше:)

Класс 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 — понимать, что это именно календарь, а не отдельная дата. Дата — это просто несколько чисел, обозначающих конкретный промежуток времени. А календарь - это целое устройство, с помощью которого можно много чего с датами делать:) Это достаточно хорошо видно, если попробовать вывести объект 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 Сколько недель в этом месяце: 5 Число: 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 есть еще много методов и констант, почитай про них в документации: И раз уж мы использовали в последнем примере класс SimpleDateFormat — очень советую тебе прочитать и сохранить в избранное крутейшую статью про форматирование дат! Этот класс позволит тебе форматировать все создаваемые объекты Date и Calendar для последующего использования. Например, если тебе не нравится такой формат вывода даты Sat Nov 25 10:42:12 MSK 2017 то с помощью SimpleDateFormat ты можешь легко поменять его на свой собственный.
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 Гораздо лучше, да? :) Почитай про него, очень полезная штука