Статьи

Форматирование / анализ даты / времени, стиль Java 8

С самого начала Java разработчики Java работали с датами и временем с помощью класса java.util.Date (начиная с JDK 1.0), а затем класса java.util.Calendar (начиная с JDK 1.1 ). За это время сотни тысяч (или, возможно, миллионов) разработчиков Java отформатировали и проанализировали даты и время Java, используя java.text.DateFormat и java.text.SimpleDateFormat . Учитывая, как часто это делалось на протяжении многих лет, неудивительно, что есть многочисленные онлайн- примеры и учебники по разбору и форматированию дат и времени с этими классами. Классические учебники по Java охватывают эти классы java.util и java.text на уроке форматирования ( даты и время ). Новый журнал «Дата-время» в Учебниках по Java охватывает новые классы Java 8 для дат и времени, а также их форматирование и анализ . В этом посте приведены примеры таких действий.

Перед демонстрацией разбора / форматирования даты / времени в стиле Java 8 с примерами полезно сравнить описания Javadoc для DateFormat / SimpleDateFormat и DateTimeFormatter . В приведенной ниже таблице содержится дифференцирующая информация, которую можно получить прямо или косвенно из сравнения Javadoc для каждого класса форматирования. Возможно, наиболее важные наблюдения из этой таблицы заключаются в том, что новый DateTimeFormatter является поточно-ориентированным и неизменным, а также общий обзор API, которые DateTimeFormatter предоставляет для анализа и форматирования дат и времени.

Характерная черта DateFormat / SimpleDateFormat DateTimeFormatter
Цель «Форматирует и анализирует даты или время независимо от языка» «Форматтер для печати и анализа объектов даты и времени».
В основном используется с java.util.Date
java.util.Calendar
java.time.LocalDate
java.time.LocalTime
java.time.LocalDateTime
java.time.OffsetTime
java.time.OffsetDateTime
java.time.ZonedDateTime
java.time.Instant
Поток безопасности «Форматы даты не синхронизированы.» «Этот класс неизменен и поточно-ориентирован».
Прямое форматирование Формат (Дата) Формат (TemporalAccessor)
Прямой анализ синтаксический анализ (String) parse (CharSequence, TemporalQuery)
Непрямое форматирование Нет [если вы не используете расширение Groovy Date.format (String) )] LocalDate.format (DateTimeFormatter)
LocalTime.format (DateTimeFormatter)
LocalDateTime.format (DateTimeFormatter)
OffsetTime.format (DateTimeFormatter)
OffsetDateTime.format (DateTimeFormatter)
ZonedDateTime.format (DateTimeFormatter)
Непрямой анализ Нет [если только вы не используете устаревшее расширение Date.parse (String) или расширение Groovy Date.parse (String, String) ] LocalDate.parse (CharSequence, DateTimeFormatter)
LocalTime.parse (CharSequence, DateTimeFormatter)
LocalDateTime.parse (CharSequence, DateTimeFormatter)
OffsetTime.parse (CharSequence, DateTimeFormatter)
OffsetDateTime.parse (CharSequence, DateTimeFormatter)
ZonedDateTime.parse (CharSequence, DateTimeFormatter)
интернационализация java.util.Locale java.util.Locale
Часовой пояс java.util.TimeZone java.time.ZoneId
java.time.ZoneOffset
Предопределенные форматеры Нет, но предлагает статические удобные методы для распространенных случаев:
getDateInstance ()
getDateInstance (целое)
getDateInstance (int, Locale)
getDateTimeInstance ()
getDateTimeInstance (int, int)
getDateTimeInstance (int, int, Locale)
деЫпзЬапсе ()
getTimeInstance ()
getTimeInstance (целое)
getTimeInstance (int, Locale)
ISO_LOCAL_DATE
ISO_LOCAL_TIME
ISO_LOCAL_DATE_TIME
ISO_OFFSET_DATE
ISO_OFFSET_TIME
ISO_OFFSET_DATE_TIME
ISO_ZONED_DATE_TIME
BASIC_ISO_DATE
ISO_DATE
ISO_DATE_TIME
ISO_ORDINAL_DATE
ISO_INSTANT ISO_WEEK_DATE
RFC_1123_DATE_TIME

В оставшейся части этого поста используются примеры для демонстрации форматирования и анализа дат в Java 8 с помощью конструкций java.time . В примерах будут использованы следующие строковые шаблоны и экземпляры.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/** Pattern to use for String representation of Dates/Times. */
private final String dateTimeFormatPattern = "yyyy/MM/dd HH:mm:ss z";
 
