3838import inspect
3939import tempfile
4040import configparser
41- from zoneinfo import ZoneInfo
4241from io import open , StringIO , BytesIO
42+ from decimal import Decimal , ROUND_HALF_UP
4343__enable_pywwwget__ = True
4444pywwwget = False
4545try :
@@ -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+
10551057def 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
10701082def 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
10861098WINDOWS_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