Skip to content

Latest commit

 

History

History
198 lines (134 loc) · 14.1 KB

date_and_calendar.md

File metadata and controls

198 lines (134 loc) · 14.1 KB

Java Time API 1.0

java.util.Date и java.util.Calendar.

До Java 8, в стандартной библиотеке существовал и существует класс для задания точки на временной оси - и этот класс java.util.Date. Рассматривать мы его будем вместе с java.util.Calendar - классом использующимся для операций с временем.

java.util.Date появился в Java 1.0 и так получилось, что был крайне неудачно спроектирован. Однако, сейчас его использование считается плохой практикой.

Давайте бегло посмотрим - что из себя представляет данный класс, а после - опишем уже почему использовать его - плохой тон.

Итак, java.util.Date.

java.util.Date

Основное поле класса - это:

private transient long fastTime;

В данном поле хранится количество количество миллисекунд с 1 января 1970 года - Unix-time в миллисекундах. Можно считать, что java.util.Date - это обертка над long. Из этого сразу следует - что точность определения точки на временной оси составляет 1 миллисекунду. Т.е java.util.Date - по сути представляет отрезок на временной оси, отрезок длиной 1 миллисекунда.

В целом - точности java.util.Date хватает и я лично еще не сталкивался с тем, что нужны библиотеки точнее. Именно поэтому, мы можем представлять себе объект java.util.Date как точку на временной оси.

Когда мы узнали что вообще из себя примерно представляет внутри java.util.Date - можно задать вопрос - хватит ли нам long-а для хранения количества миллисекунд вообще? Вспомним, что максимальное значение long в Java - это

Long.MAX_VALUE =  9223372036854775807 // так как в Java 64-битное представление

Поэтому вместимости данного поля хватит на века вперед. Так что не стоит переживать по этому поводу!

Продолжим знакомство с java.util.Date и взглянем на обратную сторону медали - на его минусы.

Минусы.

Давайте попробуем дать объяснение тому факту, что от java.util.Date сейчас все стараются уходить?

  • Date изменяемый, что порождает много ошибок - это потоконебезопасно.
  • Отсутствует поддержка часовых поясов.
  • Имя класса абсолютно не отражает его сути.
  • Из-за странного проектирования - сложное использование класса.

С первыми двумя пунктами, я думаю, интуитивно понятно что не так. Разберем последние два.

Так как java.util.Date, как уже было сказано выше, по сути представляет из себя точку на временной оси - это не может быть датой и т.д. Это очень даже некоторый Instant - некоторый момент, мгновение. Но тем не менее класс носит гордое название Date, чем вводит в заблуждение.

Ну а сложное использование класса лучше продемонстрировать на практике:

Date date = new Date(2016, 12, 13, 14, 49);

В данном коде сразу два подводных камня.

  1. На месте, где стоит 2016 - конструктор класса java.util.Date ждет отступ от 1900 года.
  2. Нумерация месяцев тут начинается с 0, поэтому месяца 12 нет.

Добавим к этому то, что в Java 8 все методы данного класса помечены как @Deprecated.

Это пробелы именно в проектировании класса - подобное API запутанно, не последовательно и легко может привести к ошибкам. Добавим к этому еще и мутабельность - получим крайне несбалансированный класс, в использовании которого легко ошибиться, эти ошибки не будут проверены на этапе компиляции - трудно уловимы, вдобавок ко всему еще и крайне критичны.

В связи со всем вышеописанным - пользоваться данным классом мягко говоря не удобно, поэтому в Java 8 ввели новое Java 8 Time API.

Для связи старого и нового API в java.util.Date существуют методы-бриджи. Это Date from(Instant instant) и Instant toInstant(), которые были добавленны начиная с Java 8.

Необходимо еще отметить вот что - для операций со временем в Java существуют классы: java.util.Calendar, java.util.TimeZone.

Операции с датами.

Для сравнения двух экземпляров java.util.Date существует три способа:

  • getTime()

    Чтобы получить количество миллисекунд, прошедших с момента полуночи 1 января 1970, для обоих объектов, а затем сравнить эти два значения.

  • Методы before(), after() и equals().

    Поскольку 12 число месяца раньше 18 числа, например:

    new Date(99, 2, 12).before(new Date (99, 2, 18))

    возвращает значение true.

  • compareTo(), который определяется сопоставимым интерфейсом и реализуется по дате.

