Skip to content

Commit c2c4ecd

Browse files
committed
Small update
1 parent aa80531 commit c2c4ecd

1 file changed

Lines changed: 30 additions & 16 deletions

File tree

pyfoxfile/pyfoxfile.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:
@@ -1052,20 +1052,32 @@ def VerbosePrintOutReturn(dbgtxt, outtype="log", dbgenable=True, dgblevel=20, **
10521052
VerbosePrintOut(dbgtxt, outtype, dbgenable, dgblevel, **kwargs)
10531053
return dbgtxt
10541054

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

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

10701082
def format_ns_utc(ts_ns, fmt='%Y-%m-%d %H:%M:%S'):
10711083
ts_ns = int(ts_ns)
@@ -1085,11 +1097,11 @@ def format_ns_local(ts_ns, fmt='%Y-%m-%d %H:%M:%S'):
10851097

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

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

1092-
def _parse_ext_timestamp_0x5455(extra_data: bytes) -> int | None:
1104+
def _parse_ext_timestamp_0x5455(extra_data):
10931105
# Layout: [flags:1][mtime?:4][atime?:4][ctime?:4] (only if flags bits set)
10941106
if len(extra_data) < 1:
10951107
return None
@@ -1101,7 +1113,7 @@ def _parse_ext_timestamp_0x5455(extra_data: bytes) -> int | None:
11011113
return int(mtime)
11021114
return None
11031115

1104-
def _parse_ntfs_0x000a(extra_data: bytes) -> int | None:
1116+
def _parse_ntfs_0x000a(extra_data):
11051117
# Layout: [reserved:4] then attributes:
11061118
# attr_tag(2), attr_size(2), attr_data(attr_size)
11071119
if len(extra_data) < 4:
@@ -1121,7 +1133,9 @@ def _parse_ntfs_0x000a(extra_data: bytes) -> int | None:
11211133
return _filetime_to_unix_seconds(mtime_filetime)
11221134
return None
11231135

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

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

0 commit comments

Comments
 (0)