Skip to content

Allow for quantity-string time delta as CxoTime initializer#54

Merged
taldcroft merged 2 commits intomasterfrom
init-with-delta-time
Oct 3, 2025
Merged

Allow for quantity-string time delta as CxoTime initializer#54
taldcroft merged 2 commits intomasterfrom
init-with-delta-time

Conversation

@taldcroft
Copy link
Copy Markdown
Member

@taldcroft taldcroft commented Sep 26, 2025

Description

This is a long-standing desire of mine to allow supplying a delta time offset from now to initialize CxoTime.
This is in the form "+/-1yr 2d 3hr 4.2min 10.25s". Any function or command line tool that uses Ska
CxoTime or DateTime will work with this format.

This is especially convenient for command line arguments like --start=-14d --stop=-7d, or similar for any function that has CxoTimeLike start / stop args.

Interface impacts

Adds a new way to initialize CxoTime.

Testing

Unit tests

  • Mac
(ska3) ➜  cxotime git:(init-with-delta-time) git rev-parse --short HEAD
19f4f95
(ska3) ➜  cxotime git:(init-with-delta-time) pytest                    
======================================== test session starts ========================================
platform darwin -- Python 3.12.8, pytest-8.3.4, pluggy-1.5.0
rootdir: /Users/aldcroft/git
configfile: pytest.ini
plugins: anyio-4.7.0, timeout-2.3.1
collected 292 items                                                                                 

cxotime/tests/test_cxotime.py ............................................................... [ 21%]
............................................................................................. [ 53%]
............................................................................................. [ 85%]
............................                                                                  [ 94%]
cxotime/tests/test_cxotime_linspace.py ...............                                        [100%]

======================================== 292 passed in 1.55s ========================================

Independent check of unit tests by Jean

  • OSX
(ska3-latest) flame:CxoTime jean$ git rev-parse HEAD
19f4f95790047263ef1215d108f20eda677ea883
(ska3-latest) flame:CxoTime jean$ pytest
=============================================================== test session starts ===============================================================
platform darwin -- Python 3.12.8, pytest-8.3.4, pluggy-1.5.0
rootdir: /Users/jean/git
configfile: pytest.ini
plugins: socket-0.7.0, anyio-4.7.0, timeout-2.3.1
collected 292 items                                                                                                                               

cxotime/tests/test_cxotime.py ............................................................................................................. [ 37%]
........................................................................................................................................... [ 84%]
.............................                                                                                                               [ 94%]
cxotime/tests/test_cxotime_linspace.py ...............                                                                                      [100%]

=============================================================== 292 passed in 1.63s

Functional tests

No functional testing.

Copy link
Copy Markdown
Contributor

@jeanconn jeanconn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me.

@jeanconn
Copy link
Copy Markdown
Contributor

jeanconn commented Oct 2, 2025

I note I was able to break this when I got silly, but I think that's to be expected (though I don't know where the natural ranges of CxoTime are documented).

In [3]: CxoTime("1e12s").iso
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[3], line 1
----> 1 CxoTime("1e12s").iso

File ~/git/cxotime/cxotime/cxotime.py:207, in CxoTime.__init__(self, *args, **kwargs)
    204     # For string input that did not already succeed, try a quantity string
    205     # TimeDelta like "-1hr" or "2d".
    206     if val.dtype.kind == "U":
--> 207         args = _try_args_as_time_delta(args, kwargs)
    209 elif fmt == "maude":
    210     args = (np.asarray(args[0], dtype="S"),) + args[1:]

File ~/git/cxotime/cxotime/cxotime.py:59, in _try_args_as_time_delta(args, kwargs)
     57 kwargs["format"] = "date"
     58 kwargs["scale"] = "utc"
---> 59 return (tm.date,)

File ~/miniforge3/envs/ska3-latest/lib/python3.12/site-packages/astropy/time/core.py:1777, in TimeBase.__getattr__(self, attr)
   1774     return cache[attr]
   1776 elif attr in self.FORMATS:
-> 1777     return self.to_value(attr, subfmt=None)
   1779 elif attr in TIME_SCALES:  # allowed ones done above (self.SCALES)
   1780     if self.scale is None:

File ~/miniforge3/envs/ska3-latest/lib/python3.12/site-packages/astropy/time/core.py:1055, in TimeBase.to_value(self, format, subfmt)
   1053     kwargs["out_subfmt"] = subfmt
   1054 try:
-> 1055     value = tm._time.to_value(parent=tm, **kwargs)
   1056 except TypeError as exc:
   1057     # Try validating subfmt, e.g. for formats like 'jyear_str' that
   1058     # do not implement out_subfmt in to_value() (because there are
   1059     # no allowed subformats).  If subfmt is not valid this gives the
   1060     # same exception as would have occurred if the call to
   1061     # `to_value()` had succeeded.
   1062     tm._time._select_subfmts(subfmt)

File ~/git/cxotime/cxotime/cxotime.py:421, in TimeDate.to_value(self, parent, **kwargs)
    419 def to_value(self, parent=None, **kwargs):
    420     if self.scale == "utc":
--> 421         return super().value
    422     else:
    423         return parent.utc._time.value

File ~/miniforge3/envs/ska3-latest/lib/python3.12/site-packages/astropy/time/formats.py:1718, in TimeString.value(self)
   1715 # Try to optimize this later.  Can't pre-allocate because length of
   1716 # output could change, e.g. year rolls from 999 to 1000.
   1717 outs = []
-> 1718 for kwargs in self.str_kwargs():
   1719     outs.append(str(self.format_string(str_fmt, **kwargs)))
   1721 return np.array(outs).reshape(self.jd1.shape)

File ~/miniforge3/envs/ska3-latest/lib/python3.12/site-packages/astropy/time/formats.py:1683, in TimeString.str_kwargs(self)
   1679 for iy, im, id, ihr, imin, isec, ifracsec in np.nditer(
   1680     [iys, ims, ids, ihrs, imins, isecs, ifracs], flags=["zerosize_ok"]
   1681 ):
   1682     if has_yday:
-> 1683         yday = datetime.datetime(iy, im, id).timetuple().tm_yday
   1685     yield {
   1686         "year": int(iy),
   1687         "mon": int(im),
   (...)
   1693         "yday": yday,
   1694     }

ValueError: year 33714 is out of range
In [7]: CxoTime("1e14s").iso
---------------------------------------------------------------------------
ErfaError                                 Traceback (most recent call last)
<ipython-input-7-8184597f593d> in ?()
----> 1 CxoTime("1e14s").iso

~/git/cxotime/cxotime/cxotime.py in ?(self, *args, **kwargs)
    203 
    204             # For string input that did not already succeed, try a quantity string
    205             # TimeDelta like "-1hr" or "2d".
    206             if val.dtype.kind == "U":
--> 207                 args = _try_args_as_time_delta(args, kwargs)
    208 
    209         elif fmt == "maude":
    210             args = (np.asarray(args[0], dtype="S"),) + args[1:]

~/git/cxotime/cxotime/cxotime.py in ?(args, kwargs)
     51         dt = TimeDelta(args[0], format="quantity_str")
     52     except Exception:
     53         return args
     54 
---> 55     tm = CxoTime.now() + dt
     56     tm.format = "date"
     57     kwargs["format"] = "date"
     58     kwargs["scale"] = "utc"

~/miniforge3/envs/ska3-latest/lib/python3.12/site-packages/astropy/time/core.py in ?(self, other)
   2737 
   2738         out._time.jd1, out._time.jd2 = day_frac(jd1, jd2)
   2739 
   2740         # Go back to left-side scale if needed
-> 2741         out._set_scale(self.scale)
   2742 
   2743         return out

~/miniforge3/envs/ska3-latest/lib/python3.12/site-packages/astropy/time/core.py in ?(self, scale)
    828                     args.append(get_dt(jd1, jd2))
    829                     break
    830 
    831             conv_func = getattr(erfa, sys1 + sys2)
--> 832             jd1, jd2 = conv_func(*args)
    833 
    834         jd1, jd2 = day_frac(jd1, jd2)
    835 

~/miniforge3/envs/ska3-latest/lib/python3.12/site-packages/erfa/core.py in ?(tai1, tai2)
  16906         Derived, with permission, from the SOFA library.  See notes at end of file.
  16907 
  16908     """
  16909     utc1, utc2, c_retval = ufunc.taiutc(tai1, tai2)
> 16910     check_errwarn(c_retval, 'taiutc')
  16911     return utc1, utc2

~/miniforge3/envs/ska3-latest/lib/python3.12/site-packages/erfa/core.py in ?(statcodes, func_name)
    120         elsemsg = STATUS_CODES[func_name].get('else', None)
    121         msgs = [STATUS_CODES[func_name].get(e, elsemsg or f'Return code {e}')
    122                 for e in errcodes]
    123         emsg = ', '.join([f'{c} of "{msg}"' for c, msg in zip(counts, msgs)])
--> 124         raise ErfaError(f'ERFA function "{func_name}" yielded {emsg}')
    125 
    126     warnidx = (statcodes > 0).nonzero()
    127     if warnidx[0].size > 0:

ErfaError: ERFA function "taiutc" yielded 1 of "unacceptable date"

@taldcroft taldcroft merged commit 53a4eef into master Oct 3, 2025
2 checks passed
@taldcroft taldcroft deleted the init-with-delta-time branch October 3, 2025 09:25
@javierggt javierggt mentioned this pull request Oct 31, 2025
@javierggt javierggt mentioned this pull request Nov 18, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants