Repository: googleapis/python-spanner-django

Issue:
django: Spanner 'week' truncates to Sunday but Django expects Monday
Example failures:
```
======================================================================
FAIL: test_related_model_traverse (dates.tests.DatesTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/tim/code/django/tests/dates/tests.py", line 61, in test_related_model_traverse
    datetime.date(2010, 7, 26),
AssertionError: Sequences differ: <QuerySet [datetime.date(2005, 7, 24), datetime.date(2010, 7, 25)]> != [datetime.date(2005, 7, 25), datetime.date(2010, 7, 26)]

First differing element 0:
datetime.date(2005, 7, 24)
datetime.date(2005, 7, 25)

- <QuerySet [datetime.date(2005, 7, 24), datetime.date(2010, 7, 25)]>
? ----------                         ^                           ^  -

+ [datetime.date(2005, 7, 25), datetime.date(2010, 7, 26)]
?                          ^                           ^

======================================================================
FAIL: test_related_model_traverse (datetimes.tests.DateTimesTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/tim/code/django/tests/datetimes/tests.py", line 59, in test_related_model_traverse
    datetime.datetime(2010, 7, 26),
AssertionError: Sequences differ: <QuerySet [datetime.datetime(2005, 7, 24,[41 chars] 0)]> != [datetime.datetime(2005, 7, 25, 0, 0), da[30 chars], 0)]

First differing element 0:
datetime.datetime(2005, 7, 24, 0, 0)
datetime.datetime(2005, 7, 25, 0, 0)

- <QuerySet [datetime.datetime(2005, 7, 24, 0, 0), datetime.datetime(2010, 7, 25, 0, 0)]>
? ----------                             ^                                     ^        -

+ [datetime.datetime(2005, 7, 25, 0, 0), datetime.datetime(2010, 7, 26, 0, 0)]
?                              ^                                     ^
```


Discussion:

Resolving patch:
diff --git a/spanner/django/features.py b/spanner/django/features.py
--- a/spanner/django/features.py
+++ b/spanner/django/features.py
@@ -45,6 +45,12 @@ 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:
+        # https://github.com/orijtech/spanner-orm/issues/182
+        'dates.tests.DatesTests.test_dates_trunc_datetime_fields',
+        # 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',
         # To be investigated: https://github.com/orijtech/spanner-orm/issues/135
         'admin_changelist.tests.ChangeListTests.test_multiuser_edit',
         # FailedPrecondition and AlreadyExists should be raised as IntegrityError:
diff --git a/spanner/django/operations.py b/spanner/django/operations.py
--- a/spanner/django/operations.py
+++ b/spanner/django/operations.py
@@ -147,12 +147,30 @@ def time_extract_sql(self, lookup_type, field_name):
 
     def date_trunc_sql(self, lookup_type, field_name):
         # https://cloud.google.com/spanner/docs/functions-and-operators#date_trunc
-        return 'DATE_TRUNC(%s, %s)' % (field_name, lookup_type)
+        if lookup_type == 'week':
+            # Spanner truncates to Sunday but Django expects Monday. First,
+            # subtract a day so that a Sunday will be truncated to the previous
+            # week...
+            field_name = 'DATE_SUB(' + field_name + ', INTERVAL 1 DAY)'
+        sql = 'DATE_TRUNC(%s, %s)' % (field_name, lookup_type)
+        if lookup_type == 'week':
+            # ...then add a day to get from Sunday to Monday.
+            sql = 'DATE_ADD(' + sql + ', INTERVAL 1 DAY)'
+        return sql
 
     def datetime_trunc_sql(self, lookup_type, field_name, tzname):
         # https://cloud.google.com/spanner/docs/functions-and-operators#timestamp_trunc
         tzname = self.connection.timezone if settings.USE_TZ else 'UTC'
-        return 'TIMESTAMP_TRUNC(%s, %s, "%s")' % (field_name, lookup_type, tzname)
+        if lookup_type == 'week':
+            # Spanner truncates to Sunday but Django expects Monday. First,
+            # subtract a day so that a Sunday will be truncated to the previous
+            # week...
+            field_name = 'TIMESTAMP_SUB(' + field_name + ', INTERVAL 1 DAY)'
+        sql = 'TIMESTAMP_TRUNC(%s, %s, "%s")' % (field_name, lookup_type, tzname)
+        if lookup_type == 'week':
+            # ...then add a day to get from Sunday to Monday.
+            sql = 'TIMESTAMP_ADD(' + sql + ', INTERVAL 1 DAY)'
+        return sql
 
     def lookup_cast(self, lookup_type, internal_type=None):
         # Cast text lookups to string to allow things like filter(x__contains=4)


Test patch: