Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 1 addition & 12 deletions .github/workflows/auto-update-bulletin-a.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,7 @@ on:
jobs:
build:

runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest]
python-version: [3.11]
env:
OS: ${{ matrix.os }}
PYTHON: ${{ matrix.python-version }}
defaults:
run:
shell: bash -l {0}

runs-on: ubuntu-slim

Copilot AI Apr 23, 2026

Copy link

Choose a reason for hiding this comment

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

ubuntu-slim is not a valid GitHub-hosted runner label, so this scheduled job will not run. Use a supported runner label such as ubuntu-latest or ubuntu-22.04.

Suggested change
runs-on: ubuntu-slim
runs-on: ubuntu-latest

Copilot uses AI. Check for mistakes.
steps:
- uses: actions/checkout@v4
- name: Set up pixi environment
Expand Down
13 changes: 1 addition & 12 deletions .github/workflows/auto-update-files.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,7 @@ on:
jobs:
build:

runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest]
python-version: [3.11]
env:
OS: ${{ matrix.os }}
PYTHON: ${{ matrix.python-version }}
defaults:
run:
shell: bash -l {0}

runs-on: ubuntu-slim

Copilot AI Apr 23, 2026

Copy link

Choose a reason for hiding this comment

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

ubuntu-slim is not a valid GitHub-hosted runner label, so this scheduled job will not run. Switch to a supported Ubuntu runner label (e.g., ubuntu-latest or a pinned ubuntu-22.04).

Suggested change
runs-on: ubuntu-slim
runs-on: ubuntu-22.04

Copilot uses AI. Check for mistakes.
steps:
- uses: actions/checkout@v4
- name: Set up pixi environment
Expand Down
8 changes: 3 additions & 5 deletions .github/workflows/python-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,13 @@ name: Upload Python Package

on:
release:
types: [created]
types: [released]

jobs:
deploy:

runs-on: ubuntu-latest

runs-on: ubuntu-slim

Copilot AI Apr 23, 2026

Copy link

Choose a reason for hiding this comment

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

ubuntu-slim is not a valid GitHub-hosted runner label, so this publish workflow will fail to start. Use a supported runner label such as ubuntu-latest/ubuntu-22.04/ubuntu-24.04.

Suggested change
runs-on: ubuntu-slim
runs-on: ubuntu-latest

