Marcin
Marcin

Reputation: 339

How to convert this time series to UTC?

My first time here : )

I have following Pandas DataFrame

    date            time
0   2018-03-24      23
1   2018-03-24      24
2   2018-03-25      1
3   2018-03-25      2
4   2018-03-25      3
5   2018-03-25      4
6   2018-03-25      5
7   2018-03-25      6
8   2018-03-25      7
9   2018-03-25      8
10  2018-03-25      9
11  2018-03-25      10
12  2018-03-25      11
13  2018-03-25      12
14  2018-03-25      13
15  2018-03-25      14
16  2018-03-25      15
17  2018-03-25      16
18  2018-03-25      17
19  2018-03-25      18
20  2018-03-25      19
21  2018-03-25      20
22  2018-03-25      21
23  2018-03-25      22
24  2018-03-25      23
25  2018-03-26      1
26  2018-03-26      2
27  2018-03-26      3
28  2018-03-26      4
29  2018-03-26      5
30  2018-03-26      6
31  2018-10-27      23
32  2018-10-27      24
33  2018-10-28      1
34  2018-10-28      2
35  2018-10-28      3
36  2018-10-28      4
37  2018-10-28      5
38  2018-10-28      6
39  2018-10-28      7
40  2018-10-28      8
41  2018-10-28      9
42  2018-10-28      10
43  2018-10-28      11
44  2018-10-28      12
45  2018-10-28      13
46  2018-10-28      14
47  2018-10-28      15
48  2018-10-28      16
49  2018-10-28      17
50  2018-10-28      18
51  2018-10-28      19
52  2018-10-28      20
53  2018-10-28      21
54  2018-10-28      22
55  2018-10-28      23
56  2018-10-28      24
57  2018-10-28      25
58  2018-10-29      1
59  2018-10-29      2

Column time is supposed to represent hour of the day minus one, e.g.

    date            time
2   2018-03-25      1 

is equal to 2018-03-25 00:00 Europe/London time.

Device, which generates this time series data, is set to work according to 'Europe/London' timezone, that's why 25th of March 2018 ends on 23rd hour (records [2:25]), and 28th of October 2018 ends of 25th hour (records [33:58]) (DST switch).

Here is stuff I already tried:

from pytz import timezone
tz = timezone("Europe/London")
dst_switch = [dt for dt in tz._utc_transition_times if dt.year == 2018]
dst_switch_date_range = pd.date_range(dst_switch[0],dst_switch[1], freq='h', tz='utc')

df['datetime'] = pd.to_datetime(df.date) + pd.to_timedelta(df.time - 1, unit='h') 

df['dt_utc']     = df['datetime'].dt.tz_localize('UTC')
df['dst_switch'] = df['datetime'].map(lambda dt: 1 if dt in dst_switch_date_range else -1)
df['dt_p']     = df['datetime'] + pd.to_timedelta(df['dst_switch'], unit='h')
df['dt_utc_p'] = df['dt_p'].dt.tz_localize('Europe/London', ambiguous='NaT', nonexistent='NaT').dt.tz_convert('UTC')

df[['date', 'time', 'dt', 'dt_utc', 'map','dt_p', 'dt_utc_p']]

which results in:

       date           time   dt                      dt_utc                       dst_switch     dt_p                    dt_utc_p
