代码之家  ›  专栏  ›  技术社区  ›  Tom Gruff

找到datetime.ioscalendar()的逆函数的最佳方法是什么?

  •  59
  • Tom Gruff  · 技术社区  · 16 年前

    蟒蛇 datetime.isocalendar() 方法返回一个元组 (ISO_year, ISO_week_number, ISO_weekday) 对于给定 datetime 对象。是否存在对应的逆函数?若并没有,是否有一种简单的方法来计算给定年份、周数和星期几的日期?

    8 回复  |  直到 13 年前
        1
  •  78
  •   Erik Cederstrand    5 年前

    Python 3.8增加了 fromisocalendar() 方法:

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

    Python 3.6增加了 %G , %V and %u directives :

    >>> 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)
    
        2
  •  17
  •   Martijn Pieters    6 年前

    从Python 3.6开始,你可以使用新的 %G , %u %V 指令。看见 issue 12006 以及 updated documentation :

    G
    ISO 8601年份,世纪代表包含ISO周大部分时间的年份( 五、 ).

    u
    ISO 8601工作日为十进制数,其中1为星期一。

    五、
    ISO 8601周为十进制数,星期一为一周的第一天。第01周是包含1月4日的一周。

    给定一个包含year、weeknumber和weekday number的字符串,很容易将其解析为日期:

    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()
    
        3
  •  10
  •   Jo So    9 年前
    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日历周和日。

    1月4日只是一个例子,因为正如Ben指出的那样,1月4号总是属于同一个ISO年和公历年,并且是一年中的第一天。

    由于周的长度都是一样的,你可以简单地减去你想要的日期的ISO和你在两种形式中都知道的日期的SO之间的天数和周数,再加上天数和周数来计算。(这些数字是正数还是负数并不重要,所以你可以选择其他“固定日期”,比如12月28日。)

    编辑

    我纠正了这一点,因为正如@JoSo所指出的那样,同样属于ISO年的公历年的第一天是1月4日,而不是1月5日。正如解释所说,选择哪个日期作为参考点并不重要,但选择1月4日会使这个选择不那么“神奇”。

        4
  •  4
  •   Jo So    9 年前

    对于下一个来到这里的人来说,这是Ben的好解决方案的一个更短、单定义的版本:

    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)
    
        5
  •  3
  •   Erik Cederstrand    9 年前

    从Python 3.6开始, datetime.strptime() 将支持 %G , %V %u 指令,因此您可以简单地执行 datetime.strptime('2015 1 2', '%G %V %u').date() 请参见: https://hg.python.org/cpython/rev/acdebfbfbdcf

        6
  •  0
  •   John Doe John Doe    16 年前

    请注意,%W是周#(0-53),与ISO周(1-53)不同。在某些边缘情况下,%W将无法工作。

        7
  •  0
  •   Wai Ha Lee captain-yossarian from Ukraine    6 年前

    我提出了一个类似于Ben James发布的解决方案,但使用了一个函数:

    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年第一周的边界值进行了测试,结果很好。

        8
  •  -1
  •   Tom Gruff    15 年前

    编辑:忽略这一点,边缘案例很痛苦。采用本的解决方案。

    好的,仔细检查后,我注意到 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 ...