Copilot uses AI. Check for mistakes.
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v4
with:
Expand Down
28 changes: 17 additions & 11 deletions .github/workflows/python-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,30 +9,31 @@ on:
- timescale/**
- test/**
- .github/workflows/python-request.yml
schedule:
# * is a special character in YAML so you have to quote this string
- cron: '0 0 1 * *'
workflow_dispatch:
types: [opened, synchronize, reopened, ready_for_review]

# set permissions for the workflow
permissions:
contents: read
pull-requests: write

jobs:
build:

if: github.event.pull_request.draft == false
Comment thread
tsutterley marked this conversation as resolved.
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: [3.11]
env:
OS: ${{ matrix.os }}
PYTHON: ${{ matrix.python-version }}
defaults:
run:
shell: bash -l {0}

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Set up pixi environment
uses: prefix-dev/setup-pixi@v0.9.1
uses: prefix-dev/setup-pixi@v0.9.4
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
Expand All @@ -48,14 +49,19 @@ jobs:
--username=${{ secrets.EARTHDATA_USERNAME }} \
--password=${{ secrets.EARTHDATA_PASSWORD }}
- name: Create coverage comment
uses: MishaKav/pytest-coverage-comment@main
if: github.event.pull_request.head.repo.full_name == github.repository
uses: MishaKav/pytest-coverage-comment@v1
with:
pytest-xml-coverage-path: coverage.xml
junitxml-path: pytest.xml
unique-id-for-comment: ${{ matrix.os }}
title: Coverage for Python on ${{ matrix.os }}
hide-report: true
remove-links-to-files: true
remove-links-to-lines: true
- name: Archive code coverage results
uses: actions/upload-artifact@v4
if: github.event.pull_request.head.repo.full_name == github.repository
uses: actions/upload-artifact@v6
with:
name: code-coverage-report-${{ matrix.os }}
path: coverage.xml
path: coverage.xml
4 changes: 2 additions & 2 deletions .github/workflows/ruff-format.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ on:

jobs:
ruff-format:
runs-on: ubuntu-latest
runs-on: ubuntu-slim
Comment thread
tsutterley marked this conversation as resolved.
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Format and annotate PR
uses: astral-sh/ruff-action@v3
with:
Expand Down
37 changes: 20 additions & 17 deletions doc/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,24 @@
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
import os

# import sys
import datetime
import warnings

# sys.path.insert(0, os.path.abspath('.'))
import importlib.metadata


# -- Project information -----------------------------------------------------
on_rtd = os.environ.get('READTHEDOCS') == 'True'
on_rtd = os.environ.get("READTHEDOCS") == "True"

# package metadata
metadata = importlib.metadata.metadata("timescale")
project = metadata["Name"]
year = datetime.date.today().year
copyright = f"2023\u2013{year}, Tyler C. Sutterley"
author = 'Tyler C. Sutterley'
author = "Tyler C. Sutterley"

# software version
version = metadata["version"]
Expand All @@ -44,32 +46,32 @@
# ones.
extensions = [
"numpydoc",
'sphinxcontrib.bibtex',
"sphinxcontrib.bibtex",
"sphinx.ext.autodoc",
"sphinx.ext.graphviz",
"sphinx.ext.viewcode",
"sphinx_design",
"sphinxarg.ext"
"sphinxarg.ext",
]

# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
templates_path = ["_templates"]

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ['**.ipynb_checkpoints']
exclude_patterns = ["**.ipynb_checkpoints"]

# location of master document (by default sphinx looks for contents.rst)
master_doc = 'index'
master_doc = "index"

# -- Configuration options ---------------------------------------------------
autosummary_generate = True
autodoc_member_order = 'bysource'
autodoc_member_order = "bysource"
numpydoc_show_class_members = False
pygments_style = 'native'
bibtex_bibfiles = ['_assets/timescale-refs.bib']
bibtex_default_style = 'plain'
pygments_style = "native"
bibtex_bibfiles = ["_assets/timescale-refs.bib"]
bibtex_default_style = "plain"

# -- Options for HTML output -------------------------------------------------

Expand All @@ -82,7 +84,7 @@
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'sphinx_rtd_theme'
html_theme = "sphinx_rtd_theme"
html_theme_options = {
"logo_only": True,
}
Expand All @@ -91,14 +93,14 @@
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_logo = "_assets/timescale_logo.png"
html_static_path = ['_static']
html_static_path = ["_static"]
# fetch the project urls
project_urls = {}
for project_url in metadata.get_all('Project-URL'):
name, _, url = project_url.partition(', ')
for project_url in metadata.get_all("Project-URL"):
name, _, url = project_url.partition(", ")
project_urls[name.lower()] = url
# fetch the repository url
repository_url = project_urls.get('repository')
repository_url = project_urls.get("repository")
# add html context
html_context = {
"menu_links": [
Expand All @@ -117,6 +119,7 @@
],
}


# Load the custom CSS files (needs sphinx >= 1.6 for this to work)
def setup(app):
app.add_css_file("style.css")
app.add_css_file("style.css")
2 changes: 1 addition & 1 deletion pixi.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ exclude = [
".git",
".pixi",
"build",
"doc",
"run",
"test",
]
Expand Down
10 changes: 10 additions & 0 deletions test/test_time.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,16 @@ def test_earth_rotation_angle():
expected = 0.8730204642501604
assert np.isclose(360.0*expected, ts.era).all()

def test_from_range():
"""Test that the from range functions expected outputs
Comment thread
tsutterley marked this conversation as resolved.
Outdated
"""
ts = timescale.from_range('2020-01-01','2020-02-01',1,'D')
exp = 58849 + np.arange(32)
assert np.all(ts.MJD == exp)
ts = timescale.from_range('2020-01-01','2020-02-01',1,'D',endpoint=False)
exp = 58849 + np.arange(31)
assert np.all(ts.MJD == exp)
Comment on lines +218 to +222

Copilot AI Apr 23, 2026

Copy link

Choose a reason for hiding this comment

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

There’s trailing whitespace after the assertions, which will cause formatter/linter noise and can fail strict checks. Remove the extra spaces so the file stays clean under ruff/flake8.

Copilot uses AI. Check for mistakes.

def test_gps_week():
"""Test that the GPS week matches expected outputs
"""
Expand Down
14 changes: 8 additions & 6 deletions timescale/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
# shortcut wrapper functions for timescale.time.Timescale methods
# delta time
def from_deltatime(*args, **kwargs):
"""Wrapper for ``timescale.time.Timescale().from_deltatime``"""
"""Wrapper for :py:func:`timescale.time.Timescale.from_deltatime`"""
return timescale.time.Timescale.from_deltatime(*args, **kwargs)
Comment thread
tsutterley marked this conversation as resolved.


Expand All @@ -27,34 +27,36 @@ def from_julian(ut1, **kwargs):

# Besselian years
def from_besselian(B, **kwargs):
"""Wrapper for creating a ``Timescale`` object from Besselian years"""
"""Wrapper for creating a :py:class:`timescale.time.Timescale`
object from Besselian years"""
# convert Besselian year to MJD
MJD = 15019.81352 + (B - 1900.0) * 365.242198781
return timescale.time.Timescale(MJD)


# calendar dates
def from_calendar(*args, **kwargs):
"""Wrapper for ``timescale.time.Timescale().from_calendar``"""
"""Wrapper for :py:func:`timescale.time.Timescale.from_calendar`"""
return timescale.time.Timescale.from_calendar(*args, **kwargs)


# datetime arrays
def from_datetime(*args, **kwargs):
"""Wrapper for ``timescale.time.Timescale().from_datetime``"""
"""Wrapper for :py:func:`timescale.time.Timescale.from_datetime`"""
return timescale.time.Timescale.from_datetime(*args, **kwargs)


# range of dates
def from_range(start, end, *args, **kwargs):
"""Wrapper for creating a ``Timescale`` object from a range of dates"""
"""Wrapper for creating a :py:class:`timescale.time.Timescale`
object from a range of dates"""
d = timescale.time.date_range(start, end, *args, **kwargs)
return timescale.time.Timescale.from_datetime(d)


# list of timescale objects
def from_list(*args, **kwargs):
"""Wrapper for ``timescale.time.Timescale().from_list``"""
"""Wrapper for :py:func:`timescale.time.Timescale.from_list`"""
return timescale.time.Timescale.from_list(*args, **kwargs)


Expand Down
17 changes: 11 additions & 6 deletions timescale/time.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env python
"""
time.py
Written by Tyler Sutterley (12/2025)
Written by Tyler Sutterley (04/2026)
Utilities for calculating time operations

PYTHON DEPENDENCIES:
Expand All @@ -16,6 +16,8 @@
utilities.py: download and management utilities for syncing files

UPDATE HISTORY:
Updated 04/2026: added endpoint option (defaults to True) to date_range
added default day and month values to from_calendar function
Updated 12/2025: allow conversion to datetime be in different units
Updated 08/2025: add nominal years (365.25 days long) to Timescale class
Updated 07/2025: verify Bulletin-A entries are not already in merged file
Expand Down Expand Up @@ -269,6 +271,7 @@ def date_range(
end: str | np.datetime64 | datetime.datetime,
step: int | float = 1,
units: str = "D",
endpoint: bool = True,
Comment thread
tsutterley marked this conversation as resolved.
):
"""
Create a range of dates
Expand All @@ -292,14 +295,16 @@ def date_range(
- ``'m'``: minute
- ``'s'``: second
- ``'ms'``: millisecond
endpoint: bool, default True
If True, include the end date in the output
"""
# convert start and end dates to datetime64
if isinstance(start, str):
start = np.array(parse(start), dtype=f"datetime64[{units}]")
if isinstance(end, str):
end = np.array(parse(end), dtype=f"datetime64[{units}]")
# create date range
return np.arange(start, end + step, step)
return np.arange(start, end + endpoint * step, step)

Copilot AI Apr 23, 2026

Copy link

Choose a reason for hiding this comment

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

date_range advertises support for datetime.datetime inputs, but only string inputs are converted to datetime64. If callers pass datetime.datetime, np.arange will receive non-datetime64 values and is likely to error or produce object arrays. Convert datetime.datetime inputs to datetime64[{units}] (or tighten the type hints/docs to match supported types).

Copilot uses AI. Check for mistakes.
Comment thread
tsutterley marked this conversation as resolved.
Outdated


# days per month in a leap and a standard year
Expand Down Expand Up @@ -877,8 +882,8 @@ def from_deltatime(
def from_calendar(
cls,
year: np.ndarray,
month: np.ndarray,
day: np.ndarray,
month: np.ndarray | float = 1.0,
day: np.ndarray | float = 1.0,
Comment thread
tsutterley marked this conversation as resolved.
hour: np.ndarray | float = 0.0,
minute: np.ndarray | float = 0.0,
second: np.ndarray | float = 0.0,
Expand All @@ -890,9 +895,9 @@ def from_calendar(
----------
year: np.ndarray
calendar year
month: np.ndarray
month: np.ndarray or float, default 1.0
month of the year
day: np.ndarray
day: np.ndarray or float, default 1.0
day of the month
hour: np.ndarray or float, default 0.0
hour of the day
Expand Down
Loading