0      2018-03-24     23     2018-03-24 23:00:00     2018-03-24 22:00:00+00:00     -1            2018-03-24 21:00:00     2018-03-24 21:00:00+00:00
1      2018-03-24     24     2018-03-25 00:00:00     2018-03-24 23:00:00+00:00     -1            2018-03-24 22:00:00     2018-03-24 22:00:00+00:00
2      2018-03-25     1      2018-03-25 01:00:00     2018-03-25 00:00:00+00:00     -1            2018-03-24 23:00:00     2018-03-24 23:00:00+00:00
3      2018-03-25     2      2018-03-25 02:00:00     2018-03-25 01:00:00+00:00     1             2018-03-25 02:00:00     2018-03-25 01:00:00+00:00
4      2018-03-25     3      2018-03-25 03:00:00     2018-03-25 02:00:00+00:00     1             2018-03-25 03:00:00     2018-03-25 02:00:00+00:00
5      2018-03-25     4      2018-03-25 04:00:00     2018-03-25 03:00:00+00:00     1             2018-03-25 04:00:00     2018-03-25 03:00:00+00:00
6      2018-03-25     5      2018-03-25 05:00:00     2018-03-25 04:00:00+00:00     1             2018-03-25 05:00:00     2018-03-25 04:00:00+00:00
7      2018-03-25     6      2018-03-25 06:00:00     2018-03-25 05:00:00+00:00     1             2018-03-25 06:00:00     2018-03-25 05:00:00+00:00
8      2018-03-25     7      2018-03-25 07:00:00     2018-03-25 06:00:00+00:00     1             2018-03-25 07:00:00     2018-03-25 06:00:00+00:00
9      2018-03-25     8      2018-03-25 08:00:00     2018-03-25 07:00:00+00:00     1             2018-03-25 08:00:00     2018-03-25 07:00:00+00:00
10     2018-03-25     9      2018-03-25 09:00:00     2018-03-25 08:00:00+00:00     1             2018-03-25 09:00:00     2018-03-25 08:00:00+00:00
11     2018-03-25     10     2018-03-25 10:00:00     2018-03-25 09:00:00+00:00     1             2018-03-25 10:00:00     2018-03-25 09:00:00+00:00
12     2018-03-25     11     2018-03-25 11:00:00     2018-03-25 10:00:00+00:00     1             2018-03-25 11:00:00     2018-03-25 10:00:00+00:00
13     2018-03-25     12     2018-03-25 12:00:00     2018-03-25 11:00:00+00:00     1             2018-03-25 12:00:00     2018-03-25 11:00:00+00:00
14     2018-03-25     13     2018-03-25 13:00:00     2018-03-25 12:00:00+00:00     1             2018-03-25 13:00:00     2018-03-25 12:00:00+00:00
15     2018-03-25     14     2018-03-25 14:00:00     2018-03-25 13:00:00+00:00     1             2018-03-25 14:00:00     2018-03-25 13:00:00+00:00
16     2018-03-25     15     2018-03-25 15:00:00     2018-03-25 14:00:00+00:00     1             2018-03-25 15:00:00     2018-03-25 14:00:00+00:00
17     2018-03-25     16     2018-03-25 16:00:00     2018-03-25 15:00:00+00:00     1             2018-03-25 16:00:00     2018-03-25 15:00:00+00:00
18     2018-03-25     17     2018-03-25 17:00:00     2018-03-25 16:00:00+00:00     1             2018-03-25 17:00:00     2018-03-25 16:00:00+00:00
19     2018-03-25     18     2018-03-25 18:00:00     2018-03-25 17:00:00+00:00     1             2018-03-25 18:00:00     2018-03-25 17:00:00+00:00
20     2018-03-25     19     2018-03-25 19:00:00     2018-03-25 18:00:00+00:00     1             2018-03-25 19:00:00     2018-03-25 18:00:00+00:00
21     2018-03-25     20     2018-03-25 20:00:00     2018-03-25 19:00:00+00:00     1             2018-03-25 20:00:00     2018-03-25 19:00:00+00:00
22     2018-03-25     21     2018-03-25 21:00:00     2018-03-25 20:00:00+00:00     1             2018-03-25 21:00:00     2018-03-25 20:00:00+00:00
23     2018-03-25     22     2018-03-25 22:00:00     2018-03-25 21:00:00+00:00     1             2018-03-25 22:00:00     2018-03-25 21:00:00+00:00
24     2018-03-25     23     2018-03-25 23:00:00     2018-03-25 22:00:00+00:00     1             2018-03-25 23:00:00     2018-03-25 22:00:00+00:00
25     2018-03-26     1      2018-03-26 01:00:00     2018-03-26 00:00:00+00:00     1             2018-03-26 01:00:00     2018-03-26 00:00:00+00:00
26     2018-03-26     2      2018-03-26 02:00:00     2018-03-26 01:00:00+00:00     1             2018-03-26 02:00:00     2018-03-26 01:00:00+00:00
27     2018-03-26     3      2018-03-26 03:00:00     2018-03-26 02:00:00+00:00     1             2018-03-26 03:00:00     2018-03-26 02:00:00+00:00
28     2018-03-26     4      2018-03-26 04:00:00     2018-03-26 03:00:00+00:00     1             2018-03-26 04:00:00     2018-03-26 03:00:00+00:00
29     2018-03-26     5      2018-03-26 05:00:00     2018-03-26 04:00:00+00:00     1             2018-03-26 05:00:00     2018-03-26 04:00:00+00:00
30     2018-03-26     6      2018-03-26 06:00:00     2018-03-26 05:00:00+00:00     1             2018-03-26 06:00:00     2018-03-26 05:00:00+00:00
31     2018-10-27     23     2018-10-27 23:00:00     2018-10-27 22:00:00+00:00     1             2018-10-27 23:00:00     2018-10-27 22:00:00+00:00
32     2018-10-27     24     2018-10-28 00:00:00     2018-10-27 23:00:00+00:00     1             2018-10-28 00:00:00     2018-10-27 23:00:00+00:00
33     2018-10-28     1      2018-10-28 01:00:00     2018-10-28 00:00:00+00:00     1             2018-10-28 01:00:00     NaT
34     2018-10-28     2      2018-10-28 02:00:00     2018-10-28 01:00:00+00:00     1             2018-10-28 02:00:00     2018-10-28 02:00:00+00:00
35     2018-10-28     3      2018-10-28 03:00:00     2018-10-28 02:00:00+00:00     -1            2018-10-28 01:00:00     NaT
36     2018-10-28     4      2018-10-28 04:00:00     2018-10-28 03:00:00+00:00     -1            2018-10-28 02:00:00     2018-10-28 02:00:00+00:00
37     2018-10-28     5      2018-10-28 05:00:00     2018-10-28 04:00:00+00:00     -1            2018-10-28 03:00:00     2018-10-28 03:00:00+00:00
38     2018-10-28     6      2018-10-28 06:00:00     2018-10-28 05:00:00+00:00     -1            2018-10-28 04:00:00     2018-10-28 04:00:00+00:00
39     2018-10-28     7      2018-10-28 07:00:00     2018-10-28 06:00:00+00:00     -1            2018-10-28 05:00:00     2018-10-28 05:00:00+00:00
40     2018-10-28     8      2018-10-28 08:00:00     2018-10-28 07:00:00+00:00     -1            2018-10-28 06:00:00     2018-10-28 06:00:00+00:00
41     2018-10-28     9      2018-10-28 09:00:00     2018-10-28 08:00:00+00:00     -1            2018-10-28 07:00:00     2018-10-28 07:00:00+00:00
42     2018-10-28     10     2018-10-28 10:00:00     2018-10-28 09:00:00+00:00     -1            2018-10-28 08:00:00     2018-10-28 08:00:00+00:00
43     2018-10-28     11     2018-10-28 11:00:00     2018-10-28 10:00:00+00:00     -1            2018-10-28 09:00:00     2018-10-28 09:00:00+00:00
44     2018-10-28     12     2018-10-28 12:00:00     2018-10-28 11:00:00+00:00     -1            2018-10-28 10:00:00     2018-10-28 10:00:00+00:00
45     2018-10-28     13     2018-10-28 13:00:00     2018-10-28 12:00:00+00:00     -1            2018-10-28 11:00:00     2018-10-28 11:00:00+00:00
46     2018-10-28     14     2018-10-28 14:00:00     2018-10-28 13:00:00+00:00     -1            2018-10-28 12:00:00     2018-10-28 12:00:00+00:00
47     2018-10-28     15     2018-10-28 15:00:00     2018-10-28 14:00:00+00:00     -1            2018-10-28 13:00:00     2018-10-28 13:00:00+00:00
48     2018-10-28     16     2018-10-28 16:00:00     2018-10-28 15:00:00+00:00     -1            2018-10-28 14:00:00     2018-10-28 14:00:00+00:00
49     2018-10-28     17     2018-10-28 17:00:00     2018-10-28 16:00:00+00:00     -1            2018-10-28 15:00:00     2018-10-28 15:00:00+00:00
50     2018-10-28     18     2018-10-28 18:00:00     2018-10-28 17:00:00+00:00     -1            2018-10-28 16:00:00     2018-10-28 16:00:00+00:00
51     2018-10-28     19     2018-10-28 19:00:00     2018-10-28 18:00:00+00:00     -1            2018-10-28 17:00:00     2018-10-28 17:00:00+00:00
52     2018-10-28     20     2018-10-28 20:00:00     2018-10-28 19:00:00+00:00     -1            2018-10-28 18:00:00     2018-10-28 18:00:00+00:00
53     2018-10-28     21     2018-10-28 21:00:00     2018-10-28 20:00:00+00:00     -1            2018-10-28 19:00:00     2018-10-28 19:00:00+00:00
54     2018-10-28     22     2018-10-28 22:00:00     2018-10-28 21:00:00+00:00     -1            2018-10-28 20:00:00     2018-10-28 20:00:00+00:00
55     2018-10-28     23     2018-10-28 23:00:00     2018-10-28 22:00:00+00:00     -1            2018-10-28 21:00:00     2018-10-28 21:00:00+00:00
56     2018-10-28     24     2018-10-29 00:00:00     2018-10-28 23:00:00+00:00     -1            2018-10-28 22:00:00     2018-10-28 22:00:00+00:00
57     2018-10-28     25     2018-10-29 01:00:00     2018-10-29 00:00:00+00:00     -1            2018-10-28 23:00:00     2018-10-28 23:00:00+00:00
58     2018-10-29     1      2018-10-29 01:00:00     2018-10-29 00:00:00+00:00     -1            2018-10-28 23:00:00     2018-10-28 23:00:00+00:00
59     2018-10-29     2      2018-10-29 02:00:00     2018-10-29 01:00:00+00:00     -1            2018-10-29 00:00:00     2018-10-29 00:00:00+00:00

