Skip to content
Merged
60 changes: 60 additions & 0 deletions gcloud/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
This module is not part of the public API surface of `gcloud`.
"""

import calendar
import datetime
import os
import socket
Expand All @@ -36,6 +37,7 @@ class Local(object):
from gcloud.environment_vars import PROJECT


_NOW = datetime.datetime.utcnow # To be replaced by tests.
_RFC3339_MICROS = '%Y-%m-%dT%H:%M:%S.%fZ'


Expand Down Expand Up @@ -207,7 +209,65 @@ def _determine_default_project(project=None):
return project


def _millis(when):
"""Convert a zone-aware datetime to integer milliseconds.

:type when: :class:`datetime.datetime`
:param when: the datetime to convert

:rtype: integer
:returns: milliseconds since epoch for ``when``
"""
micros = _microseconds_from_datetime(when)
return micros // 1000


def _datetime_from_microseconds(value):
"""Convert timestamp to datetime, assuming UTC.

:type value: float
:param value: The timestamp to convert

:rtype: :class:`datetime.datetime`
:returns: The datetime object created from the value.
"""
return _EPOCH + datetime.timedelta(microseconds=value)


def _microseconds_from_datetime(value):
"""Convert non-none datetime to microseconds.

:type value: :class:`datetime.datetime`
:param value: The timestamp to convert.

:rtype: integer
:returns: The timestamp, in microseconds.
"""
if not value.tzinfo:
value = value.replace(tzinfo=UTC)
# Regardless of what timezone is on the value, convert it to UTC.
value = value.astimezone(UTC)
# Convert the datetime to a microsecond timestamp.
return int(calendar.timegm(value.timetuple()) * 1e6) + value.microsecond


def _millis_from_datetime(value):
"""Convert non-none datetime to timestamp, assuming UTC.

:type value: :class:`datetime.datetime`, or None
:param value: the timestamp

:rtype: integer, or ``NoneType``
:returns: the timestamp, in milliseconds, or None
"""
if value is not None:
return _millis(value)


try:
from pytz import UTC # pylint: disable=unused-import
except ImportError:
UTC = _UTC() # Singleton instance to be used throughout.

# Need to define _EPOCH at the end of module since it relies on UTC.
_EPOCH = datetime.datetime.utcfromtimestamp(0).replace(tzinfo=UTC)
79 changes: 0 additions & 79 deletions gcloud/bigquery/_helpers.py

This file was deleted.

12 changes: 9 additions & 3 deletions gcloud/bigquery/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
"""Define API Datasets."""
import six

from gcloud._helpers import _datetime_from_microseconds
from gcloud.exceptions import NotFound
from gcloud.bigquery._helpers import _datetime_from_prop
from gcloud.bigquery.table import Table


Expand Down Expand Up @@ -114,7 +114,10 @@ def created(self):
:rtype: ``datetime.datetime``, or ``NoneType``
:returns: the creation time (None until set from the server).
"""
return _datetime_from_prop(self._properties.get('creationTime'))
creation_time = self._properties.get('creationTime')
if creation_time is not None:
# creation_time will be in milliseconds.
return _datetime_from_microseconds(1000.0 * creation_time)

@property
def dataset_id(self):
Expand All @@ -141,7 +144,10 @@ def modified(self):
:rtype: ``datetime.datetime``, or ``NoneType``
:returns: the modification time (None until set from the server).
"""
return _datetime_from_prop(self._properties.get('lastModifiedTime'))
modified_time = self._properties.get('lastModifiedTime')
if modified_time is not None:
# modified_time will be in milliseconds.
return _datetime_from_microseconds(1000.0 * modified_time)

@property
def self_link(self):
Expand Down
30 changes: 20 additions & 10 deletions gcloud/bigquery/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@

import six

from gcloud._helpers import _datetime_from_microseconds
from gcloud._helpers import _millis_from_datetime
from gcloud.exceptions import NotFound
from gcloud.bigquery._helpers import _datetime_from_prop
from gcloud.bigquery._helpers import _prop_from_datetime


_MARKER = object()
Expand Down Expand Up @@ -116,7 +116,10 @@ def created(self):
:rtype: ``datetime.datetime``, or ``NoneType``
:returns: the creation time (None until set from the server).
"""
return _datetime_from_prop(self._properties.get('creationTime'))
creation_time = self._properties.get('creationTime')
if creation_time is not None:
# creation_time will be in milliseconds.
return _datetime_from_microseconds(1000.0 * creation_time)

@property
def etag(self):
Expand All @@ -134,7 +137,10 @@ def modified(self):
:rtype: ``datetime.datetime``, or ``NoneType``
:returns: the modification time (None until set from the server).
"""
return _datetime_from_prop(self._properties.get('lastModifiedTime'))
modified_time = self._properties.get('lastModifiedTime')
if modified_time is not None:
# modified_time will be in milliseconds.
return _datetime_from_microseconds(1000.0 * modified_time)

@property
def num_bytes(self):
Expand Down Expand Up @@ -212,7 +218,10 @@ def expires(self):
:rtype: ``datetime.datetime``, or ``NoneType``
:returns: the expiration time, or None
"""
return _datetime_from_prop(self._properties.get('expirationTime'))
expiration_time = self._properties.get('expirationTime')
if expiration_time is not None:
# expiration_time will be in milliseconds.
return _datetime_from_microseconds(1000.0 * expiration_time)

@expires.setter
def expires(self, value):
Expand All @@ -223,7 +232,7 @@ def expires(self, value):
"""
if not isinstance(value, datetime.datetime) and value is not None:
raise ValueError("Pass a datetime, or None")
self._properties['expirationTime'] = _prop_from_datetime(value)
self._properties['expirationTime'] = _millis_from_datetime(value)

@property
def friendly_name(self):
Expand Down Expand Up @@ -405,7 +414,7 @@ def _build_resource(self):
resource['description'] = self.description

if self.expires is not None:
value = _prop_from_datetime(self.expires)
value = _millis_from_datetime(self.expires)
resource['expirationTime'] = value

if self.friendly_name is not None:
Expand Down Expand Up @@ -518,7 +527,7 @@ def patch(self,
if (not isinstance(expires, datetime.datetime) and
expires is not None):
raise ValueError("Pass a datetime, or None")
partial['expirationTime'] = _prop_from_datetime(expires)
partial['expirationTime'] = _millis_from_datetime(expires)

if description is not _MARKER:
partial['description'] = description
Expand Down Expand Up @@ -678,7 +687,7 @@ def insert_data(self,

for field, value in zip(self._schema, row):
if field.field_type == 'TIMESTAMP':
value = _prop_from_datetime(value)
value = _millis_from_datetime(value)
row_info[field.name] = value

info = {'json': row_info}
Expand Down Expand Up @@ -727,7 +736,8 @@ def _bool_from_json(value, field):

def _datetime_from_json(value, field):
if _not_null(value, field):
return _datetime_from_prop(float(value))
# Field value will be in milliseconds.
return _datetime_from_microseconds(1000.0 * float(value))


def _record_from_json(value, field):
Expand Down
103 changes: 0 additions & 103 deletions gcloud/bigquery/test__helpers.py

This file was deleted.

Loading