@@ -3104,6 +3104,134 @@ def test_str_for_named_is_name(self):
31043104 self .assertEqual (str (offset ), name )
31053105
31063106
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+
31073235if __name__ == '__main__' :
31083236 nose .runmodule (argv = [__file__ , '-vvs' , '-x' , '--pdb' , '--pdb-failure' ],
31093237 exit = False )
0 commit comments