@@ -1465,9 +1465,27 @@ def format_day_time_interval_for_display(
14651465 Formatted interval string (e.g., "INTERVAL '01:30:45' HOUR TO SECOND")
14661466 """
14671467 if isinstance (cell , datetime .timedelta ):
1468- total_seconds_float = cell .total_seconds ()
1468+ # Heuristic: Use Decimal for extreme values near 64-bit boundary, float for normal values
1469+ total_seconds_approx = cell .total_seconds ()
1470+
1471+ # Check if we're approaching values where float precision becomes problematic
1472+ # Be conservative: use Decimal for large values to ensure precision
1473+ # This corresponds to roughly 3 million years - normal use cases are well below this
1474+ if (
1475+ abs (total_seconds_approx ) > 1e11
1476+ ): # ~100 gigaseconds, very conservative threshold
1477+ # Use Decimal arithmetic for precise conversion to avoid floating-point precision loss
1478+ total_seconds_value = (
1479+ decimal .Decimal (cell .days ) * decimal .Decimal (86400 )
1480+ + decimal .Decimal (cell .seconds )
1481+ + decimal .Decimal (cell .microseconds ) / decimal .Decimal (1_000_000 )
1482+ )
1483+ else :
1484+ # Use fast float path for normal values
1485+ total_seconds_value = cell .total_seconds ()
1486+
14691487 interval_str = format_day_time_interval (
1470- total_seconds_float , start_field , end_field
1488+ total_seconds_value , start_field , end_field
14711489 )
14721490 elif isinstance (cell , str ):
14731491 # Raw string that needs to be formatted (e.g., "1 01:01:01.7878")
@@ -1492,35 +1510,96 @@ def format_day_time_interval_for_display(
14921510
14931511
14941512def format_day_time_interval (
1495- total_seconds_float : float , start_field : int , end_field : int
1513+ total_seconds_value : Union [ float , decimal . Decimal ] , start_field : int , end_field : int
14961514) -> str :
14971515 """
14981516 Format a DayTimeIntervalType value for display in _show_string_spark().
14991517
15001518 Args:
1501- total_seconds_float : Total seconds as a float (can be negative)
1519+ total_seconds_value : Total seconds as either float or Decimal (can be negative)
15021520 start_field: Start field constant from DayTimeIntervalType (DAY=0, HOUR=1, MINUTE=2, SECOND=3)
15031521 end_field: End field constant from DayTimeIntervalType (DAY=0, HOUR=1, MINUTE=2, SECOND=3)
15041522
15051523 Returns:
15061524 Formatted interval string (e.g., "01:30:45", "2 12:30", "05", etc.)
15071525 """
1508- is_negative = total_seconds_float < 0
1509- abs_total_seconds = abs (total_seconds_float )
1526+ is_negative = total_seconds_value < 0
1527+ abs_total_seconds = abs (total_seconds_value )
1528+
1529+ # Determine if we're working with Decimal for high-precision arithmetic
1530+ use_decimal = isinstance (total_seconds_value , decimal .Decimal )
15101531
15111532 days = int (abs_total_seconds ) // 86400
15121533 remaining_seconds = abs_total_seconds - (days * 86400 )
15131534 hours = int (remaining_seconds ) // 3600
15141535 remaining_after_hours = remaining_seconds - (hours * 3600 )
15151536 minutes = int (remaining_after_hours ) // 60
1516- seconds = remaining_after_hours - (minutes * 60 )
1537+
1538+ # Calculate seconds more precisely to avoid floating-point accumulation errors
1539+ # Use the original total and subtract the calculated day/hour/minute components
1540+ if use_decimal :
1541+ total_non_second_time = (
1542+ decimal .Decimal (days * 86400 )
1543+ + decimal .Decimal (hours * 3600 )
1544+ + decimal .Decimal (minutes * 60 )
1545+ )
1546+ else :
1547+ total_non_second_time = (days * 86400 ) + (hours * 3600 ) + (minutes * 60 )
1548+ seconds = abs_total_seconds - total_non_second_time
15171549
15181550 sign = "-" if is_negative else ""
15191551
15201552 def format_with_leading_zero (value : int ) -> str :
15211553 """Format integer with leading zero if < 10, otherwise as-is"""
15221554 return f"{ value :02d} " if value < 10 else f"{ value } "
15231555
1556+ def format_seconds_with_precision (
1557+ seconds_value : Union [float , decimal .Decimal ]
1558+ ) -> str :
1559+ """Format seconds with full precision, preserving trailing zeros for proper padding"""
1560+ if isinstance (seconds_value , decimal .Decimal ):
1561+ # Use Decimal precision formatting
1562+ if seconds_value == int (seconds_value ):
1563+ return f"{ int (seconds_value ):02d} "
1564+ else :
1565+ # For fractional seconds, ensure proper leading zero padding
1566+ integer_part = int (seconds_value )
1567+ if integer_part < 10 :
1568+ # Format with leading zero for the integer part
1569+ formatted = f"{ seconds_value :.6f} " .rstrip ("0" )
1570+ if formatted .endswith ("." ):
1571+ return f"{ integer_part :02d} "
1572+ # Replace the integer part with zero-padded version
1573+ decimal_part = formatted .split ("." , 1 )[1 ]
1574+ return f"{ integer_part :02d} .{ decimal_part } "
1575+ else :
1576+ # For >= 10, use normal formatting
1577+ formatted = f"{ seconds_value :.6f} " .rstrip ("0" )
1578+ if formatted .endswith ("." ):
1579+ return f"{ integer_part } "
1580+ return formatted
1581+ else :
1582+ # Float precision formatting (original logic)
1583+ if seconds_value == int (seconds_value ):
1584+ return f"{ int (seconds_value ):02d} "
1585+ else :
1586+ # For fractional seconds, ensure proper leading zero padding
1587+ integer_part = int (seconds_value )
1588+ if integer_part < 10 :
1589+ # Format with leading zero for the integer part
1590+ formatted = f"{ seconds_value :.6f} " .rstrip ("0" )
1591+ if formatted .endswith ("." ):
1592+ return f"{ integer_part :02d} "
1593+ # Replace the integer part with zero-padded version
1594+ decimal_part = formatted .split ("." , 1 )[1 ]
1595+ return f"{ integer_part :02d} .{ decimal_part } "
1596+ else :
1597+ # For >= 10, use normal formatting
1598+ formatted = f"{ seconds_value :.6f} " .rstrip ("0" )
1599+ if formatted .endswith ("." ):
1600+ return f"{ integer_part } "
1601+ return formatted
1602+
15241603 # For single field intervals, extract just that component
15251604 if start_field == end_field :
15261605 if start_field == DayTimeIntervalType .DAY :
@@ -1537,19 +1616,8 @@ def format_with_leading_zero(value: int) -> str:
15371616 total_secs_int = int (abs_total_seconds )
15381617 return f"{ sign } { format_with_leading_zero (total_secs_int )} "
15391618 else :
1540- # For fractional seconds, format with leading zero if < 10
1541- if abs_total_seconds < 10 :
1542- # Format with leading zero: split into integer and fractional parts
1543- integer_part = int (abs_total_seconds )
1544- fractional_part = abs_total_seconds - integer_part
1545- if fractional_part == 0 :
1546- return f"{ sign } { integer_part :02d} "
1547- else :
1548- # Format fractional part and remove leading '0.'
1549- frac_str = f"{ fractional_part :.6f} " [2 :].rstrip ("0" )
1550- return f"{ sign } { integer_part :02d} .{ frac_str } "
1551- else :
1552- return f"{ sign } { abs_total_seconds :g} "
1619+ # Use unified formatting that handles both float and Decimal
1620+ return f"{ sign } { format_seconds_with_precision (abs_total_seconds )} "
15531621
15541622 # For multi-field intervals, format based on start/end fields
15551623 if start_field == DayTimeIntervalType .DAY :
@@ -1566,7 +1634,7 @@ def format_with_leading_zero(value: int) -> str:
15661634 if seconds == int (seconds ):
15671635 return f"{ sign } { days } { hours_str } :{ minutes :02d} :{ int (seconds ):02d} "
15681636 else :
1569- return f"{ sign } { days } { hours_str } :{ minutes :02d} :{ seconds :06.3f } "
1637+ return f"{ sign } { days } { hours_str } :{ minutes :02d} :{ format_seconds_with_precision ( seconds ) } "
15701638 elif start_field == DayTimeIntervalType .HOUR :
15711639 # HOUR TO X format: "HH:MM:SS" (no days)
15721640 total_hours = int (abs_total_seconds ) // 3600
@@ -1580,7 +1648,7 @@ def format_with_leading_zero(value: int) -> str:
15801648 if secs == int (secs ):
15811649 return f"{ sign } { format_with_leading_zero (total_hours )} :{ mins :02d} :{ int (secs ):02d} "
15821650 else :
1583- return f"{ sign } { format_with_leading_zero (total_hours )} :{ mins :02d} :{ secs :06.3f } "
1651+ return f"{ sign } { format_with_leading_zero (total_hours )} :{ mins :02d} :{ format_seconds_with_precision ( secs ) } "
15841652 elif start_field == DayTimeIntervalType .MINUTE :
15851653 # MINUTE TO X format: "MM:SS" (no days or hours)
15861654 total_minutes = int (abs_total_seconds ) // 60
@@ -1590,7 +1658,9 @@ def format_with_leading_zero(value: int) -> str:
15901658 if remaining_secs == int (remaining_secs ):
15911659 return f"{ sign } { minutes_str } :{ int (remaining_secs ):02d} "
15921660 else :
1593- return f"{ sign } { minutes_str } :{ remaining_secs :06.3f} "
1661+ return (
1662+ f"{ sign } { minutes_str } :{ format_seconds_with_precision (remaining_secs )} "
1663+ )
15941664
15951665
15961666# Type hints
0 commit comments