55import shutil
66import unittest
77import uuid
8- from datetime import datetime , timedelta
8+ from datetime import datetime , timedelta , timezone
99
10- from dateutil import tz
1110from pathlib import Path
1211
1312from io import BytesIO
@@ -222,7 +221,7 @@ def test_history_group(self):
222221
223222 def test_add_delete_move_entry (self ):
224223 unique_str = 'test_add_entry_'
225- expiry_time = datetime .now ()
224+ expiry_time = datetime .now (timezone . utc )
226225 entry = self .kp .add_entry (
227226 self .kp .root_group ,
228227 unique_str + 'title' ,
@@ -246,8 +245,6 @@ def test_add_delete_move_entry(self):
246245 self .assertEqual (len (results .tags ), 6 )
247246 self .assertTrue (results .uuid != None )
248247 self .assertTrue (results .autotype_sequence is None )
249- # convert naive datetime to utc
250- expiry_time_utc = expiry_time .replace (tzinfo = tz .gettz ()).astimezone (tz .gettz ('UTC' ))
251248 self .assertEqual (results .icon , icons .KEY )
252249
253250 sub_group = self .kp .add_group (self .kp .root_group , 'sub_group' )
@@ -283,6 +280,52 @@ def test_raise_exception_entry(self):
283280 )
284281 self .assertRaises (Exception , entry )
285282
283+ # ---------- Timezone test -----------
284+
285+ def test_expiration_time_tz (self ):
286+ # The expiration date is compared in UTC
287+ # setting expiration date with tz offset 6 hours should result in expired entry
288+ unique_str = 'test_exptime_tz_1_'
289+ expiry_time = datetime .now (timezone (offset = timedelta (hours = 6 ))).replace (microsecond = 0 )
290+ self .kp .add_entry (
291+ self .kp .root_group ,
292+ unique_str + 'title' ,
293+ unique_str + 'user' ,
294+ unique_str + 'pass' ,
295+ expiry_time = expiry_time
296+ )
297+ results = self .kp .find_entries_by_title (unique_str + 'title' , first = True )
298+ self .assertEqual (results .expired , True )
299+ self .assertEqual (results .expiry_time , expiry_time .astimezone (timezone .utc ))
300+
301+ # setting expiration date with UTC tz should result in expired entry
302+ unique_str = 'test_exptime_tz_2_'
303+ expiry_time = datetime .now (timezone .utc ).replace (microsecond = 0 )
304+ self .kp .add_entry (
305+ self .kp .root_group ,
306+ unique_str + 'title' ,
307+ unique_str + 'user' ,
308+ unique_str + 'pass' ,
309+ expiry_time = expiry_time
310+ )
311+ results = self .kp .find_entries_by_title (unique_str + 'title' , first = True )
312+ self .assertEqual (results .expired , True )
313+ self .assertEqual (results .expiry_time , expiry_time .astimezone (timezone .utc ))
314+
315+ # setting expiration date with tz offset -6 hours while adding 6 hours should result in valid entry
316+ unique_str = 'test_exptime_tz_3_'
317+ expiry_time = datetime .now (timezone (offset = timedelta (hours = - 6 ))).replace (microsecond = 0 ) + timedelta (hours = 6 )
318+ self .kp .add_entry (
319+ self .kp .root_group ,
320+ unique_str + 'title' ,
321+ unique_str + 'user' ,
322+ unique_str + 'pass' ,
323+ expiry_time = expiry_time
324+ )
325+ results = self .kp .find_entries_by_title (unique_str + 'title' , first = True )
326+ self .assertEqual (results .expired , False )
327+ self .assertEqual (results .expiry_time , expiry_time .astimezone (timezone .utc ))
328+
286329 # ---------- Entries representation -----------
287330
288331 def test_print_entries (self ):
@@ -433,7 +476,7 @@ def test_recyclebinemptying(self):
433476class EntryTests3 (KDBX3Tests ):
434477
435478 def test_fields (self ):
436- time = datetime .now ().replace (microsecond = 0 )
479+ expiry_time = datetime .now (timezone . utc ).replace (microsecond = 0 )
437480 entry = Entry (
438481 'title' ,
439482 'username' ,
@@ -443,7 +486,7 @@ def test_fields(self):
443486 tags = 'tags' ,
444487 otp = 'otp' ,
445488 expires = True ,
446- expiry_time = time ,
489+ expiry_time = expiry_time ,
447490 icon = icons .KEY ,
448491 kp = self .kp
449492 )
@@ -456,8 +499,7 @@ def test_fields(self):
456499 self .assertEqual (entry .tags , ['tags' ])
457500 self .assertEqual (entry .otp , 'otp' )
458501 self .assertEqual (entry .expires , True )
459- self .assertEqual (entry .expiry_time ,
460- time .replace (tzinfo = tz .gettz ()).astimezone (tz .gettz ('UTC' )))
502+ self .assertEqual (entry .expiry_time , expiry_time )
461503 self .assertEqual (entry .icon , icons .KEY )
462504 self .assertEqual (entry .is_a_history_entry , False )
463505 self .assertEqual (
@@ -487,7 +529,7 @@ def test_references(self):
487529 self .assertNotEqual (clone1 , clone2 )
488530
489531 def test_set_and_get_fields (self ):
490- time = datetime .now ().replace (microsecond = 0 )
532+ time = datetime .now (timezone . utc ).replace (microsecond = 0 )
491533 changed_time = time + timedelta (hours = 9 )
492534 changed_string = 'changed_'
493535 entry = Entry (
@@ -528,8 +570,7 @@ def test_set_and_get_fields(self):
528570 self .assertEqual (entry .get_custom_property ('foo' ), None )
529571 # test time properties
530572 self .assertEqual (entry .expires , False )
531- self .assertEqual (entry .expiry_time ,
532- changed_time .replace (tzinfo = tz .gettz ()).astimezone (tz .gettz ('UTC' )))
573+ self .assertEqual (entry .expiry_time , changed_time )
533574
534575 entry .tags = 'changed_tags'
535576 self .assertEqual (entry .tags , ['changed_tags' ])
@@ -540,8 +581,8 @@ def test_set_and_get_fields(self):
540581
541582 def test_expired_datetime_offset (self ):
542583 """Test for https://github.com/pschmitt/pykeepass/issues/115"""
543- future_time = datetime .now () + timedelta (days = 1 )
544- past_time = datetime .now () - timedelta (days = 1 )
584+ future_time = datetime .now (timezone . utc ) + timedelta (days = 1 )
585+ past_time = datetime .now (timezone . utc ) - timedelta (days = 1 )
545586 entry = Entry (
546587 'title' ,
547588 'username' ,
@@ -695,7 +736,7 @@ def test_find_history_entries(self):
695736
696737 # change the active entries to test integrity of the history items
697738 backup = {}
698- now = datetime .now ()
739+ now = datetime .now (timezone . utc )
699740 for entry in res1 :
700741 backup [entry .uuid ] = {"atime" : entry .atime , "mtime" : entry .mtime , "ctime" : entry .ctime }
701742 entry .title = changed + 'title'
@@ -863,8 +904,8 @@ def test_credchange(self):
863904
864905 required_days = 5
865906 recommended_days = 5
866- unexpired_date = datetime .now () - timedelta (days = 1 )
867- expired_date = datetime .now () - timedelta (days = 10 )
907+ unexpired_date = datetime .now (timezone . utc ) - timedelta (days = 1 )
908+ expired_date = datetime .now (timezone . utc ) - timedelta (days = 10 )
868909
869910 self .kp .credchange_required_days = required_days
870911 self .kp .credchange_recommended_days = recommended_days
0 commit comments