JavaRush /Java блог /Архив info.javarush /Интуитивно понятная, надежная библиотека для работы с вре...
theGrass
24 уровень
Саратов

Интуитивно понятная, надежная библиотека для работы с временем и датами, наконец-то появилась в Java (Часть 1).

Статья из группы Архив info.javarush
    Наконец-то в Java появился интуитивный, надежный метод работы с датами и временем.     Принципы даты и времени являются фундаментальными во многих приложениях. Такие разные вещи как даты рождения, сроки аренды, время событий и часы открытия магазина, все основаны на датах и времени, но Java SE не предоставляла удобного способа работы с ними. Начиная с Java SE 8, появился набор пакетов java.time - который предоставляет хорошо структурированный API для работы с датами и временем.
Предыстория
    Когда Java впервые появилась, в версии 1.0, единственным классом для работы с датами и временем был java.util.Date. Первым на что обратили внимание разработчики было то, что он не представляет собой «дату». На самом деле он представляет собой момент времени с точностью до миллисекунд, отмеренный с даты 1-го января 1970-го года. Однако, на основании того что метод toString() у Date выводит дату и время в том часовом поясе который указан в настройках java машины, некоторые разработчики ошибочно сделали вывод о том что Date умеет работать с часовыми поясами.     Исправить этот класс оказалось настолько сложно (или настолько лениво) что в версии 1.1 пришлось добавить новый класс - java.util.Calendar. К сожалению, класс Calendar оказался не сильно лучше чем Date. Вот небольшой список имеющихся проблем в его реализации:
  • Изменяемый. Такие классы как дата и время должны быть неизменяемыми.
  • Смещения. Года в Date начинаются с 1900, месяца в обоих классах начинаются с нуля.
  • Наименования. Date это на самом деле не «дата», и Calendar не является календарем.
  • Форматирование. Форматирование работает только с Date, а не с Calendar, и не является потокобезопасным.
    В 2001м году был создан проект Joda-Time. Цель его была проста - создать качественную библиотеку для работы с датами и временем в Java. Это заняло определенное время, но в конечном итоге была выпущена версия 1.0 и она быстро стала очень популярной и широко используемой. Со временем разработчики все больше требовали предоставить аналогичную по удобству библиотеку в составе JDK. При участии Michael Nascimento Santos из Бразилии, был запущен проект JSR-310, который является официальным процессом создания и интеграции нового API для работы с датами и временем в JDK.
Обзор
Новый API java.time содержит 5 пакетов:
  • java.time - базовый пакет, содержащий объекты для хранения значений
  • java.time.chrono - предоставляет доступ к разным календарям
  • java.time.format - форматирование и распознание даты и времени
  • java.time.temporal - низкоуровневые библиотеки и расширенный функционал
  • java.time.zone - классы для работы с часовыми поясами
    Большинство разработчиков будут в основном использовать базовый пакет и форматирование, и возможно java.time.temporal. Таким образом, несмотря на то что было добавлено 68 новых типов, разработчики будут использовать не более трети из них.
Даты
    Класс LocalDate - один из самых главных в новом API. Он содержит неизменяемое значение, представляющее собой дату. Задать время или часовой пояс нельзя. Название «local» вам может быть знакомо из Joda-Time, и изначально происходит из стандарта ISO-8601. Оно обозначает именно отсутствие часового пояса. В сущности, LocalDate это описание даты, такое как «5-е апреля 2014». Фактическое же время этой даты будет отличаться, в зависимости от вашего часового пояса. К примеру, в Австралии эта дата будет на 10 часов раньше чем в Лондоне, и на 18 часов раньше чем в Сан Франциско.     В классе LocalDate есть все обычно нужные методы: LocalDate date = LocalDate.of(2014, Month.JUNE, 10); int year = date.getYear(); // 2014 Month month = date.getMonth(); // Июнь int dom = date.getDayOfMonth(); // 10 DayOfWeek dow = date.getDayOfWeek(); // Вторник int len = date.lengthOfMonth(); // 30 (дней в Июне) boolean leap = date.isLeapYear(); // false (не високосный год)     В нашем примере, мы видим дату созданную с помощью метода-фабрики (все конструкторы приватные). Дальше мы запрашиваем у объекта некоторые данные. Обратите внимание, что перечисления Month и DayOfWeek созданы для того чтобы делать код более читаемым и надежным. В следующем примере мы увидим как модифицировать дату. Так как класс неизменяемый, результатом будут новые объекты, а исходный останется как был. LocalDate date = LocalDate.of(2014, Month.JUNE, 10); date = date.withYear(2015); // 2015-06-10 date = date.plusMonths(2); // 2015-08-10 date = date.minusDays(1); // 2015-08-09     Это относительно простые изменения, но часто вам нужно произвести более сложные модификации даты. Для этого существует специальный механизм в java.time API - TemporalAdjuster. Его цель - предоставить встроенный инструмент позволяющий манипулировать датами, к примеру получить объект соответствующий последнему дню месяца. Некоторые из них входят в состав API, но вы можете добавлять и свои собственные. Использовать модификаторы очень просто, но для этого нужны статические импорты: import static java.time.DayOfWeek.* import static java.time.temporal.TemporalAdjusters.* LocalDate date = LocalDate.of(2014, Month.JUNE, 10); date = date.with(lastDayOfMonth()); date = date.with(nextOrSame(WEDNESDAY));     Использование модификаторов очень сильно упрощает ваш код. Никому не хочется видеть большое количество манипуляций с датой вручную. Если какая-то манипуляция с датой встречается в вашем проекте несколько раз, напишите ваш собственный модификатор, и ваша команда сможет воспользоваться им как уже написанным и протестированным компонентом.
