15
15
from pandas ._tseries import Timestamp
16
16
import pandas ._tseries as lib
17
17
18
+ def _utc ():
19
+ import pytz
20
+ return pytz .utc
21
+
18
22
# -------- some conversion wrapper functions
19
23
20
24
def _as_i8 (arg ):
@@ -195,48 +199,8 @@ def __new__(cls, data=None,
195
199
"supplied" )
196
200
197
201
if data is None :
198
- _normalized = True
199
-
200
- if start is not None :
201
- start = Timestamp (start )
202
- if not isinstance (start , Timestamp ):
203
- raise ValueError ('Failed to convert %s to timestamp'
204
- % start )
205
-
206
- if normalize :
207
- start = normalize_date (start )
208
- _normalized = True
209
- else :
210
- _normalized = _normalized and start .time () == _midnight
211
-
212
- if end is not None :
213
- end = Timestamp (end )
214
- if not isinstance (end , Timestamp ):
215
- raise ValueError ('Failed to convert %s to timestamp'
216
- % end )
217
-
218
- if normalize :
219
- end = normalize_date (end )
220
- _normalized = True
221
- else :
222
- _normalized = _normalized and end .time () == _midnight
223
-
224
- start , end , tz = tools ._figure_out_timezone (start , end , tz )
225
-
226
- if (offset ._should_cache () and
227
- not (offset ._normalize_cache and not _normalized ) and
228
- _naive_in_cache_range (start , end )):
229
- index = cls ._cached_range (start , end , periods = periods ,
230
- offset = offset , name = name )
231
- else :
232
- index = _generate_regular_range (start , end , periods , offset )
233
-
234
- index = index .view (cls )
235
- index .name = name
236
- index .offset = offset
237
- index .tz = tz
238
-
239
- return index
202
+ return cls ._generate (start , end , periods , name , offset ,
203
+ tz = tz , normalize = normalize )
240
204
241
205
if not isinstance (data , np .ndarray ):
242
206
if np .isscalar (data ):
@@ -292,6 +256,59 @@ def __new__(cls, data=None,
292
256
293
257
return subarr
294
258
259
+ @classmethod
260
+ def _generate (cls , start , end , periods , name , offset ,
261
+ tz = None , normalize = False ):
262
+ _normalized = True
263
+
264
+ if start is not None :
265
+ start = Timestamp (start )
266
+ if not isinstance (start , Timestamp ):
267
+ raise ValueError ('Failed to convert %s to timestamp'
268
+ % start )
269
+
270
+ if normalize :
271
+ start = normalize_date (start )
272
+ _normalized = True
273
+ else :
274
+ _normalized = _normalized and start .time () == _midnight
275
+
276
+ if end is not None :
277
+ end = Timestamp (end )
278
+ if not isinstance (end , Timestamp ):
279
+ raise ValueError ('Failed to convert %s to timestamp'
280
+ % end )
281
+
282
+ if normalize :
283
+ end = normalize_date (end )
284
+ _normalized = True
285
+ else :
286
+ _normalized = _normalized and end .time () == _midnight
287
+
288
+ start , end , tz = tools ._figure_out_timezone (start , end , tz )
289
+
290
+ if (offset ._should_cache () and
291
+ not (offset ._normalize_cache and not _normalized ) and
292
+ _naive_in_cache_range (start , end )):
293
+ index = cls ._cached_range (start , end , periods = periods ,
294
+ offset = offset , name = name )
295
+ else :
296
+ index = _generate_regular_range (start , end , periods , offset )
297
+
298
+ if tz is not None :
299
+ # Convert local to UTC
300
+ ints = index .view ('i8' )
301
+ lib .tz_localize_check (ints , tz )
302
+ index = tz_convert (ints , tz , _utc ())
303
+ index = index .view ('M8[us]' )
304
+
305
+ index = index .view (cls )
306
+ index .name = name
307
+ index .offset = offset
308
+ index .tz = tz
309
+
310
+ return index
311
+
295
312
@classmethod
296
313
def _simple_new (cls , values , name , offset , tz ):
297
314
result = values .view (cls )
@@ -621,8 +638,8 @@ def _maybe_utc_convert(self, other):
621
638
this = self
622
639
if isinstance (other , DatetimeIndex ):
623
640
if self .tz != other .tz :
624
- this = self .tz_normalize ('UTC' )
625
- other = other .tz_normalize ('UTC' )
641
+ this = self .tz_convert ('UTC' )
642
+ other = other .tz_convert ('UTC' )
626
643
return this , other
627
644
628
645
def _wrap_joined_index (self , joined , other ):
@@ -1029,7 +1046,7 @@ def _view_like(self, ndarray):
1029
1046
result .name = self .name
1030
1047
return result
1031
1048
1032
- def tz_normalize (self , tz ):
1049
+ def tz_convert (self , tz ):
1033
1050
"""
1034
1051
Convert DatetimeIndex from one time zone to another (using pytz)
1035
1052
@@ -1040,16 +1057,10 @@ def tz_normalize(self, tz):
1040
1057
tz = tools ._maybe_get_tz (tz )
1041
1058
1042
1059
if self .tz is None :
1043
- new_dates = lib .tz_localize (self .asi8 , tz )
1044
- else :
1045
- new_dates = lib .tz_convert (self .asi8 , self .tz , tz )
1060
+ return self .tz_localize (tz )
1046
1061
1047
- new_dates = new_dates .view ('M8[us]' )
1048
- new_dates = new_dates .view (type (self ))
1049
- new_dates .offset = self .offset
1050
- new_dates .tz = tz
1051
- new_dates .name = self .name
1052
- return new_dates
1062
+ # No conversion since timestamps are all UTC to begin with
1063
+ return self ._simple_new (self .values , self .name , self .offset , tz )
1053
1064
1054
1065
def tz_localize (self , tz ):
1055
1066
"""
@@ -1061,16 +1072,15 @@ def tz_localize(self, tz):
1061
1072
"""
1062
1073
if self .tz is not None :
1063
1074
raise ValueError ("Already have timezone info, "
1064
- "use tz_normalize to convert." )
1075
+ "use tz_convert to convert." )
1065
1076
tz = tools ._maybe_get_tz (tz )
1066
1077
1067
- new_dates = lib .tz_localize (self .asi8 , tz )
1078
+ lib .tz_localize_check (self .asi8 , tz )
1079
+
1080
+ # Convert to UTC
1081
+ new_dates = tz_convert (self .asi8 , tz , _utc ())
1068
1082
new_dates = new_dates .view ('M8[us]' )
1069
- new_dates = new_dates .view (self .__class__ )
1070
- new_dates .offset = self .offset
1071
- new_dates .tz = tz
1072
- new_dates .name = self .name
1073
- return new_dates
1083
+ return self ._simple_new (new_dates , self .name , self .offset , tz )
1074
1084
1075
1085
def tz_validate (self ):
1076
1086
"""
@@ -1095,6 +1105,65 @@ def tz_validate(self):
1095
1105
1096
1106
return True
1097
1107
1108
+ def tz_convert (vals , tz1 , tz2 ):
1109
+ n = len (vals )
1110
+ import pytz
1111
+ # Convert to UTC
1112
+
1113
+ if tz1 .zone != 'UTC' :
1114
+ utc_dates = np .empty (n , dtype = np .int64 )
1115
+ deltas = _get_deltas (tz1 )
1116
+ trans = _get_transitions (tz1 )
1117
+ pos = max (trans .searchsorted (vals [0 ], side = 'right' ) - 1 , 0 )
1118
+
1119
+ offset = deltas [pos ]
1120
+ for i in range (n ):
1121
+ v = vals [i ]
1122
+ if v >= trans [pos + 1 ]:
1123
+ pos += 1
1124
+ offset = deltas [pos ]
1125
+ utc_dates [i ] = v - offset
1126
+ else :
1127
+ utc_dates = vals
1128
+
1129
+ if tz2 .zone == 'UTC' :
1130
+ return utc_dates
1131
+
1132
+ # Convert UTC to other timezone
1133
+
1134
+ result = np .empty (n , dtype = np .int64 )
1135
+ trans = _get_transitions (tz2 )
1136
+ deltas = _get_deltas (tz2 )
1137
+ pos = max (trans .searchsorted (utc_dates [0 ], side = 'right' ) - 1 , 0 )
1138
+ offset = deltas [pos ]
1139
+ for i in range (n ):
1140
+ v = utc_dates [i ]
1141
+ if v >= trans [pos + 1 ]:
1142
+ pos += 1
1143
+ offset = deltas [pos ]
1144
+ result [i ] = v + offset
1145
+
1146
+ return result
1147
+
1148
+ trans_cache = {}
1149
+ utc_offset_cache = {}
1150
+
1151
+ def _get_transitions (tz ):
1152
+ """
1153
+ Get UTC times of DST transitions
1154
+ """
1155
+ if tz not in trans_cache :
1156
+ arr = np .array (tz ._utc_transition_times , dtype = 'M8[us]' )
1157
+ trans_cache [tz ] = arr .view ('i8' )
1158
+ return trans_cache [tz ]
1159
+
1160
+ def _get_deltas (tz ):
1161
+ """
1162
+ Get UTC offsets in microseconds corresponding to DST transitions
1163
+ """
1164
+ if tz not in utc_offset_cache :
1165
+ utc_offset_cache [tz ] = lib ._unbox_utcoffsets (tz ._transition_info )
1166
+ return utc_offset_cache [tz ]
1098
1167
1099
1168
def _generate_regular_range (start , end , periods , offset ):
1100
1169
if com ._count_not_none (start , end , periods ) < 2 :
0 commit comments