• dateutil:字符串转日期的瑞士军刀


    楔子

    dateutil是一个处理时间的库,可以非常智能的将字符串解析成时间类型,并且这也是pandas所依赖的库。下面来看一下用法:

    parser

    dateutil下面有一个parser模块,它是专门用来将字符串解析为时间类型的。

    from dateutil import parser
    

    直接导入即可,然后调用内部的parse方法。

    >>> parser.parse("2018-3-25")
    datetime.datetime(2018, 3, 25, 0, 0)
    >>> parser.parse("2018-03-25")
    datetime.datetime(2018, 3, 25, 0, 0)
    >>> parser.parse("2018/3/25")
    datetime.datetime(2018, 3, 25, 0, 0)
    >>> parser.parse("2018/03/25")
    datetime.datetime(2018, 3, 25, 0, 0)
    

    我们看到还是很智能的,可以使用/或者-进行分隔。

    # 即使没有分隔符也是可以的,但是必须是yyyymmdd这种形式,比如月份,如果是3月,那么要写03
    # 因为在没有分隔符的情况,写成2018325的话,那么会把2018325都当成年来解析
    >>> parser.parse("20180325")
    datetime.datetime(2018, 3, 25, 0, 0)
    
    # 如果只有两部分,那么会自动把前面的当成月、后面当成日(但是有特例,后面说)
    # 没指定的部分,默认为当前日期对应的部分
    >>> parser.parse("03-25")
    datetime.datetime(2020, 3, 25, 0, 0)
    # 我们看到月份超过了12,所以报错了
    >>> parser.parse("13-5")
    ValueError: month must be in 1..12
    # 但是如果月份超过了31,那么就不再是月份了,而是会被当成年来解析,那么同理后面的就会变成月。
    # 也就是前面的当成是年、后面的当成是月
    # 当前是两千多年,所以解释成2032年,而5则解释成5月。而"日"则是25,因为它没有指定,所以和当前日期保持一致
    >>> parser.parse("32-5")
    datetime.datetime(2032, 5, 25, 0, 0)
    # 但是超过70,那么会被解释成1970年,这与unix诞生时间有关
    >>> parser.parse("70-5")
    datetime.datetime(1970, 5, 25, 0, 0)
    # 如果超过了100,那么就是其本身
    >>> parser.parse("100-5")
    datetime.datetime(100, 5, 25, 0, 0)
    
    >>> parser.parse("1225")
    # 由于没有分割符那么1225整体会被当成是年来解释,然后月和日则和当前日期保持一致,3月25日
    # 所以如果只有两部分,最好指定分隔符。
    # 不指定分隔符的情况最好只用于yyyymmdd这种形式,如果只有两部分、还不指定分隔符的话,范围太广了
    datetime.datetime(1225, 3, 25, 0, 0)
    
    # 213一样会被解释成年,其它部分和当前日期保持一致
    >>> parser.parse("213")
    datetime.datetime(213, 3, 25, 0, 0)
    
    # 但如果小于31,那么会被解释成日
    >>> parser.parse("32")
    datetime.datetime(2032, 3, 25, 0, 0)
    # 这里被解释成"日"了,当然前提是当前月份有31天
    >>> parser.parse("31")
    datetime.datetime(2020, 3, 31, 0, 0)
    >>> parser.parse("06")
    datetime.datetime(2020, 3, 6, 0, 0)
    
    # 所以dateutil给我们做了很多的处理,但老实说我们基本不会处理像"31"、"1225"这种格式的字符串,因为它太模糊了,所以即便是这种数据,起码也要有分隔符
    # 如果有分隔符,那么处理起来会非常简单
    # 还是那句话,如果没有分隔符,还要dateutil来处理的话,那么最好是yyyymmdd的格式,其它情况要有分隔符:/或者-都可以
    

    如果不是年月日的顺序呢?

    # 首先看一下这种情况,如果都只有两位,那么会自动把最后一部分当成年来解析
    # 前面的两部分则是月和日,也就是"月日年"
    >>> parser.parse("12/25/18")
    datetime.datetime(2018, 12, 25, 0, 0)
    >>> parser.parse("12/10/11")
    datetime.datetime(2011, 12, 10, 0, 0)
    
    # 但18显然超过最大月份12,那么之前的月日年、则会变成日月年
    >>> parser.parse("18/10/11")
    datetime.datetime(2011, 10, 18, 0, 0)
    # 同理年份写全也是一样的
    >>> parser.parse("12/10/2011")
    datetime.datetime(2011, 12, 10, 0, 0)
    >>> parser.parse("18/10/2011")
    datetime.datetime(2011, 10, 18, 0, 0)
    # 但是年份只能位于开头或者结尾,不能在中间
    # 如果年在结尾,那么会按照月日年来解析,但是月份大于12,那么会按照日月年来解析
    # 如果年在开头,那么只会按照年月日来解析,不存在年日月这一说
    

    此外dateutil还可以识别英文模式的字符串

    >>> parser.parse("Mar 15 2018")
    datetime.datetime(2018, 3, 15, 0, 0)
    

    rrule

    rrule是用于生成多个连续的日期,如果你知道pandas的data_range,那么这个很好理解。

    >>> from dateutil import rrule
    >>> list(rrule.rrule(freq=rrule.DAILY, dtstart=parser.parse("2018-1-1"), until=parser.parse("2018-1-5")))
    [datetime.datetime(2018, 1, 1, 0, 0), 
     datetime.datetime(2018, 1, 2, 0, 0), 
     datetime.datetime(2018, 1, 3, 0, 0), 
     datetime.datetime(2018, 1, 4, 0, 0), 
     datetime.datetime(2018, 1, 5, 0, 0)]
    
    • freq:YEARLY, MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY, SECONDLY,年、月、星期、天、小时、分、秒,即间隔,我们上面的例子是DAILY,每一天生成一个
    • dtstart:起始时间
    • until:结束时间
    >>> list(rrule.rrule(freq=rrule.DAILY, count=3, dtstart=parser.parse("2018-1-1"), until=parser.parse("2018-1-5")))
    [datetime.datetime(2018, 1, 1, 0, 0), 
     datetime.datetime(2018, 1, 2, 0, 0), 
     datetime.datetime(2018, 1, 3, 0, 0),
    ]
    
    • count:只生成多少个
    >>> list(rrule.rrule(freq=rrule.DAILY, interval=2, dtstart=parser.parse("2018-1-1"), until=parser.parse("2018-1-5")))
    [datetime.datetime(2018, 1, 1, 0, 0), 
     datetime.datetime(2018, 1, 3, 0, 0), 
     datetime.datetime(2018, 1, 5, 0, 0)]
    
    • interval:间隔,freq指的是天,加上interval=2,所以就是每隔两天
    >>> list(rrule.rrule(freq=rrule.DAILY, byweekday=(rrule.MO, rrule.SU), dtstart=parser.parse("2018-1-1"), until=parser.parse("2018-1-5")))
    [datetime.datetime(2018, 1, 1, 0, 0)]
    
    
    • byweekday=(rrule.MO, rrule.SU),表示只保留周一和周日

    rrule还可以计算两个日期之间查了多少天、多少月、多少年等等,默认的timedelta则最大只能计算到天。

    >>> rrule.rrule(freq=rrule.DAILY, dtstart=parser.parse("2018-1-1"), until=parser.parse("2019-1-5")).count()
    370
    >>> rrule.rrule(freq=rrule.MONTHLY, dtstart=parser.parse("2018-1-1"), until=parser.parse("2019-1-5")).count()
    13
    >>> rrule.rrule(freq=rrule.YEARLY, dtstart=parser.parse("2018-1-1"), until=parser.parse("2019-1-5")).count()
    2
    

    我们看到还是使用rrule.rrule,如果调用count方法就会计算差值,至于计算什么差值,则取决于freq。

    但是它的计算方式要注意,比如计算年,就先按照年来减,然后看月,如果until的"月和日"组合起来大于等于dtstart的"月和日",那么年会加1

    >>> rrule.rrule(freq=rrule.YEARLY, dtstart=parser.parse("2018-1-1"), until=parser.parse("2019-1-1")).count()
    2
    

    明明都是1月1号,但是两者差了两年。对于月也是同理,先减去月,然后看日,如果until的日大于等于dtstart的日,那么月也会加1

    >>> rrule.rrule(freq=rrule.MONTHLY, dtstart=parser.parse("2018-1-1"), until=parser.parse("2018-3-1")).count()
    3
    >>> rrule.rrule(freq=rrule.MONTHLY, dtstart=parser.parse("2018-1-2"), until=parser.parse("2018-3-1")).count()
    2
    

    所以不单单是减完就结束了,还会进行一次判断(减的是年就判断"月和日"、减的是月就判断日),如果until不小于dtstart,那么上一步减完的结果会加1。

    但是说实话,这种做法个人觉得很不友好,所以我们计算两个日期之间的差值一般不会使用rrule,rrule主要还是用于生成多个连续日期。

    relativedelta

    个人非常推荐的一个方法,我们来看一下用法。

    from dateutil.relativedelta import relativedelta
    from datetime import datetime
    
    # 可以自动计算两个日期之间的差值
    print(
        relativedelta(datetime(2018, 1, 5), datetime(2018, 1, 1))
    )  # relativedelta(days=+4)
    
    print(
        relativedelta(datetime(2018, 2, 5), datetime(2018, 1, 9))
    )  # relativedelta(days=+27)
    
    print(
        relativedelta(datetime(2018, 2, 5), datetime(2018, 1, 1))
    )  # relativedelta(months=+1, days=+4)
    
    print(
        relativedelta(datetime(2019, 2, 5), datetime(2018, 1, 1))
    )  # relativedelta(years=+1, months=+1, days=+4)
    

    输入两个日期,然后会计算两个日期之间的差值,然后可以获取如下属性,:

    • years: 计算差了多少年
    • months:计算差了多少月
    • days:计算差了多少天
    • hours:计算差了多少小时
    • minutes:计算差了多少分钟
    • seconds:计算差了多少秒
    • microseconds:计算差了多少毫秒

    注意:上面的指的是两个日期对应部分的差值,比如:2018-3-1和2017-1-1,之间差了两个月,并不是12+2=14,它获取的是对应部分的差值

    from dateutil.relativedelta import relativedelta
    from datetime import datetime
    
    
    dt1 = datetime(2018, 12, 11, 19, 15, 25)
    dt2 = datetime(2017, 8, 3, 17, 24, 51)
    
    diff = relativedelta(dt1, dt2)
    print(diff)  # relativedelta(years=+1, months=+4, days=+8, hours=+1, minutes=+50, seconds=+34)
    
    print(diff.years)  # 1
    print(diff.months)  # 4
    print(diff.days)  # 8
    print(diff.hours)  # 1
    print(diff.minutes)  # 50
    print(diff.seconds)  # 34
    

    我们看到上面计算的结果不是我们期待的,我们希望在计算差了多少个月的时候,是希望把年算进去的,那么怎么办呢?使用pandas

    import pandas as pd
    
    for freq in ("Y", "M", "W", "D", "H", "T", "S"):
        """
        Y: 年
        M: 月
        W: 星期
        D: 天
        H: 时
        T: 分
        S: 秒
        """
        dt1 = pd.Period("2018-11-12 12:11:10", freq)
        dt2 = pd.Period("2017-11-12 11:18:35", freq)
        print((dt1 - dt2).n)
    """
    1
    12
    53
    365
    8761
    525653
    31539155
    """
    

    这一般是我们期望的结果,计算月的时候,会将差的年份乘上12再和差的月份相加,同理计算天的时候,会将年和月算进去。计算小时,则是将年、月、日都算进去。

    因此计算两个日期之间的差值的时候,如果是精确到天,那么我们可以直接将日期相减,得到timedelta。但是精确到年和月,那么我们知道可以使用rrule,但是它涉及到一个问题,就是我们说过的: 2018-2-2和2018-1-1应该差了一个月零一天,但是得到结果是两个月,而relativedelta则是计算每个单独的部分之间的差值,所以我个人推荐使用pandas

    那relativedelta都用在什么地方呢?如下:

    from dateutil.relativedelta import relativedelta
    from datetime import datetime, timedelta
    
    
    dt1 = datetime(2018, 12, 11, 19, 15, 25)
    
    # 给dt1加上5个月,变成了19年5五月
    diff = relativedelta(months=5)
    print(diff + dt1)  # 2019-05-11 19:15:25
    
    # 给dt1加上2年15天
    diff = relativedelta(years=2, days=15)
    print(diff + dt1)  # 2020-12-26 19:15:25
    
    # 给dt1加上14个小时、38分、12秒
    diff = relativedelta(hours=14, minutes=38, seconds=12)
    print(dt1 + diff)  # 2018-12-12 09:53:37
    
    # 给dt1加上3星期
    diff = relativedelta(weeks=3)
    print(dt1 + diff)  # 2019-01-01 19:15:25
    """
    所以我们可以给指定部分加上或减去任意的时间间隔
    当然datetime和timedelta也可以相加减,但是timedelta无法指定月和年
    """
    
    # 另外我们指定间隔的时候是可以无视范围的,比如一个月最多有31天,但是我们指定45也是可以的
    # 比如:dt1是2018年12月11,那么加上45天。会先拿出21天变成2019年1月1号,因为12月有31天
    # 然后剩余24天,2019-1-1再加上24天,所以是2019年1月25号
    diff = relativedelta(days=45)
    print(dt1 + diff)  # 2019-01-25 19:15:25
    

    所以relativedelta最大的用处就是能够给一个日期加上指定的时间间隔。

  • 相关阅读:
    LeetCode OJ:Merge Two Sorted Lists(合并两个链表)
    LeetCode OJ:Remove Nth Node From End of List(倒序移除List中的元素)
    LeetCode OJ:Find Peak Element(寻找峰值元素)
    LeetCode OJ:Spiral MatrixII(螺旋矩阵II)
    LeetCode OJ:Longest Palindromic Substring(最长的回文字串)
    利用生产者消费者模型实现大文件的拷贝
    Linux下用c语言实现whereis.
    Huffman编码实现文件的压缩与解压缩。
    修改MySQL数据库存储位置datadir
    python中pickle简介
  • 原文地址:https://www.cnblogs.com/traditional/p/11300448.html
Copyright © 2020-2023  润新知