From 7d6fe8e87536f94f72343e0869ca99c1a48403ba Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sun, 8 Feb 2026 02:20:27 +0000 Subject: [PATCH] fix: allow timezone-aware datetime in Block schema This change adds a validator to the `Block` schema to handle timezone-aware datetimes. It converts any aware datetime to UTC and then makes it naive, which is required by the `TIMESTAMP WITHOUT TIME ZONE` column in the database. This allows the API to accept ISO 8601 strings with 'Z' or other offsets, while maintaining compatibility with the existing naive datetime behavior. - Modified `server/app/schemas/block.py` to add `validate_timezone` validator. - Added `server/tests/schemas/test_block.py` to test the validator. Co-authored-by: kuu13580 <46004336+kuu13580@users.noreply.github.com> --- server/app/schemas/block.py | 13 ++++++- server/tests/schemas/test_block.py | 59 ++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 server/tests/schemas/test_block.py diff --git a/server/app/schemas/block.py b/server/app/schemas/block.py index c853718..8ad67e3 100644 --- a/server/app/schemas/block.py +++ b/server/app/schemas/block.py @@ -1,7 +1,7 @@ -from datetime import datetime +from datetime import datetime, timezone from enum import Enum -from pydantic import BaseModel, ConfigDict +from pydantic import BaseModel, ConfigDict, field_validator class BlockType(str, Enum): @@ -18,6 +18,15 @@ class BlockBase(BaseModel): block_type: BlockType # Use the Enum here transportation_type: str | None = None + @field_validator("start_time", "end_time") + @classmethod + def validate_timezone(cls, v: datetime | None) -> datetime | None: + if v is None: + return None + if v.tzinfo is not None: + v = v.astimezone(timezone.utc).replace(tzinfo=None) + return v + class BlockCreate(BlockBase): pass diff --git a/server/tests/schemas/test_block.py b/server/tests/schemas/test_block.py new file mode 100644 index 0000000..ecfb3cc --- /dev/null +++ b/server/tests/schemas/test_block.py @@ -0,0 +1,59 @@ +from datetime import datetime, timezone +from app.schemas.block import BlockCreate, BlockType + +def test_timezone_naive_input(): + # Naive string: 2023-01-01T10:00:00 + data = { + "title": "Naive", + "start_time": "2023-01-01T10:00:00", + "end_time": "2023-01-01T12:00:00", + "detail": "detail", + "block_type": "event", + } + block = BlockCreate(**data) + assert block.start_time.tzinfo is None + assert block.start_time == datetime(2023, 1, 1, 10, 0, 0) + assert block.end_time.tzinfo is None + assert block.end_time == datetime(2023, 1, 1, 12, 0, 0) + +def test_timezone_aware_utc_input(): + # Aware UTC string: 2023-01-01T10:00:00Z + data = { + "title": "Aware UTC", + "start_time": "2023-01-01T10:00:00Z", + "end_time": "2023-01-01T12:00:00Z", + "detail": "detail", + "block_type": "event", + } + block = BlockCreate(**data) + # Pydantic parses Z as UTC aware. The validator should convert to naive UTC. + assert block.start_time.tzinfo is None + assert block.start_time == datetime(2023, 1, 1, 10, 0, 0) + assert block.end_time.tzinfo is None + assert block.end_time == datetime(2023, 1, 1, 12, 0, 0) + +def test_timezone_aware_offset_input(): + # Aware Offset string: 2023-01-01T19:00:00+09:00 (JST) -> 10:00 UTC + data = { + "title": "Aware Offset", + "start_time": "2023-01-01T19:00:00+09:00", + "end_time": "2023-01-01T21:00:00+09:00", + "detail": "detail", + "block_type": "event", + } + block = BlockCreate(**data) + # The validator should convert to UTC and make naive. + assert block.start_time.tzinfo is None + assert block.start_time == datetime(2023, 1, 1, 10, 0, 0) + assert block.end_time.tzinfo is None + assert block.end_time == datetime(2023, 1, 1, 12, 0, 0) + +def test_optional_end_time(): + data = { + "title": "Optional End Time", + "start_time": "2023-01-01T10:00:00Z", + "detail": "detail", + "block_type": "event", + } + block = BlockCreate(**data) + assert block.end_time is None