Skip to content

Commit

Permalink
fix: datetime formatting for Pandas tables (#3383)
Browse files Browse the repository at this point in the history
Always include H:M:S.f. When the time is excluded our frontend doesn't
know how to interpret the timestamp.

Fixes #3352 

(Separately I noticed the table component doesn't display nonzero
microseconds. Microseconds are useful for HFT and real-time control.)

<img width="939" alt="image"
src="https://github.com/user-attachments/assets/4e4db1d0-9ff9-4c9e-9a6c-3373f592939c"
/>
  • Loading branch information
akshayka authored Jan 9, 2025
1 parent c8cd62d commit 0c7b786
Show file tree
Hide file tree
Showing 5 changed files with 30 additions and 11 deletions.
7 changes: 6 additions & 1 deletion marimo/_plugins/ui/_impl/tables/pandas_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,14 @@ def to_csv(
self, format_mapping: Optional[FormatMapping] = None
) -> bytes:
has_headers = len(self.get_row_headers()) > 0
# Pandas omits H:M:S for datetimes when H:M:S is identically
# 0; this doesn't play well with our frontend table component,
# so we use an explicit date format.
return (
self.apply_formatting(format_mapping)
._original_data.to_csv(index=has_headers)
._original_data.to_csv(
index=has_headers, date_format="%Y-%m-%d %H:%M:%S"
)
.encode("utf-8")
)

Expand Down
8 changes: 5 additions & 3 deletions tests/_plugins/ui/_impl/charts/test_altair_transformers.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,9 +201,9 @@ def test_data_to_json_string_with_special_characters(
"float": [1.1, 2.2, 3.3],
"bool": [True, False, True],
"datetime": [
datetime.datetime(2023, 1, 1),
datetime.datetime(2023, 1, 2),
datetime.datetime(2023, 1, 3),
datetime.datetime(2023, 1, 1, 1),
datetime.datetime(2023, 1, 2, 1),
datetime.datetime(2023, 1, 3, 1),
],
"category": ["a", "b", "c"],
},
Expand All @@ -213,6 +213,8 @@ def test_data_to_json_string_with_special_characters(
def test_data_to_csv_string_with_different_dtypes(df: IntoDataFrame):
result = _data_to_csv_string(df)
assert isinstance(result, str)
# NB: These can differ based on datetime canonicalization --- our table
# manager always includes H:M:S:.f for pandas, but narwhals doesn't.
assert result == dataframe_to_csv(df)


Expand Down
6 changes: 3 additions & 3 deletions tests/_plugins/ui/_impl/tables/snapshots/ibis.csv
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
strings,bool,int,float,date,datetime,time,nulls
a,True,1,1.0,2021-01-01,2021-01-01,01:02:03,
b,False,2,2.0,2021-01-02,2021-01-02,04:05:06,data
c,True,3,3.0,2021-01-03,2021-01-03,07:08:09,
a,True,1,1.0,2021-01-01,2021-01-01 00:00:00,01:02:03,
b,False,2,2.0,2021-01-02,2021-01-02 00:00:00,04:05:06,data
c,True,3,3.0,2021-01-03,2021-01-03 00:00:00,07:08:09,
6 changes: 3 additions & 3 deletions tests/_plugins/ui/_impl/tables/snapshots/pandas.csv
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
strings,bool,int,float,datetime,date,struct,list,nulls,category,set,imaginary,time,duration,mixed_list
a,True,1,1.0,2021-01-01,2021-01-01,"{'a': 1, 'b': 2}","[1, 2]",,cat,"{1, 2}",(1+2j),12:30:00,1 days,"[1, 'two']"
b,False,2,2.0,2021-01-02,2021-01-02,"{'a': 3, 'b': 4}","[3, 4]",data,dog,"{3, 4}",(3+4j),13:45:00,2 days,"[3.0, False]"
c,True,3,3.0,2021-01-03,2021-01-03,"{'a': 5, 'b': 6}","[5, 6]",,mouse,"{5, 6}",(5+6j),14:15:00,3 days,"[None, datetime.datetime(2021, 1, 1, 0, 0)]"
a,True,1,1.0,2021-01-01 00:00:00,2021-01-01,"{'a': 1, 'b': 2}","[1, 2]",,cat,"{1, 2}",(1+2j),12:30:00,1 days,"[1, 'two']"
b,False,2,2.0,2021-01-02 00:00:00,2021-01-02,"{'a': 3, 'b': 4}","[3, 4]",data,dog,"{3, 4}",(3+4j),13:45:00,2 days,"[3.0, False]"
c,True,3,3.0,2021-01-03 00:00:00,2021-01-03,"{'a': 5, 'b': 6}","[5, 6]",,mouse,"{5, 6}",(5+6j),14:15:00,3 days,"[None, datetime.datetime(2021, 1, 1, 0, 0)]"
14 changes: 13 additions & 1 deletion tests/_plugins/ui/_impl/tables/test_pandas_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,21 @@ def test_package_name(self) -> None:
assert self.factory.package_name() == "pandas"

def test_to_csv(self) -> None:
expected_csv = self.data.to_csv(index=False).encode("utf-8")
expected_csv = self.data.to_csv(
index=False, date_format="%Y-%m-%d %H:%M:%S"
).encode("utf-8")
assert self.manager.to_csv() == expected_csv

def test_to_csv_datetime(self) -> None:
D = pd.to_datetime("2024-12-17", errors="coerce")

data = {
"D timestamp": [D],
}
df = pd.DataFrame(data)
manager = PandasTableManagerFactory.create()(df)
assert "2024-12-17 00:00:00" in manager.to_csv().decode("utf-8")

def test_to_csv_complex(self) -> None:
complex_data = self.get_complex_data()
data = complex_data.to_csv()
Expand Down

0 comments on commit 0c7b786

Please sign in to comment.