【问题标题】:How to round the minute of a datetime object如何舍入日期时间对象的分钟
【发布时间】:2011-03-28 16:40:51
【问题描述】:

我有一个使用strptime() 生成的datetime 对象。

>>> tm
datetime.datetime(2010, 6, 10, 3, 56, 23)

我需要做的是将分钟舍入到最接近的第 10 分钟。到目前为止,我一直在做的是获取分钟值并在其上使用 round()。

min = round(tm.minute, -1)

但是,与上面的示例一样,当分钟值大于 56 时,它会给出无效时间。即:3:60

有什么更好的方法来做到这一点? datetime 支持吗?

【问题讨论】:

  • 今天,timestamp有一个方法floor(...)

标签: python datetime rounding


【解决方案1】:

如果不想使用条件,可以使用modulo操作符:

minutes = int(round(tm.minute, -1)) % 60

更新

你想要这样的东西吗?

def timeround10(dt):
    a, b = divmod(round(dt.minute, -1), 60)
    return '%i:%02i' % ((dt.hour + a) % 24, b)

timeround10(datetime.datetime(2010, 1, 1, 0, 56, 0)) # 0:56
# -> 1:00

timeround10(datetime.datetime(2010, 1, 1, 23, 56, 0)) # 23:56
# -> 0:00

.. 如果您希望结果为字符串。要获得日期时间结果,最好使用 timedelta - 查看其他回复;)

【讨论】:

  • 啊,但是这里的问题是小时也必须增加
  • @Lucas Manco - 我的解决方案也很好,我认为更有意义。
【解决方案2】:

这将使存储在 tm 中的 datetime 对象的“地板”四舍五入到 tm 之前的 10 分钟标记。

tm = tm - datetime.timedelta(minutes=tm.minute % 10,
                             seconds=tm.second,
                             microseconds=tm.microsecond)

如果您希望经典四舍五入到最接近的 10 分钟标记,请执行以下操作:

discard = datetime.timedelta(minutes=tm.minute % 10,
                             seconds=tm.second,
                             microseconds=tm.microsecond)
tm -= discard
if discard >= datetime.timedelta(minutes=5):
    tm += datetime.timedelta(minutes=10)

或者这个:

tm += datetime.timedelta(minutes=5)
tm -= datetime.timedelta(minutes=tm.minute % 10,
                         seconds=tm.second,
                         microseconds=tm.microsecond)

