From 499377a24179f21bad461bd9fe4062aef609c432 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sun, 21 Sep 2025 16:10:25 +0800 Subject: [PATCH 01/10] Figure.solar: Support datetime with timezone --- pygmt/src/solar.py | 35 ++++++++++--------- ...solar_terminator_datetime_timezone.png.dvc | 5 +++ pygmt/tests/test_solar.py | 25 +++++++++++++ 3 files changed, 48 insertions(+), 17 deletions(-) create mode 100644 pygmt/tests/baseline/test_solar_terminator_datetime_timezone.png.dvc diff --git a/pygmt/src/solar.py b/pygmt/src/solar.py index 586db1e7494..9d67a3407f2 100644 --- a/pygmt/src/solar.py +++ b/pygmt/src/solar.py @@ -14,13 +14,7 @@ @fmt_docstring -@use_alias( - B="frame", - G="fill", - R="region", - W="pen", - p="perspective", -) +@use_alias(B="frame", G="fill", R="region", W="pen", p="perspective") @kwargs_to_strings(R="sequence", p="sequence") def solar( self, @@ -36,14 +30,14 @@ def solar( r""" Plot day-night terminators and other sunlight parameters. - This function plots the day-night terminator. Alternatively, it can plot the + This method plots the day-night terminator. Alternatively, it can plot the terminators for civil twilight, nautical twilight, or astronomical twilight. Full GMT docs at :gmt-docs:`solar.html`. {aliases} - J = projection - - T = terminator, **+d**: terminator_datetime + - T = terminator, **+d**/**+z**: terminator_datetime - V = verbose - c = panel - t = transparency @@ -51,8 +45,7 @@ def solar( Parameters ---------- terminator - Set the type of terminator displayed, which can be set with either the full name - or the first letter of the name. Available options are: + Set the type of terminator. Choose one of the following: - ``"astronomical"``: Astronomical twilight - ``"civil"``: Civil twilight @@ -62,8 +55,11 @@ def solar( Refer to https://en.wikipedia.org/wiki/Twilight for the definitions of different types of twilight. terminator_datetime : str or datetime object - Set the UTC date and time of the displayed terminator [Default is the current - UTC date and time]. It can be passed as a string or Python datetime object. + Set the date and time for the terminator calculation. It can be provided as a + string or any datetime-like object recognized by :func:`pandas.to_datetime`. The + time can be specified in UTC or using a UTC offset. The offset must be an + integer number of hours (e.g., -8 or +5); fractional hours (e.g., -8.5 or +5.5) + are cast to integer. [Default is the current UTC date and time]. {region} {projection} {frame} @@ -104,12 +100,16 @@ def solar( """ self._activate_figure() - datetime_string = None + datetime_string, datetime_timezone = None, None if terminator_datetime: try: - datetime_string = pd.to_datetime(terminator_datetime).strftime( - "%Y-%m-%dT%H:%M:%S.%f" - ) + _datetime = pd.to_datetime(terminator_datetime) + datetime_string = _datetime.strftime("%Y-%m-%dT%H:%M:%S.%f") + # GMT's solar module uses the C 'atoi' function to parse the timezone + # offset. Ensure the offset is an integer number of hours (e.g., 8 or -5). + # Fractional hours (e.g., 8.5 or -5.5) are cast to integer. + if utcoffset := _datetime.utcoffset(): + datetime_timezone = int(utcoffset.total_seconds() / 3600) except ValueError as verr: raise GMTValueError(terminator_datetime, description="datetime") from verr @@ -126,6 +126,7 @@ def solar( }, ), Alias(datetime_string, name="terminator_datetime", prefix="+d"), + Alias(datetime_timezone, name="terminator_timezone", prefix="+z"), ], ).add_common( J=projection, diff --git a/pygmt/tests/baseline/test_solar_terminator_datetime_timezone.png.dvc b/pygmt/tests/baseline/test_solar_terminator_datetime_timezone.png.dvc new file mode 100644 index 00000000000..b9b496f70f7 --- /dev/null +++ b/pygmt/tests/baseline/test_solar_terminator_datetime_timezone.png.dvc @@ -0,0 +1,5 @@ +outs: +- md5: 10c5d3cadeb50764177bf49cbefeecb1 + size: 48753 + hash: md5 + path: test_solar_terminator_datetime_timezone.png diff --git a/pygmt/tests/test_solar.py b/pygmt/tests/test_solar.py index a1b716bd7d0..8c7a6059af4 100644 --- a/pygmt/tests/test_solar.py +++ b/pygmt/tests/test_solar.py @@ -119,3 +119,28 @@ def test_solar_default_terminator(): terminator_datetime="1990-02-17 04:25:00", ) return fig + + +@pytest.mark.mpl_image_compare +def test_solar_terminator_datetime_timezone(): + """ + Test passing the solar argument with a time string that includes a timezone. + """ + fig = Figure() + fig.basemap(region="d", projection="W0/15c", frame=True) + fig.solar(terminator_datetime="2020-01-01T01:02:03", pen="1p,black") + fig.solar(terminator_datetime="2020-01-01T01:02:03+0100", pen="1p,red") + fig.solar(terminator_datetime="2020-01-01T01:02:03-0100", pen="1p,blue") + fig.solar( + terminator_datetime=datetime.datetime( + 2020, 1, 1, 1, 2, 3, tzinfo=datetime.timezone(datetime.timedelta(hours=2)) + ), + pen="1p,lightred", + ) + fig.solar( + terminator_datetime=datetime.datetime( + 2020, 1, 1, 1, 2, 3, tzinfo=datetime.timezone(datetime.timedelta(hours=-2)) + ), + pen="1p,lightblue", + ) + return fig From dfb5023471339b4f579e59cc30c661c97cce8e3f Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sun, 21 Sep 2025 16:22:42 +0800 Subject: [PATCH 02/10] Use REPO_TOKEN --- .github/workflows/dvc-diff.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dvc-diff.yml b/.github/workflows/dvc-diff.yml index 6610002cbf5..0e99d0206be 100644 --- a/.github/workflows/dvc-diff.yml +++ b/.github/workflows/dvc-diff.yml @@ -55,7 +55,7 @@ jobs: # Report last updated at commit abcdef - name: Generate the image diff report env: - repo_token: ${{ github.token }} + REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }} DAGSHUB_TOKEN: ${{ secrets.DAGSHUB_TOKEN }} run: | From c278015cfe9055ae1c50fab342b617be6231c9ac Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sun, 21 Sep 2025 16:33:26 +0800 Subject: [PATCH 03/10] Set permission --- .github/workflows/dvc-diff.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/dvc-diff.yml b/.github/workflows/dvc-diff.yml index 0e99d0206be..158e955a0d4 100644 --- a/.github/workflows/dvc-diff.yml +++ b/.github/workflows/dvc-diff.yml @@ -14,7 +14,9 @@ on: paths: - 'pygmt/tests/baseline/*.png.dvc' -permissions: {} +permissions: + contents: write + pull-requests: write jobs: dvc-diff: From 2c86487d8e21b09bba00367f12de6794618d059e Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sun, 21 Sep 2025 16:37:03 +0800 Subject: [PATCH 04/10] Set content permission to read --- .github/workflows/dvc-diff.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dvc-diff.yml b/.github/workflows/dvc-diff.yml index 158e955a0d4..9319a6cb78f 100644 --- a/.github/workflows/dvc-diff.yml +++ b/.github/workflows/dvc-diff.yml @@ -15,7 +15,7 @@ on: - 'pygmt/tests/baseline/*.png.dvc' permissions: - contents: write + contents: read pull-requests: write jobs: From caba47b30b56680652cac843692ae296d3ec8c9b Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Mon, 22 Sep 2025 23:53:18 +0800 Subject: [PATCH 05/10] Clarify the truncation of time zone --- pygmt/src/solar.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pygmt/src/solar.py b/pygmt/src/solar.py index 9d67a3407f2..ae51251f368 100644 --- a/pygmt/src/solar.py +++ b/pygmt/src/solar.py @@ -59,7 +59,8 @@ def solar( string or any datetime-like object recognized by :func:`pandas.to_datetime`. The time can be specified in UTC or using a UTC offset. The offset must be an integer number of hours (e.g., -8 or +5); fractional hours (e.g., -8.5 or +5.5) - are cast to integer. [Default is the current UTC date and time]. + are truncated towards zero (e.g., -8.5 becomes -8 and +5.5 becomes +5). [Default + is the current UTC date and time]. {region} {projection} {frame} From 07ffbe90a9b0116aa5234a0584ddedcf0a7f1641 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Tue, 23 Sep 2025 19:08:13 +0800 Subject: [PATCH 06/10] Update pygmt/src/solar.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Yvonne Fröhlich <94163266+yvonnefroehlich@users.noreply.github.com> --- pygmt/src/solar.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pygmt/src/solar.py b/pygmt/src/solar.py index ae51251f368..a2657182724 100644 --- a/pygmt/src/solar.py +++ b/pygmt/src/solar.py @@ -58,9 +58,9 @@ def solar( Set the date and time for the terminator calculation. It can be provided as a string or any datetime-like object recognized by :func:`pandas.to_datetime`. The time can be specified in UTC or using a UTC offset. The offset must be an - integer number of hours (e.g., -8 or +5); fractional hours (e.g., -8.5 or +5.5) - are truncated towards zero (e.g., -8.5 becomes -8 and +5.5 becomes +5). [Default - is the current UTC date and time]. + integer number of hours (e.g., -8 or +5); fractional hours are truncated + towards zero (e.g., -8.5 becomes -8 and +5.5 becomes +5). [Default is the + current UTC date and time]. {region} {projection} {frame} From f3cefd7c4b4443fef434e20c631b38a6d9614b45 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Tue, 23 Sep 2025 20:20:17 +0800 Subject: [PATCH 07/10] Fix test comment [skip ci] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Yvonne Fröhlich <94163266+yvonnefroehlich@users.noreply.github.com> --- pygmt/tests/test_solar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/tests/test_solar.py b/pygmt/tests/test_solar.py index 8c7a6059af4..29da184bea6 100644 --- a/pygmt/tests/test_solar.py +++ b/pygmt/tests/test_solar.py @@ -124,7 +124,7 @@ def test_solar_default_terminator(): @pytest.mark.mpl_image_compare def test_solar_terminator_datetime_timezone(): """ - Test passing the solar argument with a time string that includes a timezone. + Test passing the terminator_datetime argument with a time string that includes a timezone. """ fig = Figure() fig.basemap(region="d", projection="W0/15c", frame=True) From eb0fa99506fda7571733676e79f8374899ef6ffa Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Tue, 23 Sep 2025 20:20:55 +0800 Subject: [PATCH 08/10] Use ISO 8601 datetime format [skip ci] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Yvonne Fröhlich <94163266+yvonnefroehlich@users.noreply.github.com> --- pygmt/tests/test_solar.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pygmt/tests/test_solar.py b/pygmt/tests/test_solar.py index 29da184bea6..1ef2fdb1744 100644 --- a/pygmt/tests/test_solar.py +++ b/pygmt/tests/test_solar.py @@ -129,8 +129,8 @@ def test_solar_terminator_datetime_timezone(): fig = Figure() fig.basemap(region="d", projection="W0/15c", frame=True) fig.solar(terminator_datetime="2020-01-01T01:02:03", pen="1p,black") - fig.solar(terminator_datetime="2020-01-01T01:02:03+0100", pen="1p,red") - fig.solar(terminator_datetime="2020-01-01T01:02:03-0100", pen="1p,blue") + fig.solar(terminator_datetime="2020-01-01T01:02:03+01:00", pen="1p,red") + fig.solar(terminator_datetime="2020-01-01T01:02:03-01:00", pen="1p,blue") fig.solar( terminator_datetime=datetime.datetime( 2020, 1, 1, 1, 2, 3, tzinfo=datetime.timezone(datetime.timedelta(hours=2)) From a2fd8de1b23f5b9520cbe52399b3b935a94494c6 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Tue, 23 Sep 2025 20:23:57 +0800 Subject: [PATCH 09/10] Fix styling --- pygmt/tests/test_solar.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pygmt/tests/test_solar.py b/pygmt/tests/test_solar.py index 1ef2fdb1744..b63b6b40899 100644 --- a/pygmt/tests/test_solar.py +++ b/pygmt/tests/test_solar.py @@ -124,7 +124,8 @@ def test_solar_default_terminator(): @pytest.mark.mpl_image_compare def test_solar_terminator_datetime_timezone(): """ - Test passing the terminator_datetime argument with a time string that includes a timezone. + Test passing the terminator_datetime argument with a time string that includes a + timezone. """ fig = Figure() fig.basemap(region="d", projection="W0/15c", frame=True) From 20788f413cf969333c5b92ed1aacd7128a606dcb Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 24 Sep 2025 02:18:27 +0800 Subject: [PATCH 10/10] Update pygmt/src/solar.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Yvonne Fröhlich <94163266+yvonnefroehlich@users.noreply.github.com> --- pygmt/src/solar.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pygmt/src/solar.py b/pygmt/src/solar.py index a2657182724..59cba5b3ca1 100644 --- a/pygmt/src/solar.py +++ b/pygmt/src/solar.py @@ -107,8 +107,8 @@ def solar( _datetime = pd.to_datetime(terminator_datetime) datetime_string = _datetime.strftime("%Y-%m-%dT%H:%M:%S.%f") # GMT's solar module uses the C 'atoi' function to parse the timezone - # offset. Ensure the offset is an integer number of hours (e.g., 8 or -5). - # Fractional hours (e.g., 8.5 or -5.5) are cast to integer. + # offset. Ensure the offset is an integer number of hours (e.g., -8 or +5). + # Fractional hours (e.g., -8.5 or +5.5) are truncated towards zero. if utcoffset := _datetime.utcoffset(): datetime_timezone = int(utcoffset.total_seconds() / 3600) except ValueError as verr: