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

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

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

Ответы

Ответ 1

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

Ответ 2

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

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

yyyy-Www-dTHH: MM: SS.SSS Этот формат ISO8601 имеет следующие поля:

* 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

Неделя всегда полны, а первая неделя года - та, которая включает первый четверг год. Это определение может означать, что первая неделя года начинается в в прошлом году и на прошлой неделе заканчивается в следующем году. недельное поле определено для обозначения год, который владеет неделей, что может отличаются от фактического года.

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

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

Ответ 3

/* 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);

Ответ 4

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.

введите описание изображения здесь

введите описание изображения здесь

ThreeTen-Extra - YearWeek

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

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

Сгенерируйте a 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 ) ;

Ответ 5

Просто 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

Определение недели совместимо со стандартом ISO 8601, когда getFirstDayOfWeek() - ПОНЕДЕЛЬНИК и getMinimalDaysInFirstWeek() - 4, какие значения используются в местах, где стандарт является предпочтительным. Эти значения могут быть явно заданы вызовом setFirstDayOfWeek() и setMinimalDaysInFirstWeek().

Ответ 6

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

Ответ 7

Календарный класс почти работает, но год, основанный на ISO, не совпадает с тем, что сообщает система совместимых с Olson Timezone пакетов. Этот пример из окна 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, упомянутые skaffman, справляются с этим правильно:

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 верен из Календаря, но недельный год - нет.

Страница man для отчетов 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)

Ответ 8

это наоборот: дается дата понедельника недели (в 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)[email protected]$t1[6]+1);
    } else {
        $seconds_for_week=$seconds_1_jan+3600*24*(7*($week-1)[email protected]$t1[6]+8);
    }
    my $wt=localtime($seconds_for_week);
    $$p_date=sprintf("%02d/%02d/%04d",@$wt[3],@$wt[4]+1,@$wt[5]+1900);
}