Source code for vmware.vapi.bindings.datetime_helper
"""
Utility library for converting to/from datetime objects in Python Bindings
"""
__author__ = 'VMware, Inc'
__copyright__ = 'Copyright (c) 2015 VMware, Inc. All rights reserved.'
import logging
import re
from datetime import datetime, tzinfo, timedelta
from vmware.vapi.l10n.runtime import message_factory
from vmware.vapi.exception import CoreException
logger = logging.getLogger(__name__)
[docs]class DateTimeConverter(object):
"""
Helper class to convert to/from Python datetime strings to
datetime objects.
Datetime is represented as :class:`vmware.vapi.data.value.StringValue` in the
vAPI Runtime. Datetime is a primitive string that follows a subset of
ISO 8601. DateTime string represents a complete date plus hours, minutes,
seconds and a decimal fraction of a second:
YYYY-MM-DDThh:mm:ss.sssZ (e.g. 1878-03-03T19:20:30.000Z)
where:
YYYY = four-digit year (years BC are not supported;
0001 = 1 AD is the first valid year,
0000 = 1 BC is not allowed)
MM = two-digit month (01=January, ..., 12=December)
DD = two-digit day of month (01 through 31)
"T" = separator; appears literally in the string
hh = two digits of hour (00 through 23; 24 NOT allowed;
am/pm NOT allowed)
mm = two digits of minute (00 through 59)
ss = two digits of second (00 through 59)
sss = exactly three digits representing milliseconds
"Z" = UTC time zone designator; appears literally in the string
"""
_dt_pattern = \
'(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2}).(\\d{3})Z'
_dt_expr = re.compile(_dt_pattern)
@staticmethod
[docs] def convert_to_datetime(datetime_str):
"""
Parse ISO 8601 date time from string.
:type datetime_str: :class:`str`
:param datetime_str: Datetime in string representation that is in
YYYY-MM-DDThh:mm:ss.sssZ format
:rtype: :class:`datetime.datetime`
:return: Datetime object
"""
datetime_val = None
match = DateTimeConverter._dt_expr.match(datetime_str)
if not match or len(datetime_str) > 24:
msg = message_factory.get_message(
'vapi.bindings.typeconverter.datetime.deserialize.invalid.format',
datetime_str, DateTimeConverter._dt_pattern)
logger.debug(msg)
raise CoreException(msg)
year = int(match.group(1))
month = int(match.group(2))
if month < 1 or month > 12:
msg = message_factory.get_message(
'vapi.bindings.typeconverter.datetime.deserialize.month.invalid',
datetime_str)
logger.debug(msg)
raise CoreException(msg)
day = int(match.group(3))
if day < 1 or day > 31:
msg = message_factory.get_message(
'vapi.bindings.typeconverter.datetime.deserialize.day.invalid',
datetime_str)
logger.debug(msg)
raise CoreException(msg)
hour = int(match.group(4))
if hour > 23:
msg = message_factory.get_message(
'vapi.bindings.typeconverter.datetime.deserialize.hour.invalid',
datetime_str)
logger.debug(msg)
raise CoreException(msg)
minute = int(match.group(5))
if minute > 59:
msg = message_factory.get_message(
'vapi.bindings.typeconverter.datetime.deserialize.minute.invalid',
datetime_str)
logger.debug(msg)
raise CoreException(msg)
second = int(match.group(6))
if second > 59:
msg = message_factory.get_message(
'vapi.bindings.typeconverter.datetime.deserialize.second.invalid',
datetime_str)
logger.debug(msg)
raise CoreException(msg)
# Convert from millisecond to microsecond precision
microsecond = int('%s000' % match.group(7))
try:
datetime_val = datetime(year=year, month=month, day=day, hour=hour,
minute=minute, second=second,
microsecond=microsecond)
except Exception as err:
msg = message_factory.get_message(
'vapi.bindings.typeconverter.datetime.deserialize.invalid.time',
datetime_str, str(err))
logger.error(msg)
raise CoreException(msg)
return datetime_val
@staticmethod
[docs] def convert_from_datetime(datetime_obj):
"""
Convert from Python native datetime object to the datetime format in
vAPI Runtime i.e. YYYY-MM-DDThh:mm:ss.sssZ.
datetime objects returned by datetime.now() or datetime.utcnow() does
not contain any timezone information. The caller to this method should
only pass datetime objects that have time in UTC timezone.
datetime objects have microsecond precision but the vAPI datetime
string format has millisecond precision. The method will truncate the
microsecond to millisecond and won't do any rounding of the value.
:type datetime_obj: :class:`datetime.datetime`
:param datetime_obj: Datetime object with UTC time
:rtype: :class:`str`
:return: String representation of the input datetime object
"""
if datetime_obj.tzinfo:
# If tzinfo object is present, it should be in UTC timezone
# i.e. timedelta is 0
if datetime_obj.tzinfo.utcoffset(datetime_obj) != timedelta(0):
msg = message_factory.get_message(
'vapi.bindings.typeconverter.datetime.serialize.invalid.tz',
str(datetime_obj))
logger.error(msg)
raise CoreException(msg)
# Since it is UTC timezone, replacing it with None
# the output of isoformat() does not match vAPI runtime datetime string
# format if tzinfo is present in the datetime object
datetime_obj = datetime_obj.replace(tzinfo=None)
iso_str = datetime_obj.isoformat()
if datetime_obj.microsecond:
# datetime prints microseconds, reducing precision to milliseconds
iso_str = iso_str[:-3]
else:
# Python .isoformat does not print microsecond if it is 0
iso_str = '%s.000' % iso_str
return '%sZ' % iso_str # Adding Z to indicate it is UTC timezone
[docs]class UTC(tzinfo):
"""
tzinfo class for UTC timezone
"""
# pylint: disable=W0613
[docs] def utcoffset(self, dt):
return timedelta(0)
[docs] def tzname(self, dt):
return 'UTC'
[docs] def dst(self, dt):
return timedelta(0)
[docs]def convert_to_utc(dt):
"""
Convert a given datetime object to UTC timezone
"""
if dt:
return dt.astimezone(UTC())