Repository: googleapis/python-spanner-django

Issue:
django: implement DatabaseOperations.datetime_cast_time_sql
Noticed while working on #262, when the tests crashed with a request for us to implement this method.

The signature of the function is https://github.com/django/django/blob/c1c361677d9400c8e2cdaddda0c16086bb358492/django/db/backends/base/operations.py#L125-L129
<img width="1204" alt="Screen Shot 2020-03-19 at 12 33 59 PM" src="https://user-images.githubusercontent.com/4898263/77107436-ee01b680-69dd-11ea-803d-4d43e0f35d6c.png">

It converts a DateTimeField to spanner.TIMESTAMP, yet we already save DateTimeField as spanner.TIMESTAMP, but with a timezone, so ideally this would require converting TIMESTAMP->TIMESTAMP in timezone.
However, Cloud Spanner doesn't have a way to convert between TIMESTAMPs and timezones, so we might need to do something like:
Convert from TIMESTAMP -> STRING -> TIMESTAMP with timezone
for example
```sql
TIMESTAMP(FORMAT_TIMESTAMP('%Y-%m-%d %R:%E9S %Z', %s), '%s') % (fieldname, timezone)
```
which preserves every element, provides fractional seconds to nano-second precision aka `%E9S` and also preserves the original time zone


Discussion:

Resolving patch:
diff --git a/django_spanner/features.py b/django_spanner/features.py
--- a/django_spanner/features.py
+++ b/django_spanner/features.py
@@ -163,36 +163,21 @@ class DatabaseFeatures(BaseDatabaseFeatures):
         'syndication_tests.tests.SyndicationFeedTest.test_latest_post_date',
         'syndication_tests.tests.SyndicationFeedTest.test_rss091_feed',
         'syndication_tests.tests.SyndicationFeedTest.test_template_feed',
-        # can't use QuerySet.dates() on DateTimeField:
+        # DATE_TRUNC cannot be used on DateTimeField:
         # https://github.com/orijtech/spanner-orm/issues/182
         'backends.tests.DateQuotingTest.test_django_date_trunc',
         'dates.tests.DatesTests.test_dates_trunc_datetime_fields',
+        'db_functions.datetime.test_extract_trunc.DateFunctionTests.test_trunc_func',
         # datetimes retrieved from the database with the wrong hour when
         # USE_TZ = True: https://github.com/orijtech/spanner-orm/issues/193
         'datetimes.tests.DateTimesTests.test_21432',
+        'db_functions.datetime.test_extract_trunc.DateFunctionWithTimeZoneTests.test_trunc_func_with_timezone',
         'db_functions.datetime.test_extract_trunc.DateFunctionWithTimeZoneTests.test_trunc_timezone_applied_before_truncation',  # noqa
         # extract() with timezone not working as expected:
         # https://github.com/orijtech/spanner-orm/issues/191
         'timezones.tests.NewDatabaseTests.test_query_datetimes',
         # To be investigated: https://github.com/orijtech/spanner-orm/issues/135
         'admin_changelist.tests.ChangeListTests.test_multiuser_edit',
-        # Implement DatabaseOperations.datetime_cast_date_sql():
-        # https://github.com/orijtech/spanner-orm/issues/170
-        'db_functions.datetime.test_extract_trunc.DateFunctionTests.test_trunc_date_func',
-        'db_functions.datetime.test_extract_trunc.DateFunctionTests.test_trunc_date_none',
-        'db_functions.datetime.test_extract_trunc.DateFunctionTests.test_trunc_time_func',
-        'db_functions.datetime.test_extract_trunc.DateFunctionTests.test_trunc_time_none',
-        'expressions.tests.FieldTransformTests.test_multiple_transforms_in_values',
-        'model_fields.test_datetimefield.DateTimeFieldTests.test_lookup_date_with_use_tz',
-        'model_fields.test_datetimefield.DateTimeFieldTests.test_lookup_date_without_use_tz',
-        # Implement DatabaseOperations.time_trunc_sql():
-        # https://github.com/orijtech/spanner-orm/issues/262
-        'db_functions.datetime.test_extract_trunc.DateFunctionTests.test_trunc_func',
-        'db_functions.datetime.test_extract_trunc.DateFunctionTests.test_trunc_hour_func',
-        'db_functions.datetime.test_extract_trunc.DateFunctionTests.test_trunc_minute_func',
-        'db_functions.datetime.test_extract_trunc.DateFunctionTests.test_trunc_none',
-        'db_functions.datetime.test_extract_trunc.DateFunctionTests.test_trunc_second_func',
-        'db_functions.datetime.test_extract_trunc.DateFunctionWithTimeZoneTests.test_trunc_func_with_timezone',
         # Spanner's EXTRACT() 'week' differs from Django:
         # https://github.com/orijtech/spanner-orm/issues/263
         'db_functions.datetime.test_extract_trunc.DateFunctionTests.test_extract_func',
diff --git a/django_spanner/operations.py b/django_spanner/operations.py
--- a/django_spanner/operations.py
+++ b/django_spanner/operations.py
@@ -205,6 +205,20 @@ def datetime_trunc_sql(self, lookup_type, field_name, tzname):
             sql = 'TIMESTAMP_ADD(' + sql + ', INTERVAL 1 DAY)'
         return sql
 
+    def time_trunc_sql(self, lookup_type, field_name):
+        # https://cloud.google.com/spanner/docs/functions-and-operators#timestamp_trunc
+        return 'TIMESTAMP_TRUNC(%s, %s, "UTC")' % (field_name, lookup_type)
+
+    def datetime_cast_date_sql(self, field_name, tzname):
+        # https://cloud.google.com/spanner/docs/functions-and-operators#date
+        tzname = tzname if settings.USE_TZ else 'UTC'
+        return 'DATE(%s, "%s")' % (field_name, tzname)
+
+    def datetime_cast_time_sql(self, field_name, tzname):
+        tzname = tzname if settings.USE_TZ else 'UTC'
+        # Cloud Spanner doesn't have a function for converting TIMESTAMP to another time zone.
+        return "TIMESTAMP(FORMAT_TIMESTAMP('%%Y-%%m-%%d %%R:%%E9S %%Z', %s, '%s'))" % (field_name, tzname)
+
     def date_interval_sql(self, timedelta):
         return 'INTERVAL %s MICROSECOND' % duration_microseconds(timedelta)
 


Test patch: