Skip to content

Commit e4aae03

Browse files
committed
Small update
1 parent 2982988 commit e4aae03

1 file changed

Lines changed: 30 additions & 16 deletions

File tree

pyneofile/pyneofile.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:
@@ -1045,20 +1045,32 @@ def VerbosePrintOutReturn(dbgtxt, outtype="log", dbgenable=True, dgblevel=20, **
10451045
VerbosePrintOut(dbgtxt, outtype, dbgenable, dgblevel, **kwargs)
10461046
return dbgtxt
10471047

1048+
NS_PER_SEC = 1_000_000_000
1049+
10481050
def to_ns(timestamp):
10491051
"""
1050-
Convert a second-resolution timestamp (int or float)
1051-
into a nanosecond timestamp (int) by zero-padding.
1052-
Works in Python 3.
1052+
Convert a second-resolution timestamp (int, float, str, or Decimal)
1053+
into an integer nanosecond timestamp safely.
1054+
1055+
Avoids float precision drift.
10531056
"""
1054-
try:
1055-
# Convert incoming timestamp to float so it works for int or float
1056-
seconds = float(timestamp)
1057-
except (TypeError, ValueError):
1058-
raise ValueError("Timestamp must be int or float")
1057+
if isinstance(timestamp, int):
1058+
# Already whole seconds
1059+
return timestamp * NS_PER_SEC
10591060

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

10631075
def format_ns_utc(ts_ns, fmt='%Y-%m-%d %H:%M:%S'):
10641076
ts_ns = int(ts_ns)
@@ -1078,11 +1090,11 @@ def format_ns_local(ts_ns, fmt='%Y-%m-%d %H:%M:%S'):
10781090

10791091
WINDOWS_EPOCH_DELTA = 11644473600 # seconds between 1601-01-01 and 1970-01-01
10801092

1081-
def _filetime_to_unix_seconds(filetime: int) -> int:
1093+
def _filetime_to_unix_seconds(filetime):
10821094
# FILETIME is 100-ns intervals since 1601-01-01 UTC
10831095
return int(filetime // 10_000_000 - WINDOWS_EPOCH_DELTA)
10841096

1085-
def _parse_ext_timestamp_0x5455(extra_data: bytes) -> int | None:
1097+
def _parse_ext_timestamp_0x5455(extra_data):
10861098
# Layout: [flags:1][mtime?:4][atime?:4][ctime?:4] (only if flags bits set)
10871099
if len(extra_data) < 1:
10881100
return None
@@ -1094,7 +1106,7 @@ def _parse_ext_timestamp_0x5455(extra_data: bytes) -> int | None:
10941106
return int(mtime)
10951107
return None
10961108

1097-
def _parse_ntfs_0x000a(extra_data: bytes) -> int | None:
1109+
def _parse_ntfs_0x000a(extra_data):
10981110
# Layout: [reserved:4] then attributes:
10991111
# attr_tag(2), attr_size(2), attr_data(attr_size)
11001112
if len(extra_data) < 4:
@@ -1114,7 +1126,9 @@ def _parse_ntfs_0x000a(extra_data: bytes) -> int | None:
11141126
return _filetime_to_unix_seconds(mtime_filetime)
11151127
return None
11161128

1117-
def get_unix_timestamp_zip(member, fallback_tz: str = "America/Chicago") -> int:
1129+
local_tz = datetime.datetime.now().astimezone().tzinfo
1130+
1131+
def get_unix_timestamp_zip(member, fallback_tz = local_tz):
11181132
extra = member.extra
11191133
i = 0
11201134

@@ -1138,7 +1152,7 @@ def get_unix_timestamp_zip(member, fallback_tz: str = "America/Chicago") -> int:
11381152
# 2) Fallback: DOS local time -> interpret in fallback_tz -> UTC
11391153
# ZIP DOS timestamps are "local time" with no TZ info.
11401154
local_naive = datetime.datetime(*member.date_time)
1141-
local_dt = local_naive.replace(tzinfo=ZoneInfo(fallback_tz))
1155+
local_dt = local_naive.replace(tzinfo=fallback_tz)
11421156
utc_dt = local_dt.astimezone(datetime.timezone.utc)
11431157
return int(utc_dt.timestamp())
11441158

0 commit comments

Comments
 (0)