@@ -676,6 +676,91 @@ def test_add_file_after_2107(self):
676676 zinfo = zipfp .getinfo (TESTFN )
677677 self .assertEqual (zinfo .date_time , (2107 , 12 , 31 , 23 , 59 , 59 ))
678678
679+ def test_add_file_with_ext_timestamp (self ):
680+ """Check that calling ZipFile.write() sets extra data according to
681+ with_ext_timestamps parameter."""
682+ mtime = 946684800.123456
683+ mtime_ns = 946684800_123456789
684+ atime_ns = 946684800_987654321
685+ ctime_ns = 946684800_555555555
686+
687+ with mock .patch ('os.stat_result.st_mtime' , mtime ), \
688+ mock .patch ('os.stat_result.st_mtime_ns' , mtime_ns ), \
689+ mock .patch ('os.stat_result.st_atime_ns' , atime_ns ), \
690+ mock .patch ('os.stat_result.st_ctime_ns' , ctime_ns ):
691+
692+ # with_ext_timestamps=False (default)
693+ with zipfile .ZipFile (TESTFN2 , "w" ) as zipfp :
694+ zipfp .write (TESTFN )
695+
696+ with zipfile .ZipFile (TESTFN2 ) as zipfp :
697+ zinfo = zipfp .infolist ()[0 ]
698+
699+ self .assertEqual (zinfo .extra , b'' )
700+
701+ # with_ext_timestamps=True
702+ with zipfile .ZipFile (TESTFN2 , "w" , with_ext_timestamps = True ) as zipfp :
703+ zipfp .write (TESTFN )
704+
705+ with zipfile .ZipFile (TESTFN2 ) as zipfp :
706+ zinfo = zipfp .infolist ()[0 ]
707+
708+ self .assertEqual (zinfo .date_time [0 ], 2000 )
709+
710+ # NTFS Extra Field (0x000a)
711+ delta = 116444736000000000
712+ ntfs_field = struct .unpack_from ('<HHLHHQQQ' , zinfo .extra )
713+ self .assertEqual (ntfs_field , (
714+ 0x000a , 32 ,
715+ 0 , 0x0001 , 24 ,
716+ mtime_ns // 100 + delta ,
717+ atime_ns // 100 + delta ,
718+ ctime_ns // 100 + delta ,
719+ ))
720+
721+ # Extended timestamp (0x5455)
722+ ut_field = struct .unpack_from ('<HHBL' , zinfo .extra , struct .calcsize ('<HHLHHQQQ' ))
723+ self .assertEqual (ut_field , (0x5455 , 5 , 1 , int (mtime )))
724+
725+ def test_add_file_with_ext_timestamp_after_2038 (self ):
726+ """Extended timestamp field should exist for a timestamp after
727+ 2038-01-19T03:14:07Z."""
728+ mtime = 2147483648.123456 # 2038-01-19T03:14:08.123456Z
729+
730+ with mock .patch ('os.stat_result.st_mtime' , mtime ):
731+ with zipfile .ZipFile (TESTFN2 , "w" , strict_timestamps = False ,
732+ with_ext_timestamps = True ) as zipfp :
733+ zipfp .write (TESTFN )
734+
735+ with zipfile .ZipFile (TESTFN2 ) as zipfp :
736+ zinfo = zipfp .infolist ()[0 ]
737+
738+ self .assertEqual (zinfo .date_time [0 ], 2038 )
739+
740+ # Extended timestamp (0x5455)
741+ ntfs_field_len = struct .calcsize ('<HHLHHQQQ' )
742+ ut_field = struct .unpack_from ('<HHBL' , zinfo .extra , ntfs_field_len )
743+ self .assertEqual (ut_field , (0x5455 , 5 , 1 , int (mtime )))
744+
745+ def test_add_file_with_ext_timestamp_after_2106 (self ):
746+ """Extended timestamp field should not exist for a timestamp after
747+ 2106-02-07T06:28:15Z."""
748+ mtime = 4294967296.123456 # 2106-02-07T06:28:16.123456Z
749+
750+ with mock .patch ('os.stat_result.st_mtime' , mtime ):
751+ with zipfile .ZipFile (TESTFN2 , "w" , strict_timestamps = False ,
752+ with_ext_timestamps = True ) as zipfp :
753+ zipfp .write (TESTFN )
754+
755+ with zipfile .ZipFile (TESTFN2 ) as zipfp :
756+ zinfo = zipfp .infolist ()[0 ]
757+
758+ self .assertEqual (zinfo .date_time [0 ], 2106 )
759+
760+ # Only an NTFS Extra Field (0x000a) exists
761+ ntfs_field_len = struct .calcsize ('<HHLHHQQQ' )
762+ self .assertEqual (len (zinfo .extra ), ntfs_field_len )
763+
679764
680765@requires_zlib ()
681766class DeflateTestsWithSourceFile (AbstractTestsWithSourceFile ,
0 commit comments