Round time to the closest 20-minute interval?

93 Views Asked by At

I want to round a given time to its closest 20-mintue interval.

For example, if given the time below:

Given Rounded
12:05 pm 12:00 pm
12:10 pm 12:20 pm
12:21 pm 12:20 pm
12:33 pm 12:40 pm
12:43 pm 12:40 pm
12:50 pm 1:00 pm

Note: 12:10 pm -> 12:20 pm, round up if it's in the middle of the 20-min interval.

I have tried using the code from a similar thread, as shown below: (Rounding up to nearest 30 minutes in Python)

from datetime import datetime, timedelta

def ceil_dt(dt, delta):
    return dt + (datetime.min - dt) % delta

now = datetime(2023, 2, 24, 12, 21, 00)
print(now)    
print(ceil_dt(now, timedelta(minutes=20)))
#ts.round(freq='T') # minute

As you can see, the given time is 12:21:00, but the output is 12:40:00. Instead, I want 12:21:00 to round to 12:20:00. Eventually, the goal is apply this to a datetime column in pandas, let me know if there is an easier method. Thanks!

Input dataframe:

data = pd.DataFrame({
'Experiment_ID': [52.0, 52.1, 52.2, 55.0, 55.1, 55.2, 56.0, 56.1, 56.2, 56.3, 56.4,
                 57.0, 57.1, 57.2, 59.0, 59.1, 60.0, 61.0, 62.0, 62.1, 62.2, 63.0,
                 63.1, 64.0, 64.1, 64.2, 65.0, 65.1, 65.2, 66.0],
'Datetime: ['2023-02-24 11:34:00', '2023-02-24 12:37:00', '2023-02-24 13:36:00', '2023-03-08 11:13:00',
               '2023-03-08 12:18:00', '2023-03-08 13:18:00', '2023-03-16 10:03:00', '2023-03-16 11:03:00',
               '2023-03-16 11:40:00', '2023-03-16 12:06:00', '2023-03-16 13:04:00', '2023-03-22 10:56:00',
               '2023-03-22 12:05:00', '2023-03-22 13:09:00', '2023-04-05 11:25:00', '2023-04-05 12:35:00',
               '2023-04-07 12:50:00', '2023-04-11 15:00:00', '2023-04-13 10:47:00', '2023-04-13 11:47:00',
               '2023-04-13 12:47:00', '2023-04-19 10:45:00', '2023-04-19 13:00:00', '2023-04-20 10:36:00',
               '2023-04-20 11:33:00', '2023-04-20 12:35:00', '2023-04-26 10:53:00', '2023-04-26 12:01:00',
               '2023-04-26 12:30:00', '2023-05-11 10:22:00']})
2

There are 2 best solutions below

2
Mark Tolonen On BEST ANSWER

The Decimal module has a way to select the rounding type:

import pandas as pd
import datetime as dt
import decimal as dm

SEC_PER_20MIN = 20 * 60

df = pd.DataFrame({'Datetime':
    ['2023-02-24 11:34:00', '2023-02-24 12:37:00', '2023-02-24 13:36:00', '2023-03-08 11:13:00',
     '2023-03-08 12:18:00', '2023-03-08 13:18:00', '2023-03-16 10:03:00', '2023-03-16 11:03:00',
     '2023-03-16 11:40:00', '2023-03-16 12:06:00', '2023-03-16 13:04:00', '2023-03-22 10:56:00',
     '2023-03-22 12:05:00', '2023-03-22 13:09:00', '2023-04-05 11:25:00', '2023-04-05 12:35:00',
     '2023-04-07 12:50:00', '2023-04-11 15:00:00', '2023-04-13 10:47:00', '2023-04-13 11:47:00',
     '2023-04-13 12:47:00', '2023-04-19 10:45:00', '2023-04-19 13:00:00', '2023-04-20 10:36:00',
     '2023-04-20 11:33:00', '2023-04-20 12:35:00', '2023-04-26 10:53:00', '2023-04-26 12:01:00',
     '2023-04-26 12:30:00', '2023-05-11 10:22:00']})

df.Datetime = pd.to_datetime(df.Datetime, utc=True)

def round20min(t):
    tmp = (dm.Decimal(t.timestamp()) / SEC_PER_20MIN)
    tmp = int(tmp.to_integral(rounding=dm.ROUND_HALF_UP) * SEC_PER_20MIN)
    return dt.datetime.fromtimestamp(tmp, tz=dt.timezone.utc)

df['Rounded'] = df['Datetime'].apply(round20min)
print(df)

Output:

                    Datetime                   Rounded
0  2023-02-24 11:34:00+00:00 2023-02-24 11:40:00+00:00
1  2023-02-24 12:37:00+00:00 2023-02-24 12:40:00+00:00
2  2023-02-24 13:36:00+00:00 2023-02-24 13:40:00+00:00
3  2023-03-08 11:13:00+00:00 2023-03-08 11:20:00+00:00
4  2023-03-08 12:18:00+00:00 2023-03-08 12:20:00+00:00
5  2023-03-08 13:18:00+00:00 2023-03-08 13:20:00+00:00
6  2023-03-16 10:03:00+00:00 2023-03-16 10:00:00+00:00
7  2023-03-16 11:03:00+00:00 2023-03-16 11:00:00+00:00
8  2023-03-16 11:40:00+00:00 2023-03-16 11:40:00+00:00
9  2023-03-16 12:06:00+00:00 2023-03-16 12:00:00+00:00
10 2023-03-16 13:04:00+00:00 2023-03-16 13:00:00+00:00
11 2023-03-22 10:56:00+00:00 2023-03-22 11:00:00+00:00
12 2023-03-22 12:05:00+00:00 2023-03-22 12:00:00+00:00
13 2023-03-22 13:09:00+00:00 2023-03-22 13:00:00+00:00
14 2023-04-05 11:25:00+00:00 2023-04-05 11:20:00+00:00
15 2023-04-05 12:35:00+00:00 2023-04-05 12:40:00+00:00
16 2023-04-07 12:50:00+00:00 2023-04-07 13:00:00+00:00
17 2023-04-11 15:00:00+00:00 2023-04-11 15:00:00+00:00
18 2023-04-13 10:47:00+00:00 2023-04-13 10:40:00+00:00
19 2023-04-13 11:47:00+00:00 2023-04-13 11:40:00+00:00
20 2023-04-13 12:47:00+00:00 2023-04-13 12:40:00+00:00
21 2023-04-19 10:45:00+00:00 2023-04-19 10:40:00+00:00
22 2023-04-19 13:00:00+00:00 2023-04-19 13:00:00+00:00
23 2023-04-20 10:36:00+00:00 2023-04-20 10:40:00+00:00
24 2023-04-20 11:33:00+00:00 2023-04-20 11:40:00+00:00
25 2023-04-20 12:35:00+00:00 2023-04-20 12:40:00+00:00
26 2023-04-26 10:53:00+00:00 2023-04-26 11:00:00+00:00
27 2023-04-26 12:01:00+00:00 2023-04-26 12:00:00+00:00
28 2023-04-26 12:30:00+00:00 2023-04-26 12:40:00+00:00
29 2023-05-11 10:22:00+00:00 2023-05-11 10:20:00+00:00
1
arrmansa On
from datetime import datetime, timedelta

def round_dt(dt, delta):
    increase = (datetime.min - dt) % delta
    if increase < delta / 2:
        return dt + increase
    else:
        return dt + increase - delta

now = datetime(2023, 2, 24, 12, 21, 00)
print(now)    
print(round_dt(now, timedelta(minutes=20)))

to apply it to a dataframe column, you can use functools.partial and .apply