Skip to content

Commit 5bc9bda

Browse files
committed
Small update
1 parent e875ee0 commit 5bc9bda

1 file changed

Lines changed: 30 additions & 16 deletions

File tree

pycatfile/pycatfile.py

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@
3838
import inspect
3939
import tempfile
4040
import configparser
41-
from zoneinfo import ZoneInfo
4241
from io import open, StringIO, BytesIO
42+
from decimal import Decimal, ROUND_HALF_UP
4343
__enable_pywwwget__ = True
4444
pywwwget = False
4545
try:
@@ -1051,20 +1051,32 @@ def VerbosePrintOutReturn(dbgtxt, outtype="log", dbgenable=True, dgblevel=20, **
10511051
VerbosePrintOut(dbgtxt, outtype, dbgenable, dgblevel, **kwargs)
10521052
return dbgtxt
10531053

1054+
NS_PER_SEC = 1_000_000_000
1055+
10541056
def to_ns(timestamp):
10551057
"""
1056-
Convert a second-resolution timestamp (int or float)
1057-
into a nanosecond timestamp (int) by zero-padding.
1058-
Works in Python 3.
1058+
Convert a second-resolution timestamp (int, float, str, or Decimal)
1059+
into an integer nanosecond timestamp safely.
1060+
1061+
Avoids float precision drift.
10591062
"""
1060-
try:
1061-
# Convert incoming timestamp to float so it works for int or float
1062-
seconds = float(timestamp)
1063-
except (TypeError, ValueError):
1064-
raise ValueError("Timestamp must be int or float")
1063+
if isinstance(timestamp, int):
1064+
# Already whole seconds
1065+
return timestamp * NS_PER_SEC
10651066

1066-
# Multiply by 1e9 to get nanoseconds, then cast to int
1067-
return int(seconds * 1000000000)
1067+
if isinstance(timestamp, float):
1068+
# Convert through Decimal via string to preserve exact decimal value
1069+
seconds = Decimal(str(timestamp))
1070+
return int((seconds * NS_PER_SEC).to_integral_value(rounding=ROUND_HALF_UP))
1071+
1072+
if isinstance(timestamp, Decimal):
1073+
return int((timestamp * NS_PER_SEC).to_integral_value(rounding=ROUND_HALF_UP))
1074+
1075+
if isinstance(timestamp, str):
1076+
seconds = Decimal(timestamp)
1077+
return int((seconds * NS_PER_SEC).to_integral_value(rounding=ROUND_HALF_UP))
1078+
1079+
raise ValueError("Timestamp must be int, float, Decimal, or str")
10681080

10691081
def format_ns_utc(ts_ns, fmt='%Y-%m-%d %H:%M:%S'):
10701082
ts_ns = int(ts_ns)
@@ -1084,11 +1096,11 @@ def format_ns_local(ts_ns, fmt='%Y-%m-%d %H:%M:%S'):
10841096

10851097
WINDOWS_EPOCH_DELTA = 11644473600 # seconds between 1601-01-01 and 1970-01-01
10861098

1087-
def _filetime_to_unix_seconds(filetime: int) -> int:
1099+
def _filetime_to_unix_seconds(filetime):
10881100
# FILETIME is 100-ns intervals since 1601-01-01 UTC
10891101
return int(filetime // 10_000_000 - WINDOWS_EPOCH_DELTA)
10901102

1091-
def _parse_ext_timestamp_0x5455(extra_data: bytes) -> int | None:
1103+
def _parse_ext_timestamp_0x5455(extra_data):
10921104
# Layout: [flags:1][mtime?:4][atime?:4][ctime?:4] (only if flags bits set)
10931105
if len(extra_data) < 1:
10941106
return None
@@ -1100,7 +1112,7 @@ def _parse_ext_timestamp_0x5455(extra_data: bytes) -> int | None:
11001112
return int(mtime)
11011113
return None
11021114

1103-
def _parse_ntfs_0x000a(extra_data: bytes) -> int | None:
1115+
def _parse_ntfs_0x000a(extra_data):
11041116
# Layout: [reserved:4] then attributes:
11051117
# attr_tag(2), attr_size(2), attr_data(attr_size)
11061118
if len(extra_data) < 4:
@@ -1120,7 +1132,9 @@ def _parse_ntfs_0x000a(extra_data: bytes) -> int | None:
11201132
return _filetime_to_unix_seconds(mtime_filetime)
11211133
return None
11221134

1123-
def get_unix_timestamp_zip(member, fallback_tz: str = "America/Chicago") -> int:
1135+
local_tz = datetime.datetime.now().astimezone().tzinfo
1136+
1137+
def get_unix_timestamp_zip(member, fallback_tz = local_tz):
11241138
extra = member.extra
11251139
i = 0
11261140

@@ -1144,7 +1158,7 @@ def get_unix_timestamp_zip(member, fallback_tz: str = "America/Chicago") -> int:
11441158
# 2) Fallback: DOS local time -> interpret in fallback_tz -> UTC
11451159
# ZIP DOS timestamps are "local time" with no TZ info.
11461160
local_naive = datetime.datetime(*member.date_time)
1147-
local_dt = local_naive.replace(tzinfo=ZoneInfo(fallback_tz))
1161+
local_dt = local_naive.replace(tzinfo=fallback_tz)
11481162
utc_dt = local_dt.astimezone(datetime.timezone.utc)
11491163
return int(utc_dt.timestamp())
11501164

0 commit comments

Comments
 (0)