From f58634ea36ac4f869c80e6eb2714759ab803d059 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 17 Sep 2025 20:36:03 +0800 Subject: [PATCH 1/5] sequence_join: Correctly join datetime-like values in pd.DataFrame/xr.DataArray objects --- pygmt/alias.py | 15 +++++++++++++++ pygmt/helpers/utils.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/pygmt/alias.py b/pygmt/alias.py index 89d8fb1c0f3..a0021c53b33 100644 --- a/pygmt/alias.py +++ b/pygmt/alias.py @@ -109,6 +109,21 @@ def _to_string( >>> _to_string([[1, 2], [3, 4]], sep="/", ndim=2) ['1/2', '3/4'] + + >>> import datetime + >>> import numpy as np + >>> import pandas as pd + >>> import xarray as xr + >>> _to_string( + ... [ + ... datetime.date(2010, 1, 1), + ... datetime.datetime(2010, 3, 1), + ... pd.Timestamp("2015-01-01T12:00:00.123456789"), + ... xr.DataArray(data=np.datetime64("2005-01-01T08:00:00", "ns")), + ... ], + ... sep="/", + ... ) + '2010-01-01/2010-03-01T00:00:00.000000/2015-01-01T12:00:00.123456/2005-01-01T08:00:00.000000000' """ # None and False are converted to None. if value is None or value is False: diff --git a/pygmt/helpers/utils.py b/pygmt/helpers/utils.py index 3d7fe694729..00978513863 100644 --- a/pygmt/helpers/utils.py +++ b/pygmt/helpers/utils.py @@ -16,6 +16,7 @@ from pathlib import Path from typing import Any, Literal +import numpy as np import xarray as xr from pygmt._typing import PathLike from pygmt.encodings import charset @@ -816,6 +817,30 @@ def sequence_join( ['1/2', '3/4'] >>> sequence_join([1, 2, 3, 4], separator=",") '1,2,3,4' + + >>> # Join a sequence of datetime-like objects into a string. + >>> import datetime + >>> import numpy as np + >>> import pandas as pd + >>> import xarray as xr + >>> sequence_join( + ... [ + ... datetime.date(2010, 1, 1), + ... np.datetime64("2010-01-01T16:00:00"), + ... np.array("2010-03-01T00:00:00", dtype=np.datetime64), + ... ], + ... sep="/", + ... ) + '2010-01-01/2010-01-01T16:00:00/2010-03-01T00:00:00' + >>> sequence_join( + ... [ + ... datetime.datetime(2010, 3, 1), + ... pd.Timestamp("2015-01-01T12:00:00.123456789"), + ... xr.DataArray(data=np.datetime64("2005-01-01T08:00:00", "ns")), + ... ], + ... sep="/", + ... ) + '2010-03-01T00:00:00.000000/2015-01-01T12:00:00.123456/2005-01-01T08:00:00.000000000' """ # Return the original value if it is not a sequence (e.g., None, bool, or str). if not is_nonstr_iter(value): @@ -848,6 +873,15 @@ def sequence_join( f"but got {len(value)} values." ) raise GMTInvalidInput(msg) + # 'str(v)' produces a string like '2024-01-01 00:00:00' for some datetime-like + # objects (e.g., datetime.datetime, pandas.Timestamp), which contains a space. + # If so, use np.datetime_as_string to convert it to ISO 8601 string format + # YYYY-MM-DDThh:mm:ss.ffffff. + for idx, item in enumerate(value): + if " " in str(item): + value[idx] = np.datetime_as_string( + np.asarray(item, dtype=np.datetime64) + ) return sep.join(str(v) for v in value) # Now it must be a 2-D sequence. From d36ad10b87ed137ff19036accd7e620c226ba1f1 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 18 Sep 2025 19:48:41 +0800 Subject: [PATCH 2/5] Refactor following copilot suggestions --- pygmt/helpers/utils.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/pygmt/helpers/utils.py b/pygmt/helpers/utils.py index 00978513863..b271d88f77f 100644 --- a/pygmt/helpers/utils.py +++ b/pygmt/helpers/utils.py @@ -877,12 +877,13 @@ def sequence_join( # objects (e.g., datetime.datetime, pandas.Timestamp), which contains a space. # If so, use np.datetime_as_string to convert it to ISO 8601 string format # YYYY-MM-DDThh:mm:ss.ffffff. - for idx, item in enumerate(value): - if " " in str(item): - value[idx] = np.datetime_as_string( - np.asarray(item, dtype=np.datetime64) - ) - return sep.join(str(v) for v in value) + _value = [ + np.datetime_as_string(np.asarray(item, dtype=np.datetime64)) + if " " in str(item) + else item + for item in value + ] + return sep.join(str(v) for v in _value) # Now it must be a 2-D sequence. if ndim == 1: From 548894ec5342cf0aa289ace93a78cd6fd3dd447c Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Mon, 22 Sep 2025 16:52:08 +0800 Subject: [PATCH 3/5] Avoid double str call Co-authored-by: Wei Ji <23487320+weiji14@users.noreply.github.com> --- pygmt/helpers/utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pygmt/helpers/utils.py b/pygmt/helpers/utils.py index b271d88f77f..592f83bd1cd 100644 --- a/pygmt/helpers/utils.py +++ b/pygmt/helpers/utils.py @@ -877,13 +877,13 @@ def sequence_join( # objects (e.g., datetime.datetime, pandas.Timestamp), which contains a space. # If so, use np.datetime_as_string to convert it to ISO 8601 string format # YYYY-MM-DDThh:mm:ss.ffffff. - _value = [ + _values = [ np.datetime_as_string(np.asarray(item, dtype=np.datetime64)) if " " in str(item) - else item + else str(item) for item in value ] - return sep.join(str(v) for v in _value) + return sep.join(_values) # Now it must be a 2-D sequence. if ndim == 1: From e1953552d414080ccc5a94449f561c874d179b16 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Tue, 23 Sep 2025 09:13:18 +0800 Subject: [PATCH 4/5] Use walrus operator to avoid duplicate str calls --- pygmt/helpers/utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pygmt/helpers/utils.py b/pygmt/helpers/utils.py index 592f83bd1cd..7486e227756 100644 --- a/pygmt/helpers/utils.py +++ b/pygmt/helpers/utils.py @@ -878,9 +878,9 @@ def sequence_join( # If so, use np.datetime_as_string to convert it to ISO 8601 string format # YYYY-MM-DDThh:mm:ss.ffffff. _values = [ - np.datetime_as_string(np.asarray(item, dtype=np.datetime64)) - if " " in str(item) - else str(item) + np.datetime_as_string(np.asarray(item, dtype="datetime64")) + if " " in (s := str(item)) + else s for item in value ] return sep.join(_values) From 29331323adba4689bbe1bac3d801404cdee7cab1 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Tue, 23 Sep 2025 09:13:42 +0800 Subject: [PATCH 5/5] Fix typing issue --- pygmt/helpers/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/helpers/utils.py b/pygmt/helpers/utils.py index 7486e227756..03c5ba73856 100644 --- a/pygmt/helpers/utils.py +++ b/pygmt/helpers/utils.py @@ -883,7 +883,7 @@ def sequence_join( else s for item in value ] - return sep.join(_values) + return sep.join(_values) # type: ignore[arg-type] # Now it must be a 2-D sequence. if ndim == 1: