Найти - Пользователи
Полная версия: Python неправильно получает timestamp'ы для "старых" "зимних" файлов
Начало » Python для экспертов » Python неправильно получает timestamp'ы для "старых" "зимних" файлов
1
dodger
Python неправильно получает timestamp'ы для файлов, которые были изменены при “зимнем” часовом поясе ДО ОТМЕНЫ ПЕРЕВОДА ВРЕМЕНИ.
Столкнулся с данной проблемой еще год назад, но отложит решение… теперь вопрос опять актуален для меня.
Проявляется на Windows (установлен Python 2.7.3), на Linux все нормально.
Допустим есть файл, который изменен “2008-12-16 07:55:22” (это локальное время).
Файловые менеджеры (Explorer и T-Commander) показывают время корректно, а вот Python выдает “2008-12-16 08:55:22”.

написал год назад тестовый код:
# -*- coding: utf-8 -*-
import logging, sys, os, hashlib, struct, time
from os import path
from datetime import datetime
def getFileTimes(fname, gmt=True, checkMT=True):
    try:
        fs = "%Y-%m-%d %H:%M:%S"
        os.stat_float_times(False)
        if gmt:
            ct = time.gmtime(os.path.getctime(fname))
            mt = time.gmtime(os.path.getmtime(fname))
        else:
            ct = time.localtime(os.path.getctime(fname))
            mt = time.localtime(os.path.getmtime(fname))
        if checkMT:
            pass # TODO: if ct > mt: ct = mt
        return time.strftime(fs,ct), time.strftime(fs,mt)
    except:
        return "", ""
files = {
    'test-file-1-ERR-js.txt': '2009-11-29 21:27:38',
    'test-file-2-OK-js.txt' : '2009-05-27 10:46:12',
    'test-file-3-ERR-js.txt': '2008-12-16 09:55:22',
    # после отмены перехода
    'test-file-4-OK-py.txt' : '2011-11-22 10:33:44',
    'test-file-5-OK-txt.txt': '2012-07-20 20:05:29'
    }
for fname in files.keys():
    ftime = getFileTimes(fname, False)[1]
    ftime2 = getFileTimes(fname, True)[1]
    shift = int(ftime[11:13]) - int(ftime2[11:13])
    res = 'OK: ' if files[fname] == ftime else 'ERR:'
    print '%s [%s]  ftime = %s  mustbe = %s  shift=%i' % (res, fname, ftime, files[fname], shift)

а также попробовал то же самое через WinAPI,
через API-функции винда выдает всё нормально

код на С++:
#include <windows.h>
#include <tchar.h>
#include <strsafe.h>

BOOL GetLastWriteTime(HANDLE hFile, LPTSTR lpszString, DWORD dwSize)
{
FILETIME ftCreate, ftAccess, ftWrite;
SYSTEMTIME stUTC, stLocal;
DWORD dwRet;
// Retrieve the file times for the file.
if (!GetFileTime(hFile, &ftCreate, &ftAccess, &ftWrite))
return FALSE;
// Convert the last-write time to local time.
FileTimeToSystemTime(&ftWrite, &stUTC);
SystemTimeToTzSpecificLocalTime(NULL, &stUTC, &stLocal);
// Build a string showing the date and time.
dwRet = StringCchPrintf(lpszString, dwSize, TEXT(
"%04d-%02d-%02d %02d:%02d:%02d"), stLocal.wYear, stLocal.wMonth,
stLocal.wDay, stLocal.wHour, stLocal.wMinute, stLocal.wSecond);
if (S_OK == dwRet)
return TRUE;
else
return FALSE;
}

int _tmain(int argc, TCHAR *argv[])
{
HANDLE hFile;
TCHAR szBuf[MAX_PATH];
if (argc != 2)
{
printf("This sample takes a file name as a parameter\n");
return 0;
}
hFile = CreateFile(argv[1], GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
printf("CreateFile failed with %d\n", GetLastError());
return 0;
}
if (GetLastWriteTime(hFile, szBuf, MAX_PATH))
_tprintf(TEXT("%s"), szBuf);
CloseHandle(hFile);
}

код на Python:
# -*- coding: utf-8 -*-
import logging, sys, os, hashlib, struct, time
from os import path
from datetime import datetime
files = {
    'test-file-1-ERR-js.txt': '2009-11-29 21:27:38',
    'test-file-2-OK-js.txt' : '2009-05-27 10:46:12',
    'test-file-3-ERR-js.txt': '2008-12-16 09:55:22',
    # после отмены перехода
    'test-file-4-OK-py.txt' : '2011-11-22 10:33:44',
    'test-file-5-OK-txt.txt': '2012-07-20 20:05:29',
    'test-file-6.txt': '2013-02-20 19:32:10'
    }
for fname in files.keys():
    import subprocess
    proc = subprocess.Popen('"getFileTime/getFileTime.exe" "%s"' % fname, shell=True, stdout=subprocess.PIPE)
    out = proc.stdout.readlines()
    ftime = out[0]
    
    shift = 0
    res = 'OK: ' if files[fname] == ftime else 'ERR:'
    print '%s  ftime = %s    mustbe = %s  [%s]  shift=%i' % (res, ftime, files[fname], fname, shift)

P.S.
Еще раз спасибо Диме Медведеву за его Великие Реформы.
dodger
В “NEWS.txt” нашел такую вещь:
What's New in Python 2.7.4 release candidate 1
==============================================

*Release date: 2013-03-23*

- Issue #13863: Work around buggy 'fstat' implementation on Windows / NTFS that
lead to incorrect timestamps (off by one hour) being stored in .pyc files on
some systems.

