Мне больше не нужно больше использовать java.util.Date в эти дни, но недавно я решил сделать это, и мне напомнили о боли при использовании API, связанных с Java Date . В этой статье я рассмотрю несколько удивительных ожиданий API устаревшего параметризованного конструктора Date, который принимает шесть целых чисел .
В 2016 году разработчики Java, скорее всего, будут использовать новый API даты / времени Java 8, если пишут новый код в Java SE 8, или могут использовать стороннюю библиотеку даты / времени Java, такую как Joda-Time, при использовании версии Java до Java 8. Недавно я решил использовать Date в очень простом инструменте на основе Java, который я хотел доставить в виде одного файла исходного кода Java (легко компилировать без инструмента сборки) и не зависеть от каких-либо внешних библиотек. Java SE. Средой развертывания для этого простого инструмента является Java SE 7, поэтому API даты / времени Java 8 не был выбран.
Одним из недостатков конструктора Date который принимает шесть целых чисел, является различие между этими шестью целыми числами и обеспечение их соответствия в правильном порядке. Даже при соблюдении правильного порядка, есть тонкие сюрпризы, связанные с указанием месяца и года. Возможно, самый простой способ правильно создать экземпляр объекта Date — через SimpleDateFormat . parse (String) или через конструктор Date (long) без устаревших значений, принимающий миллисекунды с нулевой эпохи.
Мой первый список кода демонстрирует создание Date представляющей «26 сентября 2016 года» с 0 часами, 0 минутами и 0 секундами. Этот листинг кода использует String для создания экземпляра Date помощью SimpleDateFormat.parse(String) .
|
1
2
3
|
final SimpleDateFormat formatter = new SimpleDateFormat(DEFAULT_FORMAT);final Date controlDate = formatter.parse(CONTROL_DATE_TIME_STR);printDate("Control Date/Time", controlDate); |
При выполнении вышеизложенного печатаемые результаты соответствуют ожидаемым, а выходная дата соответствует предоставленной строке и проанализирована для экземпляра Date .
|
1
2
3
|
============================================================== Control Date/Time -> Mon Sep 26 00:00:00 MDT 2016============================================================= |
Может быть соблазнительно использовать конструкторы Date которые принимают целые числа, для представления различных «полей» экземпляра Date , но они представляют ранее упомянутые нюансы.
В следующем листинге кода показан очень наивный подход к вызову конструктора Date который принимает шесть целых чисел, представляющих эти поля в следующем порядке: год, месяц, дата, час, минуты, секунды.
|
1
2
3
4
5
|
// This will NOT be the intended Date of 26 September 2016// with 0 hours, 0 minutes, and 0 seconds because both the// "month" and "year" parameters are NOT appropriate.final Date naiveDate = new Date(2016, 9, 26, 0, 0, 0);printDate("new Date(2016, 9, 26, 0, 0, 0)", naiveDate); |
Результат выполнения вышеуказанного кода не имеет ни того же месяца (октябрь, а сентябрь), ни того же года (не 2016), как в «контрольном» случае, показанном ранее.
|
1
2
3
|
============================================================== new Date(2016, 9, 26, 0, 0, 0) -> Thu Oct 26 00:00:00 MDT 3916============================================================= |
Месяц был на один позже, чем мы ожидали (октябрь, а не сентябрь), поскольку параметр месяца — это параметр, начинающийся с нуля, при этом январь представлен нулем, а сентябрь — 8 вместо 9. Один из самых простых способов справиться с Начинающийся с нуля месяц и более удобочитаемым вызовом конструктора Date является использование соответствующего поля java.util.Calendar для месяца. Следующий пример демонстрирует это с помощью Calendar.SEPTEMBER .
|
1
2
3
4
5
|
// This will NOT be the intended Date of 26 September 2016// with 0 hours, 0 minutes, and 0 seconds because the// "year" parameter is not correct.final Date naiveDate = new Date(2016, Calendar.SEPTEMBER, 26, 0, 0, 0);printDate("new Date(2016, Calendar.SEPTEMBER, 26, 0, 0, 0)", naiveDate); |
Только что приведенный фрагмент кода исправляет спецификацию месяца, но год по-прежнему выключен, как показано в соответствующем выводе, который показан далее.
|
1
2
3
|
============================================================== new Date(2016, Calendar.SEPTEMBER, 26, 0, 0, 0) -> Tue Sep 26 00:00:00 MDT 3916============================================================= |
В этом году еще 1900 лет (3916 вместо 2016 года). Это связано с тем, что первым целочисленным параметром в конструкторе Date с шестью целыми Date будет год, указанный как год без 1900. Таким образом, в качестве первого аргумента укажите «2016», указав год как 2016 + 1900 = 3916. Чтобы исправить это, нам нужно вместо этого предоставить 116 (2016-1900) в качестве первого параметра int конструктору. Чтобы сделать это более читабельным для обычного человека, который нашел бы это удивительным, я хотел бы закодировать его буквально как 2016-1900, как показано в следующем листинге кода.
|
1
2
|
final Date date = new Date(2016-1900, Calendar.SEPTEMBER, 26, 0, 0, 0);printDate("new Date(2016-1900, Calendar.SEPTEMBER, 26, 0, 0, 0)", date); |
С использованным месяцем, начинающимся с нуля, и с предполагаемым годом, выраженным как текущий год, за вычетом 1900, Date корректно создается, как показано в следующем выходном списке.
|
1
2
3
|
============================================================== new Date(2016-1900, Calendar.SEPTEMBER, 26, 0, 0, 0) -> Mon Sep 26 00:00:00 MDT 2016============================================================= |
Документация Javadoc для Date действительно описывает эти нюансы, но это напоминание о том, что зачастую лучше иметь понятные и понятные API, которые не нуждаются в нюансах, описанных в комментариях. Javadoc для конструктора Date (int, int, int, int, int, int) действительно объявляет, что из него нужно вычесть 1900 год и что месяцы представлены целыми числами от 0 до 11. Он также описывает, почему это шесть целых чисел конструктор не рекомендуется : «Начиная с версии 1.1 JDK, заменен на Calendar.set (год + 1900, месяц, дата, часы, минуты, секунды) или GregorianCalendar (год + 1900, месяц, дата, часы, минуты, секунды).»
Подобный шестизначный конструктор GregorianCalendar (int, int, int, int, int, int) не считается устаревшим и, хотя он все еще ожидает параметр месяца, начинающийся с нуля, он не ожидает, что один из них вычтет фактический год к 1900 году при проверке параметр года. Когда месяц указывается с использованием соответствующей константы Calendar месяца, это делает вызов API гораздо более читабельным, если для года можно передать 2016 год, а для месяца — Calendar.SEPTEMBER .
Я использую класс Date напрямую, так что теперь я забываю его нюансы и должен заново их изучить, когда мне представится редкий случай снова использовать Date . Итак, я оставляю эти наблюдения относительно Date для моего будущего я.
- Если используется Java 8+, используйте Java 8 Date / Time API.
- Если вы используете версию Java до Java 8, используйте Joda-Time или другую улучшенную библиотеку Java.
- Если вы не можете использовать Java 8 или стороннюю библиотеку, используйте
CalendarвместоDateв максимально возможной степени, особенно для создания экземпляров. - Если в любом случае используется
Date, создайте экземплярDateс помощью подходаSimpleDateFormat.parse(String)или с помощьюDate(long)для создания экземпляраDateна основе миллисекунд с нулевой эпохи. - При использовании конструкторов
Dateпринимающих несколько целых чисел, представляющих компоненты даты / времени по отдельности, используйте соответствующее полеCalendarмесяца, чтобы сделать вызовы API более читабельными, и подумайте о написании простого компоновщика, чтобы «обернуть» вызовы конструктору с шестью целыми числами.
Мы можем многое узнать о том, что делает API полезным и легким для изучения, а что делает API более трудным для изучения с помощью API других людей. Надеемся, что полученные уроки помогут нам в написании наших собственных API. Конструктор Date(int, int, int, int, int, int) который был в центре внимания этого поста, представляет несколько проблем, которые делают API менее оптимальным. Несколько параметров одного и того же типа облегчают предоставление параметров не по порядку, а «ненатуральные» правила, связанные с указанием года и месяца, создают дополнительную нагрузку на разработчика клиента, чтобы он читал Javadoc, чтобы понять эти не столь очевидные правила.
| Ссылка: | Болезненное напоминание о дате Java Нюансы от нашего партнера JCG Дастина Маркса в блоге Inspired by Actual Events . |