What I need: I need to combine these two columns into datetime column, and then convert timezone from 'Europe/London' to 'UTC' (or other way around: adjust values in time column to fit 'UTC' and then combine with date column)

My line of logic: 1 year has 24h*365days = 8760 hours. If I take above timeseries data for whole year, I should get 363 days with 24 records each, 1 day with 23 records, and 1 day with 25 records, total of 8712 + 23 + 25 = 8760 records, ergo, one record for each hour of time during the year.

Where I fail: translating my logic to code :P. I have no good idea on how to offset these dates so everything will make sense, meaning smooth transitions between DST switch dates, and no duplicate timeseries values, nor missing values (holes) in timeseries.

Expected result:

    date           time   dt_utc                   
0   2018-03-24     23     2018-03-24 22:00:00+00:00
1   2018-03-24     24     2018-03-24 23:00:00+00:00
2   2018-03-25     1      2018-03-25 00:00:00+00:00
3   2018-03-25     2      2018-03-25 01:00:00+00:00
4   2018-03-25     3      2018-03-25 02:00:00+00:00
5   2018-03-25     4      2018-03-25 03:00:00+00:00
6   2018-03-25     5      2018-03-25 04:00:00+00:00
7   2018-03-25     6      2018-03-25 05:00:00+00:00
8   2018-03-25     7      2018-03-25 06:00:00+00:00
9   2018-03-25     8      2018-03-25 07:00:00+00:00
10  2018-03-25     9      2018-03-25 08:00:00+00:00
11  2018-03-25     10     2018-03-25 09:00:00+00:00
12  2018-03-25     11     2018-03-25 10:00:00+00:00
13  2018-03-25     12     2018-03-25 11:00:00+00:00
14  2018-03-25     13     2018-03-25 12:00:00+00:00
15  2018-03-25     14     2018-03-25 13:00:00+00:00
16  2018-03-25     15     2018-03-25 14:00:00+00:00
17  2018-03-25     16     2018-03-25 15:00:00+00:00
18  2018-03-25     17     2018-03-25 16:00:00+00:00
19  2018-03-25     18     2018-03-25 17:00:00+00:00
20  2018-03-25     19     2018-03-25 18:00:00+00:00
21  2018-03-25     20     2018-03-25 19:00:00+00:00
22  2018-03-25     21     2018-03-25 20:00:00+00:00
23  2018-03-25     22     2018-03-25 21:00:00+00:00
24  2018-03-25     23     2018-03-25 22:00:00+00:00
25  2018-03-26     1      2018-03-25 23:00:00+00:00
26  2018-03-26     2      2018-03-26 00:00:00+00:00
27  2018-03-26     3      2018-03-26 01:00:00+00:00
28  2018-03-26     4      2018-03-26 02:00:00+00:00
29  2018-03-26     5      2018-03-26 03:00:00+00:00
30  2018-03-26     6      2018-03-26 04:00:00+00:00
31  2018-10-27     23     2018-10-27 21:00:00+00:00
32  2018-10-27     24     2018-10-27 22:00:00+00:00
33  2018-10-28     1      2018-10-27 23:00:00+00:00
34  2018-10-28     2      2018-10-28 00:00:00+00:00
35  2018-10-28     3      2018-10-28 01:00:00+00:00
36  2018-10-28     4      2018-10-28 02:00:00+00:00
37  2018-10-28     5      2018-10-28 03:00:00+00:00
38  2018-10-28     6      2018-10-28 04:00:00+00:00
39  2018-10-28     7      2018-10-28 05:00:00+00:00
40  2018-10-28     8      2018-10-28 06:00:00+00:00
41  2018-10-28     9      2018-10-28 07:00:00+00:00
42  2018-10-28     10     2018-10-28 08:00:00+00:00
43  2018-10-28     11     2018-10-28 09:00:00+00:00
44  2018-10-28     12     2018-10-28 10:00:00+00:00
45  2018-10-28     13     2018-10-28 11:00:00+00:00
46  2018-10-28     14     2018-10-28 12:00:00+00:00
47  2018-10-28     15     2018-10-28 13:00:00+00:00
48  2018-10-28     16     2018-10-28 14:00:00+00:00
49  2018-10-28     17     2018-10-28 15:00:00+00:00
50  2018-10-28     18     2018-10-28 16:00:00+00:00
51  2018-10-28     19     2018-10-28 17:00:00+00:00
52  2018-10-28     20     2018-10-28 18:00:00+00:00
53  2018-10-28     21     2018-10-28 19:00:00+00:00
54  2018-10-28     22     2018-10-28 20:00:00+00:00
55  2018-10-28     23     2018-10-28 21:00:00+00:00
56  2018-10-28     24     2018-10-28 22:00:00+00:00
57  2018-10-28     25     2018-10-28 23:00:00+00:00
58  2018-10-29     1      2018-10-29 00:00:00+00:00
59  2018-10-29     2      2018-10-29 01:00:00+00:00