Обрадовался и обновился до последней версии (Python 2.7.5)… но к моему удивлению и сожалению проблема осталась :(
dodger
Вопрос закрыт.
Сначала запихнул сишный код в DLL, но потом переделал всё на основе ctypes.
Самописная DLL производительнее ctypes-версии всего на 10%, поэтому в ней необходимость отпала.

###############################################################################
## немного доработал getTimestamp(), взятый отсюда:
## http://pyudd.googlecode.com/svn-history/r14/trunk/pyudd.py
###############################################################################
def getTimestamp(filename):
    ctime = ctypes.c_ulonglong(0)
    atime = ctypes.c_ulonglong(0)
    mtime = ctypes.c_ulonglong(0)
    h = ctypes.windll.kernel32.CreateFileA(ctypes.c_char_p(filename), 0, 3, 0, 3, 0x80, 0)
    ctypes.windll.kernel32.GetFileTime(h, ctypes.pointer(ctime), ctypes.pointer(atime), ctypes.pointer(mtime))
    ctypes.windll.kernel32.CloseHandle(h)
    return ctime.value, mtime.value, atime.value
###############################################################################
## вытащил немного кода отсюда:
## http://pydoc.net/Python/winappdbg/1.4/winappdbg.win32.kernel32/
###############################################################################
WORD        = ctypes.c_ushort
DWORD       = ctypes.c_uint
class SYSTEMTIME(Structure):
    _fields_ = [
        ('wYear',           WORD),
        ('wMonth',          WORD),
        ('wDayOfWeek',      WORD),
        ('wDay',            WORD),
        ('wHour',           WORD),
        ('wMinute',         WORD),
        ('wSecond',         WORD),
        ('wMilliseconds',   WORD),
    ]
LPSYSTEMTIME = POINTER(SYSTEMTIME)
class FILETIME(Structure):
    _fields_ = [
        ('dwLowDateTime',       DWORD),
        ('dwHighDateTime',      DWORD),
    ]
LPFILETIME = POINTER(FILETIME)
def FileTimeToSystemTime(lpFileTime):
    _FileTimeToSystemTime = windll.kernel32.FileTimeToSystemTime
    _FileTimeToSystemTime.argtypes = [LPFILETIME, LPSYSTEMTIME]
    _FileTimeToSystemTime.restype  = bool
    #_FileTimeToSystemTime.errcheck = RaiseIfZero
    if isinstance(lpFileTime, FILETIME):
        FileTime = lpFileTime
    else:
        FileTime = FILETIME()
        FileTime.dwLowDateTime  = lpFileTime & 0xFFFFFFFF
        FileTime.dwHighDateTime = lpFileTime >> 32
    SystemTime = SYSTEMTIME()
    _FileTimeToSystemTime(ctypes.byref(FileTime), ctypes.byref(SystemTime))
    return SystemTime
###############################################################################
# добавил конвертацию
###############################################################################
def convertFileTimeToLocalTimeStr(ftSrc):
    stUTC = FileTimeToSystemTime(ftSrc)
    stLocal = SYSTEMTIME()
    ctypes.windll.kernel32.SystemTimeToTzSpecificLocalTime(0, ctypes.byref(stUTC), ctypes.byref(stLocal))
    return "%04d-%02d-%02d %02d:%02d:%02d" % (stLocal.wYear, stLocal.wMonth, stLocal.wDay, stLocal.wHour, stLocal.wMinute, stLocal.wSecond)
###############################################################################
# тестируем
###############################################################################
if __name__ == '__main__':
    fname = 'test-file-3-ERR-js.txt'  # 'test-file-3-ERR-js.txt': '2008-12-16 09:55:22'
    ctime, mtime, atime = getTimestamp(fname)
    print convertFileTimeToLocalTimeStr(ctime), convertFileTimeToLocalTimeStr(mtime)

ну и код DLL на всякий случай:

#include <windows.h>
#include <string>

#define DLLEXPORT extern "C" __declspec(dllexport)
#define PRINTF printf

int convertFileTimeToLocalTimeStr(FILETIME ftSrc, char *outBuf)
{
SYSTEMTIME stUTC, stLocal;
if ( FALSE == ::FileTimeToSystemTime(&ftSrc, &stUTC) )
{
PRINTF("FileTimeToSystemTime failed\n");
return -1;
}
if ( FALSE == ::SystemTimeToTzSpecificLocalTime(NULL, &stUTC, &stLocal) )
{
PRINTF("SystemTimeToTzSpecificLocalTime failed\n");
return -2;
}
int n = sprintf(outBuf, "%04d-%02d-%02d %02d:%02d:%02d"
, stLocal.wYear, stLocal.wMonth, stLocal.wDay, stLocal.wHour, stLocal.wMinute, stLocal.wSecond);
if (n < 0)
{
PRINTF("sprintf failed\n");
return -3;
}
return n;
}

DLLEXPORT int getFileTimes(char* fname, char* createTime, char* writeTime)
{
HANDLE hFile = ::CreateFile(fname, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if (INVALID_HANDLE_VALUE == hFile)
{
PRINTF("CreateFile failed, GetLastError = %u\n", ::GetLastError());
return -1;
}
FILETIME ftCreate, ftAccess, ftWrite;
if (FALSE == ::GetFileTime(hFile, &ftCreate, &ftAccess, &ftWrite))
{
PRINTF("GetFileTime failed\n");
::CloseHandle(hFile);
return -2;
}

int n1 = convertFileTimeToLocalTimeStr(ftCreate, createTime);
int n2 = convertFileTimeToLocalTimeStr(ftWrite, writeTime);

::CloseHandle(hFile);
return (n1 > 0 && n2 > 0) ? 0 : -3;
}
This is a "lo-fi" version of our main content. To view the full version with more information, formatting and images, please click here.
Powered by DjangoBB