Repository: tortoise/tortoise-orm

Issue:
TimeField is missing
**Is your feature request related to a problem? Please describe.**
In my case I am storing `time` in the database, not `datetime`, as I see there is no `TimeField`.
For the moment I will need to use some workaround, for example number of seconds and then convert it to time.

**Describe the solution you'd like**
Have `TimeField` natively

**Describe alternatives you've considered**
Store number of seconds and convert it to time for other calculations.

**Additional context**
No need.



Discussion:
coveralls: ## Pull Request Test Coverage Report for [Build 2011977606](https://coveralls.io/builds/47518735)

* **36** of **48**   **(75.0%)**  changed or added relevant lines in **3** files are covered.
* No unchanged relevant lines lost coverage.
* Overall coverage decreased (**-0.2%**) to **93.619%**

---

|  Changes Missing Coverage | Covered Lines | Changed/Added Lines | % |
| :-----|--------------|--------|---: |
| [tortoise/backends/sqlite/executor.py](https://coveralls.io/builds/47518735/source?filename=tortoise%2Fbackends%2Fsqlite%2Fexecutor.py#L65) | 5 | 10 | 50.0%
| [tortoise/fields/data.py](https://coveralls.io/builds/47518735/source?filename=tortoise%2Ffields%2Fdata.py#L396) | 27 | 34 | 79.41%
<!-- | **Total:** | **36** | **48** | **75.0%** | -->


|  Totals | [![Coverage Status](https://coveralls.io/builds/47518735/badge)](https://coveralls.io/builds/47518735) |
| :-- | --: |
| Change from base [Build 2010845837](https://coveralls.io/builds/47516155): |  -0.2% |
| Covered Lines: | 5259 |
| Relevant Lines: | 5550 |

---
##### 💛  - [Coveralls](https://coveralls.io)


Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 86372f6..5928234 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -16,17 +16,18 @@ Added
 - Added a new unified and robust connection management interface to access DB connections which includes support for
   lazy connection creation and much more. For more details,
   check out this `PR <https://github.com/tortoise/tortoise-orm/pull/1001>`_
+- Added `TimeField`. (#1054)
 Fixed
 ^^^^^
 - Fix `bulk_create` doesn't work correctly with more than 1 update_fields. (#1046)
 - Fix `bulk_update` errors when setting null for a smallint column on postgres. (#1086)
 Deprecated
-^^^^^
+^^^^^^^^^^
 - Existing connection management interface and related public APIs which are deprecated:
  - `Tortoise.get_connection`
  - `Tortoise.close_connections`
 Changed
-^^^^^
+^^^^^^^
 - Refactored `tortoise.transactions.get_connection` method to `tortoise.transactions._get_connection`.
  Note that this method has now been marked **private to this module and is not part of the public API**
 
diff --git a/docs/timezone.rst b/docs/timezone.rst
index d95bc6a..444b240 100644
--- a/docs/timezone.rst
+++ b/docs/timezone.rst
@@ -11,10 +11,11 @@ The design of timezone is inspired by `Django` but also has differences. There a
 use_tz
 ------
 When set `use_tz = True`, `tortoise` will always store `UTC` time in database no matter what `timezone` set. And `MySQL` use field type `DATETIME(6)`, `PostgreSQL` use `TIMESTAMPTZ`, `SQLite` use `TIMESTAMP` when generate schema.
+For `TimeField`, `MySQL` use `TIME(6)`, `PostgreSQL` use `TIMETZ` and `SQLite` use `TIME`.
 
 timezone
 --------
-The `timezone` determine what `timezone` is when select datetime field from database, no matter what `timezone` your database is. And you should use `tortoise.timezone.now()` get aware time instead of native time `datetime.datetime.now()`.
+The `timezone` determine what `timezone` is when select `DateTimeField` and `TimeField` from database, no matter what `timezone` your database is. And you should use `tortoise.timezone.now()` get aware time instead of native time `datetime.datetime.now()`.
 
 Reference
 =========
@@ -22,4 +23,3 @@ Reference
 .. automodule:: tortoise.timezone
     :members:
     :undoc-members:
-
diff --git a/poetry.lock b/poetry.lock
index b7ee88c..2a9fb85 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -117,11 +117,18 @@ typing-extensions = {version = ">=3.6.5", markers = "python_version < \"3.8\""}
 
 [[package]]
 name = "asyncmy"
-version = "0.2.3"
+version = "0.2.4"
 description = "A fast asyncio MySQL driver"
 category = "main"
-optional = true
-python-versions = ">=3.7,<4.0"
+optional = false
+python-versions = "^3.7"
+develop = false
+
+[package.source]
+type = "git"
+url = "https://github.com/long2ice/asyncmy.git"
+reference = "dev"
+resolved_reference = "295fff234b3646458849b8554edfbd7b10df320e"
 
 [[package]]
 name = "asyncpg"
@@ -1047,15 +1054,22 @@ diagrams = ["jinja2", "railroad-diagrams"]
 
 [[package]]
 name = "pypika-tortoise"
-version = "0.1.3"
+version = "0.1.4"
 description = "Forked from pypika and streamline just for tortoise-orm"
 category = "main"
 optional = false
-python-versions = ">=3.7,<4.0"
+python-versions = "^3.7"
+develop = false
+
+[package.source]
+type = "git"
+url = "https://github.com/tortoise/pypika-tortoise.git"
+reference = "main"
+resolved_reference = "eaaa892ee13286d441b90a12374ad3f4cabc31a4"
 
 [[package]]
 name = "pytest"
-version = "7.1.0"
+version = "7.1.1"
 description = "pytest: simple powerful testing with Python"
 category = "dev"
 optional = false
@@ -1148,7 +1162,7 @@ unidecode = ["Unidecode (>=1.1.1)"]
 
 [[package]]
 name = "pytz"
-version = "2021.3"
+version = "2022.1"
 description = "World timezone definitions, modern and historical"
 category = "main"
 optional = false
@@ -1594,7 +1608,7 @@ python-versions = "*"
 
 [[package]]
 name = "types-pytz"
-version = "2021.3.5"
+version = "2021.3.6"
 description = "Typing stubs for pytz"
 category = "dev"
 optional = false
@@ -1618,7 +1632,7 @@ python-versions = ">=3.6"
 
 [[package]]
 name = "tzdata"
-version = "2021.5"
+version = "2022.1"
 description = "Provider of IANA time zone data"
 category = "main"
 optional = true
@@ -1747,7 +1761,7 @@ psycopg = ["psycopg"]
 [metadata]
 lock-version = "1.1"
 python-versions = "^3.7"
-content-hash = "386da46127a0edd90fb0fccdae7fd8b8c2b530a6838106686a8bd741cc79fe6d"
+content-hash = "64104c5e2eebc5415efd5e3a50ed2b82abd9cad077f9635818474bf0c4a294d9"
 
 [metadata.files]
 aiofiles = [
@@ -1856,21 +1870,7 @@ async-timeout = [
     {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"},
     {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"},
 ]
-asyncmy = [
-    {file = "asyncmy-0.2.3-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:c9dc38b56e3091d75ccda1ed598f90dac0489f4e6700bb3aae89b9a8b4635ad3"},
-    {file = "asyncmy-0.2.3-cp310-cp310-manylinux_2_31_x86_64.whl", hash = "sha256:38d6c2e658d13213ced685b5e41e35df1122174423b38f2bc27a6a4535de05b2"},
-    {file = "asyncmy-0.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:2b6dbfe7f4f7ffbcaa190f477f79361cbbc79248eeb05ee8c338b295b18a0119"},
-    {file = "asyncmy-0.2.3-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:28a534ceab5fbc2e20a29b2167c05c94f2cc1e2735a96fcc24b684890bb1109a"},
-    {file = "asyncmy-0.2.3-cp37-cp37m-manylinux_2_31_x86_64.whl", hash = "sha256:0c7db0d0b7c0f47fcb656c1db41f5845b3a5e7e83013264c83e943e53c4abb60"},
-    {file = "asyncmy-0.2.3-cp37-cp37m-win_amd64.whl", hash = "sha256:22ab5de34356a7331443d0076c47c78aedd3b1c41e1b6baf4017b2f207fd39d4"},
-    {file = "asyncmy-0.2.3-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:2b14ebd0c1e050a6ee0a314111527327fdb53fe490169af6ed9361a921fba2fa"},
-    {file = "asyncmy-0.2.3-cp38-cp38-manylinux_2_31_x86_64.whl", hash = "sha256:ef785957e480902d9b1a34f23e29157de84606bf758ddbfbabc1afe93f91ece3"},
-    {file = "asyncmy-0.2.3-cp38-cp38-win_amd64.whl", hash = "sha256:804fda2c96e16de5b4a7740a49cf21207fe6b856875608f28a75a486e4903b3d"},
-    {file = "asyncmy-0.2.3-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:4be0f01f79974adf7c064c3c23bd30cd24627c081a39a03461bd4fea0a59562b"},
-    {file = "asyncmy-0.2.3-cp39-cp39-manylinux_2_31_x86_64.whl", hash = "sha256:3167337d2e778b560fc109ec9d388562b4e9b0241e6d31ca2de15bf7437c781d"},
-    {file = "asyncmy-0.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:5577b695c38e01377e7ef4f5bd3877aada11d1174f80b6354be55a5447f9d1f8"},
-    {file = "asyncmy-0.2.3.tar.gz", hash = "sha256:769f749d8b03b28a31b185b77c54e8f8f363ee8a3c3719bad8a71e4e9a0ce8a6"},
-]
+asyncmy = []
 asyncpg = [
     {file = "asyncpg-0.25.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bf5e3408a14a17d480f36ebaf0401a12ff6ae5457fdf45e4e2775c51cc9517d3"},
     {file = "asyncpg-0.25.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2bc197fc4aca2fd24f60241057998124012469d2e414aed3f992579db0c88e3a"},
@@ -2814,13 +2814,10 @@ pyparsing = [
     {file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"},
     {file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"},
 ]
-pypika-tortoise = [
-    {file = "pypika-tortoise-0.1.3.tar.gz", hash = "sha256:ecdf2d6e0aeb0e15880d9e2ead41362ec7320f37fb25a3a71664c2e1105ad218"},
-    {file = "pypika_tortoise-0.1.3-py3-none-any.whl", hash = "sha256:28fb2715a94ff2f3bc1c4ef6cc46c385c244c27d100aac760231bf612361d5ba"},
-]
+pypika-tortoise = []
 pytest = [
-    {file = "pytest-7.1.0-py3-none-any.whl", hash = "sha256:b555252a95bbb2a37a97b5ac2eb050c436f7989993565f5e0c9128fcaacadd0e"},
-    {file = "pytest-7.1.0.tar.gz", hash = "sha256:f1089d218cfcc63a212c42896f1b7fbf096874d045e1988186861a1a87d27b47"},
+    {file = "pytest-7.1.1-py3-none-any.whl", hash = "sha256:92f723789a8fdd7180b6b06483874feca4c48a5c76968e03bb3e7f806a1869ea"},
+    {file = "pytest-7.1.1.tar.gz", hash = "sha256:841132caef6b1ad17a9afde46dc4f6cfa59a05f9555aae5151f73bdf2820ca63"},
 ]
 pytest-cov = [
     {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"},
@@ -2843,8 +2840,8 @@ python-slugify = [
     {file = "python_slugify-6.1.1-py2.py3-none-any.whl", hash = "sha256:8c0016b2d74503eb64761821612d58fcfc729493634b1eb0575d8f5b4aa1fbcf"},
 ]
 pytz = [
-    {file = "pytz-2021.3-py2.py3-none-any.whl", hash = "sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c"},
-    {file = "pytz-2021.3.tar.gz", hash = "sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326"},
+    {file = "pytz-2022.1-py2.py3-none-any.whl", hash = "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"},
+    {file = "pytz-2022.1.tar.gz", hash = "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7"},
 ]
 pywin32-ctypes = [
     {file = "pywin32-ctypes-0.2.0.tar.gz", hash = "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942"},
@@ -3036,8 +3033,8 @@ types-pymysql = [
     {file = "types_PyMySQL-1.0.14-py3-none-any.whl", hash = "sha256:65ce75e3f5cd30b569f5b05fc5945aac1e8175744f5fa1ff9ae518d7bc76458a"},
 ]
 types-pytz = [
-    {file = "types-pytz-2021.3.5.tar.gz", hash = "sha256:fef8de238ee95135952229a2a23bfb87bd63d5a6c8598106a46cfcf48f069ea8"},
-    {file = "types_pytz-2021.3.5-py3-none-any.whl", hash = "sha256:8831f689379ac9e2a62668157381379ed74b3702980e08e71f8673c179c4e3c7"},
+    {file = "types-pytz-2021.3.6.tar.gz", hash = "sha256:74547fd90d8d8ab4f1eedf3a344a7d186d97486973895f81221a712e1e2cd993"},
+    {file = "types_pytz-2021.3.6-py3-none-any.whl", hash = "sha256:6805c72d51118923c5bf98633c39593d5b464d2ab49a803440e2d7ab6b8920df"},
 ]
 types-pyyaml = [
     {file = "types-PyYAML-6.0.5.tar.gz", hash = "sha256:464e050914f3d1d83a8c038e1cf46da5cb96b7cd02eaa096bcaa03675edd8a2e"},
@@ -3048,8 +3045,8 @@ typing-extensions = [
     {file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"},
 ]
 tzdata = [
-    {file = "tzdata-2021.5-py2.py3-none-any.whl", hash = "sha256:3eee491e22ebfe1e5cfcc97a4137cd70f092ce59144d81f8924a844de05ba8f5"},
-    {file = "tzdata-2021.5.tar.gz", hash = "sha256:68dbe41afd01b867894bbdfd54fa03f468cfa4f0086bfb4adcd8de8f24f3ee21"},
+    {file = "tzdata-2022.1-py2.py3-none-any.whl", hash = "sha256:238e70234214138ed7b4e8a0fab0e5e13872edab3be586ab8198c407620e2ab9"},
+    {file = "tzdata-2022.1.tar.gz", hash = "sha256:8b536a8ec63dc0751342b3984193a3118f8fca2afe25752bb9b7fffd398552d3"},
 ]
 ujson = [
     {file = "ujson-5.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:644552d1e89983c08d0c24358fbcb5829ae5b5deee9d876e16d20085cfa7dc81"},
diff --git a/pyproject.toml b/pyproject.toml
index 04553fb..459777e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -35,7 +35,7 @@ classifiers = [
 
 [tool.poetry.dependencies]
 python = "^3.7"
-pypika-tortoise = "^0.1.3"
+pypika-tortoise = { git = "https://github.com/tortoise/pypika-tortoise.git", branch = "main" }
 iso8601 = "^1.0.2"
 aiosqlite = ">=0.16.0, <0.18.0"
 pytz = "*"
@@ -44,8 +44,8 @@ uvloop = { version = "*", markers = "sys_platform != 'win32' and implementation_
 orjson = { version = "*", optional = true }
 asyncpg = { version = "*", optional = true }
 aiomysql = { version = "*", optional = true }
-asyncmy = { version = "*", optional = true }
-psycopg = {extras = ["pool", "binary"], version = "*", optional = true }
+asyncmy = { git = "https://github.com/long2ice/asyncmy.git", branch = "dev" }
+psycopg = { extras = ["pool", "binary"], version = "*", optional = true }
 
 [tool.poetry.dev-dependencies]
 # Linter tools
diff --git a/tortoise/backends/sqlite/executor.py b/tortoise/backends/sqlite/executor.py
index ef2c868..c1af223 100644
--- a/tortoise/backends/sqlite/executor.py
+++ b/tortoise/backends/sqlite/executor.py
@@ -14,6 +14,7 @@ from tortoise.fields import (
     DecimalField,
     IntField,
     SmallIntField,
+    TimeField,
 )
 
 
@@ -54,11 +55,30 @@ def to_db_datetime(
     return None
 
 
+def to_db_time(
+    self: TimeField, value: Optional[datetime.time], instance: Union[Type[Model], Model]
+) -> Optional[str]:
+    if hasattr(instance, "_saved_in_db") and (
+        self.auto_now
+        or (self.auto_now_add and getattr(instance, self.model_field_name, None) is None)
+    ):
+        if timezone.get_use_tz():
+            value = datetime.datetime.now(tz=pytz.utc).time()
+        else:
+            value = datetime.datetime.now(tz=timezone.get_default_timezone()).time()
+        setattr(instance, self.model_field_name, value)
+        return value.isoformat()
+    if isinstance(value, datetime.time):
+        return value.isoformat()
+    return None
+
+
 class SqliteExecutor(BaseExecutor):
     TO_DB_OVERRIDE = {
         fields.BooleanField: to_db_bool,
         fields.DecimalField: to_db_decimal,
         fields.DatetimeField: to_db_datetime,
+        fields.TimeField: to_db_time,
     }
     EXPLAIN_PREFIX = "EXPLAIN QUERY PLAN"
     DB_NATIVE = {bytes, str, int, float}
diff --git a/tortoise/fields/__init__.py b/tortoise/fields/__init__.py
index a31813a..cd6b94a 100644
--- a/tortoise/fields/__init__.py
+++ b/tortoise/fields/__init__.py
@@ -15,6 +15,7 @@ from tortoise.fields.data import (
     SmallIntField,
     TextField,
     TimeDeltaField,
+    TimeField,
     UUIDField,
 )
 from tortoise.fields.relational import (
@@ -44,6 +45,7 @@ __all__ = [
     "CharField",
     "DateField",
     "DatetimeField",
+    "TimeField",
     "DecimalField",
     "FloatField",
     "IntEnumField",
diff --git a/tortoise/fields/data.py b/tortoise/fields/data.py
index fa96c70..5019c33 100644
--- a/tortoise/fields/data.py
+++ b/tortoise/fields/data.py
@@ -14,7 +14,7 @@ from pypika.terms import Term
 from tortoise import timezone
 from tortoise.exceptions import ConfigurationError, FieldError
 from tortoise.fields.base import Field
-from tortoise.timezone import get_timezone, get_use_tz, localtime
+from tortoise.timezone import get_default_timezone, get_timezone, get_use_tz, localtime
 from tortoise.validators import MaxLengthValidator
 
 try:
@@ -368,7 +368,6 @@ class DateField(Field, datetime.date):
     SQL_TYPE = "DATE"
 
     def to_python_value(self, value: Any) -> Optional[datetime.date]:
-
         if value is not None and not isinstance(value, datetime.date):
             value = parse_datetime(value).date()
         self.validate(value)
@@ -384,6 +383,61 @@ class DateField(Field, datetime.date):
         return value
 
 
+class TimeField(Field, datetime.time):
+    """
+    Time field.
+    """
+
+    skip_to_python_if_native = True
+    SQL_TYPE = "TIME"
+
+    def __init__(self, auto_now: bool = False, auto_now_add: bool = False, **kwargs: Any) -> None:
+        if auto_now_add and auto_now:
+            raise ConfigurationError("You can choose only 'auto_now' or 'auto_now_add'")
+        super().__init__(**kwargs)
+        self.auto_now = auto_now
+        self.auto_now_add = auto_now | auto_now_add
+
+    def to_python_value(self, value: Any) -> Optional[datetime.time]:
+        if value is not None:
+            if isinstance(value, str):
+                value = datetime.time.fromisoformat(value)
+            if timezone.is_naive(value):
+                value = value.replace(tzinfo=get_default_timezone())
+        self.validate(value)
+        return value
+
+    def to_db_value(
+        self, value: Optional[datetime.time], instance: "Union[Type[Model], Model]"
+    ) -> Optional[datetime.time]:
+
+        # Only do this if it is a Model instance, not class. Test for guaranteed instance var
+        if hasattr(instance, "_saved_in_db") and (
+            self.auto_now
+            or (self.auto_now_add and getattr(instance, self.model_field_name) is None)
+        ):
+            now = timezone.now().time()
+            setattr(instance, self.model_field_name, now)
+            return now
+        if value is not None:
+            if get_use_tz():
+                if timezone.is_naive(value):
+                    warnings.warn(
+                        "TimeField %s received a naive time (%s)"
+                        " while time zone support is active." % (self.model_field_name, value),
+                        RuntimeWarning,
+                    )
+                    value = value.replace(tzinfo=get_default_timezone())
+        self.validate(value)
+        return value
+
+    class _db_mysql:
+        SQL_TYPE = "TIME(6)"
+
+    class _db_postgres:
+        SQL_TYPE = "TIMETZ"
+
+
 class TimeDeltaField(Field, datetime.timedelta):
     """
     A field for storing time differences.
@@ -476,7 +530,7 @@ class JSONField(Field, dict, list):  # type: ignore
                 return self.decoder(value)
             except Exception:
                 raise FieldError(
-                    f"Value {value if isinstance(value,str) else value.decode()} is invalid json value."
+                    f"Value {value if isinstance(value, str) else value.decode()} is invalid json value."
                 )
 
         self.validate(value)
diff --git a/tortoise/models.py b/tortoise/models.py
index 0fd730d..1f5e903 100644
--- a/tortoise/models.py
+++ b/tortoise/models.py
@@ -1052,7 +1052,10 @@ class Model(metaclass=ModelMeta):
         db = using_db or cls._choose_db(True)
         async with in_transaction(connection_name=db.connection_name) as connection:
             try:
-                return await cls.filter(**kwargs).using_db(connection).get(), False
+                return (
+                    await cls.select_for_update().filter(**kwargs).using_db(connection).get(),
+                    False,
+                )
             except DoesNotExist:
                 try:
                     return await cls.create(using_db=connection, **defaults, **kwargs), True
diff --git a/tortoise/timezone.py b/tortoise/timezone.py
index 0be7b18..6b99816 100644
--- a/tortoise/timezone.py
+++ b/tortoise/timezone.py
@@ -1,6 +1,6 @@
 import os
-from datetime import datetime, tzinfo
-from typing import Optional
+from datetime import datetime, time, tzinfo
+from typing import Optional, Union
 
 import pytz
 
@@ -58,9 +58,9 @@ def localtime(value: Optional[datetime] = None, timezone: Optional[str] = None)
     return value.astimezone(tz)
 
 
-def is_aware(value: datetime) -> bool:
+def is_aware(value: Union[datetime, time]) -> bool:
     """
-    Determine if a given datetime.datetime is aware.
+    Determine if a given datetime.datetime or datetime.time is aware.
 
     The concept is defined in Python's docs:
     https://docs.python.org/library/datetime.html#datetime.tzinfo
@@ -71,9 +71,9 @@ def is_aware(value: datetime) -> bool:
     return value.utcoffset() is not None
 
 
-def is_naive(value: datetime) -> bool:
+def is_naive(value: Union[datetime, time]) -> bool:
     """
-    Determine if a given datetime.datetime is naive.
+    Determine if a given datetime.datetime or datetime.time is naive.
 
     The concept is defined in Python's docs:
     https://docs.python.org/library/datetime.html#datetime.tzinfo


Test patch:
diff --git a/tests/fields/test_time.py b/tests/fields/test_time.py
index b42521d..85fb2a9 100644
--- a/tests/fields/test_time.py
+++ b/tests/fields/test_time.py
@@ -1,5 +1,5 @@
 import os
-from datetime import date, datetime, timedelta
+from datetime import date, datetime, time, timedelta
 from time import sleep
 
 import pytz
@@ -148,6 +148,35 @@ class TestDatetimeFields(test.TestCase):
         os.environ["USE_TZ"] = old_use_tz
 
 
+class TestTimeFields(test.TestCase):
+    async def test_create(self):
+        now = timezone.now().timetz()
+        obj = await testmodels.TimeFields.create(time=now)
+        self.assertEqual(obj.time, now)
+
+    async def test_cast(self):
+        obj = await testmodels.TimeFields.create(time="21:00+00:00")
+        self.assertEqual(obj.time, time.fromisoformat("21:00+00:00"))
+
+    async def test_values(self):
+        now = timezone.now().timetz()
+        obj0 = await testmodels.TimeFields.create(time=now)
+        values = await testmodels.TimeFields.get(id=obj0.id).values("time")
+        self.assertEqual(values["time"], now)
+
+    async def test_values_list(self):
+        now = timezone.now().timetz()
+        obj0 = await testmodels.TimeFields.create(time=now)
+        values = await testmodels.TimeFields.get(id=obj0.id).values_list("time", flat=True)
+        self.assertEqual(values, now)
+
+    async def test_get(self):
+        now = timezone.now().timetz()
+        await testmodels.TimeFields.create(time=now)
+        obj = await testmodels.TimeFields.get(time=now)
+        self.assertEqual(obj.time, now)
+
+
 class TestDateFields(test.TestCase):
     async def test_empty(self):
         with self.assertRaises(IntegrityError):
diff --git a/tests/testmodels.py b/tests/testmodels.py
index a4a194d..1ddf71c 100644
--- a/tests/testmodels.py
+++ b/tests/testmodels.py
@@ -254,6 +254,12 @@ class DateFields(Model):
     date_null = fields.DateField(null=True)
 
 
+class TimeFields(Model):
+    id = fields.IntField(pk=True)
+    time = fields.TimeField()
+    time_null = fields.TimeField(null=True)
+
+
 class FloatFields(Model):
     id = fields.IntField(pk=True)
     floatnum = fields.FloatField()