【讨论】:

    【解决方案3】:

    以秒为单位对日期时间进行四舍五入的通用函数:

    def roundTime(dt=None, roundTo=60):
       """Round a datetime object to any time lapse in seconds
       dt : datetime.datetime object, default now.
       roundTo : Closest number of seconds to round to, default 1 minute.
       Author: Thierry Husson 2012 - Use it as you want but don't blame me.
       """
       if dt == None : dt = datetime.datetime.now()
       seconds = (dt.replace(tzinfo=None) - dt.min).seconds
       rounding = (seconds+roundTo/2) // roundTo * roundTo
       return dt + datetime.timedelta(0,rounding-seconds,-dt.microsecond)
    

    1 小时舍入和 30 分钟舍入的示例:

    print roundTime(datetime.datetime(2012,12,31,23,44,59,1234),roundTo=60*60)
    2013-01-01 00:00:00
    
    print roundTime(datetime.datetime(2012,12,31,23,44,59,1234),roundTo=30*60)
    2012-12-31 23:30:00
    

    【讨论】:

    • 不幸的是,这不适用于 tz-aware 日期时间。应该使用dt.replace(hour=0, minute=0, second=0) 而不是dt.min
    • @skoval00 + druska 根据您的建议编辑以支持 tz 感知日期时间。谢谢!
    • 谢谢@skoval00 - 我花了一段时间才弄清楚为什么这个函数不能处理我的数据
    • 这对我来说很长时间都不起作用。例如roundTime(datetime.datetime(2012,12,31,23,44,59,1234),roundTo=60*60*24*7)roundTime(datetime.datetime(2012,12,30,23,44,59,1234),roundTo=60*60*24*7)
    • 看这个了解问题:datetime.timedelta(100,1,2,3).seconds == 1
    【解决方案4】:
    def get_rounded_datetime(self, dt, freq, nearest_type='inf'):
    
        if freq.lower() == '1h':
            round_to = 3600
        elif freq.lower() == '3h':
            round_to = 3 * 3600
        elif freq.lower() == '6h':
            round_to = 6 * 3600
        else:
            raise NotImplementedError("Freq %s is not handled yet" % freq)
    
        # // is a floor division, not a comment on following line:
        seconds_from_midnight = dt.hour * 3600 + dt.minute * 60 + dt.second
        if nearest_type == 'inf':
            rounded_sec = int(seconds_from_midnight / round_to) * round_to
        elif nearest_type == 'sup':
            rounded_sec = (int(seconds_from_midnight / round_to) + 1) * round_to
        else:
            raise IllegalArgumentException("nearest_type should be  'inf' or 'sup'")
    
        dt_midnight = datetime.datetime(dt.year, dt.month, dt.day)
    
        return dt_midnight + datetime.timedelta(0, rounded_sec)
    

    【讨论】:

      【解决方案5】:

      从我修改的最佳答案到仅使用日期时间对象的改编版本,这避免了必须转换为秒并使调用代码更具可读性:

      def roundTime(dt=None, dateDelta=datetime.timedelta(minutes=1)):
          """Round a datetime object to a multiple of a timedelta
          dt : datetime.datetime object, default now.
          dateDelta : timedelta object, we round to a multiple of this, default 1 minute.
          Author: Thierry Husson 2012 - Use it as you want but don't blame me.
                  Stijn Nevens 2014 - Changed to use only datetime objects as variables
          """
          roundTo = dateDelta.total_seconds()
      
          if dt == None : dt = datetime.datetime.now()
          seconds = (dt - dt.min).seconds
          # // is a floor division, not a comment on following line:
          rounding = (seconds+roundTo/2) // roundTo * roundTo
          return dt + datetime.timedelta(0,rounding-seconds,-dt.microsecond)
      

      1 小时舍入和 15 分钟舍入的示例:

      print roundTime(datetime.datetime(2012,12,31,23,44,59),datetime.timedelta(hour=1))
      2013-01-01 00:00:00
      
      print roundTime(datetime.datetime(2012,12,31,23,44,49),datetime.timedelta(minutes=15))
      2012-12-31 23:30:00
      

      【讨论】:

      • 也不好:print roundTime(datetime.datetime(2012,12,20,23,44,49),datetime.timedelta(days=15)) 2012-12-20 00:00:00print roundTime(datetime.datetime(2012,12,21,23,44,49),datetime.timedelta(days=15)) 2012-12-21 00:00:00
      • 跟进上述:只是指出它不适用于任意时间增量,例如超过 1 天的。这个问题是关于四舍五入的,所以这是一个适当的限制,但在编写代码的方式上可能会更清楚。
      【解决方案6】:

      基于 Stijn Nevens 并针对 Django 进行了修改以将当前时间四舍五入到最接近的 15 分钟。

      from datetime import date, timedelta, datetime, time
      
          def roundTime(dt=None, dateDelta=timedelta(minutes=1)):
      
              roundTo = dateDelta.total_seconds()
      
              if dt == None : dt = datetime.now()
              seconds = (dt - dt.min).seconds
              # // is a floor division, not a comment on following line:
              rounding = (seconds+roundTo/2) // roundTo * roundTo
              return dt + timedelta(0,rounding-seconds,-dt.microsecond)
      
          dt = roundTime(datetime.now(),timedelta(minutes=15)).strftime('%H:%M:%S')
      
       dt = 11:45:00
      

      如果您需要完整的日期和时间,只需删除 .strftime('%H:%M:%S')

      【讨论】:

        【解决方案7】:

        我使用了 Stijn Nevens 代码(感谢 Stijn)并有一个小插件可以分享。向上、向下和四舍五入到最接近的值。

        更新 2019-03-09 = 评论 Spinxz 合并;谢谢。

        更新 2019-12-27 = 评论 Bart 合并;谢谢。

        针对“X 小时”或“X 分钟”或“X 秒”的 date_delta 进行测试。

        import datetime
        
        def round_time(dt=None, date_delta=datetime.timedelta(minutes=1), to='average'):
            """
            Round a datetime object to a multiple of a timedelta
            dt : datetime.datetime object, default now.
            dateDelta : timedelta object, we round to a multiple of this, default 1 minute.
            from:  http://stackoverflow.com/questions/3463930/how-to-round-the-minute-of-a-datetime-object-python
            """
            round_to = date_delta.total_seconds()
            if dt is None:
                dt = datetime.now()
            seconds = (dt - dt.min).seconds
        
            if seconds % round_to == 0 and dt.microsecond == 0:
                rounding = (seconds + round_to / 2) // round_to * round_to
            else:
                if to == 'up':
                    # // is a floor division, not a comment on following line (like in javascript):
                    rounding = (seconds + dt.microsecond/1000000 + round_to) // round_to * round_to
                elif to == 'down':
                    rounding = seconds // round_to * round_to
                else:
                    rounding = (seconds + round_to / 2) // round_to * round_to
        
            return dt + datetime.timedelta(0, rounding - seconds, - dt.microsecond)
        
        # test data
        print(round_time(datetime.datetime(2019,11,1,14,39,00), date_delta=datetime.timedelta(seconds=30), to='up'))
        print(round_time(datetime.datetime(2019,11,2,14,39,00,1), date_delta=datetime.timedelta(seconds=30), to='up'))
        print(round_time(datetime.datetime(2019,11,3,14,39,00,776980), date_delta=datetime.timedelta(seconds=30), to='up'))
        print(round_time(datetime.datetime(2019,11,4,14,39,29,776980), date_delta=datetime.timedelta(seconds=30), to='up'))
        print(round_time(datetime.datetime(2018,11,5,14,39,00,776980), date_delta=datetime.timedelta(seconds=30), to='down'))
        print(round_time(datetime.datetime(2018,11,6,14,38,59,776980), date_delta=datetime.timedelta(seconds=30), to='down'))
        print(round_time(datetime.datetime(2017,11,7,14,39,15), date_delta=datetime.timedelta(seconds=30), to='average'))
        print(round_time(datetime.datetime(2017,11,8,14,39,14,999999), date_delta=datetime.timedelta(seconds=30), to='average'))
        print(round_time(datetime.datetime(2019,11,9,14,39,14,999999), date_delta=datetime.timedelta(seconds=30), to='up'))
        print(round_time(datetime.datetime(2012,12,10,23,44,59,7769),to='average'))
        print(round_time(datetime.datetime(2012,12,11,23,44,59,7769),to='up'))
        print(round_time(datetime.datetime(2010,12,12,23,44,59,7769),to='down',date_delta=datetime.timedelta(seconds=1)))
        print(round_time(datetime.datetime(2011,12,13,23,44,59,7769),to='up',date_delta=datetime.timedelta(seconds=1)))
        print(round_time(datetime.datetime(2012,12,14,23,44,59),date_delta=datetime.timedelta(hours=1),to='down'))
        print(round_time(datetime.datetime(2012,12,15,23,44,59),date_delta=datetime.timedelta(hours=1),to='up'))
        print(round_time(datetime.datetime(2012,12,16,23,44,59),date_delta=datetime.timedelta(hours=1)))
        print(round_time(datetime.datetime(2012,12,17,23,00,00),date_delta=datetime.timedelta(hours=1),to='down'))
        print(round_time(datetime.datetime(2012,12,18,23,00,00),date_delta=datetime.timedelta(hours=1),to='up'))
        print(round_time(datetime.datetime(2012,12,19,23,00,00),date_delta=datetime.timedelta(hours=1)))
        

        【讨论】:

        • 这对我有帮助。我想补充一点,如果在 PySpark 中使用它,将日期时间解析为字符串而不是日期时间对象。
        • “向上”舍入可能没有达到大多数人的预期。即使 dt 不需要四舍五入,您也会四舍五入到下一个 date_delta:例如round_to = 60 的 15:30:00.000 将变为 15:31:00.000
        • up 的四舍五入无论如何都不准确; 2019-11-07 14:39:00.776980date_delta 等于例如30 秒和to='up' 导致2019-11-07 14:39:00
        • 非常感谢!!尽管up 舍入可能不是一个常见的用例,但在处理从分钟边界开始的应用程序时需要它
        • 很好的答案,带有向上和向下舍入的选项,对我非常有用!
        【解决方案8】:

        捕获异常时速度不是最好的,但这会起作用。

        def _minute10(dt=datetime.utcnow()):
            try:
                return dt.replace(minute=round(dt.minute, -1))
            except ValueError:
                return dt.replace(minute=0) + timedelta(hours=1)
        

        时间

        %timeit _minute10(datetime(2016, 12, 31, 23, 55))
        100000 loops, best of 3: 5.12 µs per loop
        
        %timeit _minute10(datetime(2016, 12, 31, 23, 31))
        100000 loops, best of 3: 2.21 µs per loop
        

        【讨论】:

          【解决方案9】:

          datetime 对象t 舍入到给定时间单位(此处为秒)的两行直观解决方案:

          format_str = '%Y-%m-%d %H:%M:%S'
          t_rounded = datetime.strptime(datetime.strftime(t, format_str), format_str)
          

          如果您想四舍五入到不同的单位,只需更改format_str

          这种方法不像上述方法那样四舍五入到任意时间量,而是一种很好的 Pythonic 方法来四舍五入到给定的小时、分钟或秒。

          【讨论】:

            【解决方案10】:

            我正在使用这个。它具有使用 tz 感知日期时间的优势。

            def round_minutes(some_datetime: datetime, step: int):
                """ round up to nearest step-minutes """
                if step > 60:
                    raise AttrbuteError("step must be less than 60")
            
                change = timedelta(
                    minutes= some_datetime.minute % step,
                    seconds=some_datetime.second,
                    microseconds=some_datetime.microsecond
                )
            
                if change > timedelta():
                    change -= timedelta(minutes=step)
            
                return some_datetime - change
            

            它的缺点是只能工作不到一个小时的时间片。

            【讨论】:

              【解决方案11】:

              其他解决方案:

              def round_time(timestamp=None, lapse=0):
                  """
                  Round a timestamp to a lapse according to specified minutes
              
                  Usage:
              
                  >>> import datetime, math
                  >>> round_time(datetime.datetime(2010, 6, 10, 3, 56, 23), 0)
                  datetime.datetime(2010, 6, 10, 3, 56)
                  >>> round_time(datetime.datetime(2010, 6, 10, 3, 56, 23), 1)
                  datetime.datetime(2010, 6, 10, 3, 57)
                  >>> round_time(datetime.datetime(2010, 6, 10, 3, 56, 23), -1)
                  datetime.datetime(2010, 6, 10, 3, 55)
                  >>> round_time(datetime.datetime(2019, 3, 11, 9, 22, 11), 3)
                  datetime.datetime(2019, 3, 11, 9, 24)
                  >>> round_time(datetime.datetime(2019, 3, 11, 9, 22, 11), 3*60)
                  datetime.datetime(2019, 3, 11, 12, 0)
                  >>> round_time(datetime.datetime(2019, 3, 11, 10, 0, 0), 3)
                  datetime.datetime(2019, 3, 11, 10, 0)
              
                  :param timestamp: Timestamp to round (default: now)
                  :param lapse: Lapse to round in minutes (default: 0)
                  """
                  t = timestamp or datetime.datetime.now()  # type: Union[datetime, Any]
                  surplus = datetime.timedelta(seconds=t.second, microseconds=t.microsecond)
                  t -= surplus
                  try:
                      mod = t.minute % lapse
                  except ZeroDivisionError:
                      return t
                  if mod:  # minutes % lapse != 0
                      t += datetime.timedelta(minutes=math.ceil(t.minute / lapse) * lapse - t.minute)
                  elif surplus != datetime.timedelta() or lapse < 0:
                      t += datetime.timedelta(minutes=(t.minute / lapse + 1) * lapse - t.minute)
                  return t
              

              希望这会有所帮助!

              【讨论】:

                【解决方案12】:

                Pandas 具有日期时间循环功能,但与 Pandas 中的大多数内容一样,它需要采用系列格式。

                >>> ts = pd.Series(pd.date_range(Dt(2019,1,1,1,1),Dt(2019,1,1,1,4),periods=8))
                >>> print(ts)
                0   2019-01-01 01:01:00.000000000
                1   2019-01-01 01:01:25.714285714
                2   2019-01-01 01:01:51.428571428
                3   2019-01-01 01:02:17.142857142
                4   2019-01-01 01:02:42.857142857
                5   2019-01-01 01:03:08.571428571
                6   2019-01-01 01:03:34.285714285
                7   2019-01-01 01:04:00.000000000
                dtype: datetime64[ns]
                
                >>> ts.dt.round('1min')
                0   2019-01-01 01:01:00
                1   2019-01-01 01:01:00
                2   2019-01-01 01:02:00
                3   2019-01-01 01:02:00
                4   2019-01-01 01:03:00
                5   2019-01-01 01:03:00
                6   2019-01-01 01:04:00
                7   2019-01-01 01:04:00
                dtype: datetime64[ns]
                

                Docs - 根据需要更改频率字符串。

                【讨论】:

                • 供参考,Timestamp 也包含floorceil
                • @poulter7 floor 函数虽然只适用于单个值,而不是日期时间索引
                【解决方案13】:

                这是一个更简单的通用解决方案,没有浮点精度问题和外部库依赖:

                import datetime
                
                def time_mod(time, delta, epoch=None):
                    if epoch is None:
                        epoch = datetime.datetime(1970, 1, 1, tzinfo=time.tzinfo)
                    return (time - epoch) % delta
                
                def time_round(time, delta, epoch=None):
                    mod = time_mod(time, delta, epoch)
                    if mod < delta / 2:
                       return time - mod
                    return time + (delta - mod)
                
                def time_floor(time, delta, epoch=None):
                    mod = time_mod(time, delta, epoch)
                    return time - mod
                
                def time_ceil(time, delta, epoch=None):
                    mod = time_mod(time, delta, epoch)
                    if mod:
                        return time + (delta - mod)
                    return time
                

                在你的情况下:

                >>> tm = datetime.datetime(2010, 6, 10, 3, 56, 23)
                >>> time_round(tm, datetime.timedelta(minutes=10))
                datetime.datetime(2010, 6, 10, 4, 0)
                >>> time_floor(tm, datetime.timedelta(minutes=10))
                datetime.datetime(2010, 6, 10, 3, 50)
                >>> time_ceil(tm, datetime.timedelta(minutes=10))
                datetime.datetime(2010, 6, 10, 4, 0)
                

                【讨论】:

                • 这是time round to 函数。如何使Time floor to 起作用?我的意思是,例如,如果时间在 00:00 和 00:10 之间,那么它会下降到 00:00。如果它在 00:10 和 00:20 之间,那么它会下降到 00:10 等。
                • @s.paszko 将 time_round 函数中从 if mod &lt; (delta / 2): 开始的三行替换为单行 return time - mod。相应地更新了答案。
                • 感谢更新答案!
                • time_ceil 中有错误。如果 mod 是zero 那么它应该离开原始时间。就像数学中的 Ceil(1) = 1,但 Ceil(1.000001) 是 2。
                • @s.paszko 已更正
                【解决方案14】:

                我知道的最短路径

                min = tm.minute // 10 * 10

                【讨论】:

                • 这可能不占 > 60 分钟
                【解决方案15】:

                那些看起来过于复杂

                def round_down_to():
                    num = int(datetime.utcnow().replace(second=0, microsecond=0).minute)
                    return num - (num%10)
                

                【讨论】:

                  【解决方案16】:

                  一个简单的方法:

                  def round_time(dt, round_to_seconds=60):
                      """Round a datetime object to any number of seconds
                      dt: datetime.datetime object
                      round_to_seconds: closest number of seconds for rounding, Default 1 minute.
                      """
                      rounded_epoch = round(dt.timestamp() / round_to_seconds) * round_to_seconds
                      rounded_dt = datetime.datetime.fromtimestamp(rounded_epoch).astimezone(dt.tzinfo)
                      return rounded_dt
                  

                  【讨论】:

                    【解决方案17】:

                    是的,如果您的数据属于 pandas 系列中的 DateTime 列,您可以使用内置的 pandas.Series.dt.round 函数对其进行四舍五入。 请参阅pandas.Series.dt.round 上的文档。 在四舍五入到 10 分钟的情况下,它将是 Series.dt.round('10min') 或 Series.dt.round('600s') ,如下所示:

                    pandas.Series(tm).dt.round('10min')
                    

                    编辑以添加示例代码:

                    import datetime
                    import pandas
                    
                    tm = datetime.datetime(2010, 6, 10, 3, 56, 23)
                    tm_rounded = pandas.Series(tm).dt.round('10min')
                    print(tm_rounded)
                    
                    >>> 0   2010-06-10 04:00:00
                    dtype: datetime64[ns]
                    

                    【讨论】:

                    • 我不确定这个答案是否增加了任何新的或有用的东西。已经有一个答案解释了同样的事情:stackoverflow.com/a/56010357/7851470
                    • 是的,感谢您向我指出这些。我的错误是没有在我的回复中包含示例代码,也没有查看所有其他人的回复。我会努力在这方面改进。
                    【解决方案18】:

                    对于这么简单的问题,大多数答案似乎都太复杂了。

                    假设 your_time 是您拥有的日期时间对象,接下来的几轮(实际上是地板)以分钟定义的所需分辨率。

                    from math import floor
                    
                    your_time = datetime.datetime.now() 
                    
                    g = 10  # granularity in minutes
                    print(
                    datetime.datetime.fromtimestamp(
                    floor(your_time.timestamp() / (60*g)) * (60*g)
                    ))
                    

                    【讨论】:

                      【解决方案19】:

                      我想出了这个非常简单的函数,它可以使用任何 timedelta,只要它是 60 秒的倍数或除数。它还与时区感知日期时间兼容。

                      #!/usr/env python3
                      from datetime import datetime, timedelta
                      
                      def round_dt_to_delta(dt, delta=timedelta(minutes=30)):
                          ref = datetime.min.replace(tzinfo=dt.tzinfo)
                          return ref + round((dt - ref) / delta) * delta
                      

                      输出:

                      In [1]: round_dt_to_delta(datetime(2012,12,31,23,44,49), timedelta(seconds=15))
                      Out[1]: datetime.datetime(2012, 12, 31, 23, 44, 45)
                      In [2]: round_dt_to_delta(datetime(2012,12,31,23,44,49), timedelta(minutes=15))
                      Out[2]: datetime.datetime(2012, 12, 31, 23, 45)
                      

                      【讨论】:

                        【解决方案20】:

                        将分钟数四舍五入的一般功能:

                        from datetime import datetime
                        def round_minute(date: datetime = None, round_to: int = 1):
                            """
                            round datetime object to minutes
                            """
                            if not date:
                                date = datetime.now()
                            date = date.replace(second=0, microsecond=0)
                            delta = date.minute % round_to
                            return date.replace(minute=date.minute - delta)
                        

                        【讨论】:

                          猜你喜欢
                          • 2011-01-12
                          • 1970-01-01
                          • 2013-10-17
                          • 1970-01-01
                          • 1970-01-01
                          • 2016-03-07
                          • 1970-01-01
                          • 2020-03-23
                          • 2022-01-02
                          相关资源
                          最近更新 更多