From d2e1fc697bb2f8125ce9cde1a45ed87ec5afddd3 Mon Sep 17 00:00:00 2001 From: Chris Cotter Date: Mon, 8 Feb 2021 22:29:19 -0500 Subject: [PATCH] fix: correctly decode times without microseconds Currently custom_time is not being decoded correctly if the value has a zero in the microseconds field. This fixes the issue for custom_time as well as elsewhere by replacing _datetime_to_rfc3339 with _rfc3339_nanos_to_datetime. Fixes #363 --- google/cloud/storage/blob.py | 12 ++++++------ google/cloud/storage/bucket.py | 8 ++++---- google/cloud/storage/hmac_key.py | 6 +++--- tests/system/test_system.py | 15 +++++++++++++++ 4 files changed, 28 insertions(+), 13 deletions(-) diff --git a/google/cloud/storage/blob.py b/google/cloud/storage/blob.py index f1cb5666b..fd3f98481 100644 --- a/google/cloud/storage/blob.py +++ b/google/cloud/storage/blob.py @@ -55,7 +55,7 @@ from google.cloud import exceptions from google.cloud._helpers import _bytes_to_unicode from google.cloud._helpers import _datetime_to_rfc3339 -from google.cloud._helpers import _rfc3339_to_datetime +from google.cloud._helpers import _rfc3339_nanos_to_datetime from google.cloud._helpers import _to_bytes from google.cloud.exceptions import NotFound from google.cloud.storage._helpers import _add_generation_match_parameters @@ -3644,7 +3644,7 @@ def retention_expiration_time(self): """ value = self._properties.get("retentionExpirationTime") if value is not None: - return _rfc3339_to_datetime(value) + return _rfc3339_nanos_to_datetime(value) @property def self_link(self): @@ -3730,7 +3730,7 @@ def time_deleted(self): """ value = self._properties.get("timeDeleted") if value is not None: - return _rfc3339_to_datetime(value) + return _rfc3339_nanos_to_datetime(value) @property def time_created(self): @@ -3745,7 +3745,7 @@ def time_created(self): """ value = self._properties.get("timeCreated") if value is not None: - return _rfc3339_to_datetime(value) + return _rfc3339_nanos_to_datetime(value) @property def updated(self): @@ -3760,7 +3760,7 @@ def updated(self): """ value = self._properties.get("updated") if value is not None: - return _rfc3339_to_datetime(value) + return _rfc3339_nanos_to_datetime(value) @property def custom_time(self): @@ -3775,7 +3775,7 @@ def custom_time(self): """ value = self._properties.get("customTime") if value is not None: - return _rfc3339_to_datetime(value) + return _rfc3339_nanos_to_datetime(value) @custom_time.setter def custom_time(self, value): diff --git a/google/cloud/storage/bucket.py b/google/cloud/storage/bucket.py index 31a188134..76715d835 100644 --- a/google/cloud/storage/bucket.py +++ b/google/cloud/storage/bucket.py @@ -28,7 +28,7 @@ from google.api_core import datetime_helpers from google.cloud._helpers import _datetime_to_rfc3339 from google.cloud._helpers import _NOW -from google.cloud._helpers import _rfc3339_to_datetime +from google.cloud._helpers import _rfc3339_nanos_to_datetime from google.cloud.exceptions import NotFound from google.api_core.iam import Policy from google.cloud.storage import _signing @@ -499,7 +499,7 @@ def uniform_bucket_level_access_locked_time(self): ubla = self.get("uniformBucketLevelAccess", {}) stamp = ubla.get("lockedTime") if stamp is not None: - stamp = _rfc3339_to_datetime(stamp) + stamp = _rfc3339_nanos_to_datetime(stamp) return stamp @property @@ -2556,7 +2556,7 @@ def retention_policy_effective_time(self): if policy is not None: timestamp = policy.get("effectiveTime") if timestamp is not None: - return _rfc3339_to_datetime(timestamp) + return _rfc3339_nanos_to_datetime(timestamp) @property def retention_policy_locked(self): @@ -2675,7 +2675,7 @@ def time_created(self): """ value = self._properties.get("timeCreated") if value is not None: - return _rfc3339_to_datetime(value) + return _rfc3339_nanos_to_datetime(value) @property def versioning_enabled(self): diff --git a/google/cloud/storage/hmac_key.py b/google/cloud/storage/hmac_key.py index 47ca33cfc..3fd49079e 100644 --- a/google/cloud/storage/hmac_key.py +++ b/google/cloud/storage/hmac_key.py @@ -13,7 +13,7 @@ # limitations under the License. from google.cloud.exceptions import NotFound -from google.cloud._helpers import _rfc3339_to_datetime +from google.cloud._helpers import _rfc3339_nanos_to_datetime from google.cloud.storage.constants import _DEFAULT_TIMEOUT from google.cloud.storage.retry import DEFAULT_RETRY @@ -151,7 +151,7 @@ def time_created(self): """ value = self._properties.get("timeCreated") if value is not None: - return _rfc3339_to_datetime(value) + return _rfc3339_nanos_to_datetime(value) @property def updated(self): @@ -164,7 +164,7 @@ def updated(self): """ value = self._properties.get("updated") if value is not None: - return _rfc3339_to_datetime(value) + return _rfc3339_nanos_to_datetime(value) @property def path(self): diff --git a/tests/system/test_system.py b/tests/system/test_system.py index ec05b0c72..4615c62e6 100644 --- a/tests/system/test_system.py +++ b/tests/system/test_system.py @@ -1040,6 +1040,21 @@ def test_upload_blob_custom_time(self): custom_time = same_blob.custom_time.replace(tzinfo=None) self.assertEqual(custom_time, current_time) + def test_blob_custom_time_no_micros(self): + # Test that timestamps without microseconds are treated correctly by + # custom_time encoding/decoding. + blob = self.bucket.blob("CustomTimeNoMicrosBlob") + file_contents = b"Hello World" + time_without_micros = datetime.datetime(2021, 2, 10, 12, 30) + blob.custom_time = time_without_micros + blob.upload_from_string(file_contents) + self.case_blobs_to_delete.append(blob) + + same_blob = self.bucket.blob(("CustomTimeNoMicrosBlob")) + same_blob.reload(projection="full") + custom_time = same_blob.custom_time.replace(tzinfo=None) + self.assertEqual(custom_time, time_without_micros) + def test_blob_crc32_md5_hash(self): blob = self.bucket.blob("MyBuffer") file_contents = b"Hello World"