Skip to content

sequence_join treats any string containing a space as a datetime (crashes labels= in magnetic_rose/directional_rose) #4960

Open

Description

@rcgiri-physics

Hi, thanks for maintaining PyGMT. In pygmt/helpers/utils.py, sequence_join detects datetime-like items with

elif " " in str(item):
    _values.append(np.datetime_as_string(np.asarray(item, dtype="datetime64")))

A space is normal in label text and does not mean the item is temporal. With the pinned source and NumPy 2.x:

sequence_join(["W", "E", "S", "N"], sep=",", size=4)          -> 'W,E,S,N'
sequence_join(["W", "E", "S", "Pole Nord"], sep=",", size=4)  -> ValueError: Cannot create a NumPy datetime other than NaT with generic units

pygmt/alias.py calls sequence_join for every non-string iterable whenever sep is set. magnetic_rose and directional_rose build Alias(labels, name="labels", prefix="+l", sep=",", size=4) and document labels as free text. So a label with a space crashes before GMT is even called. (magnetic_rose already escapes spaces in the scalar declination_label as \040, which suggests labels with spaces are expected.)

A type check, e.g. isinstance(item, (datetime.datetime, datetime.date, np.datetime64)) or hasattr(item, "isoformat"), instead of sniffing for a space would fix it. Plain strings could then go through str(item), with the same \040 escaping if GMT needs it.

I found this while testing physverify, a small verification tool I'm working on, against public codes. The tests and this write-up were done with help from an AI assistant; the numbers come from actually running the code.

Activity

  1. github-actions commented on Oct 23, 2026

    @github-actions
    Contributor

    👋 Thanks for opening your first issue here!

    Please make sure you filled out the template with as much detail as possible. You might also want to take a look at our contributing guidelines and code of conduct.

  2. added this to the 0.20.0 milestone on Oct 23, 2026
  3. seisman commented on Oct 26, 2026

    @seisman
    Member

    Thanks for the report, @rcgiri-physics.

    Background

    elif " " in str(item):
        _values.append(np.datetime_as_string(np.asarray(item, dtype="datetime64")))

    Some background: the code above was added in #4097 and later modified in #4358. It was originally copied from the kwargs_to_strings decorator (decorators.py#L751-L759):

    The intent was to let users pass datetime-like objects (e.g., datetime.datetime, pd.Timestamp) to parameters like region, because str() of these objects contains a space (2010-01-01 00:00:00), which GMT can't parse.

    Limitations of the current implementation

    The current implementation has two side effects:

    1. Any string containing a space is treated as a datetime, so a normal text label crashes:
      >>> sequence_join(["W", "E", "S", "Pole Nord"], sep=",", size=4)
      ValueError: Cannot create a NumPy datetime other than NaT with generic units
      This affects every parameter that takes free text, including labels in magnetic_rose/directional_rose and alabel/blabel/clabel in ternary (e.g., alabel="Clay A").
    2. Datetime-like strings with a space are rewritten in ISO 8601 format, e.g., sequence_join(["2010-01-01 01:01", "2010-01-02 01:01"]) gives 2010-01-01T01:01/2010-01-02T01:01. For region this is actually what we want, because GMT can't parse a space in a datetime:
      $ gmt basemap -R"2010-01-01 01:01/2010-01-02 01:01/0/1" -JX10c/5c -Baf -pdf map
      basemap [ERROR]: Could not parse 2010-01-01 01:01 into Geographical, Cartesian, or Temporal coordinates!
      $ gmt makecpt -Cgray -T"2010-01-01 01:01/2010-01-03 01:01/1d"
      makecpt [ERROR]: Option T: Unable to parse min value from 2010-01-01 01:01
      
      For other parameters, however, users may want to typeset the string literally even though the strings are datetime-like.

    #4961 replaces the space check with a type check. That fixes item 1 but breaks code like region=["2010-01-01 01:01", "2010-01-02 01:01", 0, 1], which works now.

    The underlying problem is that we can't tell from the value alone whether "2010-01-01 01:01" is a datetime or a literal string. Only the parameter can tell us. As far as I know, only a few GMT options accept datetimes:

    • the common -R option (region in PyGMT)
    • the -T option of grd2cpt/makecpt/histogram (series in PyGMT, which will be migrated to the alias system later).

    Proposed changes

    So I propose adding a boolean parameter (e.g., datetime or datetime_like) to sequence_join and Alias:

    • False (default): no conversion at all; every item is converted with str(). Free-text parameters are safe by default.
    • True: datetime-like objects are converted to ISO 8601 strings, and strings containing a space are parsed as datetimes (passed through unchanged if parsing fails, so GMT reports the error). This would be manually enabled only for region and series, and more parameters if necessary.

    @GenericMappingTools/pygmt-maintainers Any comments?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions