Почему Java 8 ZonedDateTime считает, что 24:01 является допустимым представлением строки времени?

Мне нужно получить два экземпляра ZonedDateTime, представляющие начало и конец текущей недели.

ZonedDateTime currentDateTime = ZonedDateTime.now();
ZonedDateTime startOfThisWeek = currentDateTime.with(DayOfWeek.MONDAY).truncatedTo(ChronoUnit.DAYS).plusMinutes(1);
ZonedDateTime endOfThisWeek = startOfThisWeek.plusDays(6);
DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd kk:mm");
String startweek = startOfThisWeek.format(df);
String endweek = endOfThisWeek.format(df);
System.out.println(startweek);
System.out.println(endweek);

Выход:

2018-06-18 24:01
2018-06-24 24:01

Что представляет эта строка? Это 1 минута после полуночи, утро 2018-06-18? Если да, то почему 24:01 действительное время? Я думал, что это должно быть 00:01 в 24-часовом представлении часов.

Изменение: и есть ли способ форматировать это, чтобы показать 00:01 вместо того, чтобы преобразовать строку в новую строку?

Ответ 1

ТЛ; др

  • Измените шаблон форматирования с kk на HH.
  • Ваше добавление одной минуты не имеет значения.

2018-06-18 24:01

... становится:

2018-06-18 00:01

Совет. При отладке используйте стандартный стандарт ISO 8601 при создании String.

ZonedDateTime.now( 
    ZoneId.of( "Africa/Tunis" ) 
)
.truncatedTo( ChronoUnit.DAYS )
.with( 
    TemporalAdjusters.previousOrSame( DayOfWeek.MONDAY ) 
)
.toString()

2018-06-18T00: 00 + 01: 00 [Африка/Тунис]

k= 1-24 часа (фактически, 24-23)

Удаленный ответ Jorn Vernee был прав: вы используете k который задокументирован как представляющий время суток, используя 24-часовое время работы от 1 до 24.

Что не так ясно в документе, так это то, что первый час считается равным 24, а не 0. Поэтому лучше сказать, что день проходит от 24-23, то есть 24, 1, 2, 3, …, 22, 23. Значение 24 представляет собой первый час дня, а не последний.

Вот простой пример, использующий время суток, чтобы продемонстрировать это поведение.

DateTimeFormatter f = DateTimeFormatter.ofPattern( "kk:mm" );
LocalTime lt = LocalTime.MIN;
String output = lt.format( f );

System.out.println( lt.toString() );  // Standard ISO 8601 format, 00-24.
System.out.println( output );         // 'kk' format is 24, 1 , 2, … , 22 , 23.

00:00

24:00

Обратите внимание, что это kk поведение java.time, где весь первый час дня помечен 24 необычным, нестандартным и не используется. См. Википедию. По-видимому, этот стиль иногда используется в некоторых специальных контекстах, где рабочие часы выходят за полночь, такие как трансляция.

Что же вы ожидали...

это должно быть 00:01 в представлении часов 24 часа.

Если вы хотите 0-23, используйте "H", как описано.

Во-первых, посмотрите на исходный формат ISO 8601.

ZonedDateTime currentDateTime = ZonedDateTime.now();
ZonedDateTime startOfThisWeek = currentDateTime.with( DayOfWeek.MONDAY ).truncatedTo( ChronoUnit.DAYS ).plusMinutes( 1 );
ZonedDateTime endOfThisWeek = startOfThisWeek.plusDays( 6 );
System.out.println( startOfThisWeek );
System.out.println( endOfThisWeek );

2018-06-18T00: 01-07: 00 [Америка /Los_Angeles]

2018-06-24T00: 01-07: 00 [Америка /Los_Angeles]

Теперь ваш пользовательский формат. Измените шаблон форматирования с kk на HH.

DateTimeFormatter df = DateTimeFormatter.ofPattern( "yyyy-MM-dd HH:mm" );
String startweek = startOfThisWeek.format( df );
String endweek = endOfThisWeek.format( df );
System.out.println( startweek );
System.out.println( endweek );

2018-06-18 00:01

2018-06-24 00:01

Неделю

Некоторые другие примечания...

Если вы пытаетесь представить неделю, нет необходимости добавлять минута. В целом наилучшей практикой представления промежуточного времени является подход Half-Open, где начало включено, в то время как окончание является эксклюзивным. Таким образом, неделя начинается в первый момент одного понедельника и проходит, но не включает, первый момент следующего понедельника.

Кроме того, я рекомендую всегда пропускание ZoneId явно в now, даже если это будет ZoneId.systemDefault, чтобы сделать ваши намерения кристально чистыми.

При настройке на понедельник возникает вопрос, что делать, если сегодня уже понедельник. Используйте любую из этих двух TemporalAdjuster найденных в TemporalAdjusters чтобы указать свой выбор поведения:

Пример кода.

ZonedDateTime now = ZonedDateTime.now( ZoneId.of( "Africa/Tunis" ) ) ;  // Or 'ZoneId.systemDefault()'.
ZonedDateTime weekStart = now.truncatedTo( ChronoUnit.DAYS ).with( TemporalAdjusters.previousOrSame( DayOfWeek.MONDAY ) );
ZonedDateTime weekStop = weekStart.plusWeeks( 1L ) ;

Для отслеживания и отладки всегда генерируйте строку, представляющую значение ваших объектов времени, использующих стандартный формат ISO 8601, а не собственный формат, как ваш вопрос. Классы java.time по умолчанию используют стандартные форматы в своих методах toString.

String outputStart = weekStart.toString() ;
String outputStop = weekStop.toString() ;

2018-06-18T00: 00 + 01: 00 [Африка/Тунис]

2018-06-25T00: 00 + 01: 00 [Африка/Тунис]

Если вам нужны значения только для даты, используйте LocalDateTime.

Вы можете представить свой временной диапазон в одном объекте, используя LocalDateRange или Interval найденные в проекте ThreeTen-Extra.

Последний совет: Пусть java.time делать тяжелую-лифтинг с помощью DateTimeFormatter.ofLocalized… методы автоматически локализовать сгенерированные строки, используя указанный Locale для определения человеческого языка и культурных норм форматирования.