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




Я считаю, что вы можете использовать объект Calendar (просто установите FirstDayOfWeek на Monday и MinimalDaysInFirstWeek на 4, чтобы он соответствовал ISO 8601) и вызовите get (Calendar.WEEK_OF_YEAR).
Работает в большинстве случаев, но не всегда. Проблема описывается здесь (да, это .Net-Code, но такая же Проблема). Правильное решение см. В ответе Базиля Бурка.
Библиотека 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 requiredWeeks 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.
это наоборот: дает вам дату понедельника недели (на 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 - Я думаю.
вместо calendar.get(Calendar.YEAR) используйте calendar.getWeekYear(), чтобы получить год недели в соответствии с ISO 8601
@JoopEggen Иногда это может быть правдой, но не всегда, потому что GregorianCalendar зависит от локали. Таким образом, в зависимости от выбранного языкового стандарта (или языкового стандарта по умолчанию) значение минимальное количество дней в первую неделю, а также первый день недели может варьироваться. Javadoc GregorianCalendar предоставляет некоторую информацию об этом.
Как вы получаете дату в своем примере? Часовой пояс не известен, если указаны только год, месяц и день.
Класс 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
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
Поддержка 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 8 и новее. Эти классы заменяют неудобные старые классы даты и времени наследие, такие как java.util.Date, Calendar, & SimpleDateFormat.
Чтобы узнать больше, см. Учебник Oracle. И поищите в Stack Overflow множество примеров и объяснений. Спецификация - JSR 310.
Проект Джода-Тайм, теперь в Режим обслуживания, советует перейти на классы java.time.
Вы можете обмениваться объектами java.time напрямую с вашей базой данных. Используйте Драйвер JDBC, совместимый с JDBC 4.2 или более поздней версии. Нет необходимости в строках, нет необходимости в классах java.sql.*.
Где взять классы java.time?
Проект 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. Известно, что старые классы доставляют много хлопот.
Как вы получаете дату в своем примере? Часовой пояс не известен, если указаны только год, месяц и день.
Ваши доводы и / или доказательства ...?