/**
 * java.util.Date instance representing now that can
 * be formatted using SimpleDateFormat based on my
 * dateTimeFormatPattern field.
 */
private final Date now = new Date();
 
/**
 * java.time.ZonedDateTime instance representing now that can
 * be formatted using DateTimeFormatter based on my
 * dateTimeFormatPattern field.
 *
 * Note that ZonedDateTime needed to be used in this example
 * instead of java.time.LocalDateTime or java.time.OffsetDateTime
 * because there is zone information in the format provided by
 * my dateTimeFormatPattern field and attempting to have
 * DateTimeFormatter.format(TemporalAccessor) instantiated
 * with a format pattern that includes time zone details
 * will lead to DateTimeException for instances of
 * TemporalAccessor that do not have time zone information
 * (such as LocalDateTime and OffsetDateTime).
 */
private final ZonedDateTime now8 = ZonedDateTime.now();
 
 
/**
 * String that can be used by both SimpleDateFormat and
 * DateTimeFormatter to parse respective date/time instances
 * from this String.
 */
private final String dateTimeString = "2014/09/03 13:59:50 MDT";

До Java 8 стандартный подход Java для дат и времени был через классы Date и Calendar, а стандартный подход к анализу и форматированию дат — через DateFormat и SimpleDateFormat . Следующий листинг кода демонстрирует эти классические подходы.

Форматирование и анализ дат Java с помощью SimpleDateFormat

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
 * Demonstrate presenting java.util.Date as String matching
 * provided pattern via use of SimpleDateFormat.
 */
public void demonstrateSimpleDateFormatFormatting()
{
   final DateFormat format = new SimpleDateFormat(dateTimeFormatPattern);
   final String nowString = format.format(now);
   out.println(
        "Date '" + now + "' formatted with SimpleDateFormat and '"
      + dateTimeFormatPattern + "': " + nowString);
}
 
/**
 * Demonstrate parsing a java.util.Date from a String
 * via SimpleDateFormat.
 */
public void demonstrateSimpleDateFormatParsing()
{
   final DateFormat format = new SimpleDateFormat(dateTimeFormatPattern);
   try
   {
      final Date parsedDate = format.parse(dateTimeString);
      out.println("'" + dateTimeString + "' is parsed with SimpleDateFormat as " + parsedDate);
   }
   // DateFormat.parse(String) throws a checked exception
   catch (ParseException parseException)
   {
      out.println(
           "ERROR: Unable to parse date/time String '"
         + dateTimeString + "' with pattern '"
         + dateTimeFormatPattern + "'.");
   }
}

В Java 8 предпочтительные классы даты / времени больше не находятся в пакете java.util , а предпочтительные классы обработки даты / времени теперь находятся в пакете java.time . Точно так же предпочтительные классы форматирования / разбора даты / времени больше не находятся в пакете java.text , а вместо этого приходят из пакета java.time.format .

Пакет java.time предлагает множество классов для моделирования дат и / или времени. К ним относятся классы, которые моделируют только даты (без информации о времени), классы, которые моделируют только время (без информации о дате), классы, которые моделируют информацию о дате и времени, классы, которые используют информацию о часовом поясе, и классы, которые не включают информацию о часовом поясе. Подход к их форматированию и синтаксическому анализу обычно аналогичен, хотя характеристики класса (например, поддерживает ли он информацию о дате, времени или часовом поясе) влияют на то, какие шаблоны можно применять. В этом посте я использую класс ZonedDateTime для своих примеров. Причина такого выбора заключается в том, что он включает в себя информацию о дате, времени и часовом поясе и позволяет мне использовать шаблон сопоставления, который включает все три из этих характеристик, как это делает экземпляр Date или Calendar . Это облегчает сравнение старого и нового подходов.

Класс DateTimeFormatter предоставляет методы ofPattern для предоставления экземпляра DateTimeFormatter на основе предоставленной строки шаблона даты / времени. Затем для этого экземпляра DateTimeFormatter можно вызвать один из методов форматирования, чтобы получить информацию о дате и / или времени, отформатированную как строка, соответствующую предоставленному шаблону. Следующий листинг кода иллюстрирует этот подход к форматированию String из ZonedDateTime на основе предоставленного шаблона.

Форматирование ZonedDateTime как String

01
02
03
04
05
06
07
08
09
10
11
12
13
14
/**
 * Demonstrate presenting ZonedDateTime as a String matching
 * provided pattern via DateTimeFormatter and its
 * ofPattern(String) method.
 */
public void demonstrateDateTimeFormatFormatting()
{
   final DateTimeFormatter formatter =
      DateTimeFormatter.ofPattern(dateTimeFormatPattern);
   final String nowString = formatter.format(now8);
   out.println(
        now8 + " formatted with DateTimeFormatter and '"
      + dateTimeFormatPattern + "': " + nowString);
}

Разбор класса даты / времени из String на основе шаблона легко выполняется. Есть несколько способов, которыми это может быть достигнуто. Один из подходов заключается в передаче экземпляра DateTimeFormatter статическому методу ZonedDateTime.parse (CharSequence, DateTimeFormatter) , который возвращает экземпляр ZonedDateTime полученный из предоставленной последовательности символов и основанный на предоставленном шаблоне. Это показано в следующем листинге кода.

Разбор ZonedDateTime из строки с использованием статического метода ZonedDateTime.parse

01
02
03
04
05
06
07
08
09
10
11
12
13
14
/**
 * Demonstrate parsing ZonedDateTime from provided String
 * via ZonedDateTime's parse(String, DateTimeFormatter) method.
 */
public void demonstrateDateTimeFormatParsingTemporalStaticMethod()
{
   final DateTimeFormatter formatter =
      DateTimeFormatter.ofPattern(dateTimeFormatPattern);
   final ZonedDateTime zonedDateTime = ZonedDateTime.parse(dateTimeString, formatter);
   out.println(
        "'" + dateTimeString
      + "' is parsed with DateTimeFormatter and ZonedDateTime.parse as "
      + zonedDateTime);
}

Второй подход к анализу ZonedDateTime из String заключается в использовании метода синтаксического анализа DateTimeFormatter (CharSequence, TemporalQuery <T>) . Это показано в следующем листинге кода, который также предоставляет возможность продемонстрировать использование ссылки на метод Java 8 (см. ZonedDateTime::from ).

Разбор ZonedDateTime из String с использованием метода DateTimeFormatter.parse

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
/**
 * Demonstrate parsing ZonedDateTime from String
 * via DateTimeFormatter.parse(String, TemporaryQuery)
 * with the Temple Query in this case being ZonedDateTime's
 * from(TemporalAccessor) used as a Java 8 method reference.
 */
public void demonstrateDateTimeFormatParsingMethodReference()
{
   final DateTimeFormatter formatter =
      DateTimeFormatter.ofPattern(dateTimeFormatPattern);
   final ZonedDateTime zonedDateTime = formatter.parse(dateTimeString, ZonedDateTime::from);
   out.println(
        "'" + dateTimeString
      + "' is parsed with DateTimeFormatter and ZoneDateTime::from as "
      + zonedDateTime);
}

Очень немногие проекты могут позволить себе роскошь быть новым проектом, который может начинаться с Java 8. Поэтому полезно иметь классы, которые связывают классы даты / времени до JDK 8 с новыми классами даты / времени, представленными в JDK 8. Один Примером этого является способность DateTimeFormatter JDK 8 предоставлять нисходящий экземпляр абстрактного класса Format до JDK 8 с помощью метода DateTimeFormatter.toFormat () . Это продемонстрировано в следующем листинге кода.

Доступ к формату до JDK 8 из DateTimeFormatter JDK 8

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
/**
 * Demonstrate formatting ZonedDateTime via DateTimeFormatter,
 * but using implementation of Format.
 */
public void demonstrateDateTimeFormatAndFormatFormatting()
{
   final DateTimeFormatter formatter =
      DateTimeFormatter.ofPattern(dateTimeFormatPattern);
   final Format format = formatter.toFormat();
   final String nowString = format.format(now8);
   out.println(
        "ZonedDateTime " + now + " formatted with DateTimeFormatter/Format (and "
      + format.getClass().getCanonicalName() + ") and '"
      + dateTimeFormatPattern + "': " + nowString);
}

Класс Instant особенно важен при работе с классами Date и Calendar до JDK 8 в сочетании с новыми классами даты и времени, представленными в JDK 8. Причина Instant настолько важна, что java.util.Date имеет методы из (Instant ) и toInstant () для получения Date от момента и получения момента от Date соответственно. Поскольку Instant очень важен при переносе обработки даты / времени до Java 8 на базовые версии Java 8, следующий список кода демонстрирует примеры форматирования и анализа Instant .

