Мне больше не нужно больше использовать 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 . |