Daylight savings and getmtime

Qvx qvx3000 at yahoo.com
Fri Jan 28 18:03:34 EST 2005


Hello,

I'we written a simple web deployment program which scans for the
changes made to local copy of web site. Changed files are than
packaged into a zip file and deployed to web server.

Now here's the catch. Changes are computed using (1) log file from the
last deployment and (2) local file system. Log file contains
datestamps (integers) returned from os.path.getmtime(f) function at
the time of last deployment. So i'm comparing two getmtime() values.
The problem is when Daylight saving kicks in: suddenly all local files
are reported as older than they were at the time of deployment.

How do I compensate for this?

Thanks,
Tvrtko



For those curious, here is the script. I apologize for Croatian
comments and literals and missing private libraries, but I think the
code is self-explanatory.
______________

# -*- coding: windows-1250 -*- 

from os.path import getmtime, join
from os import walk, rename
from zipfile import ZipFile, ZIP_DEFLATED
from sets import Set
from StringIO import StringIO
from ftplib import FTP
from qvx.io import adapt_stdout, unadapt_stdout
from qvx.composite import Dot
from qvx.compositeutil import read_composite
import sys
import time

class DeploymentError(Exception):
    pass

class Deployer:
    def __init__ (self, cfg_file):
        self.reset(cfg_file)

    def reset (self, cfg_file):
        self.read_cfg(cfg_file)
        self.local_files      = []
        self.remote_files     = []
        self.new_files        = []
        self.deleted_files    = []
        self.newer_files      = []
        self.older_files      = []
        self.www_all_time     = None
        self.old_deployed     = False

    def read_cfg (self, cfg_file):
        tree = read_composite(cfg_file)
        self.cfg = Dot(tree).DEPLOYMENT

    def prepare_file_lists (self):
        # Sastavi popis _datoteka_ u DIR direktoriju.
        # Izostavi datoteke iz _notes direktorija
        self.local_files = []
        for root, dirs, files in walk(self.cfg.DIR):
            filtered = [join(root, f).replace('\\', '/') for f in
files if f not in self.cfg.SKIP_FILES]
            self.local_files.extend(filtered)
            for skip_dir in self.cfg.SKIP_DIRS.split(','):
                if skip_dir.strip() in dirs:
                    dirs.remove(skip_dir)
        
        # Sastavi popis datoteka na serveru
        # Koristi se sa informacijama od zadnjeg deploymenta
        # Popis se nalazi u www_all.txt datoteci
        self.remote_files = []
        remote_stamps = {}
        zip = ZipFile(self.cfg.FILE, 'r')
        for line in zip.read('www_all.txt').split('\n'):
            name, stamp = line.split('\t')
            remote_stamps[name] = int(stamp)
            self.remote_files.append(name)
        self.www_all_time = zip.getinfo('www_all.txt').date_time
        
        # Deployment nije obavljen ako nije zapisan log
        self.old_deployed = 'deployment.log' in zip.namelist()
        zip.close()
        
        # Rastavi datoteke u tri kategorije: nove, obrisane i iste
        lset = Set(self.local_files)
        rset = Set(self.remote_files)
        self.new_files     = list(lset - rset)
        self.deleted_files = list(rset - lset)
        common_files       = list(lset & rset)
        
        # Pogledaj što se promijenilo u zajedničkim datotekama
        self.newer_files = []
        self.older_files = []
        for name in common_files:
            remotetime = remote_stamps[name]
            localtime  = getmtime(name) #+ 3600
            # Ako je razlika unutar sekunde, zanemari
            if abs(remotetime-localtime) > int(self.cfg.IGNORE_SEC):
                if remotetime > localtime:
                    self.older_files.append(name)
                elif localtime > remotetime:
                    self.newer_files.append(name)

    def need_redeployment (self):
        return not self.old_deployed

    def check_changes (self):
        # Ne bi trebalo biti starijih
        if self.older_files:
            raise DeploymentError('Ne smije biti starijih datoteka!')
        if not (self.new_files or self.deleted_files or
self.newer_files):
            raise DeploymentError('Nema promjena!')

    def make_deployment_file (self):
        # Uključi potrebne datoteke
        deployment = ZipFile('new_'+self.cfg.FILE, 'w', ZIP_DEFLATED)
        for name in self.new_files + self.newer_files:
            deployment.write(name)
        
        # Uključi popis svih datoteka
        all_files = '\n'.join([f+'\t'+str(getmtime(f)) for f in
self.local_files])
        deployment.writestr('www_all.txt', all_files)
        
        # Uključi popis datoteka za obrisati
        for_delete = '\n'.join(self.deleted_files)
        if for_delete:
            deployment.writestr('www_delete.txt', for_delete)
        deployment.close()
        print '\nNapravljena je nova deployment datoteka.'

        # Preimenuj deployment datoteke
        timestr = '%04d-%02d-%02d_%02d-%02d-%02d' % self.www_all_time
        old_deployment = self.cfg.FILE.replace('.zip',
'_'+timestr+'.zip')
        rename(self.cfg.FILE, old_deployment)
        rename('new_'+self.cfg.FILE, self.cfg.FILE)
        print 'Stara deployment datoteka se sada zove', old_deployment

    def exec_ftp (self, silent, logtext, func, *arg):
        try:
            self.ftp_log = self.ftp_log + '\n\n' + logtext
            self.ftp_log = self.ftp_log + '\n' + func(*arg)
            return 'OK'
        except Exception, e:
            self.ftp_log = self.ftp_log + '\n' + str(e)
            if not silent:
                raise DeploymentError(str(e))
            return 'ERROR'

    def deploy_ftp (self):
        self.ftp_log = ''

        # Spoji se na FTP server
        print '\nSpajam se na ftp server %s ...' % (self.cfg.SERVER,),
        try:
            ftp = FTP(self.cfg.SERVER)
            print 'OK'
        except Exception, e:
            print 'ERROR'
            raise DeploymentError('Ne mogu se spojiti na FTP server:
'+str(e))
        
        # Logiraj se
        print 'Logiram se kao %s ... ' % (self.cfg.USER,), 
        print self.exec_ftp(False, 'LOGIN', ftp.login, self.cfg.USER,
self.cfg.PASSWORD)
        
        # Kopiraj datoteke
        deployment = ZipFile(self.cfg.FILE, 'r')
        deployment_files = [n for n in deployment.namelist() if n not
in ['www_all.txt', 'www_delete.txt', 'deployment.log']]
        if deployment_files:
            print 'Šaljem datoteke:'
        for name in deployment_files:
            bytes = deployment.read(name)
            fp = StringIO(bytes)
            print ' ', name, len(bytes), ' bytes ...',
            print self.exec_ftp(True, 'STORBIN '+name, ftp.storbinary,
'STOR '+name, fp)
            fp.close()
        
        # Obriši datoteke
        if 'www_delete.txt' in deployment.namelist():
            deleted_files =
deployment.read('www_delete.txt').split('\n')
            print 'Brišem datoteke:'
            for name in deleted_files:
                print ' ', name, '...',
                print self.exec_ftp(True, 'DEL '+name, ftp.delete,
name)
        deployment.close()
        
        # Bye bye
        print 'Završavam s radom ...',
        print self.exec_ftp(True, 'BYE', ftp.quit)
        
        # Ispiši FTP log
        print '\nFTP log:'
        print '-'*20,
        print self.ftp_log

    def write_log (self, text):
        # Zapiši deployment log
        deployment = ZipFile(self.cfg.FILE, 'a', ZIP_DEFLATED)
        deployment.writestr('deployment.log', text)
        deployment.close()

    def run_interactively (self):
        # Adaptiraj stdout: mijenja kodnu stranicu u cp852 radi ispisa
        # hrvatskih grafema, također logira sve poruke
        ofa = adapt_stdout('cp852', True)

        try:
            try:
                # Analiziraj datoteke
                self.prepare_file_lists()
                print '*'*5, 'Obrisani', '*'*5, self.deleted_files 
                print '*'*5, 'Novi    ', '*'*5, self.new_files
                print '*'*5, 'Noviji  ', '*'*5, self.newer_files  
                print '*'*5, 'Stariji ', '*'*5, self.older_files  

                if self.need_redeployment():
                    # Pitaj korisnika da li želi poslati od prije
pripremljeni deployment na server
                    yn = raw_input('\nOd prije pripremljeni deployment
nije obavljen.\n>>> Da li želite taj deployment poslati na server?
[y/N]: ')
                    if yn.lower().strip() == 'y':
                        self.deploy_ftp()
                        self.write_log(''.join(ofa._log))
                else:
                    self.check_changes()
                    
                    # Pitaj korisnika da li želi napraviti deployment
datoteku
                    yn = raw_input('\n>>> Da li želite pripremiti
deployment? [y/N]: ')
                    if yn.lower().strip() == 'y':
                        desc = raw_input('\n>>> Upišite kratki opis:
')
                        print '\nOpis:', desc
                        
                        self.make_deployment_file()
                        
                        # Pitaj korisnika da li želi poslati
deployment na server
                        yn = raw_input('\n>>> Da li želite poslati
deployment na server? [y/N]: ')
                        if yn.lower().strip() == 'y':
                            self.deploy_ftp()
                            self.write_log(''.join(ofa._log))
            except DeploymentError, e:
                print str(e)
        finally:
            # Vrati stari stdout
            unadapt_stdout(ofa)

if __name__ == '__main__':
    deployer = Deployer('hpk.ini')
    deployer.run_interactively()
    x = raw_input('\n[Pritisnite Enter ...] ')
    sys.exit(0)



More information about the Python-list mailing list