Каков хороший и простой способ вычислить номер недели по ISO 8601?

Предположим, у меня есть дата, т.е. год, месяц и день в виде целых чисел. Какой хороший (правильный), краткий и достаточно читаемый алгоритм для вычисления ISO 8601номер недели недели, на которую попадает данная дата? Я наткнулся на действительно ужасный код, который заставляет меня думать, что должен быть способ получше.

Я хочу сделать это на Java, но псевдокод для любого объектно-ориентированного языка - это нормально.

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
20
0
12 873
8

Ответы 8

Я считаю, что вы можете использовать объект Calendar (просто установите FirstDayOfWeek на Monday и MinimalDaysInFirstWeek на 4, чтобы он соответствовал ISO 8601) и вызовите get (Calendar.WEEK_OF_YEAR).

Ваши доводы и / или доказательства ...?

technophile 16.02.2015 19:08

Работает в большинстве случаев, но не всегда. Проблема описывается здесь (да, это .Net-Code, но такая же Проблема). Правильное решение см. В ответе Базиля Бурка.

VinZ 26.09.2019 18:11

Библиотека joda-time имеет календарь ISO8601 и предоставляет следующие функции:

http://joda-time.sourceforge.net/cal_iso.html

yyyy-Www-dTHH:MM:SS.SSS This format of ISO8601 has the following fields:

* four digit weekyear, see rules below
* two digit week of year, from 01 to 53
* one digit day of week, from 1 to 7 where 1 is Monday and 7 is Sunday
* two digit hour, from 00 to 23
* two digit minute, from 00 to 59
* two digit second, from 00 to 59
* three decimal places for milliseconds if required

Weeks are always complete, and the first week of a year is the one that includes the first Thursday of the year. This definition can mean that the first week of a year starts in the previous year, and the last week finishes in the next year. The weekyear field is defined to refer to the year that owns the week, which may differ from the actual year.

Результатом всего этого является то, что вы создаете объект DateTime и вызываете довольно запутанный (но логичный) объект с именем getWeekOfWeekyear (), где weekyear - это конкретное недельное определение года, используемое ISO8601.

В общем, joda-time - фантастически полезный API, я полностью прекратил использовать java.util.Calendar и java.util.Date, за исключением тех случаев, когда мне нужно взаимодействовать с API, который их использует.

Если вы хотите быть на переднем крае, вы можете использовать последняя капля кодовой базы JSR-310 (API даты и времени), который возглавляет Стивен Колебурн (из Joda Fame). Его удобный интерфейс, по сути, является переработкой Joda снизу вверх.

К вашему сведению, JSR 310 теперь реализован в Java 8 как пакет java.time.

Basil Bourque 25.05.2014 11:25

это наоборот: дает вам дату понедельника недели (на perl)

use POSIX qw(mktime);
use Time::localtime;

sub monday_of_week {
    my $year=shift;
    my $week=shift;
    my $p_date=shift;

    my $seconds_1_jan=mktime(0,0,0,1,0,$year-1900,0,0,0);
    my $t1=localtime($seconds_1_jan);
    my $seconds_for_week;
    if (@$t1[6] < 5) {
#first of january is a thursday (or below)
        $seconds_for_week=$seconds_1_jan+3600*24*(7*($week-1)-@$t1[6]+1);
    } else {
        $seconds_for_week=$seconds_1_jan+3600*24*(7*($week-1)-@$t1[6]+8);
    }
    my $wt=localtime($seconds_for_week);
    $$p_date=sprintf("%02d/%02d/%04d",@$wt[3],@$wt[4]+1,@$wt[5]+1900);
}

/* Build a calendar suitable to extract ISO8601 week numbers
 * (see http://en.wikipedia.org/wiki/ISO_8601_week_number) */
Calendar calendar = Calendar.getInstance();
calendar.setMinimalDaysInFirstWeek(4);
calendar.setFirstDayOfWeek(Calendar.MONDAY);

/* Set date */
calendar.setTime(date);

/* Get ISO8601 week number */
calendar.get(Calendar.WEEK_OF_YEAR);

Фактически setMinimalDaysInFirstWeek(4) (минимум пол недели в начале года) является стандартом по умолчанию и не нужен в java - Я думаю.

Joop Eggen 10.02.2014 19:01

