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 :
@@ -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+
10541056def 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
10691081def 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
10851097WINDOWS_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