Reputation: 339
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
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
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