cardinal_pythonlib.django.fields.isodatetimetz
Original code copyright (C) 2009-2022 Rudolf Cardinal (rudolf@pobox.com).
This file is part of cardinal_pythonlib.
Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License. You may obtain a copy of the License at
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
Django field class implementing storage of a date/time, with its timezone, in a text field (in ISO-8601 format).
- class cardinal_pythonlib.django.fields.isodatetimetz.IsoDateTimeExact(*args, **kwargs)[source]
Lookup for a ‘=’ comparison where the LHS is a column containing an ISO-8601 field (and the RHS will be comparable to a
DATETIME
).
- class cardinal_pythonlib.django.fields.isodatetimetz.IsoDateTimeGreaterThan(*args, **kwargs)[source]
Lookup for a ‘>’ comparison where the LHS is a column containing an ISO-8601 field (and the RHS will be comparable to a
DATETIME
).
- class cardinal_pythonlib.django.fields.isodatetimetz.IsoDateTimeGreaterThanEqual(*args, **kwargs)[source]
Lookup for a ‘>=’ comparison where the LHS is a column containing an ISO-8601 field (and the RHS will be comparable to a
DATETIME
).
- class cardinal_pythonlib.django.fields.isodatetimetz.IsoDateTimeLessThan(*args, **kwargs)[source]
Lookup for a ‘<’ comparison where the LHS is a column containing an ISO-8601 field (and the RHS will be comparable to a
DATETIME
).
- class cardinal_pythonlib.django.fields.isodatetimetz.IsoDateTimeLessThanEqual(*args, **kwargs)[source]
Lookup for a ‘<=’ comparison where the LHS is a column containing an ISO-8601 field (and the RHS will be comparable to a
DATETIME
).
- class cardinal_pythonlib.django.fields.isodatetimetz.IsoDateTimeTzField(*args, **kwargs)[source]
Django field for date/time stored in ISO format. Microsecond resolution; timezone-aware. Example sent TO the database:
2015-11-11T22:21:37.000000+05:00 YYYY-MM-DD HH:MM:SS uuuuuu TZ:TZ 1234567890123456789012345678901234567890 ... so 32 chars
Will also accept FROM the database any ISO-8601 format accepted by
dateutil.parser.parse()
Python side:
datetime.datetime
The database-specific bits are tricky.
In SQLite, the
DATETIME()
andSTRFTIME()
functions can be used to convert ISO strings to UTC, so that’s a good basis for comparison. However, the actual comparisons, as always in SQLite, are string-based.DATETIME(...)
ends up like this:2015-11-11 22:21:37
STRFTIME('%Y-%m-%d %H:%M:%f', ...)
looks like this, allowing millisecond precision:2015-11-11 22:21:37.000 2015-11-11 22:21:37.123
The Django automatic RHS converter for datetime values looks like this:
2015-11-11 22:21:37 2015-11-11 22:21:37.123456
… https://stackoverflow.com/questions/14368290/django-datetimefield-and-timezone-now
… so a direct comparison with
DATETIME
works only if the microsecond component is zero (or you coerce it to zero viaget_db_prep_value
), and a direct comparison to theSTRFTIME
expression fails.Coercing the fractional seconds to zero via
get_db_prep_value()
would make sub-second comparisons meaningless in the database. So we should avoid that, and operate as close to the database resolution as possible.That means that we have to alter the RHS in the same way we altered the LHS, and that means we can’t use a transform… … oh, hang on, I’m just underestimating
get_db_prep_value()
; we .can coerce to exactly the thing yielded by SQLite’sSTRFTIME
, by .converting to a string!Next step: the data conversion specified for the RHS of a transform is governed by its
output_field
. So when we’re coercing to a date, we specify aDateField
here, and all is happy. But for adatetime
, we should NOT specifyDateTimeField
, because that will bypass the equality lookup from our custom field, and also bypass the RHS conversion (viaget_db_prep_value()
etc.).Specifically, for a
DateTimeField
, conversion to a database value is done bydjango/db/models/fields/__init__.py : DateTimeField.get_prep_value django/db/backends/{backend}/operations.py : DatabaseOperations.value_to_db_datetime - for sqlite3: calls str(value) [via six.texttype] and for a datetime, that gives 2015-11-17 00:00:00 # if microsecond == 0 2015-11-17 00:00:00.001000 # if microsecond != 0
So for a proper conversion, we need to convert SQLite stuff to that. The MySQL backend does the same thing, but MySQL has a concept of a datetime anyway.
For MySQL, see also:
Declare that we’re a
VARCHAR(32)
on the database side.- deconstruct() Tuple[str, str, List[Any], Dict[str, Any]] [source]
Takes an instance and calculates the arguments to pass to
__init__
to reconstruct it.
- from_db_value(value, expression, connection)[source]
Convert database value to Python value. Called when data is loaded from the database.
- get_db_prep_save(value, connection, prepared=False)[source]
Convert Python value to database value for SAVING. We save with full timezone information.
- get_db_prep_value(value, connection, prepared=False)[source]
Further conversion of Python value to database value for QUERYING. This follows
get_prep_value()
, and is for backend-specific stuff. See notes above.
- class cardinal_pythonlib.django.fields.isodatetimetz.IsoStringToSourceDate(*args, **kwargs)[source]
SQL expression: converts ISO-8601 field into DATE, using the date part of the local ISO time (not the UTC date).
- property output_field: Field
Return the output type of this expressions.
- class cardinal_pythonlib.django.fields.isodatetimetz.IsoStringToUtcDate(*args, **kwargs)[source]
SQL expression: converts ISO-8601 field into DATE, using the UTC date.
- property output_field: Field
Return the output type of this expressions.
- class cardinal_pythonlib.django.fields.isodatetimetz.IsoStringToUtcDateTime(*args, **kwargs)[source]
SQL expression: converts ISO-8601 field into UTC DATETIME.
- property output_field: Field
Return the output type of this expressions.
- cardinal_pythonlib.django.fields.isodatetimetz.iso_string_to_python_datetime(isostring: str) datetime | None [source]
Takes an ISO-8601 string and returns a
datetime
.
- cardinal_pythonlib.django.fields.isodatetimetz.iso_string_to_sql_date_mysql(x: str) str [source]
Provides MySQL SQL to convert an ISO-8601-format string (with punctuation) to a
DATE
, just by taking the date fields (without any timezone conversion). The argumentx
is the SQL expression to be converted (such as a column name).
- cardinal_pythonlib.django.fields.isodatetimetz.iso_string_to_sql_date_sqlite(x: str) str [source]
Provides SQLite SQL to convert a column to a
DATE
, just by taking the date fields (without any timezone conversion). The argumentx
is the SQL expression to be converted (such as a column name).
- cardinal_pythonlib.django.fields.isodatetimetz.iso_string_to_sql_utcdate_mysql(x: str) str [source]
Provides MySQL SQL to convert an ISO-8601-format string (with punctuation) to a
DATE
in UTC. The argumentx
is the SQL expression to be converted (such as a column name).
- cardinal_pythonlib.django.fields.isodatetimetz.iso_string_to_sql_utcdate_sqlite(x: str) str [source]
Provides SQLite SQL to convert a column to a
DATE
in UTC. The argumentx
is the SQL expression to be converted (such as a column name).
- cardinal_pythonlib.django.fields.isodatetimetz.iso_string_to_sql_utcdatetime_mysql(x: str) str [source]
Provides MySQL SQL to convert an ISO-8601-format string (with punctuation) to a
DATETIME
in UTC. The argumentx
is the SQL expression to be converted (such as a column name).
- cardinal_pythonlib.django.fields.isodatetimetz.iso_string_to_sql_utcdatetime_pythonformat_sqlite(x: str) str [source]
Provides SQLite SQL to convert a column to a
DATETIME
in UTC, in a string format that matches a common Python format. The argumentx
is the SQL expression to be converted (such as a column name).Output like
2015-11-14 18:52:47 2015-11-14 18:52:47.247000
i.e.
gets rid of trailing
.000
for zero milliseconds, appends trailing000
for everything else,… thus matching the output of Python’s
str(x)
wherex
is adatetime
.… thus matching the RHS of a Django default
datetime
comparison.
- cardinal_pythonlib.django.fields.isodatetimetz.iso_string_to_sql_utcdatetime_sqlite(x: str) str [source]
Provides SQLite SQL to convert a column to a
DATETIME
in UTC. The argumentx
is the SQL expression to be converted (such as a column name).Output like:
2015-11-14 18:52:47.000 2015-11-14 18:52:47.247
Internally, we don’t use
DATETIME()
; usingSTRFTIME()
allows millsecond precision.
- cardinal_pythonlib.django.fields.isodatetimetz.isodt_lookup_mysql(lookup, compiler, connection, operator) Tuple[str, Any] [source]
For a comparison “LHS op RHS”, transforms the LHS from a column containing an ISO-8601 date/time into an SQL
DATETIME
, for MySQL.
- cardinal_pythonlib.django.fields.isodatetimetz.isodt_lookup_sqlite(lookup, compiler, connection, operator) Tuple[str, Any] [source]
For a comparison “LHS op RHS”, transforms the LHS from a column containing an ISO-8601 date/time into an SQL
DATETIME
, for SQLite.
- cardinal_pythonlib.django.fields.isodatetimetz.python_localized_datetime_to_human_iso(value: datetime) str [source]
Converts a Python
datetime
that has a timezone to an ISO-8601 string with:
between the hours and minutes of the timezone.Example
>>> import datetime >>> x = datetime.datetime.now(datetime.timezone.utc) >>> python_localized_datetime_to_human_iso(x) '2017-08-21T20:47:18.952971+00:00'