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 :
@@ -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+
10481050def 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
10631075def 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
10791091WINDOWS_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