Please help :)

Upvotes: 3

Views: 1726

Answers (2)

Marcin
Marcin

Reputation: 339

I ended up writting a function calc_hour_offset which I mapped over date column, in order to produce int offset, which is then used to adjust values in time column to match UTC timezone. Solution works for all of my edge cases

import pandas as pd
from pandas.compat import StringIO
from pytz import timezone

def calc_hour_offset(date, to_timezone='UTC'):
    tz = timezone('Europe/London')
    dst_switch_start, dst_switch_end = [
        tzdt.date() 
        for tzdt 
        in tz._utc_transition_times 
        if tzdt.year == date.year]
    dst_switch_date_range = pd.date_range(dst_switch_start, 
                                          dst_switch_end, 
                                          freq='h', 
                                          tz=to_timezone)
    if date.date() not in dst_switch_date_range:
        return -1
    elif date.date() == dst_switch_start:
        return -1
    elif date.date() == dst_switch_end:
        return -2
    elif date.date() in dst_switch_date_range:
        return -2
    else:
        raise

str_df = r"""date           time   target_dt_utc                   
0   2018-03-24     23     2018-03-24 22:00:00+00:00
1   2018-03-24     24     2018-03-24 23:00:00+00:00
2   2018-03-25     1      2018-03-25 00:00:00+00:00
3   2018-03-25     2      2018-03-25 01:00:00+00:00
4   2018-03-25     3      2018-03-25 02:00:00+00:00
5   2018-03-25     4      2018-03-25 03:00:00+00:00
6   2018-03-25     5      2018-03-25 04:00:00+00:00
7   2018-03-25     6      2018-03-25 05:00:00+00:00
8   2018-03-25     7      2018-03-25 06:00:00+00:00
9   2018-03-25     8      2018-03-25 07:00:00+00:00
10  2018-03-25     9      2018-03-25 08:00:00+00:00
11  2018-03-25     10     2018-03-25 09:00:00+00:00
12  2018-03-25     11     2018-03-25 10:00:00+00:00
13  2018-03-25     12     2018-03-25 11:00:00+00:00
14  2018-03-25     13     2018-03-25 12:00:00+00:00
15  2018-03-25     14     2018-03-25 13:00:00+00:00
16  2018-03-25     15     2018-03-25 14:00:00+00:00
17  2018-03-25     16     2018-03-25 15:00:00+00:00
18  2018-03-25     17     2018-03-25 16:00:00+00:00
19  2018-03-25     18     2018-03-25 17:00:00+00:00
20  2018-03-25     19     2018-03-25 18:00:00+00:00
21  2018-03-25     20     2018-03-25 19:00:00+00:00
22  2018-03-25     21     2018-03-25 20:00:00+00:00
23  2018-03-25     22     2018-03-25 21:00:00+00:00
24  2018-03-25     23     2018-03-25 22:00:00+00:00
25  2018-03-26     1      2018-03-25 23:00:00+00:00
26  2018-03-26     2      2018-03-26 00:00:00+00:00
27  2018-03-26     3      2018-03-26 01:00:00+00:00
28  2018-03-26     4      2018-03-26 02:00:00+00:00
29  2018-03-26     5      2018-03-26 03:00:00+00:00
30  2018-03-26     6      2018-03-26 04:00:00+00:00
31  2018-10-27     23     2018-10-27 21:00:00+00:00
32  2018-10-27     24     2018-10-27 22:00:00+00:00
33  2018-10-28     1      2018-10-27 23:00:00+00:00
34  2018-10-28     2      2018-10-28 00:00:00+00:00
35  2018-10-28     3      2018-10-28 01:00:00+00:00
36  2018-10-28     4      2018-10-28 02:00:00+00:00
37  2018-10-28     5      2018-10-28 03:00:00+00:00
38  2018-10-28     6      2018-10-28 04:00:00+00:00
39  2018-10-28     7      2018-10-28 05:00:00+00:00
40  2018-10-28     8      2018-10-28 06:00:00+00:00
41  2018-10-28     9      2018-10-28 07:00:00+00:00
42  2018-10-28     10     2018-10-28 08:00:00+00:00
43  2018-10-28     11     2018-10-28 09:00:00+00:00
44  2018-10-28     12     2018-10-28 10:00:00+00:00
45  2018-10-28     13     2018-10-28 11:00:00+00:00
46  2018-10-28     14     2018-10-28 12:00:00+00:00
47  2018-10-28     15     2018-10-28 13:00:00+00:00
48  2018-10-28     16     2018-10-28 14:00:00+00:00
49  2018-10-28     17     2018-10-28 15:00:00+00:00
50  2018-10-28     18     2018-10-28 16:00:00+00:00
51  2018-10-28     19     2018-10-28 17:00:00+00:00
52  2018-10-28     20     2018-10-28 18:00:00+00:00
53  2018-10-28     21     2018-10-28 19:00:00+00:00
54  2018-10-28     22     2018-10-28 20:00:00+00:00
55  2018-10-28     23     2018-10-28 21:00:00+00:00
56  2018-10-28     24     2018-10-28 22:00:00+00:00
57  2018-10-28     25     2018-10-28 23:00:00+00:00
58  2018-10-29     1      2018-10-29 00:00:00+00:00
59  2018-10-29     2      2018-10-29 01:00:00+00:00"""

