【问题标题】:How to construct a timedelta object from a simple string如何从一个简单的字符串构造一个 timedelta 对象
【发布时间】:2022-04-07 12:16:19
【问题描述】:

我正在编写一个需要将字符串解析为 timedelta 的函数。用户必须输入类似 "32m""2h32m",甚至 "4:13""5hr34m56s"... 是否有图书馆或其他已经实现此类功能的东西?

【问题讨论】:

  • 对于那些只想使用一行(在导入datetime之后)构造d天、h小时、m分钟和s秒的timedelta对象的人:datetime.timedelta(days = d, hours = h, minutes=m, seconds=s)

标签: python datetime timedelta


【解决方案1】:

对我来说,最优雅的解决方案是使用datetime's强大的strptime字符串解析方法,而无需求助于dateutil等外部库或手动解析输入。

from datetime import datetime, timedelta
# we specify the input and the format...
t = datetime.strptime("05:20:25","%H:%M:%S")
# ...and use datetime's hour, min and sec properties to build a timedelta
delta = timedelta(hours=t.hour, minutes=t.minute, seconds=t.second)

在此之后,您可以像往常一样使用您的 timedelta 对象,将其转换为秒以确保我们做了正确的事情等。

print(delta)
assert(5*60*60+20*60+25 == delta.total_seconds())

【讨论】:

  • 请注意,此方法仅在时间跨度小于 24 小时时有效(datetime.strptime("32:20:25","%H:%M:%S") 无效),并且您必须知道确切的输入格式。
  • @verdesmarald 那么,从 python 3.5 开始,是否有一个不使用外部库且不假设时间跨度小于 24 小时的优雅解决方案?
  • 我发现需要为 timedelta 参数手动指定命名参数非常烦人,但我能想到的最好的避免这种情况的方法是:delta = t - datetime.combine(t.date(), time.min),这……太可怕了。
  • 这种方法的一个严重问题是,如果您包含天数,那么将 %d 发送到 strptime,将无法让您输入第 0 天,因为只有 >=1 的天数对日期有效。
  • 提及 dateutil 是一种不必要的干扰。 dateutil.parse.parse 不支持 timedelta 对象。
【解决方案2】:

昨天有点时间,所以我将@virhiloanswer开发成一个Python模块,添加了更多的时间表达式格式,包括@priestc要求的所有格式。

Source code is on github(麻省理工学院许可证)给任何想要它的人。它也在 PyPI 上:

pip install pytimeparse

以秒数返回时间:

>>> from pytimeparse.timeparse import timeparse
>>> timeparse('32m')
1920
>>> timeparse('2h32m')
9120
>>> timeparse('4:13')
253
>>> timeparse('5hr34m56s')
20096
>>> timeparse('1.2 minutes')
72

【讨论】:

  • 是否有 Java/Scala 等价物?
  • @luca.giovagnoli 在 Scala 中你可以使用 Duration 类。持续时间可以由字符串构成,例如“15 秒”、“4 分钟”等。
【解决方案3】:

对于第一种格式(5hr34m56s),您应该使用正则表达式进行解析

这是基于重新解决方案:

import re
from datetime import timedelta


regex = re.compile(r'((?P<hours>d+?)hr)?((?P<minutes>d+?)m)?((?P<seconds>d+?)s)?')


def parse_time(time_str):
    parts = regex.match(time_str)
    if not parts:
        return
    parts = parts.groupdict()
    time_params = {}
    for name, param in parts.items():
        if param:
            time_params[name] = int(param)
    return timedelta(**time_params)


>>> from parse_time import parse_time
>>> parse_time('12hr')
datetime.timedelta(0, 43200)
>>> parse_time('12hr5m10s')
datetime.timedelta(0, 43510)
>>> parse_time('12hr10s')
datetime.timedelta(0, 43210)
>>> parse_time('10s')
datetime.timedelta(0, 10)
>>> 

【讨论】:

  • 我正在考虑某种函数,它可以接受你扔给它的任何东西,并且仍然能够处理转换为 timedelta。
  • 我添加了基于 re 的解决方案示例:)
  • 我没有看到 dateutil.parser.parse 如何解析持续时间,似乎它总是返回日期时间。我错过了什么?
  • dateutil.parser.parse 不会解析 timedelta 对象。它返回一个datetime,它会触发像'28:32:11.10'这样的字符串的异常。
【解决方案4】:

我修改了 virhilo's nice answer 并进行了一些升级:

  • 添加了字符串是有效时间字符串的断言
  • 将“hr”小时指示器替换为“h”
  • 允许使用“d” - 天指示符
  • 允许非整数时间(例如3m0.25s是3分0.25秒)

.

import re
from datetime import timedelta


regex = re.compile(r'^((?P<days>[.d]+?)d)?((?P<hours>[.d]+?)h)?((?P<minutes>[.d]+?)m)?((?P<seconds>[.d]+?)s)?$')


def parse_time(time_str):
    """
    Parse a time string e.g. (2h13m) into a timedelta object.

    Modified from virhilo's answer at https://stackoverflow.com/a/4628148/851699

    :param time_str: A string identifying a duration.  (eg. 2h13m)
    :return datetime.timedelta: A datetime.timedelta object
    """
    parts = regex.match(time_str)
    assert parts is not None, "Could not parse any time information from '{}'.  Examples of valid strings: '8h', '2d8h5m20s', '2m4s'".format(time_str)
    time_params = {name: float(param) for name, param in parts.groupdict().items() if param}
    return timedelta(**time_params)

【讨论】:

  • 伟大的!我在元素之间添加了“*”以允许“1d 3h 5m”
  • @MarcelWaldvogel 很好,如果您复制新正则表达式的文本,我会将您的答案添加到
  • @virhilo 和 Peter:我对您的代码的细微改进在这里:github.com/zeitgitter/zeitgitterd/blob/master/zeitgitter/…。我认为可以使用您的代码。您对许可有任何偏好吗?麻省理工学院、阿帕奇、GPL,……?
  • 马塞尔,你能把你的地址发给我吗,这样我就可以起诉了? JK 继续任何许可证都可以。
  • 这是新的正则表达式;区别在于“*”:regex = re.compile(r'^((?P<days>[.d]+?)d)? *' r'((?P<hours>[.d] +?)h)?*'r'((?P<分钟>[.d]+?)m)?*'r'((?P<秒>[.d]+?)s)?$' )
【解决方案5】:

我只想输入一个时间,然后将其添加到不同的日期,所以这对我有用:

from datetime import datetime as dtt

time_only = dtt.strptime('15:30', "%H:%M") - dtt.strptime("00:00", "%H:%M")

【讨论】:

  • dtt.strptime(myduration, "%H:%M:%S") - dtt(1900, 1, 1) 也有效...
  • 知道了。我不确定 dtt(1900,1,1) 是否适用于所有可能的操作系统
【解决方案6】:

如果 Pandas 已经在你的依赖项中,它会做得很好:

>>> import pandas as pd
>>> pd.Timedelta('5hr34m56s')
Timedelta('0 days 05:34:56')

>>> pd.Timedelta('2h32m')
Timedelta('0 days 02:32:00')

>>> pd.Timedelta('5hr34m56s')
Timedelta('0 days 05:34:56')

>>> # It is pretty forgiving:
>>> pd.Timedelta('2 days 24:30:00 10 sec')
Timedelta('3 days 00:30:10')

要转换为 datetime.timedelta(如果您喜欢该类型):

>>> pd.Timedelta('1 days').to_pytimedelta()
datetime.timedelta(1)

不幸的是,这不起作用:

>>> pd.Timedelta('4:13')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "pandas_libs	slibs	imedeltas.pyx", line 1217, in 
pandas._libs.tslibs.timedeltas.Timedelta.__new__
  File "pandas_libs	slibs	imedeltas.pyx", line 454, in 
pandas._libs.tslibs.timedeltas.parse_timedelta_string
ValueError: expected hh:mm:ss format

Pandas 实际上有相当广泛的日期和时间工具,尽管这不是它的主要目的。

安装熊猫:

# If you use pip
pip install pandas

# If you use conda
conda install pandas

