Compare commits

...

1 Commits

Author SHA1 Message Date
Evan Rusackas
4026ac9134 fix(charts): handle MSSQL datetime values outside JavaScript's safe range
MSSQL datetime values outside JavaScript's Date range (1938-2286) were causing charts to display "0NaN-NaN-NaN". This fix returns ISO strings for dates outside the safe range instead of epoch milliseconds that overflow.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-01 15:14:22 -07:00
3 changed files with 46 additions and 1 deletions

View File

@@ -19,6 +19,11 @@ from datetime import datetime
import pytz
EPOCH = datetime(1970, 1, 1)
# JavaScript's Date object can safely represent dates within this range
# These are the min/max values that can be represented as milliseconds since epoch
# without overflowing JavaScript's Number type (±2^53)
JS_DATE_RANGE_MIN = datetime(1938, 4, 24, 22, 13, 20, 0)
JS_DATE_RANGE_MAX = datetime(2286, 11, 20, 17, 46, 39, 999000)
def datetime_to_epoch(dttm: datetime) -> float:

View File

@@ -29,7 +29,12 @@ from jsonpath_ng import parse
from simplejson import JSONDecodeError
from superset.constants import PASSWORD_MASK
from superset.utils.dates import datetime_to_epoch, EPOCH
from superset.utils.dates import (
datetime_to_epoch,
EPOCH,
JS_DATE_RANGE_MAX,
JS_DATE_RANGE_MIN,
)
logging.getLogger("MARKDOWN").setLevel(logging.INFO)
logger = logging.getLogger(__name__)
@@ -155,9 +160,27 @@ def json_int_dttm_ser(obj: Any) -> Any:
"""
if isinstance(obj, (datetime, pd.Timestamp)):
# Check if datetime is within JavaScript's safe date range
# If not, return ISO string instead of epoch milliseconds
if isinstance(obj, pd.Timestamp):
dttm = obj.to_pydatetime()
else:
dttm = obj
# Remove timezone info for comparison
dttm_no_tz = dttm.replace(tzinfo=None) if dttm.tzinfo else dttm
if dttm_no_tz < JS_DATE_RANGE_MIN or dttm_no_tz > JS_DATE_RANGE_MAX:
# Return ISO string for dates outside JavaScript's safe range
return obj.isoformat()
return datetime_to_epoch(obj)
if isinstance(obj, date):
# Check if date is within JavaScript's safe date range
date_as_datetime = datetime.combine(obj, datetime.min.time())
if date_as_datetime < JS_DATE_RANGE_MIN or date_as_datetime > JS_DATE_RANGE_MAX:
return obj.isoformat()
return (obj - EPOCH.date()).total_seconds() * 1000
return base_json_conv(obj)

View File

@@ -254,6 +254,23 @@ def test_json_int_dttm_ser():
assert json.json_int_dttm_ser(dttm + timedelta(milliseconds=1)) == (ts + 1)
assert json.json_int_dttm_ser(np.int64(1)) == 1
# Test edge cases for JavaScript Date range
# Dates outside JavaScript's safe range should return ISO strings
assert (
json.json_int_dttm_ser(datetime(1938, 4, 24, 22, 13, 19))
== "1938-04-24T22:13:19"
)
assert (
json.json_int_dttm_ser(datetime(2286, 11, 20, 17, 46, 40))
== "2286-11-20T17:46:40"
)
assert json.json_int_dttm_ser(date(1938, 4, 23)) == "1938-04-23"
assert json.json_int_dttm_ser(date(2286, 11, 21)) == "2286-11-21"
# Dates within JavaScript's safe range should return epoch milliseconds
assert isinstance(json.json_int_dttm_ser(datetime(2000, 1, 1)), (int, float))
assert isinstance(json.json_int_dttm_ser(date(2000, 1, 1)), (int, float))
with pytest.raises(TypeError):
json.json_int_dttm_ser(np.datetime64())