Create zipfile in Python3 preserving modification time of files/folders

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

I am trying to implement the _ function at
delocate/tools.py at master · matthew-brett/delocate · GitHub and get error:

open_readable = ensure_permissions(stat.S_IRUSR)(open)
TypeError: 'NoneType' object is not callable

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 information at all. 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:

open_readable = ensure_permissions(stat.S_IRUSR)(open)
TypeError: 'NoneType' object is not callable

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 cs@cskk.id.au

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.

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)

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.

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.