Skip to content

refactor: read the US/Eastern zone from one constant - #154

Merged
MarketDataDev02 merged 3 commits into
mainfrom
refactor/eastern-timezone-constant
Sep 29, 2026
Merged

MarketDataDev02 merged 3 commits into
mainfrom
refactor/eastern-timezone-constant

Conversation

@MarketDataDev02

@MarketDataDev02 MarketDataDev02 commented Sep 25, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #115

Summary

Every timestamp the SDK renders is US/Eastern, and the zone was built from a literal in four places: utils.DEFAULT_TIMEZONE, the pandas handler, the polars handler (.zone) and the exception timestamp. The zone is now built once, in internal_settings._DEFAULT_TIMEZONE, and every site reads it, so a DataFrame and an exception from the same call cannot end up in different zones after a change that misses one copy. marketdata.utils.DEFAULT_TIMEZONE stays public, assigned from the shared value. The change is specific to Python; no sibling SDK is involved.

Version impact

None. The public surface is the one on main: marketdata.utils.DEFAULT_TIMEZONE is still a name that utils defines (an assignment, not an import), and the new shared value is private. No output changes: every site gets the same object it built before (see Proof). No CHANGELOG entry.

For reference, pyproject.toml is at 1.3.0, and the CHANGELOG's [Unreleased] section, which already has BREAKING entries, is the 2.0.0 in progress.

The change

  • internal_settings.py: _DEFAULT_TIMEZONE = pytz.timezone("US/Eastern"). It sits here and not in utils because utils imports exceptions, which needs the zone too.
  • utils.py: DEFAULT_TIMEZONE = _DEFAULT_TIMEZONE, assigned so the name stays part of the module's surface. Its functions read DEFAULT_TIMEZONE as on main, and utils no longer imports pytz.
  • types.py imports _DEFAULT_TIMEZONE from internal_settings instead of DEFAULT_TIMEZONE from utils.
  • exceptions.py: _coerce_timestamp stamps datetime.now(_DEFAULT_TIMEZONE). The pytz import is gone.
  • output_handlers/pandas.py and output_handlers/polars.py: _convert_timestamp_columns reads _DEFAULT_TIMEZONE, and polars takes its name with .zone. Neither imports pytz any more.
  • The three functions touched get Args/Returns docstrings. The pandas one has the same text as on fix/null-date-float-overflow (fix(dataframe): read null dates without pandas 2.x overflowing #158), so the two branches merge cleanly.

Proof

Same object at every site. pytz keeps one object per zone name, so _DEFAULT_TIMEZONE is the very object each site built with pytz.timezone("US/Eastern"), and its .zone is the same string polars got. With pytz 2026.3.post1 from uv.lock:

>>> pytz.timezone("US/Eastern") is pytz.timezone("US/Eastern"), pytz.timezone("US/Eastern").zone
(True, 'US/Eastern')

Suite. uv run pytest -n 4 --cov=marketdata, Python 3.10. main bc0619c: 1954 passed. This branch at 0e0f1c2: 1959 passed, the same 1954 plus the 5 in test_default_timezone.py, coverage 100% (3196 statements, 0 missed). No existing test changed, so the ones that pin pytz.timezone("US/Eastern") and pl.Datetime("us", "US/Eastern") still check the public contract on their own.

The tests catch every site that stops reading the constant. Mutation on 0e0f1c2, one site at a time, a literal put back while the module keeps its import. Each run: 1 failed, 4 passed, and the failure is an assertion, not the setup.

site mutation failing test assertion
utils.format_timestamp fromtimestamp(number / scale, tz=__import__("pytz").timezone("US/Eastern")) test_internal_dates_and_the_credit_reset_follow_the_constant str(quotes[0].updated.tzinfo) == "Asia/Tokyo": 'US/Eastern' == 'Asia/Tokyo'
UserRateLimits.__post_init__ fromtimestamp(float(reset_time), tz=__import__("pytz").timezone("US/Eastern")) test_internal_dates_and_the_credit_reset_follow_the_constant str(reset_time.tzinfo) == "Asia/Tokyo": 'US/Eastern' == 'Asia/Tokyo'
BaseMarketdataException._coerce_timestamp datetime.now(__import__("pytz").timezone("US/Eastern")) test_an_error_timestamp_follows_the_constant '2026-09-28 23:31:47' <= '2026-09-28 10:31:47'
pandas _convert_timestamp_columns default_tz = __import__("pytz").timezone("US/Eastern") test_dataframe_dates_follow_the_constant[pandas] 'US/Eastern' == 'Asia/Tokyo'
polars _convert_timestamp_columns default_tz = __import__("pytz").timezone("US/Eastern").zone test_dataframe_dates_follow_the_constant[polars] 'US/Eastern' == 'Asia/Tokyo'

Restored: 5 passed.

Against main's package code (git checkout origin/main -- src/marketdata): the four client tests error at setup with AttributeError: <module 'marketdata.types' ...> has no attribute '_DEFAULT_TIMEZONE', because main has no shared constant; test_utils_default_timezone_is_us_eastern passes, because the public name is the same on main.

Tests

src/tests/test_default_timezone.py, 5 tests. The tokyo fixture sets the zone to Asia/Tokyo (no daylight saving time, 13 or 14 hours from US/Eastern) in the five modules that read it: DEFAULT_TIMEZONE in utils, and _DEFAULT_TIMEZONE in types, exceptions and both handlers. Each module binds the name when it is imported, so the fixture replaces it where it is read, and a site that built its own zone would stay in US/Eastern. Every call goes through client.stocks.quotes with respx:

  • test_internal_dates_and_the_credit_reset_follow_the_constant: the INTERNAL updated date (read by utils.format_timestamp) and get_meta(result).rate_limits.reset_time, read from a real x-api-ratelimit-reset header by UserRateLimits, are in Asia/Tokyo.
  • test_dataframe_dates_follow_the_constant[pandas] and [polars]: the updated column of the DataFrame each library builds is in Asia/Tokyo.
  • test_an_error_timestamp_follows_the_constant: a 400 answer raises BadRequestError stamped with the time in Asia/Tokyo.
  • test_utils_default_timezone_is_us_eastern: marketdata.utils.DEFAULT_TIMEZONE is pytz.timezone("US/Eastern").

Review round 1

2026-09-28, on 0e0f1c2.

id finding what changed
3bf2e7d3 marketdata.utils.DEFAULT_TIMEZONE became an imported name utils assigns it from the shared value, and the shared value is private. The surface is the one on main, so the version impact stays none.
dfa4f13d the AST test pins source layout The AST test is removed, and so is the render test that built the handlers and an exception by hand. The respx tests through client.stocks.quotes replace both, and they also cover the two readers the render test did not patch, utils and types (see the mutation table).

Public surface, main against this branch:

name main this branch breaking
marketdata.utils.DEFAULT_TIMEZONE defined in utils: pytz.timezone("US/Eastern") defined in utils: DEFAULT_TIMEZONE = _DEFAULT_TIMEZONE, the same object no
marketdata.internal_settings._DEFAULT_TIMEZONE absent private no

git merge-tree of 0e0f1c2 against main and against the open #157, #158 and #159 is clean. ./lint.sh passes.

DataFrames from both handlers, INTERNAL dates, the rate-limit reset time
and exception timestamps all render in US/Eastern, but the zone was built
from a literal in four places. DEFAULT_TIMEZONE moves from utils to
internal_settings, which exceptions can import (utils imports
exceptions), and every site reads it; the polars handler takes its name
with .zone.

pytz returns one cached object per zone name, so each site gets the same
object it built before, and polars the same string.
@codecov

codecov Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (bc0619c) to head (0e0f1c2).

Additional details and impacted files
@@            Coverage Diff            @@
##              main      #154   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           62        62           
  Lines         3196      3196           
=========================================
  Hits          3196      3196           
Flag Coverage Δ
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.

The fixture's docstring said it pointed every module that renders a timestamp at Asia/Tokyo. It patches the exception module and the two DataFrame handlers, the sites the test checks; utils and types also read the constant and keep it.
@marketdata-code-review

marketdata-code-review Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

✅ Ready — Code review

Version impact: none. This internal refactor centralizes the US/Eastern timezone object while preserving marketdata.utils.DEFAULT_TIMEZONE and existing runtime output; no sibling SDK is affected.

Rating patch 6/6 (exemplary) · proof 6/6 (comprehensive) · overall 6/6

Since last review

  • Resolved: 3bf2e7d3, dfa4f13d
  • 0 still open, 0 new

Reviewed 0e0f1c2 · revision 2 · codex (gpt-5.6-sol) · claude unavailable · rules: default, sdk

…h the client

The shared zone becomes internal_settings._DEFAULT_TIMEZONE, and utils
assigns DEFAULT_TIMEZONE from it rather than importing it. An imported name
is not part of a module's public surface, so the surface stays the one on
main and nothing changes for a caller.

The AST scan for the "US/Eastern" literal and the render test that built the
handlers and an exception by hand give way to respx tests through
client.stocks.quotes. With the zone set to Asia/Tokyo in the five modules
that read it, the INTERNAL dates, the credit reset, the pandas and polars
DataFrames and the timestamp of a 400 answer all come out in Asia/Tokyo.
Another test pins utils.DEFAULT_TIMEZONE to US/Eastern.

The pandas docstring of _convert_timestamp_columns takes the text the same
docstring has on fix/null-date-float-overflow, so the two branches merge
cleanly.
@MarketDataDev02
MarketDataDev02 merged commit 2090695 into main Sep 29, 2026
12 checks passed
@MarketDataDev02
MarketDataDev02 deleted the refactor/eastern-timezone-constant branch September 29, 2026 18:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The US/Eastern zone is still written three times, and the sites agree only because the literals match

2 participants