вместо calendar.get(Calendar.YEAR) используйте calendar.getWeekYear(), чтобы получить год недели в соответствии с ISO 8601

Archit 27.01.2015 09:09

@JoopEggen Иногда это может быть правдой, но не всегда, потому что GregorianCalendar зависит от локали. Таким образом, в зависимости от выбранного языкового стандарта (или языкового стандарта по умолчанию) значение минимальное количество дней в первую неделю, а также первый день недели может варьироваться. Javadoc GregorianCalendar предоставляет некоторую информацию об этом.

Sky 30.08.2016 21:28

Как вы получаете дату в своем примере? Часовой пояс не известен, если указаны только год, месяц и день.

Gustave 12.05.2018 09:06

Класс Calendar почти работает, но недельный год ISO не совпадает с тем, что сообщает система, совместимая с «Olson's Timezone package». Этот пример из коробки Linux показывает, как недельное значение года (2009) может отличаться от фактического года (2010):

$ TZ=UTC /usr/bin/date --date = "2010-01-01 12:34:56" "+%a %b %d %T %Z  %%Y=%Y,%%G=%G  %%W=%W,%%V=%V  %s"
Fri Jan 01 12:34:56 UTC  %Y=2010,%G=2009  %W=00,%V=53  1262349296

Но класс Java Calendar по-прежнему сообщает 2010, хотя неделя в году правильная. Классы Joda-Time, упомянутые Скаффман, справляются с этим правильно:

import java.util.Calendar;
import java.util.TimeZone;
import org.joda.time.DateTime;

Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
cal.setTimeInMillis(1262349296 * 1000L);
cal.setMinimalDaysInFirstWeek(4);
cal.setFirstDayOfWeek(Calendar.MONDAY);
System.out.println(cal.get(Calendar.WEEK_OF_YEAR));     // %V
System.out.println(cal.get(Calendar.YEAR));             // %G

DateTime dt = new DateTime(1262349296 * 1000L);
System.out.println(dt.getWeekOfWeekyear());             // %V
System.out.println(dt.getWeekyear());                   // %G

Запуск этой программы показывает:

53 2010 53 2009

Таким образом, номер недели ISO 8601 правильный из календаря, а год, основанный на неделях, - нет.

Страница руководства для отчетов strftime (3):

  %G     The ISO 8601 week-based year (see NOTES) with century as  a  decimal  number.   The
         4-digit year corresponding to the ISO week number (see %V).  This has the same for‐
         mat and value as %Y, except that if the ISO week number belongs to the previous  or
         next year, that year is used instead. (TZ)

вместо cal.get(Calendar.YEAR) используйте cal.getWeekYear(), чтобы получить год недели в соответствии с ISO 8601

Archit 27.01.2015 09:11

tl; dr

LocalDate.of( 2015 , 12 , 30 )
         .get ( 
             IsoFields.WEEK_OF_WEEK_BASED_YEAR 
         )

53

…или же…

org.threeten.extra.YearWeek.from (
    LocalDate.of( 2015 , 12 , 30 )
)

2015-W53

java.time

Поддержка ISO 8601 неделя теперь встроена в Java 8 и более поздние версии, в структуру java.time. Избегайте старых и заведомо проблемных классов java.util.Date/.Calendar, поскольку они были вытеснены java.time.

Эти новые классы java.time включают LocalDate для значения только даты без времени суток или часового пояса. Обратите внимание, что вы должны указать часовой пояс, чтобы определить «сегодня», поскольку дата в разных странах мира одновременно не совпадает.

ZoneId zoneId = ZoneId.of ( "America/Montreal" );
ZonedDateTime now = ZonedDateTime.now ( zoneId );

Или укажите год, месяц и день месяца, как предложено в Вопросе.

LocalDate localDate = LocalDate.of( year , month , dayOfMonth );

Класс IsoFields предоставляет информацию в соответствии со стандартом ISO 8601, включая неделю в году для недельного года.

int calendarYear = now.getYear();
int weekNumber = now.get ( IsoFields.WEEK_OF_WEEK_BASED_YEAR );
int weekYear = now.get ( IsoFields.WEEK_BASED_YEAR ); 

