Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Constrainted datetime based on timezone offset / UTCDateTime #8088

Open
4 of 13 tasks
bram-tv opened this issue Nov 11, 2023 · 5 comments · May be fixed by #8158
Open
4 of 13 tasks

Constrainted datetime based on timezone offset / UTCDateTime #8088

bram-tv opened this issue Nov 11, 2023 · 5 comments · May be fixed by #8158

Comments

@bram-tv
Copy link

bram-tv commented Nov 11, 2023

Initial Checks

  • I have searched Google & GitHub for similar requests and couldn't find anything
  • I have read and followed the docs and still think this feature is missing

Description

This is basically the same request as #4385 but that was closed as duplicate of #4330 (which is closed now too) but several things have changed since then so creating a new issue.

Summary of the request: being able to add a constraint on the timezone offset of an AwareDatetime.

Support for constraining the timezone offset was added in pydantic-core in May 2023 with
pydantic/pydantic-core#653

So far I can see two ways to achieve this:

  • Levering the tz_constraint option from pydantic-core by extending from AwareDatetime and overriding __get_pydantic_core_schema__,
Example code
from typing import Any
from datetime import datetime, timezone, timedelta
from pydantic import BaseModel, AwareDatetime
from pydantic_core import core_schema

# Load GetCoreSchemaHandler via try/except because it was made public in v2.4.0
try:
    from pydantic.annotated_handlers import GetCoreSchemaHandler
except:
    from pydantic._internal._annotated_handlers import GetCoreSchemaHandler


class UTCAwareDatetime(AwareDatetime):
    @classmethod
    def __get_pydantic_core_schema__(
        cls, source: type[Any], handler: GetCoreSchemaHandler
    ) -> core_schema.CoreSchema:
        if cls is source:
            # used directly as a type
            return core_schema.datetime_schema(tz_constraint=0)
        else:
            schema = handler(source)
            _check_annotated_type(schema["type"], "datetime", cls.__name__)
            schema["tz_constraint"] = 0
            return schema

    def __repr__(self) -> str:
        return "UTCAwareDatetime"


class TestModel(BaseModel):
    created_at: UTCAwareDatetime


now = datetime.now(tz=timezone(timedelta(seconds=1800)))

model = TestModel(created_at=now)
print(model.model_dump_json())
  • Using an AfterValidator to do the extra validation
Example code
from typing_extensions import Annotated
from datetime import datetime, timezone, timedelta
from pydantic import BaseModel, AwareDatetime
from pydantic.functional_validators import AfterValidator


def check_tz(v: datetime):
    assert v.utcoffset() == timedelta(seconds=0)
    return v


UTCAwareDatetime = Annotated[AwareDatetime, AfterValidator(check_tz)]


class TestModel(BaseModel):
    created_at: UTCAwareDatetime


now = datetime.now(tz=timezone(timedelta(seconds=1800)))

model = TestModel(created_at=now)
print(model.model_dump_json())

In both cases there is validation error (as expected), and the error from pydantic-core is superior.

Now back to the future request: Make it easier to use the tz_constraint option of pydantic-core to restrict the tz offset of a datetime.
Could be via:

  • an extra type? (UTCDatetime? RestrictedTzOffsetAwareDatetime?)
  • extend Field to support tz_constraint?
  • ...

Affected Components

@Viicos
Copy link
Contributor

Viicos commented Nov 12, 2023

I think we could make use of annotated-types' Timezone constraint. It might already be supported, but I can't find any reference in Pydantic docs

@samuelcolvin
Copy link
Member

I'm happy to add this via full support for the annotated-types type.

@bram-tv by the way, you can use custom errors to make the errors nicer.

@sydney-runkle
Copy link
Member

@bram-tv,

Any interest in opening a PR to add support for this?

@Zidane786
Copy link

Zidane786 commented Nov 16, 2023

I would appreciate it if @sydney-runkle , @bram-tv , @samuelcolvin could provide me with some context regarding the implementation of this Feature. Specifically, I am interested in understanding the various Cases that need to be covered and the desired outcomes we aim to achieve by implementing these constraint types.

@Zidane786 Zidane786 linked a pull request Nov 17, 2023 that will close this issue
5 tasks
@hirotasoshu
Copy link

hirotasoshu commented Nov 18, 2023

@Zidane786 @samuelcolvin @bram-tv It would be great if this constraint will support not only specifying one specific timezone, but also specifying a UTC offset range or a list of supported timezones. Example of use: postgres supports timezones with UTC offset from -12:00 to +13:00. If we pass a datetime with a utc offset that goes beyond this limit, we get an error. For myself, so far I've done something like this

from datetime import datetime, timedelta
from typing import Annotated

from pydantic import AfterValidator


def check_timezone_utcoffset_range(v: datetime) -> datetime:
    utc_offset = v.utcoffset()
    if utc_offset is not None and (
        utc_offset < timedelta(days=-1, seconds=43200)
        or utc_offset > timedelta(seconds=46800)
    ):
        raise ValueError("timezone utc offset should be in range from -12:00 to +13:00")

    return v


DatetimeRestrictedUtcOffset = Annotated[
    datetime, AfterValidator(check_timezone_utcoffset_range)
]

Another approach would be to add constraint support to pydantic with a specific timezone, and add to pydantic extra types a datetime type for postgres that verifies that the timezone is correct

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants