Какой лучший способ найти инверсию datetime.isocalendar ()?

Метод Python datetime.isocalendar() возвращает кортеж (ISO_year, ISO_week_number, ISO_weekday) для данного объекта datetime. Есть ли соответствующая обратная функция? Если нет, есть ли простой способ вычислить дату с учетом года, номера недели и дня недели?

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
51
0
13 688
8
Перейти к ответу Данный вопрос помечен как решенный

Ответы 8

Обновлено: игнорируйте это, крайние случаи - это боль. Пойдите с решением Бена.

Хорошо, при ближайшем рассмотрении я заметил, что strptime имеет параметры %W и %w, поэтому работает следующее:

def fromisocalendar(y,w,d):
   return datetime.strptime( "%04dW%02d-%d"%(y,w-1,d), "%YW%W-%w")

Пара ошибок: номер недели ISO начинается с 1, а %W начинается с 0. Рабочий день ISO начинается с 1 (понедельник), что совпадает с %w, поэтому воскресенье, вероятно, должно быть 0, а не 7 ...

Какую версию вы используете? Я тестировал 2.4.2 и у меня не получилось, нашел bugs.python.org/issue1045381, но сказал, что это сделано на 2.3

Vinko Vrsalovic 20.11.2008 06:58

Похоже, вы на него ответили. Почему бы не принять ваш ответ?

S.Lott 20.11.2008 15:17

@Tom: S.O. позволяет вам принимать свои собственные ответы. См. Это обсуждение по мета: meta.stackexchange.com/questions/9933/…

GreenMatt 11.02.2010 10:10

@GreenMatt, да, эта функция новая с тех пор, как я сделал этот комментарий. И в любом случае этот ответ не так хорош, как у Бена.

Tom 17.02.2010 12:22

@Tom: Ой! Не понял, что это было из '08.

GreenMatt 17.02.2010 16:54

это не удается в 2012/2013 гг.

Dominique Guardiola 22.12.2012 18:39

Обратите внимание, что% W - это неделя № (0-53), которая НЕ ТО ЖЕ, что неделя ISO (1-53). Будут крайние случаи, когда% W не будет работать.

Я как бы заметил, что в моем ответе выше, есть ли шанс, что вы могли бы расширить, какими будут эти крайние случаи?

Tom 21.12.2008 00:24

@Tom, например, 2009 и 2013 годы.

akaihola 02.01.2013 13:52
Ответ принят как подходящий

Python 3.8 добавил метод fromisocalendar ():

>>> datetime.fromisocalendar(2011, 22, 1)
datetime.datetime(2011, 5, 30, 0, 0)

Python 3.6 добавил Директивы %G, %V и %u:

>>> datetime.strptime('2011 22 1', '%G %V %u')
datetime.datetime(2011, 5, 30, 0, 0)

Оригинальный ответ

Недавно мне пришлось самому решать эту проблему, и я пришел к следующему решению:

import datetime

def iso_year_start(iso_year):
    "The gregorian calendar date of the first day of the given ISO year"
    fourth_jan = datetime.date(iso_year, 1, 4)
    delta = datetime.timedelta(fourth_jan.isoweekday()-1)
    return fourth_jan - delta 

def iso_to_gregorian(iso_year, iso_week, iso_day):
    "Gregorian calendar date for the given ISO year, week and day"
    year_start = iso_year_start(iso_year)
    return year_start + datetime.timedelta(days=iso_day-1, weeks=iso_week-1)

Несколько тестовых примеров:

>>> iso = datetime.date(2005, 1, 1).isocalendar()
>>> iso
(2004, 53, 6)
>>> iso_to_gregorian(*iso)
datetime.date(2005, 1, 1)

>>> iso = datetime.date(2010, 1, 4).isocalendar()    
>>> iso
(2010, 1, 1)
>>> iso_to_gregorian(*iso)
datetime.date(2010, 1, 4)

>>> iso = datetime.date(2010, 1, 3).isocalendar()
>>> iso
(2009, 53, 7)
>>> iso_to_gregorian(*iso)
datetime.date(2010, 1, 3)

Не могли бы вы прояснить значение 4 января? Есть ли подходящее описание календарного стандарта ISO?

Tom 10.11.2009 08:42

Том: из формального определения ISO недели 1 (недели, содержащей первый четверг года) следует, что 4 января - последнее, что может начаться неделя 1.

Ben James 10.11.2009 12:32

Спасибо за это решение. Однако генерация fourth_jan у меня не работает (с использованием Python 2.7), и мне пришлось написать: fourth_jan = datetime.strptime('{0}-01-04'.format(iso_year), '%Y-%m-%d').

Joël 06.03.2014 13:55

Это хороший ответ, но может быть проще. Вам не нужно определять дату начала года. Смотрите мой ответ.

jwg 13.10.2015 14:15
import datetime

def iso_to_gregorian(iso_year, iso_week, iso_day):
    "Gregorian calendar date for the given ISO year, week and day"
    fourth_jan = datetime.date(iso_year, 1, 4)
    _, fourth_jan_week, fourth_jan_day = fourth_jan.isocalendar()
    return fourth_jan + datetime.timedelta(days=iso_day-fourth_jan_day, weeks=iso_week-fourth_jan_week)

Это было адаптировано из очень хорошего ответа @BenJames. Вам не обязательно знать первый день года. Вам просто нужно знать пример даты, которая обязательно находится в том же году ISO, а также календарную неделю и день ISO этой даты.