【讨论】:

    【解决方案7】:

    如果你想使用 : 作为分隔符,我使用这个函数:

    import re
    from datetime import timedelta
    
    def timedelta_parse(value):
        """
        convert input string to timedelta
        """
        value = re.sub(r"[^0-9:.]", "", value)
        if not value:
            return
    
        return timedelta(**{key:float(val)
                            for val, key in zip(value.split(":")[::-1], 
                                                ("seconds", "minutes", "hours", "days"))
                   })
    

    例子:

    In [4]: timedelta_parse("1:0:0:1")
    Out[4]: datetime.timedelta(days=1, seconds=1)
    
    In [5]: timedelta_parse("123.5")
    Out[5]: datetime.timedelta(seconds=123, microseconds=500000)
    
    In [6]: timedelta_parse("1:6:34:9.983")
    Out[6]: datetime.timedelta(days=1, seconds=23649, microseconds=983000)
    
    In [8]: timedelta_parse("23:45:00")
    Out[8]: datetime.timedelta(seconds=85500)
    

    【讨论】:

    • 它不会在点后处理微秒
    • 你是对的:re.sub 应该在字符串中留下点。我纠正了这个功能,它现在应该可以工作了。谢谢!
    【解决方案8】:

    Django 自带实用函数parse_duration()。来自documentation

    解析字符串并返回 datetime.timedelta

    需要格式为 "DD HH:MM:SS.uuuuuu" 或 ISO 8601 指定的数据(例如 P4DT1H15M20S 等同于 4 1:15:20)或 PostgreSQL 的日间时间间隔格式(例如 3 days 04:05:06)。

    【讨论】:

    • 如需更多信息:Django 的parse_duration() 函数在后台使用正则表达式匹配。
    【解决方案9】:

    使用 isodate 库解析 ISO 8601 持续时间字符串。例如:

    isodate.parse_duration('PT1H5M26S')
    

    另见Is there an easy way to convert ISO 8601 duration to timedelta?

    【讨论】:

      【解决方案10】:

      如果您使用 Python 3,那么这里是我使用的 Hari Shankar 解决方案的更新版本:

      from datetime import timedelta
      import re
      
      regex = re.compile(r'(?P<hours>d+?)/'
                         r'(?P<minutes>d+?)/'
                         r'(?P<seconds>d+?)$')
      
      def parse_time(time_str):
          parts = regex.match(time_str)
          if not parts:
              return
          parts = parts.groupdict()
          print(parts)
          time_params = {}
          for name, param in parts.items():
              if param:
                  time_params[name] = int(param)
          return timedelta(**time_params)
      

      【讨论】:

        【解决方案11】:

        考虑试试tempora.parse_timedelta

        $ pip-run 'tempora>=4.1.1'
        Collecting tempora>=4.1.1
          Downloading tempora-4.1.1-py3-none-any.whl (15 kB)
        Collecting jaraco.functools>=1.20
          Using cached jaraco.functools-3.3.0-py3-none-any.whl (6.8 kB)
        Collecting pytz
          Using cached pytz-2021.1-py2.py3-none-any.whl (510 kB)
        Collecting more-itertools
          Using cached more_itertools-8.8.0-py3-none-any.whl (48 kB)
        Installing collected packages: more-itertools, pytz, jaraco.functools, tempora
        Successfully installed jaraco.functools-3.3.0 more-itertools-8.8.0 pytz-2021.1 tempora-4.1.1
        Python 3.9.2 (v3.9.2:1a79785e3e, Feb 19 2021, 09:06:10) 
        [Clang 6.0 (clang-600.0.57)] on darwin
        Type "help", "copyright", "credits" or "license" for more information.
        >>> from tempora import parse_timedelta
        >>> parse_timedelta("32m")
        datetime.timedelta(seconds=1920)
        >>> parse_timedelta("2h32m")
        datetime.timedelta(seconds=9120)
        >>> parse_timedelta("4:13")
        datetime.timedelta(seconds=15180)
        >>> parse_timedelta("5hr34m56s")
        datetime.timedelta(seconds=20096)
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2017-04-13
          • 2015-07-18
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2022-01-09
          • 2023-03-09
          相关资源
          最近更新 更多