@@ -3104,6 +3104,134 @@ def test_str_for_named_is_name(self):
3104
3104
self .assertEqual (str (offset ), name )
3105
3105
3106
3106
3107
+ def get_utc_offset_hours (ts ):
3108
+ # take a Timestamp and compute total hours of utc offset
3109
+ o = ts .utcoffset ()
3110
+ return (o .days * 24 * 3600 + o .seconds ) / 3600.0
3111
+
3112
+
3113
+ class TestDST (tm .TestCase ):
3114
+ """
3115
+ test DateOffset additions over Daylight Savings Time
3116
+ """
3117
+ # one microsecond before the DST transition
3118
+ ts_pre_fallback = "2013-11-03 01:59:59.999999"
3119
+ ts_pre_springfwd = "2013-03-10 01:59:59.999999"
3120
+
3121
+ # test both basic names and dateutil timezones
3122
+ timezone_utc_offsets = {
3123
+ 'US/Eastern' : dict (
3124
+ utc_offset_daylight = - 4 ,
3125
+ utc_offset_standard = - 5 ,
3126
+ ),
3127
+ 'dateutil/US/Pacific' : dict (
3128
+ utc_offset_daylight = - 7 ,
3129
+ utc_offset_standard = - 8 ,
3130
+ )
3131
+ }
3132
+ valid_date_offsets_singular = [
3133
+ 'weekday' , 'day' , 'hour' , 'minute' , 'second' , 'microsecond'
3134
+ ]
3135
+ valid_date_offsets_plural = [
3136
+ 'weeks' , 'days' ,
3137
+ 'hours' , 'minutes' , 'seconds' ,
3138
+ 'milliseconds' , 'microseconds'
3139
+ ]
3140
+
3141
+ def _test_all_offsets (self , n , ** kwds ):
3142
+ valid_offsets = self .valid_date_offsets_plural if n > 1 \
3143
+ else self .valid_date_offsets_singular
3144
+
3145
+ for name in valid_offsets :
3146
+ self ._test_offset (offset_name = name , offset_n = n , ** kwds )
3147
+
3148
+ def _test_offset (self , offset_name , offset_n , tstart , expected_utc_offset ):
3149
+ offset = DateOffset (** {offset_name : offset_n })
3150
+ t = tstart + offset
3151
+ if expected_utc_offset is not None :
3152
+ self .assertTrue (get_utc_offset_hours (t ) == expected_utc_offset )
3153
+
3154
+ if offset_name == 'weeks' :
3155
+ # dates should match
3156
+ self .assertTrue (
3157
+ t .date () ==
3158
+ timedelta (days = 7 * offset .kwds ['weeks' ]) + tstart .date ()
3159
+ )
3160
+ # expect the same day of week, hour of day, minute, second, ...
3161
+ self .assertTrue (
3162
+ t .dayofweek == tstart .dayofweek and
3163
+ t .hour == tstart .hour and
3164
+ t .minute == tstart .minute and
3165
+ t .second == tstart .second
3166
+ )
3167
+ elif offset_name == 'days' :
3168
+ # dates should match
3169
+ self .assertTrue (timedelta (offset .kwds ['days' ]) + tstart .date () == t .date ())
3170
+ # expect the same hour of day, minute, second, ...
3171
+ self .assertTrue (
3172
+ t .hour == tstart .hour and
3173
+ t .minute == tstart .minute and
3174
+ t .second == tstart .second
3175
+ )
3176
+ elif offset_name in self .valid_date_offsets_singular :
3177
+ # expect the signular offset value to match between tstart and t
3178
+ datepart_offset = getattr (t , offset_name if offset_name != 'weekday' else 'dayofweek' )
3179
+ self .assertTrue (datepart_offset == offset .kwds [offset_name ])
3180
+ else :
3181
+ # the offset should be the same as if it was done in UTC
3182
+ self .assertTrue (
3183
+ t == (tstart .tz_convert ('UTC' ) + offset ).tz_convert ('US/Pacific' )
3184
+ )
3185
+
3186
+ def _make_timestamp (self , string , hrs_offset , tz ):
3187
+ offset_string = '{hrs:02d}00' .format (hrs = hrs_offset ) if hrs_offset >= 0 else \
3188
+ '-{hrs:02d}00' .format (hrs = - 1 * hrs_offset )
3189
+ return Timestamp (string + offset_string ).tz_convert (tz )
3190
+
3191
+ def test_fallback_plural (self ):
3192
+ """test moving from daylight savings to standard time"""
3193
+ for tz , utc_offsets in self .timezone_utc_offsets .items ():
3194
+ hrs_pre = utc_offsets ['utc_offset_daylight' ]
3195
+ hrs_post = utc_offsets ['utc_offset_standard' ]
3196
+ self ._test_all_offsets (
3197
+ n = 3 ,
3198
+ tstart = self ._make_timestamp (self .ts_pre_fallback , hrs_pre , tz ),
3199
+ expected_utc_offset = hrs_post
3200
+ )
3201
+
3202
+ def test_springforward_plural (self ):
3203
+ """test moving from standard to daylight savings"""
3204
+ for tz , utc_offsets in self .timezone_utc_offsets .items ():
3205
+ hrs_pre = utc_offsets ['utc_offset_standard' ]
3206
+ hrs_post = utc_offsets ['utc_offset_daylight' ]
3207
+ self ._test_all_offsets (
3208
+ n = 3 ,
3209
+ tstart = self ._make_timestamp (self .ts_pre_springfwd , hrs_pre , tz ),
3210
+ expected_utc_offset = hrs_post
3211
+ )
3212
+
3213
+ def test_fallback_singular (self ):
3214
+ # in the case of signular offsets, we dont neccesarily know which utc offset
3215
+ # the new Timestamp will wind up in (the tz for 1 month may be different from 1 second)
3216
+ # so we don't specify an expected_utc_offset
3217
+ for tz , utc_offsets in self .timezone_utc_offsets .items ():
3218
+ hrs_pre = utc_offsets ['utc_offset_standard' ]
3219
+ self ._test_all_offsets (
3220
+ n = 1 ,
3221
+ tstart = self ._make_timestamp (self .ts_pre_fallback , hrs_pre , tz ),
3222
+ expected_utc_offset = None
3223
+ )
3224
+
3225
+ def test_springforward_singular (self ):
3226
+ for tz , utc_offsets in self .timezone_utc_offsets .items ():
3227
+ hrs_pre = utc_offsets ['utc_offset_standard' ]
3228
+ self ._test_all_offsets (
3229
+ n = 1 ,
3230
+ tstart = self ._make_timestamp (self .ts_pre_springfwd , hrs_pre , tz ),
3231
+ expected_utc_offset = None
3232
+ )
3233
+
3234
+
3107
3235
if __name__ == '__main__' :
3108
3236
nose .runmodule (argv = [__file__ , '-vvs' , '-x' , '--pdb' , '--pdb-failure' ],
3109
3237
exit = False )
0 commit comments