Source code for zhmcclient._utils

# Copyright 2016-2017 IBM Corp. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
Utility functions.
"""

from __future__ import absolute_import

import six
from collections import OrderedDict, Mapping, MutableSequence, Iterable
from datetime import datetime
import pytz

__all__ = ['datetime_from_timestamp', 'timestamp_from_datetime']


_EPOCH_DT = datetime(1970, 1, 1, 0, 0, 0, 0, pytz.utc)


def _indent(text, amount, ch=' '):
    """Return the indent text, where each line is indented by `amount`
    characters `ch`."""
    padding = amount * ch
    return ''.join(padding + line for line in text.splitlines(True))


def repr_text(text, indent):
    """Return a debug representation of a multi-line text (e.g. the result
    of another repr...() function)."""
    if text is None:
        return 'None'
    ret = _indent(text, amount=indent)
    return ret.lstrip(' ')


def repr_list(_list, indent):
    """Return a debug representation of a list or tuple."""
    # pprint represents lists and tuples in one row if possible. We want one
    # per row, so we iterate ourselves.
    if _list is None:
        return 'None'
    if isinstance(_list, MutableSequence):
        bm = '['
        em = ']'
    elif isinstance(_list, Iterable):
        bm = '('
        em = ')'
    else:
        raise TypeError("Object must be an iterable, but is a %s" %
                        type(_list))
    ret = bm + '\n'
    for value in _list:
        ret += _indent('%r,\n' % value, 2)
    ret += em
    ret = repr_text(ret, indent=indent)
    return ret.lstrip(' ')


def repr_dict(_dict, indent):
    """Return a debug representation of a dict or OrderedDict."""
    # pprint represents OrderedDict objects using the tuple init syntax,
    # which is not very readable. Therefore, dictionaries are iterated over.
    if _dict is None:
        return 'None'
    if not isinstance(_dict, Mapping):
        raise TypeError("Object must be a mapping, but is a %s" %
                        type(_dict))
    if isinstance(_dict, OrderedDict):
        kind = 'ordered'
        ret = '%s {\n' % kind  # non standard syntax for the kind indicator
        for key in six.iterkeys(_dict):
            value = _dict[key]
            ret += _indent('%r: %r,\n' % (key, value), 2)
    else:  # dict
        kind = 'sorted'
        ret = '%s {\n' % kind  # non standard syntax for the kind indicator
        for key in sorted(six.iterkeys(_dict)):
            value = _dict[key]
            ret += _indent('%r: %r,\n' % (key, value), 2)
    ret += '}'
    ret = repr_text(ret, indent=indent)
    return ret.lstrip(' ')


def repr_timestamp(timestamp):
    """Return a debug representation of an HMC timestamp number."""
    if timestamp is None:
        return 'None'
    dt = datetime_from_timestamp(timestamp)
    ret = "%d (%s)" % (timestamp,
                       dt.strftime('%Y-%m-%d %H:%M:%S.%f %Z'))
    return ret


def repr_manager(manager, indent):
    """Return a debug representation of a manager object."""
    return repr_text(repr(manager), indent=indent)


[docs]def datetime_from_timestamp(ts, tzinfo=pytz.utc): """ Convert an :term:`HMC timestamp number <timestamp>` into a :class:`~py:datetime.datetime` object. The resulting object will be timezone-aware and will be represented in the specified timezone, defaulting to UTC. The HMC timestamp number must be non-negative. This means the special timestamp value -1 cannot be represented as datetime and will cause ``ValueError`` to be raised. The date and time range supported by this function has the following bounds: * The upper bounds is determined by :attr:`py:datetime.datetime.max` and additional limitations, as follows: * 9999-12-31 23:59:59 UTC, for 32-bit and 64-bit CPython on Linux and OS-X. * 3001-01-01 07:59:59 UTC, for 32-bit and 64-bit CPython on Windows, due to a limitation in `gmtime() in Visual C++ <https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/gmtime-gmtime32-gmtime64>`_. * 2038-01-19 03:14:07 UTC, for some 32-bit Python implementations, due to the `Year 2038 problem <https://en.wikipedia.org/wiki/Year_2038_problem>`_. * The lower bounds is the UNIX epoch: 1970-01-01 00:00:00 UTC. Parameters: ts (:term:`timestamp`): Point in time as an HMC timestamp number. Must not be `None`. tzinfo (:class:`py:datetime.tzinfo`): Timezone in which the returned object will be represented. This may be any object derived from :class:`py:datetime.tzinfo`, including but not limited to objects returned by :func:`pytz.timezone`. Note that this parameter does not affect how the HMC timestamp value is interpreted; i.e. the effective point in time represented by the returned object is not affected. What is affected by this parameter is for example the timezone in which the point in time is shown when printing the returned object. Must not be `None`. Returns: :class:`~py:datetime.datetime`: Point in time as a timezone-aware Python datetime object for the specified timezone. Raises: ValueError """ # Note that in Python 2, "None < 0" is allowed and will return True, # therefore we do an extra check for None. if ts is None: raise ValueError("HMC timestamp value must not be None.") if ts < 0: raise ValueError( "Negative HMC timestamp value {} cannot be represented as " "datetime.".format(ts)) epoch_seconds = ts // 1000 delta_microseconds = ts % 1000 * 1000 if tzinfo is None: raise ValueError("Timezone must not be None.") try: dt = datetime.fromtimestamp(epoch_seconds, tzinfo) except (ValueError, OSError) as exc: raise ValueError(str(exc)) dt = dt.replace(microsecond=delta_microseconds) return dt
[docs]def timestamp_from_datetime(dt): """ Convert a :class:`~py:datetime.datetime` object into an :term:`HMC timestamp number <timestamp>`. The date and time range supported by this function has the following bounds: * The upper bounds is :attr:`py:datetime.datetime.max`, as follows: * 9999-12-31 23:59:59 UTC, for 32-bit and 64-bit CPython on Linux and OS-X. * 2038-01-19 03:14:07 UTC, for some 32-bit Python implementations, due to the `Year 2038 problem <https://en.wikipedia.org/wiki/Year_2038_problem>`_. * The lower bounds is the UNIX epoch: 1970-01-01 00:00:00 UTC. Parameters: dt (:class:`~py:datetime.datetime`): Point in time as a Python datetime object. The datetime object may be timezone-aware or timezone-naive. If timezone-naive, the UTC timezone is assumed. Must not be `None`. Returns: :term:`timestamp`: Point in time as an HMC timestamp number. Raises: ValueError """ if dt is None: raise ValueError("datetime value must not be None.") if dt.tzinfo is None: # Apply default timezone to the timezone-naive input dt = pytz.utc.localize(dt) epoch_seconds = (dt - _EPOCH_DT).total_seconds() ts = int(epoch_seconds * 1000) return ts