Форматирование и анализ экземпляров Instant

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
/**
 * Demonstrate formatting and parsing an instance of Instant.
 */
public void demonstrateDateTimeFormatFormattingAndParsingInstant()
{
   // Instant instances don't have timezone information
   final Instant instant = now.toInstant();
   final DateTimeFormatter formatter =
      DateTimeFormatter.ofPattern(
         dateTimeFormatPattern).withZone(ZoneId.systemDefault());
   final String formattedInstance = formatter.format(instant);
   out.println(
        "Instant " + instant + " formatted with DateTimeFormatter and '"
      + dateTimeFormatPattern + "' to '" + formattedInstance + "'");
   final Instant instant2 =
      formatter.parse(formattedInstance, ZonedDateTime::from).toInstant();
      out.println(formattedInstance + " parsed back to " + instant2);
}

Все приведенные выше примеры взяты из примера класса, показанного в следующем листинге кода для полноты.

DateFormatDemo.java

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
package dustin.examples.numberformatdemo;
 
import static java.lang.System.out;
 
import java.text.DateFormat;
import java.text.Format;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
 
/**
 * Demonstrates formatting dates as strings and parsing strings
 * into dates and times using pre-Java 8 (java.text.SimpleDateFormat)
 * and Java 8 (java.time.format.DateTimeFormatter) mechanisms.
 */
public class DateFormatDemo
{
   /** Pattern to use for String representation of Dates/Times. */
   private final String dateTimeFormatPattern = "yyyy/MM/dd HH:mm:ss z";
 
   /**
    * java.util.Date instance representing now that can
    * be formatted using SimpleDateFormat based on my
    * dateTimeFormatPattern field.
    */
   private final Date now = new Date();
 
   /**
    * java.time.ZonedDateTime instance representing now that can
    * be formatted using DateTimeFormatter based on my
    * dateTimeFormatPattern field.
    *
    * Note that ZonedDateTime needed to be used in this example
    * instead of java.time.LocalDateTime or java.time.OffsetDateTime
    * because there is zone information in the format provided by
    * my dateTimeFormatPattern field and attempting to have
    * DateTimeFormatter.format(TemporalAccessor) instantiated
    * with a format pattern that includes time zone details
    * will lead to DateTimeException for instances of
    * TemporalAccessor that do not have time zone information
    * (such as LocalDateTime and OffsetDateTime).
    */
   private final ZonedDateTime now8 = ZonedDateTime.now();
 
 
   /**
    * String that can be used by both SimpleDateFormat and
    * DateTimeFormatter to parse respective date/time instances
    * from this String.
    */
   private final String dateTimeString = "2014/09/03 13:59:50 MDT";
 
   /**
    * Demonstrate presenting java.util.Date as String matching
    * provided pattern via use of SimpleDateFormat.
    */
   public void demonstrateSimpleDateFormatFormatting()
   {
      final DateFormat format = new SimpleDateFormat(dateTimeFormatPattern);
      final String nowString = format.format(now);
      out.println(
           "Date '" + now + "' formatted with SimpleDateFormat and '"
         + dateTimeFormatPattern + "': " + nowString);
   }
 
   /**
    * Demonstrate parsing a java.util.Date from a String
    * via SimpleDateFormat.
    */
   public void demonstrateSimpleDateFormatParsing()
   {
      final DateFormat format = new SimpleDateFormat(dateTimeFormatPattern);
      try
      {
         final Date parsedDate = format.parse(dateTimeString);
         out.println("'" + dateTimeString + "' is parsed with SimpleDateFormat as " + parsedDate);
      }
      // DateFormat.parse(String) throws a checked exception
      catch (ParseException parseException)
      {
         out.println(
              "ERROR: Unable to parse date/time String '"
            + dateTimeString + "' with pattern '"
            + dateTimeFormatPattern + "'.");
      }
   }
 
   /**
    * Demonstrate presenting ZonedDateTime as a String matching
    * provided pattern via DateTimeFormatter and its
    * ofPattern(String) method.
    */
   public void demonstrateDateTimeFormatFormatting()
   {
      final DateTimeFormatter formatter =
         DateTimeFormatter.ofPattern(dateTimeFormatPattern);
      final String nowString = formatter.format(now8);
      out.println(
           now8 + " formatted with DateTimeFormatter and '"
         + dateTimeFormatPattern + "': " + nowString);
   }
 