df = pd.read_csv(StringIO(str_df), sep='\s{2,}', index_col=0)
df['target_dt_utc'] = pd.to_datetime(df['target_dt_utc'])
df['date'] = pd.to_datetime(df['date'])

df['time_utc'] = df['date'].map(calc_hour_offset) + df['time'] 

df['dt_utc_calculated'] = (pd.to_datetime(df['date']) + pd.to_timedelta(df['time_utc'], 'h')).dt.tz_localize('UTC')

df['is dt_utc_calculated equal to target_dt_utc'] = df['target_dt_utc'] == df['dt_utc_calculated']

print(df)

Out:

         date  time             target_dt_utc  time_utc         dt_utc_calculated  is dt_utc_calculated equal to target_dt_utc
0  2018-03-24  23   2018-03-24 22:00:00+00:00  22       2018-03-24 22:00:00+00:00  True                                       
1  2018-03-24  24   2018-03-24 23:00:00+00:00  23       2018-03-24 23:00:00+00:00  True                                       
2  2018-03-25  1    2018-03-25 00:00:00+00:00  0        2018-03-25 00:00:00+00:00  True                                       
3  2018-03-25  2    2018-03-25 01:00:00+00:00  1        2018-03-25 01:00:00+00:00  True                                       
4  2018-03-25  3    2018-03-25 02:00:00+00:00  2        2018-03-25 02:00:00+00:00  True                                       
5  2018-03-25  4    2018-03-25 03:00:00+00:00  3        2018-03-25 03:00:00+00:00  True                                       
6  2018-03-25  5    2018-03-25 04:00:00+00:00  4        2018-03-25 04:00:00+00:00  True                                       
7  2018-03-25  6    2018-03-25 05:00:00+00:00  5        2018-03-25 05:00:00+00:00  True                                       
8  2018-03-25  7    2018-03-25 06:00:00+00:00  6        2018-03-25 06:00:00+00:00  True                                       
9  2018-03-25  8    2018-03-25 07:00:00+00:00  7        2018-03-25 07:00:00+00:00  True                                       
10 2018-03-25  9    2018-03-25 08:00:00+00:00  8        2018-03-25 08:00:00+00:00  True                                       
11 2018-03-25  10   2018-03-25 09:00:00+00:00  9        2018-03-25 09:00:00+00:00  True                                       
12 2018-03-25  11   2018-03-25 10:00:00+00:00  10       2018-03-25 10:00:00+00:00  True                                       
13 2018-03-25  12   2018-03-25 11:00:00+00:00  11       2018-03-25 11:00:00+00:00  True                                       
14 2018-03-25  13   2018-03-25 12:00:00+00:00  12       2018-03-25 12:00:00+00:00  True                                       
15 2018-03-25  14   2018-03-25 13:00:00+00:00  13       2018-03-25 13:00:00+00:00  True                                       
16 2018-03-25  15   2018-03-25 14:00:00+00:00  14       2018-03-25 14:00:00+00:00  True                                       
17 2018-03-25  16   2018-03-25 15:00:00+00:00  15       2018-03-25 15:00:00+00:00  True                                       
18 2018-03-25  17   2018-03-25 16:00:00+00:00  16       2018-03-25 16:00:00+00:00  True                                       
19 2018-03-25  18   2018-03-25 17:00:00+00:00  17       2018-03-25 17:00:00+00:00  True                                       
20 2018-03-25  19   2018-03-25 18:00:00+00:00  18       2018-03-25 18:00:00+00:00  True                                       
21 2018-03-25  20   2018-03-25 19:00:00+00:00  19       2018-03-25 19:00:00+00:00  True                                       
22 2018-03-25  21   2018-03-25 20:00:00+00:00  20       2018-03-25 20:00:00+00:00  True                                       
23 2018-03-25  22   2018-03-25 21:00:00+00:00  21       2018-03-25 21:00:00+00:00  True                                       
24 2018-03-25  23   2018-03-25 22:00:00+00:00  22       2018-03-25 22:00:00+00:00  True                                       
25 2018-03-26  1    2018-03-25 23:00:00+00:00 -1        2018-03-25 23:00:00+00:00  True                                       
26 2018-03-26  2    2018-03-26 00:00:00+00:00  0        2018-03-26 00:00:00+00:00  True                                       
27 2018-03-26  3    2018-03-26 01:00:00+00:00  1        2018-03-26 01:00:00+00:00  True                                       
28 2018-03-26  4    2018-03-26 02:00:00+00:00  2        2018-03-26 02:00:00+00:00  True                                       
29 2018-03-26  5    2018-03-26 03:00:00+00:00  3        2018-03-26 03:00:00+00:00  True                                       
30 2018-03-26  6    2018-03-26 04:00:00+00:00  4        2018-03-26 04:00:00+00:00  True                                       
31 2018-10-27  23   2018-10-27 21:00:00+00:00  21       2018-10-27 21:00:00+00:00  True                                       
32 2018-10-27  24   2018-10-27 22:00:00+00:00  22       2018-10-27 22:00:00+00:00  True                                       
33 2018-10-28  1    2018-10-27 23:00:00+00:00 -1        2018-10-27 23:00:00+00:00  True                                       
34 2018-10-28  2    2018-10-28 00:00:00+00:00  0        2018-10-28 00:00:00+00:00  True                                       
35 2018-10-28  3    2018-10-28 01:00:00+00:00  1        2018-10-28 01:00:00+00:00  True                                       
36 2018-10-28  4    2018-10-28 02:00:00+00:00  2        2018-10-28 02:00:00+00:00  True                                       
37 2018-10-28  5    2018-10-28 03:00:00+00:00  3        2018-10-28 03:00:00+00:00  True                                       
38 2018-10-28  6    2018-10-28 04:00:00+00:00  4        2018-10-28 04:00:00+00:00  True                                       
39 2018-10-28  7    2018-10-28 05:00:00+00:00  5        2018-10-28 05:00:00+00:00  True                                       
40 2018-10-28  8    2018-10-28 06:00:00+00:00  6        2018-10-28 06:00:00+00:00  True                                       
41 2018-10-28  9    2018-10-28 07:00:00+00:00  7        2018-10-28 07:00:00+00:00  True                                       
42 2018-10-28  10   2018-10-28 08:00:00+00:00  8        2018-10-28 08:00:00+00:00  True                                       
43 2018-10-28  11   2018-10-28 09:00:00+00:00  9        2018-10-28 09:00:00+00:00  True                                       
44 2018-10-28  12   2018-10-28 10:00:00+00:00  10       2018-10-28 10:00:00+00:00  True                                       
45 2018-10-28  13   2018-10-28 11:00:00+00:00  11       2018-10-28 11:00:00+00:00  True                                       
46 2018-10-28  14   2018-10-28 12:00:00+00:00  12       2018-10-28 12:00:00+00:00  True                                       
47 2018-10-28  15   2018-10-28 13:00:00+00:00  13       2018-10-28 13:00:00+00:00  True                                       
48 2018-10-28  16   2018-10-28 14:00:00+00:00  14       2018-10-28 14:00:00+00:00  True                                       
49 2018-10-28  17   2018-10-28 15:00:00+00:00  15       2018-10-28 15:00:00+00:00  True                                       
50 2018-10-28  18   2018-10-28 16:00:00+00:00  16       2018-10-28 16:00:00+00:00  True                                       
51 2018-10-28  19   2018-10-28 17:00:00+00:00  17       2018-10-28 17:00:00+00:00  True                                       
52 2018-10-28  20   2018-10-28 18:00:00+00:00  18       2018-10-28 18:00:00+00:00  True                                       
53 2018-10-28  21   2018-10-28 19:00:00+00:00  19       2018-10-28 19:00:00+00:00  True                                       
54 2018-10-28  22   2018-10-28 20:00:00+00:00  20       2018-10-28 20:00:00+00:00  True                                       
55 2018-10-28  23   2018-10-28 21:00:00+00:00  21       2018-10-28 21:00:00+00:00  True                                       
56 2018-10-28  24   2018-10-28 22:00:00+00:00  22       2018-10-28 22:00:00+00:00  True                                       
57 2018-10-28  25   2018-10-28 23:00:00+00:00  23       2018-10-28 23:00:00+00:00  True                                       
58 2018-10-29  1    2018-10-29 00:00:00+00:00  0        2018-10-29 00:00:00+00:00  True                                       
59 2018-10-29  2    2018-10-29 01:00:00+00:00  1        2018-10-29 01:00:00+00:00  True       

