Skip to content
Permalink
Browse files
fix: correctly decode times without microseconds (#375)
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
  • Loading branch information
tritone committed Feb 10, 2021
1 parent 921553c commit 37a1eb54095b4f857771784007dd049ffafbc11d
Showing with 28 additions and 13 deletions.
  1. +6 −6 google/cloud/storage/blob.py
  2. +4 −4 google/cloud/storage/bucket.py
  3. +3 −3 google/cloud/storage/hmac_key.py
  4. +15 −0 tests/system/test_system.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
@@ -3645,7 +3645,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):
@@ -3731,7 +3731,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):
@@ -3746,7 +3746,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):
@@ -3761,7 +3761,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):
@@ -3776,7 +3776,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):
@@ -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):
@@ -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):
@@ -1063,6 +1063,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"

0 comments on commit 37a1eb5

Please sign in to comment.