   /**
    * Demonstrate parsing ZonedDateTime from provided String
    * via ZonedDateTime's parse(String, DateTimeFormatter) method.
    */
   public void demonstrateDateTimeFormatParsingTemporalStaticMethod()
   {
      final DateTimeFormatter formatter =
         DateTimeFormatter.ofPattern(dateTimeFormatPattern);
      final ZonedDateTime zonedDateTime = ZonedDateTime.parse(dateTimeString, formatter);
      out.println(
           "'" + dateTimeString
         + "' is parsed with DateTimeFormatter and ZonedDateTime.parse as "
         + zonedDateTime);
   }
 
   /**
    * Demonstrate parsing ZonedDateTime from String
    * via DateTimeFormatter.parse(String, TemporaryQuery)
    * with the Temple Query in this case being ZonedDateTime's
    * from(TemporalAccessor) used as a Java 8 method reference.
    */
   public void demonstrateDateTimeFormatParsingMethodReference()
   {
      final DateTimeFormatter formatter =
         DateTimeFormatter.ofPattern(dateTimeFormatPattern);
      final ZonedDateTime zonedDateTime = formatter.parse(dateTimeString, ZonedDateTime::from);
      out.println(
           "'" + dateTimeString
         + "' is parsed with DateTimeFormatter and ZoneDateTime::from as "
         + zonedDateTime);
   }
 
   /**
    * Demonstrate formatting ZonedDateTime via DateTimeFormatter,
    * but using implementation of Format.
    */
   public void demonstrateDateTimeFormatAndFormatFormatting()
   {
      final DateTimeFormatter formatter =
         DateTimeFormatter.ofPattern(dateTimeFormatPattern);
      final Format format = formatter.toFormat();
      final String nowString = format.format(now8);
      out.println(
           "ZonedDateTime " + now + " formatted with DateTimeFormatter/Format (and "
         + format.getClass().getCanonicalName() + ") and '"
         + dateTimeFormatPattern + "': " + nowString);
   }
 
   /**
    * Demonstrate formatting and parsing an instance of Instant.
    */
   public void demonstrateDateTimeFormatFormattingAndParsingInstant()
   {
      // Instant instances don't have timezone information
      final Instant instant = now.toInstant();
      final DateTimeFormatter formatter =
         DateTimeFormatter.ofPattern(
            dateTimeFormatPattern).withZone(ZoneId.systemDefault());
      final String formattedInstance = formatter.format(instant);
      out.println(
           "Instant " + instant + " formatted with DateTimeFormatter and '"
         + dateTimeFormatPattern + "' to '" + formattedInstance + "'");
      final Instant instant2 =
         formatter.parse(formattedInstance, ZonedDateTime::from).toInstant();
      out.println(formattedInstance + " parsed back to " + instant2);
   }
 
   /**
    * Demonstrate java.text.SimpleDateFormat and
    * java.time.format.DateTimeFormatter.
    *
    * @param arguments Command-line arguments; none anticipated.
    */
   public static void main(final String[] arguments)
   {
      final DateFormatDemo demo = new DateFormatDemo();
      out.print("\n1: ");
      demo.demonstrateSimpleDateFormatFormatting();
      out.print("\n2: ");
      demo.demonstrateSimpleDateFormatParsing();
      out.print("\n3: ");
      demo.demonstrateDateTimeFormatFormatting();
      out.print("\n4: ");
      demo.demonstrateDateTimeFormatParsingTemporalStaticMethod();
      out.print("\n5: ");
      demo.demonstrateDateTimeFormatParsingMethodReference();
      out.print("\n6: ");
      demo.demonstrateDateTimeFormatAndFormatFormatting();
      out.print("\n7: ");
      demo.demonstrateDateTimeFormatFormattingAndParsingInstant();
   }
}

Результат выполнения вышеприведенной демонстрации показан на следующем снимке экрана.

jdk8DateTimeFormattingDemonstrationOutput

Вывод

Классы даты / времени JDK 8 и связанные с ними классы форматирования и синтаксического анализа намного проще в использовании, чем их аналоги до JDK 8. Этот пост попытался продемонстрировать, как применять эти новые классы и воспользоваться некоторыми из их преимуществ.