From 01bbac69787c54484db5f55f25b12b32ffc327ca Mon Sep 17 00:00:00 2001 From: Arun Sharma Date: Fri, 17 Jul 2026 18:15:52 -0700 Subject: [PATCH 1/4] fix: use DAYS_PER_YEAR (365) for whole years in interval-to-timedelta conversion PyQueryResult::convertValueToPyObject (pybind path) and all interval conversion sites in _lbug_capi.py (C-API path) now use days = years * 365 + months * 30 + days instead of days = months * 30 + days This gives 365 days per whole year instead of 360, matching the engine's Interval::DAYS_PER_YEAR constant. --- src_cpp/py_query_result.cpp | 7 ++++++- src_py/_lbug_capi.py | 16 ++++++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src_cpp/py_query_result.cpp b/src_cpp/py_query_result.cpp index 080180c..d76df30 100644 --- a/src_cpp/py_query_result.cpp +++ b/src_cpp/py_query_result.cpp @@ -235,7 +235,12 @@ py::object PyQueryResult::convertValueToPyObject(const Value& value) { } case LogicalTypeID::INTERVAL: { auto intervalVal = value.getValue(); - auto days = Interval::DAYS_PER_MONTH * intervalVal.months + intervalVal.days; + // Use DAYS_PER_YEAR for whole years instead of DAYS_PER_MONTH * 12, + // giving 365 days/year + 30 days/month for the remainder. + auto years = intervalVal.months / Interval::MONTHS_PER_YEAR; + auto months = intervalVal.months % Interval::MONTHS_PER_YEAR; + auto days = years * Interval::DAYS_PER_YEAR + months * Interval::DAYS_PER_MONTH + + intervalVal.days; return py::cast(importCache->datetime.timedelta()(py::arg("days") = days, py::arg("microseconds") = intervalVal.micros)); diff --git a/src_py/_lbug_capi.py b/src_py/_lbug_capi.py index f4d6eef..332249d 100644 --- a/src_py/_lbug_capi.py +++ b/src_py/_lbug_capi.py @@ -1853,7 +1853,11 @@ def _convert_value(self, value: _LbugValue) -> Any: ), "Failed to read interval", ) - total_days = int(out.days) + int(out.months) * 30 + total_days = ( + int(out.days) + + (int(out.months) // 12) * 365 + + (int(out.months) % 12) * 30 + ) return dt.timedelta(days=total_days, microseconds=int(out.micros)) if type_id in (_LBUG_LIST, _LBUG_ARRAY): size = ctypes.c_uint64(0) @@ -1937,7 +1941,8 @@ def _convert_value(self, value: _LbugValue) -> Any: ): total_days = ( int(interval_probe.days) - + int(interval_probe.months) * 30 + + (int(interval_probe.months) // 12) * 365 + + (int(interval_probe.months) % 12) * 30 ) out_obj[key] = dt.timedelta( days=total_days, @@ -2042,7 +2047,8 @@ def _convert_value(self, value: _LbugValue) -> Any: ): total_days = ( int(interval_probe.days) - + int(interval_probe.months) * 30 + + (int(interval_probe.months) // 12) * 365 + + (int(interval_probe.months) % 12) * 30 ) out_obj[key] = dt.timedelta( days=total_days, @@ -2095,7 +2101,9 @@ def _convert_value(self, value: _LbugValue) -> Any: == _LBUG_SUCCESS ): total_days = ( - int(interval_probe.days) + int(interval_probe.months) * 30 + int(interval_probe.days) + + (int(interval_probe.months) // 12) * 365 + + (int(interval_probe.months) % 12) * 30 ) return dt.timedelta( days=total_days, microseconds=int(interval_probe.micros) From 1a13d9f56565775537b78f6d7d3a94c07caa2003 Mon Sep 17 00:00:00 2001 From: Arun Sharma Date: Wed, 22 Jul 2026 09:36:20 -0700 Subject: [PATCH 2/4] fix: update test expectations to match 365-day year interval values Interval months-to-days conversion now uses DAYS_PER_YEAR (365) for whole years instead of DAYS_PER_MONTH * 12 (360). Updated all test assertions: 3 years 2 days 13:02:00 -> 1097 days (was 1082) 10 years 5 months 13h -> 3800 days (was 3750) 26 years 52 days 48h -> 9544 days (was 9414) --- test/ground_truth.py | 16 ++++++++-------- test/test_arrow.py | 10 +++++----- test/test_datatype.py | 4 ++-- test/test_df.py | 30 +++++++++++++++--------------- test/test_networkx.py | 24 ++++++++++++------------ 5 files changed, 42 insertions(+), 42 deletions(-) diff --git a/test/ground_truth.py b/test/ground_truth.py index 83cac88..c21974a 100644 --- a/test/ground_truth.py +++ b/test/ground_truth.py @@ -14,7 +14,7 @@ "eyeSight": 5.0, "birthdate": date(1900, 1, 1), "registerTime": datetime(2011, 8, 20, 11, 25, 30), - "lastJobDuration": timedelta(days=1082, seconds=46920), + "lastJobDuration": timedelta(days=1097, seconds=46920), "workedHours": [10, 5], "usedNames": ["Aida"], "courseScoresPerTerm": [[10, 8], [6, 7, 8]], @@ -34,7 +34,7 @@ "eyeSight": 5.1, "birthdate": date(1900, 1, 1), "registerTime": datetime(2008, 11, 3, 15, 25, 30, 526), - "lastJobDuration": timedelta(days=3750, seconds=46800, microseconds=24), + "lastJobDuration": timedelta(days=3800, seconds=46800, microseconds=24), "workedHours": [12, 8], "usedNames": ["Bobby"], "courseScoresPerTerm": [[8, 9], [9, 10]], @@ -74,7 +74,7 @@ "eyeSight": 4.8, "birthdate": date(1950, 7, 23), "registerTime": datetime(2031, 11, 30, 12, 25, 30), - "lastJobDuration": timedelta(days=3750, seconds=46800, microseconds=24), + "lastJobDuration": timedelta(days=3800, seconds=46800, microseconds=24), "workedHours": [1, 9], "usedNames": ["Wolfeschlegelstein", "Daniel"], "courseScoresPerTerm": [[7, 4], [8, 8], [9]], @@ -134,7 +134,7 @@ "eyeSight": 4.9, "birthdate": date(1980, 10, 26), "registerTime": datetime(1976, 12, 23, 4, 41, 42), - "lastJobDuration": timedelta(days=3750, seconds=46800, microseconds=24), + "lastJobDuration": timedelta(days=3800, seconds=46800, microseconds=24), "workedHours": [1], "usedNames": ["Grad"], "courseScoresPerTerm": [[10]], @@ -154,7 +154,7 @@ "eyeSight": 4.9, "birthdate": date(1990, 11, 27), "registerTime": datetime(2023, 2, 21, 13, 25, 30), - "lastJobDuration": timedelta(days=1082, seconds=46920), + "lastJobDuration": timedelta(days=1097, seconds=46920), "workedHours": [10, 11, 12, 3, 4, 5, 6, 7], "usedNames": ["Ad", "De", "Hi", "Kye", "Orlan"], "courseScoresPerTerm": [[7], [10], [6, 7]], @@ -192,7 +192,7 @@ "mark": 4.1, "score": -100, "history": "2 years 4 days 10 hours", - "licenseValidInterval": timedelta(days=9414), + "licenseValidInterval": timedelta(days=9544), "rating": 0.78, "state": { "revenue": 152, @@ -235,7 +235,7 @@ (0, 2): { "date": date(2021, 6, 30), "meetTime": datetime(1986, 10, 21, 21, 8, 31, 521000), - "validInterval": timedelta(days=3750, seconds=46800, microseconds=24), + "validInterval": timedelta(days=3800, seconds=46800, microseconds=24), "comments": ["rnme", "m8sihsdnf2990nfiwf"], "summary": { "locations": ["'toronto'", "'waterloo'"], @@ -268,7 +268,7 @@ (2, 0): { "date": date(2021, 6, 30), "meetTime": datetime(1946, 8, 25, 19, 7, 22), - "validInterval": timedelta(days=3750, seconds=46800, microseconds=24), + "validInterval": timedelta(days=3800, seconds=46800, microseconds=24), "comments": ["2huh9y89fsfw23", "23nsihufhw723"], "summary": { "locations": ["'paris'"], diff --git a/test/test_arrow.py b/test/test_arrow.py index 3eb784e..cec5266 100644 --- a/test/test_arrow.py +++ b/test/test_arrow.py @@ -229,14 +229,14 @@ def _test_person_table( col_name="a.lastJobDuration", return_type=return_type, expected_values=[ - timedelta(days=1082, seconds=46920), - timedelta(days=3750, seconds=46800, microseconds=24), + timedelta(days=1097, seconds=46920), + timedelta(days=3800, seconds=46800, microseconds=24), timedelta(days=2, seconds=1451), - timedelta(days=3750, seconds=46800, microseconds=24), + timedelta(days=3800, seconds=46800, microseconds=24), timedelta(days=2, seconds=1451), timedelta(seconds=1080, microseconds=24000), - timedelta(days=3750, seconds=46800, microseconds=24), - timedelta(days=1082, seconds=46920), + timedelta(days=3800, seconds=46800, microseconds=24), + timedelta(days=1097, seconds=46920), ], ) diff --git a/test/test_datatype.py b/test/test_datatype.py index 8ad1432..dcfed75 100644 --- a/test/test_datatype.py +++ b/test/test_datatype.py @@ -249,7 +249,7 @@ def test_interval(conn_db_readonly: ConnDB) -> None: conn, _ = conn_db_readonly result = conn.execute("MATCH (a:person) WHERE a.ID = 0 RETURN a.lastJobDuration;") assert result.has_next() - assert result.get_next() == [datetime.timedelta(days=1082, seconds=46920)] + assert result.get_next() == [datetime.timedelta(days=1097, seconds=46920)] assert not result.has_next() result.close() @@ -298,7 +298,7 @@ def test_node(conn_db_readonly: ConnDB) -> None: assert n["eyeSight"] == pytest.approx(5.0) assert n["birthdate"] == datetime.date(1900, 1, 1) assert n["registerTime"] == datetime.datetime(2011, 8, 20, 11, 25, 30) - assert n["lastJobDuration"] == datetime.timedelta(days=1082, seconds=46920) + assert n["lastJobDuration"] == datetime.timedelta(days=1097, seconds=46920) assert n["courseScoresPerTerm"] == [[10, 8], [6, 7, 8]] assert n["usedNames"] == ["Aida"] assert not result.has_next() diff --git a/test/test_df.py b/test/test_df.py index d71fca5..e4e142d 100644 --- a/test/test_df.py +++ b/test/test_df.py @@ -70,14 +70,14 @@ def _test_person_to_df(conn: lb.Connection) -> None: ] assert str(pd["p.registerTime"].dtype) == "datetime64[us]" assert pd["p.lastJobDuration"].tolist() == [ - Timedelta("1082 days 13:02:00"), - Timedelta("3750 days 13:00:00.000024"), + Timedelta("1097 days 13:02:00"), + Timedelta("3800 days 13:00:00.000024"), Timedelta("2 days 00:24:11"), - Timedelta("3750 days 13:00:00.000024"), + Timedelta("3800 days 13:00:00.000024"), Timedelta("2 days 00:24:11"), Timedelta("0 days 00:18:00.024000"), - Timedelta("3750 days 13:00:00.000024"), - Timedelta("1082 days 13:02:00"), + Timedelta("3800 days 13:00:00.000024"), + Timedelta("1097 days 13:02:00"), ] assert str(pd["p.lastJobDuration"].dtype) == "timedelta64[ns]" assert pd["p.workedHours"].tolist() == [ @@ -319,14 +319,14 @@ def test_df_get_node(conn_db_readonly: ConnDB) -> None: Timestamp("2023-02-21 13:25:30"), ], "lastJobDuration": [ - Timedelta("1082 days 13:02:00"), - Timedelta("3750 days 13:00:00.000024"), + Timedelta("1097 days 13:02:00"), + Timedelta("3800 days 13:00:00.000024"), Timedelta("2 days 00:24:11"), - Timedelta("3750 days 13:00:00.000024"), + Timedelta("3800 days 13:00:00.000024"), Timedelta("2 days 00:24:11"), Timedelta("0 days 00:18:00.024000"), - Timedelta("3750 days 13:00:00.000024"), - Timedelta("1082 days 13:02:00"), + Timedelta("3800 days 13:00:00.000024"), + Timedelta("1097 days 13:02:00"), ], "workedHours": [ [10, 5], @@ -406,7 +406,7 @@ def test_df_get_node_rel(conn_db_readonly: ConnDB) -> None: ], "lastJobDuration": [ Timedelta("48 hours 24 minutes 11 seconds"), - Timedelta("3750 days 13:00:00.000024"), + Timedelta("3800 days 13:00:00.000024"), Timedelta("2 days 00:24:11"), ], "workedHours": [[4, 5], [1, 9], [2]], @@ -431,7 +431,7 @@ def test_df_get_node_rel(conn_db_readonly: ConnDB) -> None: "2 years 4 hours 22 us 34 minutes", ], "licenseValidInterval": [ - Timedelta(days=9414), + Timedelta(days=9544), Timedelta(days=3, seconds=36000, microseconds=100000), Timedelta(days=3, seconds=36000, microseconds=100000), ], @@ -476,7 +476,7 @@ def test_df_get_recursive_join(conn_db_readonly: ConnDB) -> None: "isStudent": True, "isWorker": False, "lastJobDuration": datetime.timedelta( - days=3750, seconds=46800, microseconds=24 + days=3800, seconds=46800, microseconds=24 ), "registerTime": datetime.datetime(2008, 11, 3, 15, 25, 30, 526), "usedNames": ["Bobby"], @@ -505,7 +505,7 @@ def test_df_get_recursive_join(conn_db_readonly: ConnDB) -> None: }, "someMap": {"a": "b"}, "validInterval": datetime.timedelta( - days=3750, seconds=46800, microseconds=24 + days=3800, seconds=46800, microseconds=24 ), }, { @@ -529,7 +529,7 @@ def test_df_get_recursive_join(conn_db_readonly: ConnDB) -> None: }, "someMap": {"a": "b"}, "validInterval": datetime.timedelta( - days=3750, seconds=46800, microseconds=24 + days=3800, seconds=46800, microseconds=24 ), }, ], diff --git a/test/test_networkx.py b/test/test_networkx.py index 645856e..4b4636a 100644 --- a/test/test_networkx.py +++ b/test/test_networkx.py @@ -53,14 +53,14 @@ def test_to_networkx_node(conn_db_readonly: ConnDB) -> None: Timestamp("2023-02-21 13:25:30"), ], "lastJobDuration": [ - Timedelta("1082 days 13:02:00"), - Timedelta("3750 days 13:00:00.000024"), + Timedelta("1097 days 13:02:00"), + Timedelta("3800 days 13:00:00.000024"), Timedelta("2 days 00:24:11"), - Timedelta("3750 days 13:00:00.000024"), + Timedelta("3800 days 13:00:00.000024"), Timedelta("2 days 00:24:11"), Timedelta("0 days 00:18:00.024000"), - Timedelta("3750 days 13:00:00.000024"), - Timedelta("1082 days 13:02:00"), + Timedelta("3800 days 13:00:00.000024"), + Timedelta("1097 days 13:02:00"), ], "workedHours": [ [10, 5], @@ -170,14 +170,14 @@ def test_networkx_undirected(conn_db_readonly: ConnDB) -> None: Timestamp("2023-02-21 13:25:30"), ], "lastJobDuration": [ - Timedelta("1082 days 13:02:00"), - Timedelta("3750 days 13:00:00.000024"), + Timedelta("1097 days 13:02:00"), + Timedelta("3800 days 13:00:00.000024"), Timedelta("2 days 00:24:11"), - Timedelta("3750 days 13:00:00.000024"), + Timedelta("3800 days 13:00:00.000024"), Timedelta("2 days 00:24:11"), Timedelta("0 days 00:18:00.024000"), - Timedelta("3750 days 13:00:00.000024"), - Timedelta("1082 days 13:02:00"), + Timedelta("3800 days 13:00:00.000024"), + Timedelta("1097 days 13:02:00"), ], "workedHours": [ [10, 5], @@ -282,7 +282,7 @@ def test_networkx_directed(conn_db_readonly: ConnDB) -> None: ], "lastJobDuration": [ Timedelta("48 hours 24 minutes 11 seconds"), - Timedelta("3750 days 13:00:00.000024"), + Timedelta("3800 days 13:00:00.000024"), Timedelta("2 days 00:24:11"), ], "workedHours": [[4, 5], [1, 9], [2]], @@ -315,7 +315,7 @@ def test_networkx_directed(conn_db_readonly: ConnDB) -> None: "score": [-100, 7], "history": ["2 years 4 days 10 hours", "2 years 4 hours 22 us 34 minutes"], "licenseValidInterval": [ - Timedelta(days=9414), + Timedelta(days=9544), Timedelta(days=3, seconds=36000, microseconds=100000), ], "rating": [0.78, 0.52], From f1008e74c67e63ba87580d1c7e34df489e05a474 Mon Sep 17 00:00:00 2001 From: Arun Sharma Date: Wed, 22 Jul 2026 09:50:25 -0700 Subject: [PATCH 3/4] Revert "fix: update test expectations to match 365-day year interval values" This reverts commit 1a13d9f56565775537b78f6d7d3a94c07caa2003. --- test/ground_truth.py | 16 ++++++++-------- test/test_arrow.py | 10 +++++----- test/test_datatype.py | 4 ++-- test/test_df.py | 30 +++++++++++++++--------------- test/test_networkx.py | 24 ++++++++++++------------ 5 files changed, 42 insertions(+), 42 deletions(-) diff --git a/test/ground_truth.py b/test/ground_truth.py index c21974a..83cac88 100644 --- a/test/ground_truth.py +++ b/test/ground_truth.py @@ -14,7 +14,7 @@ "eyeSight": 5.0, "birthdate": date(1900, 1, 1), "registerTime": datetime(2011, 8, 20, 11, 25, 30), - "lastJobDuration": timedelta(days=1097, seconds=46920), + "lastJobDuration": timedelta(days=1082, seconds=46920), "workedHours": [10, 5], "usedNames": ["Aida"], "courseScoresPerTerm": [[10, 8], [6, 7, 8]], @@ -34,7 +34,7 @@ "eyeSight": 5.1, "birthdate": date(1900, 1, 1), "registerTime": datetime(2008, 11, 3, 15, 25, 30, 526), - "lastJobDuration": timedelta(days=3800, seconds=46800, microseconds=24), + "lastJobDuration": timedelta(days=3750, seconds=46800, microseconds=24), "workedHours": [12, 8], "usedNames": ["Bobby"], "courseScoresPerTerm": [[8, 9], [9, 10]], @@ -74,7 +74,7 @@ "eyeSight": 4.8, "birthdate": date(1950, 7, 23), "registerTime": datetime(2031, 11, 30, 12, 25, 30), - "lastJobDuration": timedelta(days=3800, seconds=46800, microseconds=24), + "lastJobDuration": timedelta(days=3750, seconds=46800, microseconds=24), "workedHours": [1, 9], "usedNames": ["Wolfeschlegelstein", "Daniel"], "courseScoresPerTerm": [[7, 4], [8, 8], [9]], @@ -134,7 +134,7 @@ "eyeSight": 4.9, "birthdate": date(1980, 10, 26), "registerTime": datetime(1976, 12, 23, 4, 41, 42), - "lastJobDuration": timedelta(days=3800, seconds=46800, microseconds=24), + "lastJobDuration": timedelta(days=3750, seconds=46800, microseconds=24), "workedHours": [1], "usedNames": ["Grad"], "courseScoresPerTerm": [[10]], @@ -154,7 +154,7 @@ "eyeSight": 4.9, "birthdate": date(1990, 11, 27), "registerTime": datetime(2023, 2, 21, 13, 25, 30), - "lastJobDuration": timedelta(days=1097, seconds=46920), + "lastJobDuration": timedelta(days=1082, seconds=46920), "workedHours": [10, 11, 12, 3, 4, 5, 6, 7], "usedNames": ["Ad", "De", "Hi", "Kye", "Orlan"], "courseScoresPerTerm": [[7], [10], [6, 7]], @@ -192,7 +192,7 @@ "mark": 4.1, "score": -100, "history": "2 years 4 days 10 hours", - "licenseValidInterval": timedelta(days=9544), + "licenseValidInterval": timedelta(days=9414), "rating": 0.78, "state": { "revenue": 152, @@ -235,7 +235,7 @@ (0, 2): { "date": date(2021, 6, 30), "meetTime": datetime(1986, 10, 21, 21, 8, 31, 521000), - "validInterval": timedelta(days=3800, seconds=46800, microseconds=24), + "validInterval": timedelta(days=3750, seconds=46800, microseconds=24), "comments": ["rnme", "m8sihsdnf2990nfiwf"], "summary": { "locations": ["'toronto'", "'waterloo'"], @@ -268,7 +268,7 @@ (2, 0): { "date": date(2021, 6, 30), "meetTime": datetime(1946, 8, 25, 19, 7, 22), - "validInterval": timedelta(days=3800, seconds=46800, microseconds=24), + "validInterval": timedelta(days=3750, seconds=46800, microseconds=24), "comments": ["2huh9y89fsfw23", "23nsihufhw723"], "summary": { "locations": ["'paris'"], diff --git a/test/test_arrow.py b/test/test_arrow.py index cec5266..3eb784e 100644 --- a/test/test_arrow.py +++ b/test/test_arrow.py @@ -229,14 +229,14 @@ def _test_person_table( col_name="a.lastJobDuration", return_type=return_type, expected_values=[ - timedelta(days=1097, seconds=46920), - timedelta(days=3800, seconds=46800, microseconds=24), + timedelta(days=1082, seconds=46920), + timedelta(days=3750, seconds=46800, microseconds=24), timedelta(days=2, seconds=1451), - timedelta(days=3800, seconds=46800, microseconds=24), + timedelta(days=3750, seconds=46800, microseconds=24), timedelta(days=2, seconds=1451), timedelta(seconds=1080, microseconds=24000), - timedelta(days=3800, seconds=46800, microseconds=24), - timedelta(days=1097, seconds=46920), + timedelta(days=3750, seconds=46800, microseconds=24), + timedelta(days=1082, seconds=46920), ], ) diff --git a/test/test_datatype.py b/test/test_datatype.py index dcfed75..8ad1432 100644 --- a/test/test_datatype.py +++ b/test/test_datatype.py @@ -249,7 +249,7 @@ def test_interval(conn_db_readonly: ConnDB) -> None: conn, _ = conn_db_readonly result = conn.execute("MATCH (a:person) WHERE a.ID = 0 RETURN a.lastJobDuration;") assert result.has_next() - assert result.get_next() == [datetime.timedelta(days=1097, seconds=46920)] + assert result.get_next() == [datetime.timedelta(days=1082, seconds=46920)] assert not result.has_next() result.close() @@ -298,7 +298,7 @@ def test_node(conn_db_readonly: ConnDB) -> None: assert n["eyeSight"] == pytest.approx(5.0) assert n["birthdate"] == datetime.date(1900, 1, 1) assert n["registerTime"] == datetime.datetime(2011, 8, 20, 11, 25, 30) - assert n["lastJobDuration"] == datetime.timedelta(days=1097, seconds=46920) + assert n["lastJobDuration"] == datetime.timedelta(days=1082, seconds=46920) assert n["courseScoresPerTerm"] == [[10, 8], [6, 7, 8]] assert n["usedNames"] == ["Aida"] assert not result.has_next() diff --git a/test/test_df.py b/test/test_df.py index e4e142d..d71fca5 100644 --- a/test/test_df.py +++ b/test/test_df.py @@ -70,14 +70,14 @@ def _test_person_to_df(conn: lb.Connection) -> None: ] assert str(pd["p.registerTime"].dtype) == "datetime64[us]" assert pd["p.lastJobDuration"].tolist() == [ - Timedelta("1097 days 13:02:00"), - Timedelta("3800 days 13:00:00.000024"), + Timedelta("1082 days 13:02:00"), + Timedelta("3750 days 13:00:00.000024"), Timedelta("2 days 00:24:11"), - Timedelta("3800 days 13:00:00.000024"), + Timedelta("3750 days 13:00:00.000024"), Timedelta("2 days 00:24:11"), Timedelta("0 days 00:18:00.024000"), - Timedelta("3800 days 13:00:00.000024"), - Timedelta("1097 days 13:02:00"), + Timedelta("3750 days 13:00:00.000024"), + Timedelta("1082 days 13:02:00"), ] assert str(pd["p.lastJobDuration"].dtype) == "timedelta64[ns]" assert pd["p.workedHours"].tolist() == [ @@ -319,14 +319,14 @@ def test_df_get_node(conn_db_readonly: ConnDB) -> None: Timestamp("2023-02-21 13:25:30"), ], "lastJobDuration": [ - Timedelta("1097 days 13:02:00"), - Timedelta("3800 days 13:00:00.000024"), + Timedelta("1082 days 13:02:00"), + Timedelta("3750 days 13:00:00.000024"), Timedelta("2 days 00:24:11"), - Timedelta("3800 days 13:00:00.000024"), + Timedelta("3750 days 13:00:00.000024"), Timedelta("2 days 00:24:11"), Timedelta("0 days 00:18:00.024000"), - Timedelta("3800 days 13:00:00.000024"), - Timedelta("1097 days 13:02:00"), + Timedelta("3750 days 13:00:00.000024"), + Timedelta("1082 days 13:02:00"), ], "workedHours": [ [10, 5], @@ -406,7 +406,7 @@ def test_df_get_node_rel(conn_db_readonly: ConnDB) -> None: ], "lastJobDuration": [ Timedelta("48 hours 24 minutes 11 seconds"), - Timedelta("3800 days 13:00:00.000024"), + Timedelta("3750 days 13:00:00.000024"), Timedelta("2 days 00:24:11"), ], "workedHours": [[4, 5], [1, 9], [2]], @@ -431,7 +431,7 @@ def test_df_get_node_rel(conn_db_readonly: ConnDB) -> None: "2 years 4 hours 22 us 34 minutes", ], "licenseValidInterval": [ - Timedelta(days=9544), + Timedelta(days=9414), Timedelta(days=3, seconds=36000, microseconds=100000), Timedelta(days=3, seconds=36000, microseconds=100000), ], @@ -476,7 +476,7 @@ def test_df_get_recursive_join(conn_db_readonly: ConnDB) -> None: "isStudent": True, "isWorker": False, "lastJobDuration": datetime.timedelta( - days=3800, seconds=46800, microseconds=24 + days=3750, seconds=46800, microseconds=24 ), "registerTime": datetime.datetime(2008, 11, 3, 15, 25, 30, 526), "usedNames": ["Bobby"], @@ -505,7 +505,7 @@ def test_df_get_recursive_join(conn_db_readonly: ConnDB) -> None: }, "someMap": {"a": "b"}, "validInterval": datetime.timedelta( - days=3800, seconds=46800, microseconds=24 + days=3750, seconds=46800, microseconds=24 ), }, { @@ -529,7 +529,7 @@ def test_df_get_recursive_join(conn_db_readonly: ConnDB) -> None: }, "someMap": {"a": "b"}, "validInterval": datetime.timedelta( - days=3800, seconds=46800, microseconds=24 + days=3750, seconds=46800, microseconds=24 ), }, ], diff --git a/test/test_networkx.py b/test/test_networkx.py index 4b4636a..645856e 100644 --- a/test/test_networkx.py +++ b/test/test_networkx.py @@ -53,14 +53,14 @@ def test_to_networkx_node(conn_db_readonly: ConnDB) -> None: Timestamp("2023-02-21 13:25:30"), ], "lastJobDuration": [ - Timedelta("1097 days 13:02:00"), - Timedelta("3800 days 13:00:00.000024"), + Timedelta("1082 days 13:02:00"), + Timedelta("3750 days 13:00:00.000024"), Timedelta("2 days 00:24:11"), - Timedelta("3800 days 13:00:00.000024"), + Timedelta("3750 days 13:00:00.000024"), Timedelta("2 days 00:24:11"), Timedelta("0 days 00:18:00.024000"), - Timedelta("3800 days 13:00:00.000024"), - Timedelta("1097 days 13:02:00"), + Timedelta("3750 days 13:00:00.000024"), + Timedelta("1082 days 13:02:00"), ], "workedHours": [ [10, 5], @@ -170,14 +170,14 @@ def test_networkx_undirected(conn_db_readonly: ConnDB) -> None: Timestamp("2023-02-21 13:25:30"), ], "lastJobDuration": [ - Timedelta("1097 days 13:02:00"), - Timedelta("3800 days 13:00:00.000024"), + Timedelta("1082 days 13:02:00"), + Timedelta("3750 days 13:00:00.000024"), Timedelta("2 days 00:24:11"), - Timedelta("3800 days 13:00:00.000024"), + Timedelta("3750 days 13:00:00.000024"), Timedelta("2 days 00:24:11"), Timedelta("0 days 00:18:00.024000"), - Timedelta("3800 days 13:00:00.000024"), - Timedelta("1097 days 13:02:00"), + Timedelta("3750 days 13:00:00.000024"), + Timedelta("1082 days 13:02:00"), ], "workedHours": [ [10, 5], @@ -282,7 +282,7 @@ def test_networkx_directed(conn_db_readonly: ConnDB) -> None: ], "lastJobDuration": [ Timedelta("48 hours 24 minutes 11 seconds"), - Timedelta("3800 days 13:00:00.000024"), + Timedelta("3750 days 13:00:00.000024"), Timedelta("2 days 00:24:11"), ], "workedHours": [[4, 5], [1, 9], [2]], @@ -315,7 +315,7 @@ def test_networkx_directed(conn_db_readonly: ConnDB) -> None: "score": [-100, 7], "history": ["2 years 4 days 10 hours", "2 years 4 hours 22 us 34 minutes"], "licenseValidInterval": [ - Timedelta(days=9544), + Timedelta(days=9414), Timedelta(days=3, seconds=36000, microseconds=100000), ], "rating": [0.78, 0.52], From cb00563febe45304e9fb8dcaedb7d41317b6cdd6 Mon Sep 17 00:00:00 2001 From: Arun Sharma Date: Wed, 22 Jul 2026 09:50:25 -0700 Subject: [PATCH 4/4] Revert "fix: use DAYS_PER_YEAR (365) for whole years in interval-to-timedelta conversion" This reverts commit 01bbac69787c54484db5f55f25b12b32ffc327ca. --- src_cpp/py_query_result.cpp | 7 +------ src_py/_lbug_capi.py | 16 ++++------------ 2 files changed, 5 insertions(+), 18 deletions(-) diff --git a/src_cpp/py_query_result.cpp b/src_cpp/py_query_result.cpp index d76df30..080180c 100644 --- a/src_cpp/py_query_result.cpp +++ b/src_cpp/py_query_result.cpp @@ -235,12 +235,7 @@ py::object PyQueryResult::convertValueToPyObject(const Value& value) { } case LogicalTypeID::INTERVAL: { auto intervalVal = value.getValue(); - // Use DAYS_PER_YEAR for whole years instead of DAYS_PER_MONTH * 12, - // giving 365 days/year + 30 days/month for the remainder. - auto years = intervalVal.months / Interval::MONTHS_PER_YEAR; - auto months = intervalVal.months % Interval::MONTHS_PER_YEAR; - auto days = years * Interval::DAYS_PER_YEAR + months * Interval::DAYS_PER_MONTH + - intervalVal.days; + auto days = Interval::DAYS_PER_MONTH * intervalVal.months + intervalVal.days; return py::cast(importCache->datetime.timedelta()(py::arg("days") = days, py::arg("microseconds") = intervalVal.micros)); diff --git a/src_py/_lbug_capi.py b/src_py/_lbug_capi.py index 332249d..f4d6eef 100644 --- a/src_py/_lbug_capi.py +++ b/src_py/_lbug_capi.py @@ -1853,11 +1853,7 @@ def _convert_value(self, value: _LbugValue) -> Any: ), "Failed to read interval", ) - total_days = ( - int(out.days) - + (int(out.months) // 12) * 365 - + (int(out.months) % 12) * 30 - ) + total_days = int(out.days) + int(out.months) * 30 return dt.timedelta(days=total_days, microseconds=int(out.micros)) if type_id in (_LBUG_LIST, _LBUG_ARRAY): size = ctypes.c_uint64(0) @@ -1941,8 +1937,7 @@ def _convert_value(self, value: _LbugValue) -> Any: ): total_days = ( int(interval_probe.days) - + (int(interval_probe.months) // 12) * 365 - + (int(interval_probe.months) % 12) * 30 + + int(interval_probe.months) * 30 ) out_obj[key] = dt.timedelta( days=total_days, @@ -2047,8 +2042,7 @@ def _convert_value(self, value: _LbugValue) -> Any: ): total_days = ( int(interval_probe.days) - + (int(interval_probe.months) // 12) * 365 - + (int(interval_probe.months) % 12) * 30 + + int(interval_probe.months) * 30 ) out_obj[key] = dt.timedelta( days=total_days, @@ -2101,9 +2095,7 @@ def _convert_value(self, value: _LbugValue) -> Any: == _LBUG_SUCCESS ): total_days = ( - int(interval_probe.days) - + (int(interval_probe.months) // 12) * 365 - + (int(interval_probe.months) % 12) * 30 + int(interval_probe.days) + int(interval_probe.months) * 30 ) return dt.timedelta( days=total_days, microseconds=int(interval_probe.micros)