This is an automated email from the ASF dual-hosted git repository. rusackas pushed a commit to branch fix/mssql-datetime-range-charts in repository https://gitbox.apache.org/repos/asf/superset.git
commit 4026ac9134459a2137c0397a904f2a63b61aa3dd Author: Evan Rusackas <[email protected]> AuthorDate: Fri Aug 1 15:14:22 2025 -0700 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 <[email protected]> --- superset/utils/dates.py | 5 +++++ superset/utils/json.py | 25 ++++++++++++++++++++++++- tests/unit_tests/utils/json_tests.py | 17 +++++++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/superset/utils/dates.py b/superset/utils/dates.py index b2b422a1f0..5b79e27060 100644 --- a/superset/utils/dates.py +++ b/superset/utils/dates.py @@ -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: diff --git a/superset/utils/json.py b/superset/utils/json.py index 689b0af6de..e1da205673 100644 --- a/superset/utils/json.py +++ b/superset/utils/json.py @@ -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) diff --git a/tests/unit_tests/utils/json_tests.py b/tests/unit_tests/utils/json_tests.py index 3356577407..9e124b0dc1 100644 --- a/tests/unit_tests/utils/json_tests.py +++ b/tests/unit_tests/utils/json_tests.py @@ -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())
