← 输入框|github.com|刷新
Skip to content

Fix naive local default for end_date in get_historical_features - #7041

Open
NUAAwqy wants to merge 1 commit into
feast-dev:masterfrom
NUAAwqy:fix-end-date-utc-default
Open

NUAAwqy wants to merge 1 commit into
feast-dev:masterfrom
NUAAwqy:fix-end-date-utc-default

Conversation

@NUAAwqy

@NUAAwqy NUAAwqy commented Oct 19, 2026

Copy link
Copy Markdown

Problem

get_historical_features with entity_df=None defaults end_date to datetime.now() — naive host-local wall-clock time. Offline stores read naive datetimes as UTC, so the retrieval window shifts by the host's UTC offset. Repro table in #7036: under TZ=Asia/Tokyo omitting end_date returns a row stamped 1h in the future; under TZ=America/Sao_Paulo it misses the newest row.

Fix

One line in sdk/python/feast/feature_store.py:

         if entity_df is None and end_date is None:
-            end_date = datetime.now()
+            end_date = datetime.now(tz=_timezone.utc)

This makes the default equal to passing end_date=datetime.now(timezone.utc) (the documented behavior, "defaults to the current time"), and matches the existing precedent in this same file: now_ts.FromDatetime(datetime.now(tz=_timezone.utc)).

fixes #7036


Disclosure: this change was prepared with AI assistance; the defect analysis and the fix were verified by hand against the current source.

@NUAAwqy
NUAAwqy requested a review from a team as a code owner October 19, 2026 04:17
@luc-pimentel

Copy link
Copy Markdown

Hey, thanks for picking up the issue I opened. I ran its reproduction against this PR's current head (Dask offline store, three host time zones), and with end_date omitted the result now matches passing end_date=datetime.now(timezone.utc) everywhere:

TZ end_date omitted, master end_date omitted, this PR
UTC (1, d1_recent), (2, d2_old) (1, d1_recent), (2, d2_old)
America/Sao_Paulo (1, d1_old), (2, d2_old) (1, d1_recent), (2, d2_old)
Asia/Tokyo (1, d1_recent), (2, d2_future) (1, d1_recent), (2, d2_old)

Passing a naive datetime.now() explicitly still shifts by the host offset, which this change leaves alone on purpose. A unit test asserting that the end_date handed to the provider is UTC-aware would lock this in; I have one that fails on master and passes here if you want it.

Tested with Python 3.11, pandas 2.3.3, pyarrow 25.0.1 and dask 2026.8.0 on 2026-10-24.

@NUAAwqy

NUAAwqy commented Oct 27, 2026

Copy link
Copy Markdown
Author

Thanks for verifying the fix across all three host time zones — that table matches exactly what the change is meant to do, and thanks for catching that a naive datetime.now() still shifts by the host offset (left alone intentionally, as you noted).

Yes, please do share the unit test asserting the end_date handed to the provider is UTC-aware — I'll include it in this PR. Happy to credit you as the author of that test, just let me know the name/affiliation you'd like in the header.

When entity_df is None, end_date defaulted to datetime.now(), a naive
host-local wall-clock timestamp. Offline stores interpret naive
datetimes as UTC, so the retrieval window shifts by the host's UTC
offset (rows missing west of UTC, future rows returned east of it).
Default to datetime.now(tz=_timezone.utc), matching the existing
usage of the same expression in this file.

Signed-off-by: NUAAwqy <146524024+NUAAwqy@users.noreply.github.com>
@NUAAwqy
NUAAwqy force-pushed the fix-end-date-utc-default branch from 72a1784 to 95391a6 Compare October 27, 2026 14:31
@luc-pimentel

Copy link
Copy Markdown

Thanks. Here it is, written for sdk/python/tests/unit/test_unit_feature_store.py, which already imports MagicMock, patch, utils and FeatureStore:

def test_get_historical_features_non_entity_default_end_date_is_utc(tmp_path):
    """With entity_df=None and no end_date, the end bound handed to the provider
    must be the current time in UTC. Offline stores read a naive datetime as UTC
    (utils.make_tzaware), so a naive local value shifts the window by the host offset."""
    from datetime import datetime, timedelta, timezone

    from feast import Entity, FeatureView, Field, FileSource
    from feast.infra.offline_stores.dask import DaskOfflineStoreConfig
    from feast.infra.online_stores.sqlite import SqliteOnlineStoreConfig
    from feast.repo_config import RepoConfig
    from feast.types import Int64

    store = FeatureStore(
        config=RepoConfig(
            project="non_entity_default_end_date",
            registry=str(tmp_path / "registry.db"),
            provider="local",
            entity_key_serialization_version=3,
            offline_store=DaskOfflineStoreConfig(),
            online_store=SqliteOnlineStoreConfig(path=str(tmp_path / "online.db")),
        )
    )
    driver = Entity(name="driver", join_keys=["driver_id"])
    driver_stats = FeatureView(
        name="driver_stats",
        entities=[driver],
        ttl=timedelta(days=1),
        schema=[Field(name="driver_id", dtype=Int64), Field(name="value", dtype=Int64)],
        source=FileSource(
            path=str(tmp_path / "driver_stats.parquet"),
            timestamp_field="event_timestamp",
        ),
    )
    store.apply([driver, driver_stats])

    provider = MagicMock()
    before = datetime.now(timezone.utc)
    with patch.object(FeatureStore, "_get_provider", return_value=provider):
        store.get_historical_features(entity_df=None, features=["driver_stats:value"])
    after = datetime.now(timezone.utc)

    end_date = provider.get_historical_features.call_args.kwargs["end_date"]
    assert end_date.utcoffset() == timedelta(0)
    assert before <= utils.make_tzaware(end_date) <= after
cd sdk/python && TZ=America/Sao_Paulo python -m pytest tests/unit/test_unit_feature_store.py -k default_end_date_is_utc

On master it fails with assert None == datetime.timedelta(0) because end_date is naive; on this branch it passes under TZ=UTC, America/Sao_Paulo and Asia/Tokyo. Fold it in however you like. Crediting it to @luc-pimentel is fine.

@NUAAwqy
NUAAwqy force-pushed the fix-end-date-utc-default branch from 95391a6 to 07d7792 Compare October 28, 2026 00:44

This branch has not been deployed

No deployments
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.

Entity-less get_historical_features defaults end_date to the host's local time, read as UTC

3 participants