Upvotes: 0

Rich Andrews
Rich Andrews

Reputation: 1670

Excellent MCVE in the question. Let the datetime, timezone and timedeltas do the work for handling DST. I did not check the results, but it should be good.

import pandas as pd
from pandas.compat import StringIO
print(pd.__version__)

data = """index   date            hour
0   2018-03-24      23
1   2018-03-24      24
2   2018-03-25      1
3   2018-03-25      2
4   2018-03-25      3
5   2018-03-25      4
6   2018-03-25      5
7   2018-03-25      6
8   2018-03-25      7
9   2018-03-25      8
10  2018-03-25      9
11  2018-03-25      10
12  2018-03-25      11
13  2018-03-25      12
14  2018-03-25      13
15  2018-03-25      14
16  2018-03-25      15
17  2018-03-25      16
18  2018-03-25      17
19  2018-03-25      18
20  2018-03-25      19
21  2018-03-25      20
22  2018-03-25      21
23  2018-03-25      22
24  2018-03-25      23
25  2018-03-26      1
26  2018-03-26      2
27  2018-03-26      3
28  2018-03-26      4
29  2018-03-26      5
30  2018-03-26      6
31  2018-10-27      23
32  2018-10-27      24
33  2018-10-28      1
34  2018-10-28      2
35  2018-10-28      3
36  2018-10-28      4
37  2018-10-28      5
38  2018-10-28      6
39  2018-10-28      7
40  2018-10-28      8
41  2018-10-28      9
42  2018-10-28      10
43  2018-10-28      11
44  2018-10-28      12
45  2018-10-28      13
46  2018-10-28      14
47  2018-10-28      15
48  2018-10-28      16
49  2018-10-28      17
50  2018-10-28      18
51  2018-10-28      19
52  2018-10-28      20
53  2018-10-28      21
54  2018-10-28      22
55  2018-10-28      23
56  2018-10-28      24
57  2018-10-28      25
58  2018-10-29      1
59  2018-10-29      2"""