4 января - это просто один из примеров, потому что, как указал Бен, 4 января всегда принадлежит одному и тому же году ISO и григорианскому календарю и является первым днем ​​года, в котором это происходит.

Поскольку все недели имеют одинаковую длину, вы можете просто вычесть дни и недели между ISO желаемой даты и ISO даты, которую вы знаете в обеих формах, и добавить это количество дней и недель. (Неважно, положительные или отрицательные эти числа, поэтому вы можете выбрать другой «фиксированный день», например, 28 декабря.)

Редактировать

Я исправил это, потому что, как любезно указал @JoSo, первым днем ​​григорианского года, который также принадлежит к году ISO, является 4 января, а не 5 января. Как говорится в объяснении, не имеет значения, какая дата выбрана в качестве ориентира, но выбор 4 января делает этот выбор менее «волшебным».

Подскажите, пожалуйста, действительно ли он работает безупречно в каждой ситуации?

Geeocode 30.10.2015 14:33

@GeorgeSolymosi Я использовал библиотеку тестирования гипотез Python для поиска любых допустимых объектов datetime, для которых эта функция не является инверсией isocalendar. Он ничего не нашел / См. github.com/jwg4/qual/blob/master/qual/tests/test_iso.py Это, конечно, не гарантия - мне было бы интересно узнать о каких-либо ошибках. Если вы работаете с логикой кода, вы можете быть уверены, а можете и не убедиться, что он работает правильно.

jwg 31.10.2015 13:19

это сложнее, чем необходимо. Посмотрите ответ Бена или в Википедии. 4 января тоже подойдет, и, кроме того, это всегда первая неделя ISO года.

Jo So 09.07.2016 19:08

@JoSo Чем это сложнее, чем ответ Бена?

jwg 11.07.2016 10:22

не лежит на первой неделе ISO в этом году. Вы можете использовать любую дату между 4 января и 28 декабря, но только 4 января всегда приходится на первую неделю. Выбор 5 января является странным, если вы рассматриваете Википедию, и вам нужно рассчитывать с неделей ISO 5 января, чего вы не сделаете, если возьмете 4 января (см. Мой ответ по очистке).

Jo So 11.07.2016 15:48

Вы правы насчет 4 января и 5 января. Как видно из моего ответа, по какой-то причине я ошибочно подумал, что 5 января было первым днем ​​григорианского года, гарантированно принадлежащего тому же году ISO. Что касается вашего ответа, я бы сказал, что они похожи по сложности. Вы звоните на isoweekday вместо isocalendar. Но я бы сказал, что ваше менее ясно - не сразу очевидно, является ли начало датой, количеством дней, днем ​​недели или чем-то еще, не говоря уже о том, что должно быть вычислено при первом вычислении timedelta.

jwg 11.07.2016 15:56

Начиная с Python 3.6, datetime.strptime() будет поддерживать директивы %G, %V и %u, поэтому вы можете просто использовать datetime.strptime('2015 1 2', '%G %V %u').date(). См .: https://hg.python.org/cpython/rev/acdebfbfbdcf

Начиная с Python 3.6, вы можете использовать новые директивы %G, %u и %V. См. выпуск 12006 и обновленная документация:

%G
ISO 8601 year with century representing the year that contains the greater part of the ISO week (%V).

%u
ISO 8601 weekday as a decimal number where 1 is Monday.

%V
ISO 8601 week as a decimal number with Monday as the first day of the week. Week 01 is the week containing Jan 4.

Учитывая строку с годом, номером недели и номером дня недели, легко преобразовать их в дату с помощью:

from datetime import datetime

datetime.strptime('2002 01 1', '%G %V %u').date()

или как функция с целочисленными входами:

from datetime import datetime

def date_from_isoweek(iso_year, iso_weeknumber, iso_weekday):
    return datetime.strptime(
        '{:04d} {:02d} {:d}'.format(iso_year, iso_weeknumber, iso_weekday),
        '%G %V %u').date()

Если вы получаете сообщение об ошибке AttributeError: type object 'datetime.date' has no attribute 'strptime', используйте вместо него from datetime import datetime.

yl_low 08.12.2018 02:48

@yl_low, это абсолютно верно, не знаю, почему я сделал эту ошибку здесь или почему так долго никто не заметил! Теперь все исправлено.

Martijn Pieters 08.12.2018 02:58

Для следующих людей, которые придут сюда, короткая версия хорошего решения Бена с одним разрешением:

def iso_to_gregorian(iso_year, iso_week, iso_day):
    jan4 = datetime.date(iso_year, 1, 4)
    start = jan4 - datetime.timedelta(days=jan4.isoweekday()-1)
    return start + datetime.timedelta(weeks=iso_week-1, days=iso_day-1)

Я придумал решение, подобное тому, что написал Бен Джеймс, но с использованием одной функции:

import datetime
def getDateFromWeek(year,week,day):
    """Method to retrieve the date from the specified week, year and weekday"""

    year_start = datetime.date(year,1,1)
    ys_weekday =  year_start.weekday()
    delta = (week*7)+(day-ys_weekday)
    if ys_weekday<4:
        delta -= 7

    return  year_start  + datetime.timedelta(days=delta)

Я протестировал его с граничными значениями, такими как последняя неделя 2020 года и первая неделя 2021 года, и это сработало довольно хорошо.

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