# -*- coding: utf-8 -*-
"""
Module containing a UTC-based datetime class.
:copyright:
The ObsPy Development Team (devs@obspy.org)
:license:
GNU Lesser General Public License, Version 3
(https://www.gnu.org/copyleft/lesser.html)
"""
from __future__ import (absolute_import, division, print_function,
unicode_literals)
from future.builtins import * # NOQA @UnusedWildImport
from future.utils import native_str
import datetime
import math
import re
import sys
import time
import numpy as np
# Regular expression used in the init function of the UTCDateTime objects which
# is called a lot. Thus pre-compile it.
_YEAR0REGEX = re.compile(r"^(\d{1,3}[-/,])(.*)$")
TIMESTAMP0 = datetime.datetime(1970, 1, 1, 0, 0)
# XXX the strftime problem seems to be specific to Python < 3.2
# XXX so this can be removed after dropping Python 2 support
STRFTIME_MAPPING = (
('%Y', '04d', 'year', None),
('%m', '02d', 'month', None),
('%d', '02d', 'day', None),
('%H', '02d', 'hour', None),
('%M', '02d', 'minute', None),
('%S', '02d', 'second', None),
('%f', '06d', 'microsecond', None),
('%y', '02d', 'year', lambda x: x % 100),
)
[docs]class UTCDateTime(object):
"""
A UTC-based datetime object.
This datetime class is based on the POSIX time, a system for describing
instants in time, defined as the number of seconds elapsed since midnight
Coordinated Universal Time (UTC) of Thursday, January 1, 1970. Using a
single float timestamp allows higher precision as the default Python
:class:`datetime.datetime` class. It features the full `ISO8601:2004`_
specification and some additional string patterns during object
initialization.
:type args: int, float, str, :class:`datetime.datetime`, optional
:param args: The creation of a new `UTCDateTime` object depends from the
given input parameters. All possible options are summarized in the
`Examples`_ section below.
:type iso8601: bool, optional
:param iso8601: Enforce `ISO8601:2004`_ detection. Works only with a string
as first input argument.
:type precision: int, optional
:param precision: Sets the precision used by the rich comparison operators.
Defaults to ``6`` digits after the decimal point. See also `Precision`_
section below.
.. versionchanged:: 0.5.1
UTCDateTime is no longer based on Python's datetime.datetime class
instead uses timestamp as a single floating point value which allows
higher precision.
.. rubric:: Supported Operations
``UTCDateTime = UTCDateTime + delta``
Adds/removes ``delta`` seconds (given as int or float) to/from the
current ``UTCDateTime`` object and returns a new ``UTCDateTime``
object.
See also: :meth:`~obspy.core.utcdatetime.UTCDateTime.__add__`.
``delta = UTCDateTime - UTCDateTime``
Calculates the time difference in seconds between two ``UTCDateTime``
objects. The time difference is given as float data type and may also
contain a negative number.
See also: :meth:`~obspy.core.utcdatetime.UTCDateTime.__sub__`.
.. rubric:: _`Examples`
(1) Using a timestamp.
>>> UTCDateTime(0)
UTCDateTime(1970, 1, 1, 0, 0)
>>> UTCDateTime(1240561632)
UTCDateTime(2009, 4, 24, 8, 27, 12)
>>> UTCDateTime(1240561632.5)
UTCDateTime(2009, 4, 24, 8, 27, 12, 500000)
(2) Using a `ISO8601:2004`_ string. The detection may be enforced by
setting the ``iso8601`` parameter to True.
* Calendar date representation.
>>> UTCDateTime("2009-12-31T12:23:34.5")
UTCDateTime(2009, 12, 31, 12, 23, 34, 500000)
>>> UTCDateTime("20091231T122334.5") # compact
UTCDateTime(2009, 12, 31, 12, 23, 34, 500000)
>>> UTCDateTime("2009-12-31T12:23:34.5Z") # w/o time zone
UTCDateTime(2009, 12, 31, 12, 23, 34, 500000)
>>> UTCDateTime("2009-12-31T12:23:34+01:15") # w/ time zone
UTCDateTime(2009, 12, 31, 11, 8, 34)
* Ordinal date representation.
>>> UTCDateTime("2009-365T12:23:34.5")
UTCDateTime(2009, 12, 31, 12, 23, 34, 500000)
>>> UTCDateTime("2009365T122334.5") # compact
UTCDateTime(2009, 12, 31, 12, 23, 34, 500000)
>>> UTCDateTime("2009001", iso8601=True) # enforce ISO8601
UTCDateTime(2009, 1, 1, 0, 0)
* Week date representation.
>>> UTCDateTime("2009-W53-7T12:23:34.5")
UTCDateTime(2010, 1, 3, 12, 23, 34, 500000)
>>> UTCDateTime("2009W537T122334.5") # compact
UTCDateTime(2010, 1, 3, 12, 23, 34, 500000)
>>> UTCDateTime("2009W011", iso8601=True) # enforce ISO8601
UTCDateTime(2008, 12, 29, 0, 0)
(3) Using not ISO8601 compatible strings.
>>> UTCDateTime("1970-01-01 12:23:34")
UTCDateTime(1970, 1, 1, 12, 23, 34)
>>> UTCDateTime("1970,01,01,12:23:34")
UTCDateTime(1970, 1, 1, 12, 23, 34)
>>> UTCDateTime("1970,001,12:23:34")
UTCDateTime(1970, 1, 1, 12, 23, 34)
>>> UTCDateTime("20090701121212")
UTCDateTime(2009, 7, 1, 12, 12, 12)
>>> UTCDateTime("19700101")
UTCDateTime(1970, 1, 1, 0, 0)
>>> UTCDateTime("20110818_03:00:00")
UTCDateTime(2011, 8, 18, 3, 0)
>>> UTCDateTime("1970/01/17 12:23:34")
UTCDateTime(1970, 1, 17, 12, 23, 34)
(4) Using multiple arguments in the following order: `year, month,
day[, hour[, minute[, second[, microsecond]]]`. The year, month and day
arguments are required.
>>> UTCDateTime(1970, 1, 1)
UTCDateTime(1970, 1, 1, 0, 0)
>>> UTCDateTime(1970, 1, 1, 12, 23, 34, 123456)
UTCDateTime(1970, 1, 1, 12, 23, 34, 123456)
(5) Using the following keyword arguments: `year, month, day, julday, hour,
minute, second, microsecond`. Either the combination of year, month and
day, or year and Julian day are required.
>>> UTCDateTime(year=1970, month=1, day=1, minute=15, microsecond=20)
UTCDateTime(1970, 1, 1, 0, 15, 0, 20)
>>> UTCDateTime(year=2009, julday=234, hour=14, minute=13)
UTCDateTime(2009, 8, 22, 14, 13)
(6) Using a Python :class:`datetime.datetime` object.
>>> dt = datetime.datetime(2009, 5, 24, 8, 28, 12, 5001)
>>> UTCDateTime(dt)
UTCDateTime(2009, 5, 24, 8, 28, 12, 5001)
.. rubric:: _`Precision`
The :class:`UTCDateTime` class works with a default precision of ``6``
digits which effects the comparison of date/time values, e.g.:
>>> dt = UTCDateTime(0)
>>> dt2 = UTCDateTime(0.00001)
>>> dt3 = UTCDateTime(0.0000001)
>>> print(dt.precision)
6
>>> dt == dt2 # 5th digit is within current precision
False
>>> dt == dt3 # 7th digit will be neglected
True
You may change that behavior either by,
(1) using the ``precision`` keyword during object initialization:
>>> dt = UTCDateTime(0, precision=4)
>>> dt2 = UTCDateTime(0.00001, precision=4)
>>> print(dt.precision)
4
>>> dt == dt2
True
(2) or set it the class attribute ``DEFAULT_PRECISION`` for all new
:class:`UTCDateTime` objects using a monkey patch:
>>> UTCDateTime.DEFAULT_PRECISION = 4
>>> dt = UTCDateTime(0)
>>> dt2 = UTCDateTime(0.00001)
>>> print(dt.precision)
4
>>> dt == dt2
True
Don't forget to reset ``DEFAULT_PRECISION`` if not needed anymore!
>>> UTCDateTime.DEFAULT_PRECISION = 6
.. _ISO8601:2004: https://en.wikipedia.org/wiki/ISO_8601
"""
DEFAULT_PRECISION = 6
[docs] def __init__(self, *args, **kwargs):
"""
Creates a new UTCDateTime object.
"""
# set default precision
self.precision = kwargs.pop('precision', self.DEFAULT_PRECISION)
# set directly to nanoseconds if given
ns = kwargs.pop('ns', None)
if ns is not None:
self._ns = ns
return
# iso8601 flag
iso8601 = kwargs.pop('iso8601', False) is True
# check parameter
if len(args) == 0 and len(kwargs) == 0:
# use current date/time if no argument is given
self._from_timestamp(time.time())
return
elif len(args) == 1 and len(kwargs) == 0:
value = args[0]
if isinstance(value, UTCDateTime):
# ugly workaround to be able to unpickle UTCDateTime objects
# that were pickled on ObsPy <1.1
try:
self._ns = value._ns
except AttributeError:
# work around floating point accuracy/rounding issue on
# Py3.3, see
# https://travis-ci.org/obspy/obspy/jobs/208941376#L751
# timestamp is 1251073203.0399999618 so when converting to
# integer nanosecond based UTCDateTime this should be
# rounded to 1251073203040000 nanoseconds.. but on Py3.3 it
# ends up as 1251073203039999, so we manually set
# microseconds with correct rounding without artifacts from
# floating point precision. see #1664
timestamp_seconds = int(value.__dict__['timestamp'])
timestamp_microseconds = round(
(value.__dict__['timestamp'] % 1.0) * 1e6)
dt_ = datetime.datetime.utcfromtimestamp(timestamp_seconds)
dt_ = dt_.replace(microsecond=timestamp_microseconds)
self._from_datetime(dt_)
return
# check types
# The string instance check is mainly needed to not convert
# numpy strings as these can be converted to floats on
# numpy >= 1.14.
if not isinstance(value, (str, bytes)):
try:
# got a timestamp
self._from_timestamp(value.__float__())
return
except Exception:
pass
if isinstance(value, datetime.datetime):
# got a Python datetime.datetime object
self._from_datetime(value)
return
elif isinstance(value, datetime.date):
# got a Python datetime.date object
dt = datetime.datetime(value.year, value.month, value.day)
self._from_datetime(dt)
return
elif isinstance(value, (bytes, str)):
if not isinstance(value, (str, native_str)):
value = value.decode()
# got a string instance
value = value.strip()
# Raising in the case where the leading string is less than 4
# chars; linked to #2167
if re.match(_YEAR0REGEX, value):
raise ValueError(
"'%s' does not start with a 4 digit year" % value)
# check for ISO8601 date string
if value.count("T") == 1 or iso8601:
try:
self._from_iso8601_string(value)
return
except Exception:
if iso8601:
raise
# try to apply some standard patterns
value = value.replace('T', ' ')
value = value.replace('_', ' ')
value = value.replace('-', ' ')
value = value.replace(':', ' ')
value = value.replace(',', ' ')
value = value.replace('/', ' ')
value = value.replace('Z', ' ')
value = value.replace('W', ' ')
# check for ordinal date (julian date)
parts = value.split(' ')
# check for patterns
if len(parts) == 1 and len(value) == 7 and value.isdigit():
# looks like an compact ordinal date string
pattern = "%Y%j"
elif len(parts) > 1 and len(parts[1]) == 3 and \
parts[1].isdigit():
# looks like an ordinal date string
value = ''.join(parts)
if len(parts) > 2:
pattern = "%Y%j%H%M%S"
else:
pattern = "%Y%j"
else:
# some parts should have 2 digits
for i in range(1, min(len(parts), 6)):
if len(parts[i]) == 1:
parts[i] = '0' + parts[i]
value = ''.join(parts)
# fill missing elements with zeros
value += '0' * (14 - len(value))
pattern = "%Y%m%d%H%M%S"
ms = 0
if '.' in value:
parts = value.split('.')
value = parts[0].strip()
try:
ms = float('.' + parts[1].strip())
except Exception:
pass
# all parts should be digits now - here we filter unknown
# patterns and pass it directly to Python's datetime.datetime
if not ''.join(parts).isdigit():
dt = datetime.datetime(*args, **kwargs)
self._from_datetime(dt)
return
dt = datetime.datetime.strptime(value, pattern)
dt += datetime.timedelta(seconds=ms)
self._from_datetime(dt)
return
# check for ordinal/julian date kwargs
if 'julday' in kwargs:
try:
int(kwargs['julday'])
except (ValueError, TypeError):
msg = "Failed to convert 'julday' to int: {!s}".format(
kwargs['julday'])
raise TypeError(msg)
if not (1 <= int(kwargs['julday']) <= 366):
msg = "'julday' out of bounds: {!s}".format(kwargs['julday'])
raise ValueError(msg)
if 'year' in kwargs:
# year given as kwargs
year = kwargs['year']
elif len(args) == 1:
# year is first (and only) argument
year = args[0]
try:
temp = "%4d%03d" % (int(year),
int(kwargs['julday']))
dt = datetime.datetime.strptime(temp, '%Y%j')
except Exception:
pass
else:
kwargs['month'] = dt.month
kwargs['day'] = dt.day
kwargs.pop('julday')
# check if seconds are given as float value
if len(args) == 6 and isinstance(args[5], float):
_frac, _sec = math.modf(round(args[5], 6))
kwargs['second'] = int(_sec)
kwargs['microsecond'] = int(round(_frac * 1e6))
args = args[0:5]
dt = datetime.datetime(*args, **kwargs)
self._from_datetime(dt)
[docs] def _set(self, **kwargs):
"""
Sets current timestamp using kwargs.
"""
year = kwargs.get('year', self.year)
month = kwargs.get('month', self.month)
day = kwargs.get('day', self.day)
hour = kwargs.get('hour', self.hour)
minute = kwargs.get('minute', self.minute)
second = kwargs.get('second', self.second)
microsecond = kwargs.get('microsecond', self.microsecond)
julday = kwargs.get('julday', None)
if julday:
self._ns = UTCDateTime(year=year, julday=julday, hour=hour,
minute=minute, second=second,
microsecond=microsecond)._ns
else:
self._ns = UTCDateTime(year, month, day, hour, minute,
second, microsecond)._ns
[docs] def _get_ns(self):
return self.__ns
[docs] def _set_ns(self, value):
# allow setting numpy integer types..
if isinstance(value, np.integer):
value_ = int(value)
# ..and be paranoid and check that it's still the same value after
# type casting
if value_ != value:
msg = ('Numpy integer value ({!s}) changed during casting to '
'Python builtin integer ({!s}).').format(value, value_)
raise ValueError(msg)
value = value_
if not isinstance(value, int):
raise TypeError('nanoseconds must be set as int/long type')
self.__ns = value
_ns = property(_get_ns, _set_ns)
[docs] def _from_datetime(self, dt):
"""
Use Python datetime object to set current time.
:type dt: :class:`datetime.datetime`
:param dt: Python datetime object.
"""
# see datetime.timedelta.total_seconds
try:
td = (dt - TIMESTAMP0)
except TypeError:
td = (dt.replace(tzinfo=None) - dt.utcoffset()) - TIMESTAMP0
self._ns = \
(td.days * 86400 + td.seconds) * 10**9 + td.microseconds * 1000
[docs] def _from_timestamp(self, value):
"""
Use given timestamp to set current time.
:type value: int, float
:param value: Timestamp in seconds.
"""
self._ns = int(round(value * 10**9))
[docs] def _from_iso8601_string(self, value):
"""
Parses an ISO8601:2004 date time string.
"""
# remove trailing 'Z'
value = value.replace('Z', '')
# split between date and time
try:
(date, time) = value.split("T")
except Exception:
date = value
time = ""
# remove all hyphens in date
date = date.replace('-', '')
# remove colons in time
time = time.replace(':', '')
# guess date pattern
length_date = len(date)
if date.count('W') == 1 and length_date == 8:
# we got a week date: YYYYWwwD
# remove week indicator 'W'
date = date.replace('W', '')
date_pattern = "%Y%W%w"
year = int(date[0:4])
# [Www] is the week number prefixed by the letter 'W', from W01
# through W53.
# strpftime %W == Week number of the year (Monday as the first day
# of the week) as a decimal number [00,53]. All days in a new year
# preceding the first Monday are considered to be in week 0.
week = int(date[4:6]) - 1
# [D] is the weekday number, from 1 through 7, beginning with
# Monday and ending with Sunday.
# strpftime %w == Weekday as a decimal number [0(Sunday),6]
day = int(date[6])
if day == 7:
day = 0
date = "%04d%02d%1d" % (year, week, day)
elif length_date == 7 and date.isdigit() and value.count('-') != 2:
# we got a ordinal date: YYYYDDD
date_pattern = "%Y%j"
elif length_date == 8 and date.isdigit():
# we got a calendar date: YYYYMMDD
date_pattern = "%Y%m%d"
else:
raise ValueError("Wrong or incomplete ISO8601:2004 date format")
# check for time zone information
# note that the zone designator is the actual offset from UTC and
# does not include any information on daylight saving time
if time.count('+') == 1 and '+' in time[-6:]:
(time, tz) = time.rsplit('+')
delta = -1
elif time.count('-') == 1 and '-' in time[-6:]:
(time, tz) = time.rsplit('-')
delta = 1
else:
delta = 0
if delta:
while len(tz) < 3:
tz += '0'
delta = delta * (int(tz[0:2]) * 60 * 60 + int(tz[2:]) * 60)
# split microseconds
ms = 0
if '.' in time:
(time, ms) = time.split(".")
ms = float('0.' + ms.strip())
# guess time pattern
length_time = len(time)
if length_time == 6 and time.isdigit():
time_pattern = "%H%M%S"
elif length_time == 4 and time.isdigit():
time_pattern = "%H%M"
elif length_time == 2 and time.isdigit():
time_pattern = "%H"
elif length_time == 0:
time_pattern = ""
else:
raise ValueError("Wrong or incomplete ISO8601:2004 time format")
# parse patterns
dt = datetime.datetime.strptime(date + 'T' + time,
date_pattern + 'T' + time_pattern)
# add microseconds and eventually correct time zone
dt += datetime.timedelta(seconds=float(delta) + ms)
self._from_datetime(dt)
[docs] def _get_timestamp(self):
"""
Returns UTC timestamp in seconds.
:rtype: float
:return: Timestamp in seconds.
.. rubric:: Example
>>> dt = UTCDateTime(2008, 10, 1, 12, 30, 35, 123456)
>>> dt.timestamp
1222864235.123456
"""
return self._ns / 1e9
timestamp = property(_get_timestamp)
[docs] def __float__(self):
"""
Returns UTC timestamp in seconds.
:rtype: float
:return: Timestamp in seconds.
.. rubric:: Example
>>> dt = UTCDateTime(2008, 10, 1, 12, 30, 35, 123456)
>>> float(dt)
1222864235.123456
"""
return self.timestamp
[docs] def _get_datetime(self):
"""
Returns a Python datetime object.
:rtype: :class:`datetime.datetime`
:return: Python datetime object.
.. rubric:: Example
>>> dt = UTCDateTime(2008, 10, 1, 12, 30, 35, 45020)
>>> dt.datetime
datetime.datetime(2008, 10, 1, 12, 30, 35, 45020)
"""
# datetime.utcfromtimestamp will cut off but not round
# avoid through adding timedelta - also avoids the year 2038 problem
dt = datetime.timedelta(seconds=self._ns // 10**9,
microseconds=self._ns % 10**9 // 1000)
try:
return TIMESTAMP0 + dt
except OverflowError:
# for very large future / past dates
return datetime.datetime.utcfromtimestamp(self.timestamp)
datetime = property(_get_datetime)
[docs] def _get_date(self):
"""
Returns a Python date object..
:rtype: :class:`datetime.date`
:return: Python date object.
.. rubric:: Example
>>> dt = UTCDateTime(2008, 10, 1, 12, 30, 35, 45020)
>>> dt.date
datetime.date(2008, 10, 1)
"""
return self.datetime.date()
date = property(_get_date)
[docs] def _get_year(self):
"""
Returns year of the current UTCDateTime object.
:rtype: int
:return: Returns year as an integer.
.. rubric:: Example
>>> dt = UTCDateTime(2012, 2, 11)
>>> dt.year
2012
"""
return self.datetime.year
[docs] def _set_year(self, value):
"""
Sets year of current UTCDateTime object.
:param value: Year
:type value: int
.. rubric:: Example
>>> dt = UTCDateTime(2012, 2, 11, 10, 11, 12)
>>> dt.year = 2010
>>> dt
UTCDateTime(2010, 2, 11, 10, 11, 12)
"""
self._set(year=value)
year = property(_get_year, _set_year)
[docs] def _get_month(self):
"""
Returns month as an integer (January is 1, December is 12).
:rtype: int
:return: Returns month as an integer, where January is 1 and December
is 12.
.. rubric:: Example
>>> dt = UTCDateTime(2012, 2, 11)
>>> dt.month
2
"""
return self.datetime.month
[docs] def _set_month(self, value):
"""
Sets month of current UTCDateTime object.
:param value: Month
:type value: int
.. rubric:: Example
>>> dt = UTCDateTime(2012, 2, 11, 10, 11, 12)
>>> dt.month = 10
>>> dt
UTCDateTime(2012, 10, 11, 10, 11, 12)
"""
self._set(month=value)
month = property(_get_month, _set_month)
[docs] def _get_day(self):
"""
Returns day as an integer.
:rtype: int
:return: Returns day as an integer.
.. rubric:: Example
>>> dt = UTCDateTime(2012, 2, 11)
>>> dt.day
11
"""
return self.datetime.day
[docs] def _set_day(self, value):
"""
Sets day of current UTCDateTime object.
:param value: Day
:type value: int
.. rubric:: Example
>>> dt = UTCDateTime(2012, 2, 11, 10, 11, 12)
>>> dt.day = 20
>>> dt
UTCDateTime(2012, 2, 20, 10, 11, 12)
"""
self._set(day=value)
day = property(_get_day, _set_day)
[docs] def _get_weekday(self):
"""
Return the day of the week as an integer (Monday is 0, Sunday is 6).
:rtype: int
:return: Returns day of the week as an integer, where Monday is 0 and
Sunday is 6.
.. rubric:: Example
>>> dt = UTCDateTime(2008, 10, 1, 12, 30, 35, 45020)
>>> dt.weekday
2
"""
return self.datetime.weekday()
weekday = property(_get_weekday)
[docs] def _get_time(self):
"""
Returns a Python time object.
:rtype: :class:`datetime.time`
:return: Python time object.
.. rubric:: Example
>>> dt = UTCDateTime(2008, 10, 1, 12, 30, 35, 45020)
>>> dt.time
datetime.time(12, 30, 35, 45020)
"""
return self.datetime.time()
time = property(_get_time)
[docs] def _get_hour(self):
"""
Returns hour as an integer.
:rtype: int
:return: Returns hour as an integer.
.. rubric:: Example
>>> dt = UTCDateTime(2012, 2, 11, 10, 11, 12)
>>> dt.hour
10
"""
return self.datetime.hour
[docs] def _set_hour(self, value):
"""
Sets hours of current UTCDateTime object.
:param value: Hours
:type value: int
.. rubric:: Example
>>> dt = UTCDateTime(2012, 2, 11, 10, 11, 12)
>>> dt.hour = 20
>>> dt
UTCDateTime(2012, 2, 11, 20, 11, 12)
"""
self._set(hour=value)
hour = property(_get_hour, _set_hour)
[docs] def _get_minute(self):
"""
Returns minute as an integer.
:rtype: int
:return: Returns minute as an integer.
.. rubric:: Example
>>> dt = UTCDateTime(2012, 2, 11, 10, 11, 12)
>>> dt.minute
11
"""
return self.datetime.minute
[docs] def _set_minute(self, value):
"""
Sets minutes of current UTCDateTime object.
:param value: Minutes
:type value: int
.. rubric:: Example
>>> dt = UTCDateTime(2012, 2, 11, 10, 11, 12)
>>> dt.minute = 20
>>> dt
UTCDateTime(2012, 2, 11, 10, 20, 12)
"""
self._set(minute=value)
minute = property(_get_minute, _set_minute)
[docs] def _get_second(self):
"""
Returns seconds as an integer.
:rtype: int
:return: Returns seconds as an integer.
.. rubric:: Example
>>> dt = UTCDateTime(2012, 2, 11, 10, 11, 12)
>>> dt.second
12
"""
return self.datetime.second
[docs] def _set_second(self, value):
"""
Sets seconds of current UTCDateTime object.
:param value: Seconds
:type value: int
.. rubric:: Example
>>> dt = UTCDateTime(2012, 2, 11, 10, 11, 12)
>>> dt.second = 20
>>> dt
UTCDateTime(2012, 2, 11, 10, 11, 20)
"""
self._set(second=value)
second = property(_get_second, _set_second)
[docs] def _get_microsecond(self):
"""
Returns microseconds as an integer.
:rtype: int
:return: Returns microseconds as an integer.
.. rubric:: Example
>>> dt = UTCDateTime(2012, 2, 11, 10, 11, 12, 345234)
>>> dt.microsecond
345234
"""
return int(self._ns % 10**9 // 1000)
[docs] def _set_microsecond(self, value):
"""
Sets microseconds of current UTCDateTime object.
:param value: Microseconds
:type value: int
.. rubric:: Example
>>> dt = UTCDateTime(2012, 2, 11, 10, 11, 12, 345234)
>>> dt.microsecond = 999123
>>> dt
UTCDateTime(2012, 2, 11, 10, 11, 12, 999123)
"""
self._set(microsecond=value)
microsecond = property(_get_microsecond, _set_microsecond)
[docs] def _get_julday(self):
"""
Returns Julian day as an integer.
:rtype: int
:return: Julian day as an integer.
.. rubric:: Example
>>> dt = UTCDateTime(2008, 10, 1, 12, 30, 35, 45020)
>>> dt.julday
275
"""
return self.utctimetuple().tm_yday
[docs] def _set_julday(self, value):
"""
Sets Julian day of current UTCDateTime object.
:param value: Julian day
:type value: int
.. rubric:: Example
>>> dt = UTCDateTime(2008, 12, 5, 12, 30, 35, 45020)
>>> dt.julday = 275
>>> dt
UTCDateTime(2008, 10, 1, 12, 30, 35, 45020)
"""
self._set(julday=value)
julday = property(_get_julday, _set_julday)
[docs] def timetuple(self):
"""
Return a time.struct_time such as returned by time.localtime().
:rtype: time.struct_time
"""
return self.datetime.timetuple()
[docs] def utctimetuple(self):
"""
Return a time.struct_time of current UTCDateTime object.
:rtype: time.struct_time
"""
return self.datetime.utctimetuple()
[docs] def __add__(self, value):
"""
Adds seconds and microseconds to current UTCDateTime object.
:type value: int, float
:param value: Seconds to add
:rtype: :class:`~obspy.core.utcdatetime.UTCDateTime`
:return: New UTCDateTime object.
.. rubric:: Example
>>> dt = UTCDateTime(1970, 1, 1, 0, 0)
>>> dt + 2
UTCDateTime(1970, 1, 1, 0, 0, 2)
>>> UTCDateTime(1970, 1, 1, 0, 0) + 1.123456
UTCDateTime(1970, 1, 1, 0, 0, 1, 123456)
"""
if isinstance(value, datetime.timedelta):
# see datetime.timedelta.total_seconds
value = (value.microseconds + (value.seconds + value.days *
86400) * 10**6) / 1e6
elif isinstance(value, UTCDateTime):
msg = ("unsupported operand type(s) for +: 'UTCDateTime' and "
"'UTCDateTime'")
raise TypeError(msg)
return UTCDateTime(ns=self._ns + int(round(value * 1e9)))
[docs] def __sub__(self, value):
"""
Subtracts seconds and microseconds from current UTCDateTime object.
:type value: int, float or :class:`~obspy.core.utcdatetime.UTCDateTime`
:param value: Seconds or UTCDateTime object to subtract. Subtracting an
UTCDateTime objects results into a relative time span in seconds.
:rtype: :class:`~obspy.core.utcdatetime.UTCDateTime` or float
:return: New UTCDateTime object or relative time span in seconds.
.. rubric:: Example
>>> dt = UTCDateTime(1970, 1, 2, 0, 0)
>>> dt - 2
UTCDateTime(1970, 1, 1, 23, 59, 58)
>>> UTCDateTime(1970, 1, 2, 0, 0) - 1.123456
UTCDateTime(1970, 1, 1, 23, 59, 58, 876544)
>>> UTCDateTime(1970, 1, 2, 0, 0) - UTCDateTime(1970, 1, 1, 0, 0)
86400.0
"""
if isinstance(value, UTCDateTime):
return round((self._ns - value._ns) / 1e9, self.__precision)
elif isinstance(value, datetime.timedelta):
# see datetime.timedelta.total_seconds
value = (value.microseconds + (value.seconds + value.days *
86400) * 10**6) / 1e6
return UTCDateTime(ns=self._ns - int(round(value * 1e9)))
[docs] def __str__(self):
"""
Returns ISO8601 string representation from current UTCDateTime object.
:return: string
.. rubric:: Example
>>> dt = UTCDateTime(2008, 10, 1, 12, 30, 35, 45020)
>>> str(dt)
'2008-10-01T12:30:35.045020Z'
"""
dt = self.datetime
pattern = "%%.%dlf" % (self.precision)
ns = pattern % ((self._ns % 10**9) / 1e9)
return "%04d-%02d-%02dT%02d:%02d:%02d.%sZ" % (
dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second,
ns[2:self.precision + 2])
[docs] def _repr_pretty_(self, p, cycle): # @UnusedVariable
p.text(str(self))
[docs] def __unicode__(self):
"""
Returns ISO8601 unicode representation from current UTCDateTime object.
:return: string
.. rubric:: Example
>>> dt = UTCDateTime(2008, 10, 1, 12, 30, 35, 45020)
>>> dt.__unicode__()
'2008-10-01T12:30:35.045020Z'
"""
return str(self.__str__())
[docs] def __eq__(self, other):
"""
Rich comparison operator '=='.
.. rubric: Example
Comparing two UTCDateTime object will always compare timestamps rounded
to a precision of 6 digits by default.
>>> t1 = UTCDateTime(123.000000012)
>>> t2 = UTCDateTime(123.000000099)
>>> t1 == t2
True
But the actual timestamp differ
>>> t1.timestamp == t2.timestamp
False
Resetting the precision changes the behavior of the operator
>>> t1.precision = 11
>>> t1 == t2
False
"""
if isinstance(other, UTCDateTime):
return round((self._ns - other._ns) / 1e9, self.__precision) == 0
elif isinstance(other, float) or isinstance(other, int):
return round(self.timestamp - float(other), self.__precision) == 0
elif isinstance(other, datetime.datetime):
return self.datetime == other
return False
[docs] def __ne__(self, other):
"""
Rich comparison operator '!='.
.. rubric: Example
Comparing two UTCDateTime object will always compare timestamps rounded
to a precision of 6 digits by default.
>>> t1 = UTCDateTime(123.000000012)
>>> t2 = UTCDateTime(123.000000099)
>>> t1 != t2
False
But the actual timestamp differ
>>> t1.timestamp != t2.timestamp
True
Resetting the precision changes the behavior of the operator
>>> t1.precision = 11
>>> t1 != t2
True
"""
return not self.__eq__(other)
[docs] def __lt__(self, other):
"""
Rich comparison operator '<'.
.. rubric: Example
Comparing two UTCDateTime object will always compare timestamps rounded
to a precision of 6 digits by default.
>>> t1 = UTCDateTime(123.000000012)
>>> t2 = UTCDateTime(123.000000099)
>>> t1 < t2
False
But the actual timestamp differ
>>> t1.timestamp < t2.timestamp
True
Resetting the precision changes the behavior of the operator
>>> t1.precision = 11
>>> t1 < t2
True
"""
if isinstance(other, UTCDateTime):
return round((self._ns - other._ns) / 1e9, self.__precision) < 0
elif isinstance(other, float) or isinstance(other, int):
return round(self.timestamp - float(other), self.__precision) < 0
elif isinstance(other, datetime.datetime):
return self.datetime < other
return False
[docs] def __le__(self, other):
"""
Rich comparison operator '<='.
.. rubric: Example
Comparing two UTCDateTime object will always compare timestamps rounded
to a precision of 6 digits by default.
>>> t1 = UTCDateTime(123.000000099)
>>> t2 = UTCDateTime(123.000000012)
>>> t1 <= t2
True
But the actual timestamp differ
>>> t1.timestamp <= t2.timestamp
False
Resetting the precision changes the behavior of the operator
>>> t1.precision = 11
>>> t1 <= t2
False
"""
if isinstance(other, UTCDateTime):
return round((self._ns - other._ns) / 1e9, self.__precision) <= 0
elif isinstance(other, float) or isinstance(other, int):
return round(self.timestamp - float(other), self.__precision) <= 0
elif isinstance(other, datetime.datetime):
return self.datetime <= other
return False
[docs] def __gt__(self, other):
"""
Rich comparison operator '>'.
.. rubric: Example
Comparing two UTCDateTime object will always compare timestamps rounded
to a precision of 6 digits by default.
>>> t1 = UTCDateTime(123.000000099)
>>> t2 = UTCDateTime(123.000000012)
>>> t1 > t2
False
But the actual timestamp differ
>>> t1.timestamp > t2.timestamp
True
Resetting the precision changes the behavior of the operator
>>> t1.precision = 11
>>> t1 > t2
True
"""
if isinstance(other, UTCDateTime):
return round((self._ns - other._ns) / 1e9, self.__precision) > 0
elif isinstance(other, float) or isinstance(other, int):
return round(self.timestamp - float(other), self.__precision) > 0
elif isinstance(other, datetime.datetime):
return self.datetime > other
return False
[docs] def __ge__(self, other):
"""
Rich comparison operator '>='.
.. rubric: Example
Comparing two UTCDateTime object will always compare timestamps rounded
to a precision of 6 digits by default.
>>> t1 = UTCDateTime(123.000000012)
>>> t2 = UTCDateTime(123.000000099)
>>> t1 >= t2
True
But the actual timestamp differ
>>> t1.timestamp >= t2.timestamp
False
Resetting the precision changes the behavior of the operator
>>> t1.precision = 11
>>> t1 >= t2
False
"""
if isinstance(other, UTCDateTime):
return round((self._ns - other._ns) / 1e9, self.__precision) >= 0
elif isinstance(other, float) or isinstance(other, int):
return round(self.timestamp - float(other), self.__precision) >= 0
elif isinstance(other, datetime.datetime):
return self.datetime >= other
return False
[docs] def __repr__(self):
"""
Returns a representation of UTCDatetime object.
"""
return 'UTCDateTime' + self.datetime.__repr__()[17:]
[docs] def __abs__(self):
"""
Returns absolute timestamp value of the current UTCDateTime object.
"""
# needed for unittest.assertAlmostEqual tests on Linux
return abs(self.timestamp)
[docs] def __hash__(self):
"""
An object is hashable if it has a hash value which never changes
during its lifetime. As an UTCDateTime object may change over time,
it's not hashable. Use the :meth:`~UTCDateTime.datetime()` method to
generate a :class:`datetime.datetime` object for hashing. But be aware:
once the UTCDateTime object changes, the hash is not valid anymore.
"""
# explicitly flag it as unhashable
return None
[docs] def strftime(self, format):
"""
Return a string representing the date and time, controlled by an
explicit format string.
:type format: str
:param format: Format string.
:return: Formatted string representing the date and time.
Format codes referring to hours, minutes or seconds will see 0 values.
See methods :meth:`~datetime.datetime.strftime()` and
:meth:`~datetime.datetime.strptime()` for more information.
"""
# See https://bugs.python.org/issue32195
# This is an attempt to get consistent behavior across platforms.
if sys.version_info.major > 2 and sys.platform.startswith("linux"):
format = format.replace("%Y", "%04Y")
try:
ret = self.datetime.strftime(format)
# this is trying to work around strftime refusing to work with years
# <1900
# XXX this problem seems to be specific to Python < 3.2
# XXX so this can be removed after dropping Python 2 support
except ValueError as e:
# some other error? just raise it..
if 'the datetime strftime() methods require year' not in str(e):
raise
# otherwise, try to do replace all strftime '%' commands with
# simple string formatting
format_ = self._strftime_replacement(format)
# if there's still some strftime commands in there, we have a
# problem still ('%%' is a %-sign literal)
if '%' in format_.replace('%%', ''):
raise
ret = format_
return ret
[docs] def _strftime_replacement(self, strftime_string):
"""
Replace all simple, year-independent strftime commands
>>> t = UTCDateTime(1813, 10, 30, 12, 34, 56, 789012)
>>> print(t._strftime_replacement('"%Y-%m-%dT%H:%M:%S.%f %y"'))
"1813-10-30T12:34:56.789012 13"
"""
for strftime_key, format_spec, property_name, func in STRFTIME_MAPPING:
if strftime_key not in strftime_string:
continue
strftime_string = strftime_string.replace(
strftime_key, '{%s:%s}' % (property_name, format_spec))
replacement = getattr(self, property_name)
if func is not None:
replacement = func(replacement)
strftime_string = strftime_string.format(
**{property_name: replacement})
return strftime_string
[docs] @staticmethod
def strptime(date_string, format):
"""
Return a UTCDateTime corresponding to date_string, parsed according to
given format.
:type date_string: str
:param date_string: Date and time string.
:type format: str
:param format: Format string.
:return: :class:`~obspy.core.utcdatetime.UTCDateTime`
See methods :meth:`~datetime.datetime.strftime()` and
:meth:`~datetime.datetime.strptime()` for more information.
"""
return UTCDateTime(datetime.datetime.strptime(date_string, format))
[docs] def timetz(self):
"""
Return time object with same hour, minute, second, microsecond, and
tzinfo attributes. See also method :meth:`datetime.datetime.time()`.
.. rubric:: Example
>>> dt = UTCDateTime(2008, 10, 1, 12, 30, 35, 45020)
>>> dt.timetz()
datetime.time(12, 30, 35, 45020)
"""
return self.datetime.timetz()
[docs] def utcoffset(self):
"""
Returns None (to stay compatible with :class:`datetime.datetime`)
.. rubric:: Example
>>> dt = UTCDateTime(2008, 10, 1, 12, 30, 35, 45020)
>>> dt.utcoffset()
"""
return self.datetime.utcoffset()
[docs] def dst(self):
"""
Returns None (to stay compatible with :class:`datetime.datetime`)
.. rubric:: Example
>>> dt = UTCDateTime(2008, 10, 1, 12, 30, 35, 45020)
>>> dt.dst()
"""
return self.datetime.dst()
[docs] def tzname(self):
"""
Returns None (to stay compatible with :class:`datetime.datetime`)
.. rubric:: Example
>>> dt = UTCDateTime(2008, 10, 1, 12, 30, 35, 45020)
>>> dt.tzname()
"""
return self.datetime.tzname()
[docs] def ctime(self):
"""
Return a string representing the date and time.
.. rubric:: Example
>>> UTCDateTime(2002, 12, 4, 20, 30, 40).ctime()
'Wed Dec 4 20:30:40 2002'
"""
return self.datetime.ctime()
[docs] def isoweekday(self):
"""
Return the day of the week as an integer (Monday is 1, Sunday is 7).
:rtype: int
:return: Returns day of the week as an integer, where Monday is 1 and
Sunday is 7.
.. rubric:: Example
>>> dt = UTCDateTime(2008, 10, 1, 12, 30, 35, 45020)
>>> dt.isoweekday()
3
"""
return self.datetime.isoweekday()
[docs] def isocalendar(self):
"""
Returns a tuple containing (ISO year, ISO week number, ISO weekday).
:rtype: tuple of ints
:return: Returns a tuple containing ISO year, ISO week number and ISO
weekday.
.. rubric:: Example
>>> dt = UTCDateTime(2008, 10, 1, 12, 30, 35, 45020)
>>> dt.isocalendar()
(2008, 40, 3)
"""
return self.datetime.isocalendar()
[docs] def _get_precision(self):
"""
Returns precision of current UTCDateTime object.
:return: int
.. rubric:: Example
>>> dt = UTCDateTime()
>>> dt.precision
6
"""
return self.__precision
[docs] def _set_precision(self, value=6):
"""
Set precision of current UTCDateTime object.
:type value: int, optional
:param value: Precision value used by the rich comparison operators.
Defaults to ``6``.
.. rubric:: Example
(1) Default precision
>>> dt = UTCDateTime()
>>> dt.precision
6
(2) Set precision during initialization of UTCDateTime object.
>>> dt = UTCDateTime(precision=5)
>>> dt.precision
5
(3) Set precision for an existing UTCDateTime object.
>>> dt = UTCDateTime()
>>> dt.precision = 12
>>> dt.precision
12
"""
self.__precision = int(value)
precision = property(_get_precision, _set_precision)
[docs] def toordinal(self):
"""
Return proleptic Gregorian ordinal. January 1 of year 1 is day 1.
See :meth:`datetime.datetime.toordinal()`.
:return: int
.. rubric:: Example
>>> dt = UTCDateTime(2012, 1, 1)
>>> dt.toordinal()
734503
"""
return self.datetime.toordinal()
[docs] @staticmethod
def now():
"""
Returns current UTC datetime.
"""
return UTCDateTime()
[docs] @staticmethod
def utcnow():
"""
Returns current UTC datetime.
"""
return UTCDateTime()
[docs] def _get_hours_after_midnight(self):
"""
Calculate foating point hours after midnight.
>>> t = UTCDateTime("2015-09-27T03:16:12.123456Z")
>>> t._get_hours_after_midnight()
3.270034293333333
"""
timedelta = (
self.datetime -
self.datetime.replace(hour=0, minute=0, second=0, microsecond=0))
return timedelta.total_seconds() / 3600.0
@property
def matplotlib_date(self):
"""
Maplotlib date number representation.
Useful for plotting on matplotlib time-based axes, like created by e.g.
:meth:`obspy.core.stream.Stream.plot()`.
>>> t = UTCDateTime("2009-08-24T00:20:07.700000Z")
>>> t.matplotlib_date
733643.0139780092
:rtype: float
"""
from matplotlib.dates import date2num
return date2num(self.datetime)
if __name__ == '__main__':
import doctest
doctest.testmod(exclude_empty=True)