Если мы собираемся взаимодействовать с временем не только как с точками на временной оси, то нам необходимо использовать дополнительные классы. Классы java.util.Calendar и java.util.TimeZone описывают сущности календаря и часового пояса в Java соответственно.

Я думаю, интуитивно понятно для чего они нужны, но считаю, что необходимо также сделать некоторое overview на них.

Итак.

java.util.Calendar.

К сожалению, странный подход к проектированию коснулся и этого класса:

  1. Месяцы идут с 0.
  2. Дни - начинаются с 1.
  3. При установке полной даты количество миллисекунд не сбрасывается(необходим вызов специального метода), а остается равным количеству миллисекунд с предыдущего момента или текущего времени - если не было изменения календаря.

Третий пункат означает вот что:

TimeZone tz = TimeZone.getTimeZone("Europe/Moscow");

Calendar calendar = Calendar.getInstance(tz);

calendar.set(2018, Calendar.MARCH, 4, 9, 2, 11);
System.out.println(calendar.getTimeInMillis());

calendar.set(Calendar.MILLISECOND, 0);
System.out.println(calendar.getTimeInMillis());

И получим два разных значения - отличающиеся именно в трех последних числах - как раз из-за того, что описано в пункте 3:

1520143331925
1520143331000

Теперь должен возникнуть вопрос - откуда java.util.Date берет значения для инициализации? Где источник?

Источники времени в Java

Источником - понятное дело - выступает система.

В Java есть следующие методы для получения времени:

  • java.lang.System.currentTimeMills

Из документации:

* @return  the difference, measured in milliseconds, between
*          the current time and midnight, January 1, 1970 UTC.

Что значит, что он возвращает количество миллисекунд прошедших с полночи 1 января 1970 по UTC.

Гарантий про многопоточность данный метод не дает - в результате перевода системных часов могут быть погрешности. К счастью, подобных переводов часто не делают - я не сталкивался ни разу.

При создании экземпляров java.util.Date и java.util.Calendar используется именно этот метод.

Ключевое слово native означает, что метод реализован в платформенно-зависимом коде, чаще всего на C/C++, и скомпонован в виде динамической библиотеки.

Эта реализация зависит от JVM.

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

  • java.lang.System.nanoTime Из документации:
* @return the current value of the running Java Virtual Machine's
*         high-resolution time source, in nanoseconds

Что значит, что он возвращает некоторое абстрактное количество наносекундных 'тиков'. Реализация зависит от железа, типа и версии ОС.

Исходя из описаний методов можно заключить следующее - для измерения точнойдлительности операций допустимо использовать именно java.lang.System.nanoTime. Более-менее современный код именно так и работает - для примера можно посмотреть реализации интерфейса java.util.concurrent.ExecutorService. В более старом коде же до сих пор иногда можно встретить использование java.lang.System.currentTimeMills для подобных вещей - для примера можно посмотреть на java.lang.Thread.

Совершенно ясно, что использовать конструкции вида:

event.setGeneratedDate(new Date());
event.setProcessingDate(new Date());

Не должны быть в вашем коде - они должны быть представлены в виде:

Date now = new Date();
event.setGeneratedDate(now);
event.setProcessingDate(now);

Наследники java.util.Date

Так как раньше java.util.Date был единственным классом для работы с временем в Java, если не учитывать сторонние библиотеки, то он является родительским классом для сразу нескольких классов, связанных с представлением хранения времени в БД.

  • java.sql.Timestamp представление TIMESTAMP в БД.
  • java.sql.Date представление DATE в БД.
  • java.sql.Time представление TIME в БД.

Подробнее об этом в Хранение даты в БД

Также, важно рассказать еще и то, как работать с датой, если она представлена как строка - для этой цели существуют специальные классы форматтеры, для java.util.Date такой форматтер - это SimpleDateFormat

Заключение

Данный класс до сих пор используется в некоторых проектах в качестве помощника для работы с временем. Однако из-за явных минусов, описанных выше, лично я не рекомендовал бы использовать данный API сейчас. Если вы пишите на Java до 8-й версии, то лучше использовать стороннюю библиотеку joda-time.

При использовании Java 8+ - вполне подойдет использование Java 8 Time API.

С недавних пор даже Hibernate умеет работать с Java 8 Time API, так что проблем быть не должно.