Вблизи начала / конца года недельный год может отличаться от календарного года на ± 1. Например, обратите внимание на разницу между григорианским календарем и календарем ISO 8601 на конец 2015 года: 52 и 1 недели становятся 52 и 53.

ТриТен-Экстра - YearWeek

Класс YearWeek представляет как номер недели на основе недели ISO 8601, и, так и номер недели вместе как единый объект. Этот класс находится в проекте ThreeTen-Extra. Проект добавляет функциональность классам java.time, встроенным в Java.

ZoneId zoneId = ZoneId.of ( "America/Montreal" );
YearWeek yw = YearWeek.now( zoneId ) ;

Создайте YearWeek из даты.

YearWeek yw = YearWeek.from (
    LocalDate.of( 2015 , 12 , 30 )
)

Этот класс может генерировать и анализировать строки в стандартном формате ISO 8601.

String output = yw.toString() ;

2015-W53

YearWeek yw = YearWeek.parse( "2015-W53" ) ;

Вы можете извлечь номер недели или годовой номер недели.

int weekNumber = yw.getWeek() ;
int weekBasedYearNumber = yw.getYear() ;

Вы можете сгенерировать конкретную дату (LocalDate), указав желаемый день недели в пределах этой недели. Чтобы указать день недели, используйте перечисление DayOfWeek, встроенное в Java 8 и новее.

LocalDate ld = yw.atDay( DayOfWeek.WEDNESDAY ) ;

О java.time

Фреймворк java.time встроен в Java 8 и новее. Эти классы заменяют неудобные старые классы даты и времени наследие, такие как java.util.Date, Calendar, & SimpleDateFormat.

Чтобы узнать больше, см. Учебник Oracle. И поищите в Stack Overflow множество примеров и объяснений. Спецификация - JSR 310.

Проект Джода-Тайм, теперь в Режим обслуживания, советует перейти на классы java.time.

Вы можете обмениваться объектами java.time напрямую с вашей базой данных. Используйте Драйвер JDBC, совместимый с JDBC 4.2 или более поздней версии. Нет необходимости в строках, нет необходимости в классах java.sql.*.

Где взять классы java.time?

  • Java SE 8, Java SE 9, Java SE 10, Java SE 11 и более поздние версии - часть стандартного Java API с объединенной реализацией.
    • В Java 9 добавлены некоторые незначительные функции и исправления.
  • Java SE 6 и Java SE 7
    • Большая часть функциональности java.time перенесена на Java 6 и 7 в ThreeTen-Backport.
  • Android
    • Более поздние версии Android связывают реализации классов java.time.
    • Для более ранних версий Android (<26) проект ThreeTenABP адаптирует ThreeTen-Backport (упомянутый выше). См. Как использовать ThreeTenABP….

Проект ThreeTen-Extra расширяет java.time дополнительными классами. Этот проект является испытательной площадкой для возможных будущих дополнений к java.time. Здесь вы можете найти несколько полезных классов, таких как Interval, YearWeek, YearQuarter и более.

Только Java.util.Calendar может помочь: Вы можете создать экземпляр календаря и установить Воскресенье и Минимальные дни в первую неделю

Calendar calendar = Calendar.getInstance();
calendar.setMinimalDaysInFirstWeek(4);
calendar.setFirstDayOfWeek(Calendar.MONDAY);
calendar.setTime(date);

// Now you are ready to take the week of year.
calendar.get(Calendar.WEEK_OF_YEAR);

Это обеспечивается javaDoc

The week determination is compatible with the ISO 8601 standard when getFirstDayOfWeek() is MONDAY and getMinimalDaysInFirstWeek() is 4, which values are used in locales where the standard is preferred. These values can explicitly be set by calling setFirstDayOfWeek() and setMinimalDaysInFirstWeek().

Хороший ответ для тех, кто не имеет доступа ни к фреймворку java.time в Java 8+, ни к библиотеке Джода-Тайм. Но в противном случае следует избегать старых классов даты и времени, таких как java.util.Date/.Calendar, которые были связаны с самыми ранними версиями Java. Известно, что старые классы доставляют много хлопот.

Basil Bourque 04.02.2016 00:49

Как вы получаете дату в своем примере? Часовой пояс не известен, если указаны только год, месяц и день.

Gustave 12.05.2018 09:04

Другие вопросы по теме