Skip to content

Convert 0-D arrays in the time converters - #802

Merged
d-chambers merged 2 commits into
devfrom
fix-0d-time-arrays
Aug 4, 2026
Merged

Convert 0-D arrays in the time converters#802
d-chambers merged 2 commits into
devfrom
fix-0d-time-arrays

Conversation

@d-chambers

@d-chambers d-chambers commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Description

to_datetime64 and to_timedelta64 raised TypeError: len() of unsized object for a 0-D array, unless its dtype happened to match the converter's own time type:

dc.to_timedelta64(np.array(np.datetime64("2020-01-01")))  # TypeError
dc.to_datetime64(np.array(5.0))                           # TypeError

The empty-array guard is len(array) == 0, which a 0-D array cannot answer. Only the matching time type worked, because that branch returns before reaching the check. Every one of these has a 1-D equivalent that works fine, so the rank was the only thing standing in the way.

Both converters now reshape a 0-D input to the length-one array it stands for and unpack the scalar back out, so every rank takes the same path and a 0-D input converts exactly like array.reshape(1) would. _array_to_timedelta64's early returns become an if/elif/else to give it a single exit to unpack at.

Nothing about 1-D or higher input changes. 2-D input still raises the same ValueError it always did.

Found while reviewing #800.

Changelog

  • fixed: dc.to_datetime64 and dc.to_timedelta64 convert a 0-D array instead of raising TypeError: len() of unsized object.
  • fixed: to_timedelta64 normalizes a datetime64 array's unit before viewing it as integers, so a non-nanosecond array (e.g. datetime64[s]) is no longer off by a factor of a billion.

Checklist

I have (if applicable):

  • referenced the GitHub issue this PR closes.
  • documented the new feature with docstrings and/or appropriate doc page.
  • included tests. See testing guidelines.
  • added the "ready_for_review" tag once the PR is ready to be reviewed.

to_datetime64 and to_timedelta64 raised "TypeError: len() of unsized
object" for any 0-D array whose dtype missed the first branch: a 0-D
float, int, or the other time type. Only the matching time type worked,
because that branch returns before the length check.

Both converters now reshape a 0-D input to the length-one array it
stands for and unpack the scalar back out, so every rank takes the same
path. _array_to_timedelta64's early returns become an if/elif/else to
give it a single exit to unpack at.
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@d-chambers, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 33 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 53b8e2b5-680a-47a3-bc4c-5e78c3a805cc

📥 Commits

Reviewing files that changed from the base of the PR and between 071b3dd and 0e6fd46.

📒 Files selected for processing (2)
  • dascore/utils/time.py
  • tests/test_utils/test_time.py

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@d-chambers d-chambers added the ready_for_review PR is ready for review label Aug 4, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5855a1c84a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread dascore/utils/time.py Outdated
Comment on lines +269 to +270
elif np.issubdtype(array.dtype, np.datetime64):
int_array = array.view(np.int64)
return np.array(int_array).astype("timedelta64[ns]")

assert np.isreal(array[0])
invalid = pd.isnull(array) | ~np.isfinite(array)
# Need to make copy to 1) not change original array and 2) handle
# immutable arrays. See #575.
if np.any(invalid):
array = np.array(array)
array[invalid] = 0
# inf/NaN complain, salience these types of warnings for this block.
with np.errstate(divide="ignore", invalid="ignore"):
out = _float_array_to_ns(array).astype("timedelta64[ns]")
out[invalid] = _NAT_TIMEDELTA64
return out
out = np.array(array.view(np.int64)).astype("timedelta64[ns]")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Normalize datetime units before reinterpreting them

When the new 0-D path receives a datetime64 whose unit is not nanoseconds, this views the raw unit count and then labels it as nanoseconds. For example, to_timedelta64(np.array(np.datetime64("2020-01-01", "s"))) now returns 1577836800 ns (about 1.58 seconds) rather than the nanosecond-normalized offset from the epoch. Cast the datetime array to datetime64[ns] before viewing its integer representation; the new test currently misses this because it compares against the same incorrect length-one-array path.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Correct, and worse than the 0-D case alone — the 1-D path had the same bug for every non-ns unit. to_timedelta64(np.array([np.datetime64("2020-01-01", "D")])) returned 18262 ns instead of 18262 days; only an array already in ns was right. Fixed in 0e6fd46 by casting to ns before the view, with a test parametrized over D/s/ms/ns asserting the epoch offset directly rather than against the other rank.

@codecov

codecov Bot commented Aug 4, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (071b3dd) to head (0e6fd46).
⚠️ Report is 1 commits behind head on dev.

Additional details and impacted files
@@            Coverage Diff            @@
##               dev      #802   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files          164       164           
  Lines        17829     17835    +6     
=========================================
+ Hits         17829     17835    +6     
Flag Coverage Δ
network 48.38% <35.29%> (+<0.01%) ⬆️
unittests 100.00% <100.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread dascore/utils/time.py Outdated
Comment on lines +140 to +142
degenerate = _is_degenerate(array)
if degenerate:
array = array.reshape(1)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Too much code; just check if it is degenerate and delete the helper function.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 0e6fd46 — inlined as degenerate = array.ndim == 0 with a two-line comment, helper deleted.

Comment thread dascore/utils/time.py Outdated
Comment on lines +260 to +262
degenerate = _is_degenerate(array)
if degenerate:
array = array.reshape(1)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

same here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done — same inline check, pointing at the note in _array_to_datetime64 rather than repeating it.

to_timedelta64 of a datetime64 array viewed its raw integer and labeled
the result nanoseconds, so only an array already in ns was right: a
datetime64[D] of 2020-01-01 came back as 18262 ns rather than 18262 days.
Casting to ns first makes every unit give the same epoch offset.

Also inlines the degenerate check and drops the helper, per review.
@d-chambers
d-chambers merged commit f9a5f8b into dev Aug 4, 2026
27 checks passed
@d-chambers
d-chambers deleted the fix-0d-time-arrays branch August 4, 2026 10:25
@d-chambers d-chambers removed the ready_for_review PR is ready for review label Aug 11, 2026
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.

1 participant