@NessAnnon (NessAnnon) posted a new topic, subject "Create zipfile in Python3 preserving modification time of files/folders". It read:

   I am trying to create a Zip file of a folder in Python3 and when I follow the zipfile tutorials I have read or use shutil.make_archive I have issues with the modification times of the files in the archive.
    
    Either the modification time is the time the archive is created (time the script was ran) or the time is different by one hour. I assume DST is causing this.
    
    This is the opposite of what I want to do
    
    stackoverflow.com
    
    ####  Extract files from zip file and retain mod date?
    
    **python, zip, extraction**
    
    asked by  MTAdmin  on 09:36PM - 21 Mar 12 UTC
    
    I am trying to implement the _ function at  
    delocate/tools.py at master · matthew-brett/delocate · GitHub and get error:
    [code] 
        open_readable = ensure_permissions(stat.S_IRUSR)(open)
        TypeError: 'NoneType' object is not callable
        
    [/code]

After which, @cameron (Cameron Simpson) added

    > I am trying to create a Zip file of a folder in Python3 and when I  
    >  follow the zipfile tutorials I have read or use shutil.make_archive I  
    >  have issues with the modification times of the files in the archive.
    > 
    > Either the modification time is the time the archive is created (time the script was ran) or the time is different by one hour. I assume DST is causing this.
    
    Those are 2 differet outcomings. Please post code showing how you're  
    making the archive which makes each outcome.
    
    Are you saying the dates inside the archive (eg when you list it)  
    arewrong, or tht the dates on the files/folders when you extract the  
    archive are wrong?
    
    Looking at the information for a ZipInfo obj3ect:  
    https://docs.python.org/3//library/zipfile.html#zipinfo-objects
    
    I can only assume that the Zip Archive format uses human like time  
    fields with no timezone melanieelliott@example.com. Ghastly.
    
    So, _at best_ , you're going to get an archive with the timestamps in the  
    local time of the system on which they were made, and can only expect  
    the right timestamps if you extract in the same timezone (including the  
    same daylight saving/summer time offset, if that is in play where you  
    are).
    
    > I am trying to implement the _ function at
    
    The “_” functionI'm not clear what you mean.
    
    > delocate/tools.py at master · matthew-brett/delocate · GitHub and get error:
    [code] 
    >     open_readable = ensure_permissions(stat.S_IRUSR)(open)
    >     TypeError: 'NoneType' object is not callable
    >     
    [/code]
    
    It looks like the return of `ensure_permissions(stat.S_IRUSR)` is  
    `None`. Have your implemeneted your own version or used the decorator  
    from that file?
    
    Cheers,  
    Cameron Simpson melanieelliott@example.com

Then @NessAnnon (NessAnnon) said

    The following example code is based off the tools.py script from the delocate project code I found. I set the variable for the src_dir to be a folder that should exist on all Windows systems, I just set it dst_dir to a temp folder.
    
    For me I get mod_time mismatches of approx. 1 hour for C:\Users\Public , when I run the script on that folder, some files/folders (ex Desktop) may be missing due to permissions errors (I picked a folder I thought wouldn't have any for the example), for this specific example, I am not concerned, from the files are that copied I can see mod_time mismatch and that is what I am concerned about.
    [code] 
        import os, sys, zipfile, time, stat
        from socket import gethostname
        from datetime import datetime
        
        hostname = gethostname()
        
        date = datetime.now()
        date = date.strftime('%Y-%m-%d--%H.%M.%S')
        
        src_dir = r"C:\Users\Public"
        dst_dir = r"C:\tmp"
        outfile_path = os.path.join(dst_dir, f"{hostname}___{date}___Archive.zip")
        
        
        def dir2zip(in_dir, zip_fname):
            """Make a zip file `zip_fname` with contents of directory `in_dir`
            The recorded filenames are relative to `in_dir`, so doing a standard zip
            unpack of the resulting `zip_fname` in an empty directory will result in
            the original directory contents.
            Parameters
            ----------
            in_dir : str
                Directory path containing files to go in the zip archive
            zip_fname : str
                Filename of zip archive to write
            """
        
            print('runing dir2zip')
            z = zipfile.ZipFile(zip_fname, "w", compression=zipfile.ZIP_DEFLATED)
            for root, dirs, files in os.walk(in_dir):
                for file in files:
                    in_fname = os.path.join(root, file)
                    in_stat = os.stat(in_fname)
                    # Preserve file permissions but allow copy
                    info = zipfile.ZipInfo(in_fname)
                    info.filename = os.path.relpath(in_fname, in_dir)
                    if os.path.sep == "\\":
                        # Make the paths unix friendly on windows.
                        info.filename = os.path.relpath(in_fname, in_dir).replace("\\", "/")
                    # Set time from modification time
                    info.date_time = time.localtime(in_stat.st_mtime)
                    # See https://stackoverflow.com/questions/434641/how-do-i-set-permissions-attributes-on-a-file-in-a-zip-file-using-pythons-zip/48435482#48435482 # noqa: E501
                    # Also set regular file permissions
                    perms = stat.S_IMODE(in_stat.st_mode) | stat.S_IFREG
                    info.external_attr = perms << 16
                    with open_readable(in_fname, "rb") as fobj:
                        contents = fobj.read()
                    z.writestr(info, contents, zipfile.ZIP_DEFLATED)
                pass
            z.close()
        
        
        # https://github.com/matthew-brett/delocate/blob/master/delocate/tools.py
        #def dir2zip(in_dir, zip_fname):
        
        def chmod_perms(fname):
            # Permissions relevant to chmod
            return stat.S_IMODE(os.stat(fname).st_mode)
        
        def ensure_permissions(mode_flags=stat.S_IWUSR):
            """decorator to ensure a filename has given permissions.
            If changed, original permissions are restored after the decorated
            modification.
            """
            def decorator(f):
                def modify(filename, *args, **kwargs):
                    m = 0
                    m = chmod_perms(filename) if os.path.exists(filename) else mode_flags
                    if not m & mode_flags:
                        os.chmod(filename, m | mode_flags)
                    try:
                        return f(filename, *args, **kwargs)
                    finally:
                        # restore original permissions
                        if not m & mode_flags:
                            os.chmod(filename, m)
                return modify
            return decorator
        
        open_readable = ensure_permissions(stat.S_IRUSR)(open)
        
        dir2zip(src_dir, outfile_path)
        
    [/code]
    
    I don't understand why on the line  
    `open_readable = ensure_permissions(stat.S_IRUSR)(open)`  
    There are two sets of parenthesis after the call to ensure_permissions and open inside parenthesis without any arguments.
    
    I do not recall correctly what pipes (|) mean.

To which @NessAnnon (NessAnnon) replied

    I meant to say dir2zip function, I put _ as a placeholder while typing my post and forgot to replace the underscore with the name of the function, dir2zip. I missed that when going over my post.
    
    I fixed the NoneType not callable, I forgot to uncomment the return statements that I commented out when they were just placeholders there for syntax.
    
    That dir2zip seems to have the same effect as shutil.make_archive in regards to time stamps.
    
    I have a pending post with the code from dir2zip and more details (hopefully it can be approved and merged with this one, I cannot edit my first post or the pending one, there are anti-spam privilege blockers for new users, and I can understand the reasons why).
    
    When I create a zip (or 7zip) archive in 7-Zip File Manager and extract it, I get the same time on both sides.


{'source': 'discourse', 'url': 'https://discuss.python.org/t/create-zipfile-in-python3-preserving-modification-time-of-files-folders/12598', 'title': 'Create zipfile in Python3 preserving modification time of files/folders', 'views': 729, 'has_accepted_answer': False, 'word_count': 1085, 'like_count': 0, 'reply_count': 1, 'participant_count': 2, 'posts': 4, 'tags': '', 'author': '@NessAnnon (NessAnnon)', 'participants': '@NessAnnon (NessAnnon), @cameron (Cameron Simpson)', 'image_url': [None], 'created_at': '2021-12-16T22:09:06.960Z', 'last_posted_at': '2021-12-18T22:17:53.190Z'}