df = pd.read_csv(StringIO(data), sep='\s+', index_col=0)
df['hour'] = pd.to_timedelta(df['hour'] - 1, 'h')
df['date'] = pd.to_datetime(df['date'])
df['naive_datetime'] = df['date'] + df['hour']
df.set_index(df['naive_datetime'], inplace=True)
df.index = df.index.tz_localize('Europe/London').tz_convert('UTC')

print(df)

Produces

pytz.exceptions.NonExistentTimeError: 2018-03-25 01:00:00

Therefore, the non-existent datetimes must be deal with. There are a number of options to do this, one is to ignore the datetimes which do not exist in the local DST timezone that jumps time twice a year.

# receives non-existent time exception because of naive datetime that does not exist in Europe/London
#df.index = df.index.tz_localize('Europe/London').tz_convert('UTC')

# receives AmbiguousTimeError: Cannot infer dst time from 2018-10-28 01:00:00 as there are no repeated times
#df.index = df.index.tz_localize('Europe/London', ambiguous='infer').tz_convert('UTC')

df.index = df.index.tz_localize('Europe/London', ambiguous='NaT', nonexistent='NaT').tz_convert('UTC')
df.index.name = "datetime Europe/London"

# if there is timestamped data in the dataframe, something has to be done with it.
# The data for the missing time is probably best dropped
#df = df[df.index.notnull()]

# interpolate doesn't work: https://github.com/pandas-dev/pandas/issues/11701, but then again the time does not exist in local tz...
#df['datetime Europe/London interpolated'] = df.index.to_series().interpolate(method='linear')

Upvotes: 1

Related Questions