adf0d17497
publish / version_or_publish (push) Waiting to run
storybook-build / changes (push) Waiting to run
storybook-build / :storybook-build (push) Blocked by required conditions
Sync Gradio Skills to Hugging Face / sync-skills (push) Waiting to run
functional / changes (push) Waiting to run
functional / build-frontend (push) Blocked by required conditions
functional / functional-test-SSR=false (push) Blocked by required conditions
functional / functional-reload (push) Blocked by required conditions
functional / functional-test-SSR=true (push) Blocked by required conditions
hygiene / hygiene-test (push) Waiting to run
python / changes (push) Waiting to run
python / build (push) Blocked by required conditions
python / test-ubuntu-latest-flaky (push) Blocked by required conditions
python / test-ubuntu-latest-not-flaky (push) Blocked by required conditions
python / test-windows-latest-flaky (push) Blocked by required conditions
python / test-windows-latest-not-flaky (push) Blocked by required conditions
js / changes (push) Waiting to run
js / js-test (push) Blocked by required conditions
docs-build / changes (push) Waiting to run
docs-build / docs-build (push) Blocked by required conditions
docs-build / website-build (push) Blocked by required conditions
269 lines
11 KiB
Python
269 lines
11 KiB
Python
from datetime import datetime, timezone
|
|
from typing import cast
|
|
|
|
import pytest
|
|
|
|
import gradio as gr
|
|
|
|
|
|
def within_range(time1, time2, delta=30):
|
|
return abs(time1 - time2) < delta
|
|
|
|
|
|
class TestDateTimePreprocess:
|
|
def test_preprocess_returns_none_for_empty_inputs(self):
|
|
"""None, empty string, and whitespace-only all return None."""
|
|
dt = gr.DateTime(timezone="US/Pacific")
|
|
assert dt.preprocess(None) is None
|
|
assert dt.preprocess("") is None
|
|
# Bug fix: whitespace-only used to raise ValueError
|
|
assert dt.preprocess(" ") is None
|
|
assert dt.preprocess("\t") is None
|
|
|
|
def test_preprocess_timestamp_type_with_timezone(self):
|
|
"""Date strings are localized to the given timezone before converting to epoch."""
|
|
dt_pacific = gr.DateTime(timezone="US/Pacific")
|
|
dt_paris = gr.DateTime(timezone="Europe/Paris")
|
|
dt_paris_date_only = gr.DateTime(timezone="Europe/Paris", include_time=False)
|
|
|
|
assert dt_pacific.preprocess("2020-02-01 08:10:25") == 1580573425.0
|
|
assert dt_paris.preprocess("2020-02-01 08:10:25") == 1580541025.0
|
|
assert dt_paris_date_only.preprocess("2020-02-01") == 1580511600.0
|
|
|
|
def test_preprocess_timestamp_type_no_timezone(self):
|
|
"""Without a timezone the result is a float (local-time interpretation)."""
|
|
dt = gr.DateTime()
|
|
result = dt.preprocess("2020-02-01 08:10:25")
|
|
assert isinstance(result, float)
|
|
|
|
def test_preprocess_datetime_type_returns_datetime_object(self):
|
|
"""type='datetime' returns a Python datetime for both a date string and 'now'."""
|
|
dt = gr.DateTime(type="datetime", timezone="US/Pacific")
|
|
result = dt.preprocess("2020-02-01 08:10:25")
|
|
assert isinstance(result, datetime)
|
|
assert result.year == 2020
|
|
assert result.month == 2
|
|
assert result.day == 1
|
|
assert result.hour == 8
|
|
assert result.minute == 10
|
|
assert result.second == 25
|
|
|
|
result_now = dt.preprocess("now")
|
|
assert isinstance(result_now, datetime)
|
|
assert within_range(result_now.timestamp(), datetime.now().timestamp(), delta=5)
|
|
|
|
def test_preprocess_string_type_passthrough(self):
|
|
"""type='string' returns the raw string for non-'now' values."""
|
|
dt = gr.DateTime(type="string", timezone="US/Pacific")
|
|
assert dt.preprocess("2020-02-01 08:10:25") == "2020-02-01 08:10:25"
|
|
|
|
def test_preprocess_string_type_now_returns_formatted_string(self):
|
|
"""type='string' with 'now' patterns returns a formatted datetime string."""
|
|
dt = gr.DateTime(type="string", timezone="US/Pacific")
|
|
result = dt.preprocess("now - 10m")
|
|
assert isinstance(result, str)
|
|
assert len(result) == 19 # "YYYY-MM-DD HH:MM:SS"
|
|
|
|
def test_preprocess_now_keyword(self):
|
|
"""'now' returns the current timestamp, within tolerance."""
|
|
dt = gr.DateTime(timezone="US/Pacific")
|
|
dt2 = gr.DateTime(timezone="Europe/Paris")
|
|
now = datetime.now().timestamp()
|
|
|
|
assert within_range(dt.preprocess("now"), now)
|
|
assert within_range(dt2.preprocess("now"), now)
|
|
|
|
def test_preprocess_now_minus_seconds(self):
|
|
dt = gr.DateTime(timezone="US/Pacific")
|
|
now = datetime.now().timestamp()
|
|
assert within_range(dt.preprocess("now - 20s"), now - 20)
|
|
# Use 60s (> default 30s tolerance) to verify the offset is real
|
|
assert not within_range(dt.preprocess("now - 60s"), now)
|
|
|
|
def test_preprocess_now_minus_minutes(self):
|
|
dt = gr.DateTime(timezone="US/Pacific")
|
|
now = datetime.now().timestamp()
|
|
assert within_range(dt.preprocess("now - 10m"), now - 10 * 60)
|
|
assert not within_range(dt.preprocess("now - 1m"), now)
|
|
|
|
def test_preprocess_now_minus_hours(self):
|
|
dt = gr.DateTime(timezone="US/Pacific")
|
|
now = datetime.now().timestamp()
|
|
assert within_range(dt.preprocess("now - 3h"), now - 3 * 60 * 60)
|
|
|
|
def test_preprocess_now_minus_days(self):
|
|
dt = gr.DateTime(timezone="US/Pacific")
|
|
now = datetime.now().timestamp()
|
|
assert within_range(dt.preprocess("now - 12d"), now - 12 * 24 * 60 * 60)
|
|
|
|
def test_preprocess_invalid_now_raises(self):
|
|
"""Malformed 'now' expressions raise ValueError."""
|
|
dt = gr.DateTime(timezone="US/Pacific")
|
|
with pytest.raises(ValueError):
|
|
dt.preprocess("now + 5m")
|
|
with pytest.raises(ValueError):
|
|
dt.preprocess("now - abc")
|
|
|
|
|
|
class TestDateTimePostprocess:
|
|
def test_postprocess_none_returns_none(self):
|
|
dt = gr.DateTime(timezone="US/Pacific")
|
|
assert dt.postprocess(None) is None
|
|
|
|
def test_postprocess_datetime_object(self):
|
|
"""datetime objects are formatted using time_format (timezone ignored)."""
|
|
dt = gr.DateTime(timezone="US/Pacific")
|
|
result = dt.postprocess(datetime(2020, 2, 1, 8, 10, 25))
|
|
assert result == "2020-02-01 08:10:25"
|
|
|
|
def test_postprocess_datetime_object_date_only(self):
|
|
"""include_time=False formats without time component."""
|
|
dt = gr.DateTime(timezone="US/Pacific", include_time=False)
|
|
result = dt.postprocess(datetime(2020, 2, 1, 8, 10, 25))
|
|
assert result == "2020-02-01"
|
|
|
|
def test_postprocess_string_passthrough(self):
|
|
"""String values are returned unchanged."""
|
|
dt = gr.DateTime(timezone="US/Pacific")
|
|
assert dt.postprocess("2020-02-01 08:10:25") == "2020-02-01 08:10:25"
|
|
|
|
def test_postprocess_timestamp_with_pacific_timezone(self):
|
|
dt = gr.DateTime(timezone="US/Pacific")
|
|
assert dt.postprocess(1500000000) == "2017-07-13 19:40:00"
|
|
|
|
def test_postprocess_timestamp_with_paris_timezone(self):
|
|
dt = gr.DateTime(timezone="Europe/Paris")
|
|
assert dt.postprocess(1500000000) == "2017-07-14 04:40:00"
|
|
|
|
def test_postprocess_datetime_with_utc_timezone(self):
|
|
"""datetime objects with timezone info are formatted as-is."""
|
|
dt = gr.DateTime(type="datetime", timezone="US/Pacific")
|
|
utc_dt = datetime(2020, 2, 1, 8, 10, 25, tzinfo=timezone.utc)
|
|
result = dt.postprocess(utc_dt)
|
|
assert result == "2020-02-01 08:10:25"
|
|
|
|
|
|
class TestGetDatetimeFromStr:
|
|
def test_parses_regular_datetime_string(self):
|
|
dt = gr.DateTime(timezone="US/Pacific")
|
|
result = dt.get_datetime_from_str("2020-02-01 08:10:25")
|
|
assert result.year == 2020
|
|
assert result.month == 2
|
|
assert result.day == 1
|
|
assert result.hour == 8
|
|
assert result.minute == 10
|
|
assert result.second == 25
|
|
|
|
def test_parses_date_only_string(self):
|
|
dt = gr.DateTime(timezone="Europe/Paris", include_time=False)
|
|
result = dt.get_datetime_from_str("2020-02-01")
|
|
assert result.year == 2020
|
|
assert result.month == 2
|
|
assert result.day == 1
|
|
|
|
def test_applies_timezone_to_parsed_string(self):
|
|
import pytz
|
|
|
|
dt = gr.DateTime(timezone="US/Pacific")
|
|
result = dt.get_datetime_from_str("2020-02-01 08:10:25")
|
|
assert result.tzinfo is not None
|
|
assert cast(pytz.BaseTzInfo, result.tzinfo).zone == "US/Pacific"
|
|
|
|
def test_now_returns_current_time(self):
|
|
dt = gr.DateTime(timezone="US/Pacific")
|
|
result = dt.get_datetime_from_str("now")
|
|
assert within_range(result.timestamp(), datetime.now().timestamp(), delta=5)
|
|
|
|
def test_now_minus_seconds_delta(self):
|
|
dt = gr.DateTime(timezone="US/Pacific")
|
|
now = datetime.now().timestamp()
|
|
result = dt.get_datetime_from_str("now - 30s")
|
|
assert within_range(result.timestamp(), now - 30)
|
|
|
|
def test_now_minus_minutes_delta(self):
|
|
dt = gr.DateTime(timezone="US/Pacific")
|
|
now = datetime.now().timestamp()
|
|
result = dt.get_datetime_from_str("now - 5m")
|
|
assert within_range(result.timestamp(), now - 300)
|
|
|
|
def test_now_minus_hours_delta(self):
|
|
dt = gr.DateTime(timezone="US/Pacific")
|
|
now = datetime.now().timestamp()
|
|
result = dt.get_datetime_from_str("now - 2h")
|
|
assert within_range(result.timestamp(), now - 7200)
|
|
|
|
def test_now_minus_days_delta(self):
|
|
dt = gr.DateTime(timezone="US/Pacific")
|
|
now = datetime.now().timestamp()
|
|
result = dt.get_datetime_from_str("now - 7d")
|
|
assert within_range(result.timestamp(), now - 7 * 86400)
|
|
|
|
def test_invalid_now_format_raises(self):
|
|
dt = gr.DateTime(timezone="US/Pacific")
|
|
with pytest.raises(ValueError, match="Invalid 'now' time format"):
|
|
dt.get_datetime_from_str("now + 5m")
|
|
|
|
def test_now_no_space_around_minus(self):
|
|
"""'now-5m' (no spaces) is actually valid: the regex uses \\s* (zero or more spaces)."""
|
|
dt = gr.DateTime(timezone="US/Pacific")
|
|
now = datetime.now().timestamp()
|
|
# The regex allows zero spaces around '-', so "now-5m" works just like "now - 5m"
|
|
result = dt.get_datetime_from_str("now-5m")
|
|
assert within_range(result.timestamp(), now - 5 * 60)
|
|
|
|
|
|
class TestDateTimeApiInfo:
|
|
def test_api_info_with_time(self):
|
|
dt = gr.DateTime(include_time=True)
|
|
info = dt.api_info()
|
|
assert info["type"] == "string"
|
|
assert "YYYY-MM-DD HH:MM:SS" in info["description"]
|
|
|
|
def test_api_info_date_only(self):
|
|
dt = gr.DateTime(include_time=False)
|
|
info = dt.api_info()
|
|
assert info["type"] == "string"
|
|
assert "YYYY-MM-DD" in info["description"]
|
|
assert "HH:MM:SS" not in info["description"]
|
|
|
|
|
|
class TestDateTimeExampleValues:
|
|
def test_example_payload(self):
|
|
dt = gr.DateTime()
|
|
assert dt.example_payload() == "2020-10-01 05:20:15"
|
|
|
|
def test_example_value(self):
|
|
dt = gr.DateTime()
|
|
assert dt.example_value() == "2020-10-01 05:20:15"
|
|
|
|
def test_example_values_include_time_regardless_of_setting(self):
|
|
"""Discrepancy: example values always include time even when include_time=False."""
|
|
dt = gr.DateTime(include_time=False)
|
|
# These return the full datetime string regardless of include_time
|
|
assert dt.example_payload() == "2020-10-01 05:20:15"
|
|
assert dt.example_value() == "2020-10-01 05:20:15"
|
|
|
|
|
|
class TestDateTimeInterface:
|
|
def test_in_interface_timestamp_type(self):
|
|
dt = gr.DateTime(timezone="US/Pacific")
|
|
iface = gr.Interface(lambda x: x, dt, "number")
|
|
assert iface("2017-07-13 19:40:00") == 1500000000
|
|
|
|
def test_in_interface_datetime_type(self):
|
|
"""type='datetime' preprocess returns a datetime; Interface with 'text' output
|
|
then postprocesses it to a string (timezone-aware formatted string)."""
|
|
dt = gr.DateTime(type="datetime", timezone="US/Pacific")
|
|
iface = gr.Interface(lambda x: x, dt, "text")
|
|
result = iface("2020-02-01 08:10:25")
|
|
# The datetime is postprocessed by the text output, yielding a string
|
|
assert isinstance(result, str)
|
|
assert "2020-02-01" in result
|
|
|
|
def test_can_pass_datetime_value_on_construction(self):
|
|
"""datetime objects with timezone info are accepted as default values."""
|
|
gr.DateTime(
|
|
type="datetime",
|
|
value=datetime.now(tz=timezone.utc),
|
|
)
|