Время и дата как значения
    Имеет смысл потратить немного времени на то чтобы разобраться, что превращает LocalDate в значения. Значения - простые типы данных, которые являются полностью взаимозаменяемыми когда они равны, идентичность объектов теряет всякий смысл. Классический пример класса-значения - String. Мы сравниваем строки через equals(), и нас не волнует идентичны ли объекты при сравнении оператором ==.     Большая часть классов для работы с датами и временем тоже являются значениями. Так что, сравнивать их с помощью оператора == является плохой идеей, о чем говорится и в документации. Тем кто хочет знать больше, советую почитать мое недавнее определение VALJOs, в котором указан строгий набор правил которым должны следовать объекты-значения в Java, включая неизменяемость, наличие методов-фабрик и правильное определение методов equals(), hashCode, toString() и compareTo().
Альтернативные календари
    Класс LocalDate, как и все главные классы в java.time, привязан к единственному календарю - описанному в стандарте ISO-8601.     В стандарте 8601 описан общемировой стандартный календарь, также известный как грегорианский. Стандартный год включает в себя 365 дней, високосный - 366. Високосным является каждый четвертый год, если он не делится на 100 либо делится на 400. Год перед первым годом новой эры считается нулевым для удобства вычислений.     Первым последствием от того что эта система принята по умолчанию, является то что результаты не всегда совпадают с рассчитанными с помощью GregorianCalendar. В классе GregorianCalendar встроено переключение на юлианскую систему для всех дат раньше 15го октября 1582 года. В юлианской системе високосным годом был каждый четвертый год без исключений.     Спрашивается, раз переход от одной системы к другой является историческим фактом, почему java.time его не моделирует? Да потому что разные страны переходили на грегорианскую систему в разное время, и учитывая только дату перехода Ватикана мы получим неверные данные для большинства других стран. К примеру, Британская Империя, включая колонии в Северной Америке, перешла на грегорианский календарь 14го сентября 1752го года, почти 200 лет спустя. Россия не меняла свой календарь до 14го февраля 1918го года, а переход Швеции вообще дело темное. В результате, реальное значение дат до 1918го года сильно зависит от обстоятельств. Авторами кода LocalDate было принято вполне рациональное решение не моделировать переход от юлианского календаря к грегорианскому вообще, чтобы избежать разночтений.     Вторым последствием использования ISO-8601 в качестве календаря по умолчанию во всех основных классах, является необходимость дополнительного набора классов для работы с остальными календарями. Интерфейс Chronology является основой для работы с альтернативными календарями, позволяющей найти нужный календарь по имени локали. С Java 8 поставляется 4 дополнительных календаря - Тайский буддистский, Мингуо (тайваньский), Японский и Исламский. Другие календари могут поставляться с программами.     Для каждого календаря есть специальный класс даты, такой как ThaiBuddhistDate, MinguoDate, JapaneseDate и HijrahDate. Использовать их имеет смысл в очень сильно локализованных приложениях, таких как приложения для японского правительства. Дополнительный интерфейс, ChronoLocalDate, применяется как основная абстракция четырех вышеперечисленных классов вместе с LocalDate, что позволяет писать код независимый от используемого типа календаря. Несмотря на существование этой абстракции, ее использование не рекомендуется.     Понимание почему использование данной абстракции не рекомендуется, является важным для понимания работы всего java.time API. Суть в том, что большая часть кода который пишут без привязки к конкретному календарю, оказывается нерабочей. К примеру, вы не можете быть увереными что в году 12 месяцев, но некоторые разработчики прибавляют 12 месяцев и считают что добавили целый год. Вы не можете быть уверены что все месяцы содержат примерно одинаковое количество дней - в Коптском календаре 12 месяцев по 30 дней, и 1 месяц из пяти или шести дней. Также вы не можете быть увереными, что номер следующего года будет на 1 больше чем текущего, потому что в Японском календаре годы считаются от провозглашения очередного императора (в данном случае, даже 2 дня одного и того же месяца могут принадлежать разным годам).     Единственный способ написать качественный и рабочий код работающий с несколькими календарями сразу - ядро вашего кода, производящее все операции над датами и временем, должно быть написано с использованием стандартного календаря, и только при вводе/выводе дат производить преобразование в другие календарные системы. То есть, рекомендуется использовать LocalDate для хранения и всех манипуляций с датами в вашем приложении. И только при локализации вводимых и выводимых дат использовать ChronoLocalDate, которую обычно получают из хранимого в профиле пользователя класса календаря. Правда, большинство приложений не нуждаются в столь серьезной локализации.     Если вам нужно более подробное обоснование всего описанного в этой главе - добро пожаловать в документацию класса ChronoLocalDate.     Продолжение статьи     Оригинал статьи
Комментарии (1)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Artemius Уровень 21
10 июня 2014
Спасибо за перевод! Очень большую и качественную работу проделала!