Source code for spacepy.time

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Time conversion, manipulation and implementation of Ticktock class


Examples:
=========

>>> import spacepy.time as spt
>>> import datetime as dt

Day of year calculations

>>> dts = spt.doy2date([2002]*4, range(186,190), dtobj=True)
>>> dts
[datetime.datetime(2002, 7, 5, 0, 0),
datetime.datetime(2002, 7, 6, 0, 0),
datetime.datetime(2002, 7, 7, 0, 0),
datetime.datetime(2002, 7, 8, 0, 0)]

>>> dts = spt.Ticktock(dts,'UTC')
>>> dts.DOY
array([ 186.,  187.,  188.,  189.])

Ticktock object creation

>>> isodates = ['2009-12-01T12:00:00', '2009-12-04T00:00:00', '2009-12-06T12:00:00']
>>> dts = spt.Ticktock(isodates, 'ISO')

OR

>>> dtdates = [dt.datetime(2009,12,1,12), dt.datetime(2009,12,4), dt.datetime(2009,12,6,12)]
>>> dts = spt.Ticktock(dtdates, 'UTC')

ISO time formatting

>>> dts = spt.tickrange('2009-12-01T12:00:00','2009-12-06T12:00:00',2.5)

OR

>>> dts = spt.tickrange(dt.datetime(2009,12,1,12),dt.datetime(2009,12,6,12), \
    dt.timedelta(days=2, hours=12))

>>> dts
Ticktock( ['2009-12-01T12:00:00', '2009-12-04T00:00:00', '2009-12-06T12:00:00'] ), dtype=ISO

>>> dts.isoformat()
Current ISO output format is %Y-%m-%dT%H:%M:%S
Options are: [('seconds', '%Y-%m-%dT%H:%M:%S'), ('microseconds', '%Y-%m-%dT%H:%M:%S.%f')]

>>> dts.isoformat('microseconds')
>>> dts.ISO
['2009-12-01T12:00:00.000000',
 '2009-12-04T00:00:00.000000',
 '2009-12-06T12:00:00.000000']

Time manipulation

>>> new_dts = dts + tdelt
>>> new_dts.UTC
[datetime.datetime(2009, 12, 2, 18, 0),
 datetime.datetime(2009, 12, 5, 6, 0),
 datetime.datetime(2009, 12, 7, 18, 0)]

Other time formats

>>> dts.RDT  # Gregorian ordinal time
array([ 733742.5,  733745. ,  733747.5])

>>> dts.GPS # GPS time
array([  9.43704015e+08,   9.43920015e+08,   9.44136015e+08])

>>> dts.JD # Julian day
array([ 2455167. ,  2455169.5,  2455172. ])

And so on.

.. currentmodule:: spacepy.time

.. NOTE... there is an error with this reference

Authors: Steve Morley, Josef Koller, Brian Larsen, Jon Niehof
Institution: Los Alamos National Laboratory
Contact: smorley@lanl.gov,


Copyright 2010 Los Alamos National Security, LLC.
"""
import bisect
try:
    from collections.abc import Callable, MutableSequence
except ImportError:
    from collections import Callable, MutableSequence
import datetime

try:
    from itertools import izip as zip
except ImportError:
    pass  # just use system zip. In python3 itertools.izip is just python zip
import os.path
import re
import warnings

import dateutil.parser as dup
import numpy as np

from spacepy import help
import spacepy.datamodel

__contact__ = 'Steve Morley, smorley@lanl.gov'

"""
Notes:
Python's assert statement is a good way to catch situations that should never happen. And it can be removed by Python
optimization when the code is trusted to be correct. Assert is not to be used in program flow.

In Python, on the other hand, there is no strict distinction between debug and release mode. The interpreter features
an "optimization flag" (-O), but currently this does not actually optimize the byte code, but only removes asserts.
"""


# -----------------------------------------------
# Ticktock class
# -----------------------------------------------
[docs]class Ticktock(MutableSequence): """ Ticktock( data, dtype ) Ticktock class holding various time coordinate systems (TAI, UTC, ISO, JD, MJD, UNX, RDT, CDF, DOY, eDOY) Possible input data types: ISO: ISO standard format like '2002-02-25T12:20:30' UTC: datetime object with UTC time TAI: elapsed seconds since 1958/1/1 (includes leap seconds) UNX: elapsed seconds since 1970/1/1 (all days have 86400 secs sometimes unequal lenghts) JD: Julian days elapsed MJD: Modified Julian days RDT: Rata Die days elapsed since 1/1/1 CDF: CDF epoch: milliseconds since 1/1/0000 Possible output data types: All those listed above, plus DOY: Integer day of year, starts with day 1 eDOY: Fractional day of year, starts at day 0 Parameters ========== data : array_like (int, datetime, float, string) time stamp dtype : string {`CDF`, `ISO`, `UTC`, `TAI`, `UNX`, `JD`, `MJD`, `RDT`} or function data type for data, if a function it must convert input time format to Python datetime Returns ======= out : Ticktock instance with self.data, self.dtype, self.UTC etc Examples ======== >>> x = Ticktock([2452331.0142361112, 2452332.0142361112], 'JD') >>> x.ISO dmarray(['2002-02-25T12:20:30', '2002-02-26T12:20:30'], dtype='|S19') >>> x.DOY # Day of year dmarray([ 56., 57.]) >>> y = Ticktock(['01-01-2013', '20-03-2013'], lambda x: datetime.datetime.strptime(x, '%d-%m-%Y')) >>> y.UTC dmarray([2013-01-01 00:00:00, 2013-03-20 00:00:00], dtype=object) >>> y.DOY # Day of year dmarray([ 1., 79.]) .. autosummary:: ~Ticktock.append ~Ticktock.argsort ~Ticktock.convert ~Ticktock.getCDF ~Ticktock.getDOY ~Ticktock.getGPS ~Ticktock.getISO ~Ticktock.getJD ~Ticktock.getMJD ~Ticktock.getRDT ~Ticktock.getTAI ~Ticktock.getUNX ~Ticktock.getUTC ~Ticktock.geteDOY ~Ticktock.getleapsecs ~Ticktock.isoformat ~Ticktock.now ~Ticktock.sort ~Ticktock.update_items .. automethod:: append .. automethod:: argsort .. automethod:: convert .. automethod:: getCDF .. automethod:: getDOY .. automethod:: getGPS .. automethod:: getISO .. automethod:: getJD .. automethod:: getMJD .. automethod:: getRDT .. automethod:: getTAI .. automethod:: getUNX .. automethod:: getUTC .. automethod:: geteDOY .. automethod:: getleapsecs .. automethod:: isoformat .. automethod:: now .. automethod:: sort .. automethod:: update_items """ _keylist = ['UTC', 'TAI', 'ISO', 'JD', 'MJD', 'UNX', 'RDT', 'CDF', 'GPS', 'DOY', 'eDOY', 'leaps'] _keylist_upper = [key.upper() for key in _keylist] _isoformatstr = {'seconds': '%Y-%m-%dT%H:%M:%S', 'microseconds': '%Y-%m-%dT%H:%M:%S.%f'} def __init__(self, data, dtype=None): self._isofmt = Ticktock._isoformatstr['seconds'] if isinstance(data, Ticktock): dtype = data.data.attrs['dtype'] self.data = data.data else: try: spacepy.datamodel.dmarray(data)[0] except IndexError: self.data = spacepy.datamodel.dmarray([data]) else: self.data = spacepy.datamodel.dmarray(data) if not isinstance(dtype, Callable): if isinstance(self.data[0], str): dtype = 'ISO' elif isinstance(self.data[0], datetime.datetime): dtype = 'UTC' elif self.data[0] > 1e13: dtype = 'CDF' if dtype.upper() not in Ticktock._keylist_upper: raise ValueError("data type " + dtype + " not provided, only " + str(Ticktock._keylist)) else: # process input data using callable dtype to convert to datetime/UTC dtype_func = np.vectorize(dtype) self.data = dtype_func(self.data) self.UTC = no_tzinfo(self.data) try: self.data.attrs['dtype'] = dtype.upper() except AttributeError: self.data.attrs['dtype'] = str(dtype_func) else: if dtype.upper() == 'ISO': self.ISO = self.data self.update_items(self, 'data') if dtype.upper() == 'TAI': self.TAI = self.data elif dtype.upper() == 'JD': self.JD = self.data elif dtype.upper() == 'MJD': self.MJD = self.data elif dtype.upper() == 'UNX': self.UNX = self.data elif dtype.upper() == 'RDT': self.RDT = self.data elif dtype.upper() == 'CDF': self.CDF = self.data elif dtype.upper() == 'UTC': self.UTC = no_tzinfo(self.data) ## Brian and Steve were looking at this to see about making plot work directly on the object ## is also making iterate as an array of datetimes # def __iter__(self): # i = 0 # try: # while True: # v = self[i].UTC[0] # yield v # i += 1 # except IndexError: # return # ----------------------------------------------- def __str__(self): """ a.__str__() or a Will be called when printing Ticktock instance a Returns ======= out : string string representaion of the class Examples ======== >>> a = Ticktock('2002-02-02T12:00:03', 'ISO') >>> a Ticktock( ['2002-02-02T12:00:00'], dtype=ISO) """ return 'Ticktock( ' + str(self.data) + ', dtype=' + str(self.data.attrs['dtype'] + ')') __repr__ = __str__ # ----------------------------------------------- def __getstate__(self): """ Is called when pickling See Also http://docs.python.org/library/pickle.html """ odict = self.__dict__.copy() # copy the dict since we change it return odict def __setstate__(self, dict): """ Is called when unpickling See Also http://docs.python.org/library/pickle.html """ self.__dict__.update(dict) return # ----------------------------------------------- def __getitem__(self, idx): """ a.__getitem__(idx) or a[idx] Will be called when requesting items in this instance Parameters ========== idx : int the item index to get Returns ======= out : Ticktock Ticktock instance with requested values Examples ======== >>> a = Ticktock('2002-02-02T12:00:03', 'ISO') >>> a[0] '2002-02-02T12:00:00' See Also ======== a.__setitem__ """ return Ticktock(self.UTC[idx]) # ----------------------------------------------- def __setitem__(self, idx, vals): """ a.__setitem__(idx, vals) or a[idx] = vals Will be called setting items in this instance Parameters ========== idx : int integer numbers as index vals: {float, string, datetime} new values Examples ======== >>> a = Ticktock('2002-02-02T12:00:03', 'ISO') >>> a[0] = '2003-03-03T00:00:00' See Also ======== a.__getitem__ """ tmp = Ticktock(vals) if len(tmp) > 1: self.data[idx] = tmp.__getattribute__(self.data.attrs['dtype'])[:] else: self.data[idx] = tmp.__getattribute__(self.data.attrs['dtype'])[0] self.update_items(self, 'data') # ----------------------------------------------- def __delitem__(self, idx): """ a.__delitem(index) will be called when deleting items in the sequence """ self.data = np.delete(self.data, idx) self.update_items(self, 'data') # ----------------------------------------------- def __len__(self): """ a.__len__() or len(a) Will be called when requesting the length, i.e. number of items Returns ======= out : int length Examples ======== >>> a = Ticktock('2002-02-02T12:00:03', 'ISO') >>> a.len 1 """ return len(self.data) # ----------------------------------------------- def __gt__(self, other): if isinstance(other, datetime.datetime): other = Ticktock(other, 'UTC') return self.UNX > other.UNX def __lt__(self, other): if isinstance(other, datetime.datetime): other = Ticktock(other, 'UTC') return self.UNX < other.UNX def __ge__(self, other): if isinstance(other, datetime.datetime): other = Ticktock(other, 'UTC') return self.UNX >= other.UNX def __le__(self, other): if isinstance(other, datetime.datetime): other = Ticktock(other, 'UTC') return self.UNX <= other.UNX def __eq__(self, other): if isinstance(other, datetime.datetime): other = Ticktock(other, 'UTC') return self.UNX == other.UNX def __ne__(self, other): if isinstance(other, datetime.datetime): other = Ticktock(other, 'UTC') return self.UNX != other.UNX # ----------------------------------------------- def __sub__(self, other): """ a.__sub__(other) Will be called if a timedelta object is subtracted from this instance and returns a new Ticktock instance. If a Ticktock is subtracted from another Ticktock then a list of timedeltas is returned. Parameters ========== other : Ticktock or datetime.timedelta instance for comparison Examples ======== >>> a = Ticktock('2002-02-02T12:00:00', 'ISO') >>> dt = datetime.timedelta(3) >>> a - dt Ticktock( ['2002-02-05T12:00:00'] ), dtype=ISO See Also ======== __add__ """ if isinstance(other, datetime.timedelta): newobj = Ticktock(self.UTC - other, 'UTC') elif isinstance(other, Ticktock): if not (len(other) == len(self.data)) and not (len(other) == 1): raise ValueError('Ticktock lengths are mismatched, subtraction is not possible') same = True if len(other) == 1: same = False if same: return [datetime.timedelta(seconds=t - other.TAI[i]) for i, t in enumerate(self.TAI)] else: return [datetime.timedelta(seconds=t - other.TAI[0]) for t in self.TAI] elif hasattr(other, '__iter__'): if not isinstance(other[0], datetime.timedelta): raise TypeError("Data supplied for addition is of the wrong type") if not (len(other) == len(self.data)) or (len(other) == 1): raise TypeError("Data supplied for addition is of the wrong shape") same = True if len(other) == 1: same = False if same: newUTC = [utc - o for utc, o in zip(self.UTC, other)] else: newUTC = [utc - other for utc in self.UTC] newobj = Ticktock(newUTC, 'UTC') else: raise TypeError("unsupported operand type(s) for -: {0} and {1}".format(type(other), type(self))) return newobj # ----------------------------------------------- def __add__(self, other): """ a.__add__(other) Will be called if an iterable of datetime.timedeltas is added to this instance and returns a new Ticktock instance Parameters ========== other : datetime.timedelta instance for comparison Examples ======== >>> a = Ticktock('2002-02-02T12:00:00', 'ISO') >>> import datetime as dt >>> delt = dt.timedelta(minutes=1) >>> a + delt Ticktock( ['2002-02-02T12:01:00'] , dtype=ISO) See Also ======== __sub__ """ if isinstance(other, datetime.timedelta): newobj = Ticktock(self.UTC + other, 'UTC') elif hasattr(other, '__iter__'): if not isinstance(other[0], datetime.timedelta): raise TypeError("Data supplied for addition is of the wrong type") if not (len(other) == len(self.data)) or (len(other) == 1): raise TypeError("Data supplied for addition is of the wrong shape") same = True if len(other) == 1: same = False if same: newUTC = [utc + o for utc, o in zip(self.UTC, other)] else: newUTC = [utc + other for utc in self.UTC] newobj = Ticktock(newUTC, 'UTC') else: raise TypeError("unsupported operand type(s) for +: {0} and {1}".format(type(other), type(self))) return newobj def __radd__(self, other): """ a.__radd__(other) reverse add -- Will be called if this object is added to a datetime.timedelta and returns a new Ticktock instance Parameters ========== other : datetime.timedelta instance for comparison Examples ======== >>> a = Ticktock('2002-02-02T12:00:00', 'ISO') >>> import datetime as dt >>> delt = dt.timedelta(minutes=1) >>> a + delt Ticktock( ['2002-02-02T12:01:00'] , dtype=ISO) See Also ======== __sub__ """ return self.__add__(other) # ----------------------------------------------- def __getattr__(self, name): """ a.__getattr__(name) Will be called if attribute "name" is not found in Ticktock class instance. It will add TAI, RDT, etc Parameters ========== name : string a string from the list of time systems 'UTC', 'TAI', 'ISO', 'JD', 'MJD', 'UNX', 'RDT', 'CDF', 'DOY', 'eDOY', 'leaps' Returns ======== out: list, array requested values as either list/numpy array """ assert name in Ticktock._keylist, "data type " + str(name) + " not provided, only " + str(Ticktock._keylist) if name.upper() == 'TAI': self.TAI = self.getTAI() if name.upper() == 'ISO': self.ISO = self.getISO() if name.upper() == 'JD': self.JD = self.getJD() if name.upper() == 'MJD': self.MJD = self.getMJD() if name.upper() == 'UNX': self.UNX = self.getUNX() if name.upper() == 'RDT': self.RDT = self.getRDT() if name.upper() == 'CDF': self.CDF = self.getCDF() if name.upper() == 'DOY': self.DOY = self.getDOY() if name.upper() == 'EDOY': self.eDOY = self.geteDOY() if name.upper() == 'GPS': self.GPS = self.getGPS() # if name == 'isoformat': self.__isofmt = self.isoformat() if name == 'leaps': self.leaps = self.getleapsecs() return getattr(self, name) # ----------------------------------------------- def insert(self, idx, val, dtype=None): """ insert values into the TickTock object .. note:: If more than one value to insert a slice must be specified as the index. See numpy.insert Parameters ========== idx : int, slice or sequence of ints Object that defines the index or indices before which `val` is inserted. val : array_like values to insert dtype : str (optional) must be specified if not CDF, ISO, or UTC """ fmt = self.data.attrs['dtype'] if not dtype: dum = Ticktock(val) else: dum = Ticktock(val, dtype=dtype) ival = getattr(dum, fmt) self.data = np.insert(self.data, idx, ival) self.update_items(self, 'data') # ----------------------------------------------- def remove(self, idx): """ a.remove(idx) This will remove the Ticktock value at index idx """ del self[idx] # -----------------------------------------------
[docs] def sort(self, kind='quicksort'): """ a.sort() This will sort the Ticktock values in place, if you need a stable sort use kind='mergesort' """ RDT = self.RDT # TODO does this not being stable matter? to make stable use kind='mergesort' RDTsorted = np.sort(RDT, kind=kind) tmp = Ticktock(RDTsorted, 'RDT').convert(self.data.attrs['dtype']) self.data = tmp.data self.update_items(self, 'data') return
# -----------------------------------------------
[docs] def argsort(self, kind='mergesort'): """ idx = a.argsort() This will return the indices that would sort the Ticktock values Returns ======= out : list indices that would sort the Ticktock values """ RDT = self.RDT idx = np.argsort(RDT, kind=kind) return idx
# -----------------------------------------------
[docs] def isoformat(self, fmt=None): """ a.update_items(b, attrib) This changes the self._isofmt attribute by and subsequently this function will update the ISO attribute. Parameters ========== fmt : string, optional """ if fmt is None: print('Current ISO output format is %s' % self._isofmt) print('Options are: {0}'.format([(k, Ticktock._isoformatstr[k]) for k in list(Ticktock._isoformatstr.keys())])) else: try: self._isofmt = Ticktock._isoformatstr[fmt] self.update_items(self, 'data') except KeyError: raise (ValueError('Not a valid option: Use {0}'.format(list(Ticktock._isoformatstr.keys()))))
# -----------------------------------------------
[docs] def update_items(self, cls, attrib): """ a.update_items(b, attrib) After changing the self.data attribute by either __setitem__ or __add__ etc this function will update all other attributes. This function is called automatically in __add__ and __setitem__ Parameters ========== cls : Ticktock attrib : string attribute to update See Also ======== spacepy.Ticktock.__setitem__ spacepy.Ticktock.__add__ spacepy.Ticktock.__sub__ """ keylist = list(cls.__dict__.keys()) # keylist.remove('dtype') keylist.remove('data') if attrib is not 'data': keylist.remove(attrib) self.UTC = self.getUTC() for key in keylist: if key.upper() == 'TAI': self.TAI = self.getTAI() if key.upper() == 'ISO': self.ISO = self.getISO() if key.upper() == 'JD': self.JD = self.getJD() if key.upper() == 'MJD': self.MJD = self.getMJD() if key.upper() == 'UNX': self.UNX = self.getUNX() if key.upper() == 'RDT': self.RDT = self.getRDT() if key.upper() == 'CDF': self.CDF = self.getCDF() if key.upper() == 'DOY': self.DOY = self.getDOY() if key.upper() == 'eDOY': self.eDOY = self.geteDOY() if key.upper() == 'GPS': self.GPS = self.getGPS() if key == 'leaps': self.leaps = self.getleapsecs() return
# -----------------------------------------------
[docs] def convert(self, dtype): """ a.convert(dtype) convert a Ticktock instance into a new time coordinate system provided in dtype Parameters ========== dtype : string data type for new system, possible values are {`CDF`, `ISO`, `UTC`, `TAI`, `UNX`, `JD`, `MJD`, `RDT`} Returns ======= out : Ticktock Ticktock instance with new time coordinates Examples ======== >>> a = Ticktock(['2002-02-02T12:00:00', '2002-02-02T12:00:00'], 'ISO') >>> s = a.convert('TAI') >>> type(s) <class 'time.Ticktock'> >>> s Ticktock( [1391342432 1391342432] ), dtype=TAI See Also ======== CDF ISO UTC """ newdat = getattr(self, dtype) return Ticktock(newdat, dtype)
# -----------------------------------------------
[docs] def append(self, other): """ a.append(other) Will be called when another Ticktock instance has to be appended to the current one Parameters ========== other : Ticktock other (Ticktock instance) """ otherdata = getattr(other, self.data.attrs['dtype']) return Ticktock(np.append(self.data, otherdata), dtype=self.data.attrs['dtype'])
# -----------------------------------------------
[docs] def getCDF(self): """ a.getCDF() or a.CDF Return CDF time which is milliseconds since 01-Jan-0000 00:00:00.000. "Year zero" is a convention chosen by NSSDC to measure epoch values. This date is more commonly referred to as 1 BC. Remember that 1 BC was a leap year. The CDF date/time calculations do not take into account the changes to the Gregorian calendar, and cannot be directly converted into Julian date/times. Returns ======= out : numpy array days elapsed since Jan. 1st Examples ======== >>> a = Ticktock('2002-02-02T12:00:00', 'ISO') >>> a.CDF array([ 6.31798704e+13]) See Also ======== getUTC getUNX getRDT getJD getMJD getISO getTAI getDOY geteDOY """ RDTdata = self.getRDT() CDF = RDTdata * 86400000.0 + 86400000.0 * 365.0 self.CDF = CDF return self.CDF
# -----------------------------------------------
[docs] def getDOY(self): """ a.DOY or a.getDOY() extract DOY (days since January 1st of given year) Returns ======= out : numpy array day of the year Examples ======== >>> a = Ticktock('2002-02-02T12:00:00', 'ISO') >>> a.DOY array([ 33]) See Also ======== getUTC getUNX getRDT getJD getMJD getISO getTAI getDOY geteDOY """ DOY = [utc.toordinal() - datetime.date(utc.year, 1, 1).toordinal() + 1 for utc in self.UTC] self.DOY = spacepy.datamodel.dmarray(DOY, attrs={'dtype': 'DOY'}).astype(int) return self.DOY
# -----------------------------------------------
[docs] def geteDOY(self): """ a.eDOY or a.geteDOY() extract eDOY (elapsed days since midnight January 1st of given year) Returns ======= out : numpy array days elapsed since midnight bbedJan. 1st Examples ======== >>> a = Ticktock('2002-02-02T12:00:00', 'ISO') >>> a.eDOY array([ 32.5]) See Also ======== getUTC getUNX getRDT getJD getMJD getISO getTAI getDOY geteDOY """ eDOY = [utc.toordinal() - datetime.date(utc.year, 1, 1).toordinal() for utc in self.UTC] eDOY = [edoy + utc.hour / 24. + utc.minute / 1440. + utc.second / 86400. + utc.microsecond / 86400000000. for edoy, utc in zip(eDOY, self.UTC)] self.eDOY = spacepy.datamodel.dmarray(eDOY, attrs={'dtype': 'eDOY'}) return self.eDOY
# -----------------------------------------------
[docs] def getJD(self): """ a.JD or a.getJD() convert dtype data into Julian Date (JD) Returns ======= out : numpy array elapsed days since 12:00 January 1, 4713 BC Examples ======== >>> a = Ticktock('2002-02-02T12:00:00', 'ISO') >>> a.JD array([ 2452308.]) See Also ======== getUTC getUNX getRDT getJD getMJD getISO getTAI getDOY geteDOY """ import decimal nTAI = len(self.data) # convert all types in to UTC first and call again UTCdata = self.UTC if UTCdata[0] < datetime.datetime(1582, 10, 15): warnings.warn("Calendar date before the switch from Julian to Gregorian\n" + " Calendar 1582-Oct-15: Use Julian Calendar dates as input") # include offset if given JD = spacepy.datamodel.dmarray(np.zeros(nTAI), attrs={'dtype': 'JD'}) twelve, twofour, mind = decimal.Decimal('12.0'), decimal.Decimal('24.0'), decimal.Decimal('1440.0') sind, usind = decimal.Decimal('86400.0'), decimal.Decimal('86400000000.0') for i in np.arange(nTAI): offset = UTCdata[i].utcoffset() if offset: UTCdata[i] = UTCdata[i] - offset # extract year, month, day Y = int(UTCdata[i].year) M = int(UTCdata[i].month) D = int(UTCdata[i].day) # the following is from Wikipedia (but is wrong by 2 days) # JDN = D-32075+1461*(Y+4800+(M-14)/12)/4+367*(M-2-(M-14)/12*12)/12-3*((Y+4900+(M-14)/12)/100)/4 # JD = JDN + (data.hour-12)/24. + data.minute/1440. + data.second/86400. # following Press, "Numerical Recipes", Fct: JULDAY, p. 10 igreg = 15 + 31 * (10 + 12 * 1582) if M > 2: JY = Y JM = M + 1 else: JY = Y - 1 JM = M + 13 JD[i] = int(365.25 * JY) + int(30.6001 * JM) + D + 1720995 c_val = (D + 31 * (M + 12 * Y)) if c_val >= igreg: # yes if date after the Gregorian Switch in 1582-Oct-15 JA = int(0.01 * JY) JD[i] = JD[i] + 2 - JA + int(0.25 * JA) # add this to num.recipes to get fractional days # twelve, twofour, mind = decimal.Decimal('12.0'), decimal.Decimal('24.0'), decimal.Decimal('1440.0') # sind, usind = decimal.Decimal('86400.0'), decimal.Decimal('86400000000.0') JD[i] = decimal.Decimal(str(JD[i])) + (decimal.Decimal(str(UTCdata[i].hour)) - twelve) / twofour + \ decimal.Decimal(str(UTCdata[i].minute / 1440.)) + (decimal.Decimal(str(UTCdata[i].second)) / sind) + \ (decimal.Decimal(str(UTCdata[i].microsecond)) / usind) JD[i] = float(JD[i]) # JD[i] = JD[i] + (UTCdata[i].hour-12)/24. + UTCdata[i].minute/1440. + \ # UTCdata[i].second/86400. + UTCdata[i].microsecond/86400000000. self.JD = JD return self.JD
# -----------------------------------------------
[docs] def getMJD(self): """ a.MJD or a.getMJD() convert dtype data into MJD (modified Julian date) Returns ======== out : numpy array elapsed days since November 17, 1858 (Julian date was 2,400 000) Examples ======== >>> a = Ticktock('2002-02-02T12:00:00', 'ISO') >>> a.MJD array([ 52307.5]) See Also ======== getUTC, getUNX, getRDT, getJD, getISO, getCDF, getTAI, getDOY, geteDOY """ if self.UTC[0] < datetime.datetime(1582, 10, 15): warnings.warn("WARNING: Calendar date before the switch from Julian to Gregorian\n" + "Calendar 1582-Oct-15: Use Julian Calendar dates as input") MJD = self.JD - 2400000.5 self.MJD = MJD return self.MJD
# -----------------------------------------------
[docs] def getUNX(self): """ a.UNX or a.getUNX() convert dtype data into Unix Time (Posix Time) seconds since 1970-Jan-1 (not counting leap seconds) Returns ======== out : numpy array elapsed secs since 1970/1/1 (not counting leap secs) Examples ======== >>> a = Ticktock('2002-02-02T12:00:00', 'ISO') >>> a.UNX array([ 1.01265120e+09]) See Also ========= getUTC, getISO, getRDT, getJD, getMJD, getCDF, getTAI, getDOY, geteDOY """ UNX0 = datetime.datetime(1970, 1, 1) d = [utc - UNX0 for utc in self.UTC] UNX = [dd.days * 86400 + dd.seconds + dd.microseconds / 1.e6 for dd in d] self.UNX = spacepy.datamodel.dmarray(UNX, attrs={'dtype': 'UNX'}) return self.UNX
# -----------------------------------------------
[docs] def getRDT(self): """ a.RDT or a.RDT() convert dtype data into Rata Die (lat.) Time (days since 1/1/0001) Returns ======== out : numpy array elapsed days since 1/1/1 Examples ======== >>> a = Ticktock('2002-02-02T12:00:00', 'ISO') >>> a.RDT array([ 730883.5]) See Also ========= getUTC, getUNX, getISO, getJD, getMJD, getCDF, getTAI, getDOY, geteDOY """ from matplotlib.dates import date2num # This is how to do this without date2num # nTAI = len(self.data) # RDT = np.zeros(nTAI) # for i in np.arange(nTAI): # RDT[i] = UTC[i].toordinal() + UTC[i].hour/24. + UTC[i].minute/1440. + \ # UTC[i].second/86400. + UTC[i].microsecond/86400000000. self.RDT = spacepy.datamodel.dmarray(date2num(self.UTC), attrs={'dtype': 'RDT'}) return self.RDT
# -----------------------------------------------
[docs] def getUTC(self): """ a.UTC or a.getUTC() convert dtype data into UTC object a la datetime() Returns ======== out : list of datetime objects datetime object in UTC time Examples ======== >>> a = Ticktock('2002-02-02T12:00:00', 'ISO') >>> a.UTC [datetime.datetime(2002, 2, 2, 12, 0)] See Also ========= getISO, getUNX, getRDT, getJD, getMJD, getCDF, getTAI, getDOY, geteDOY """ from matplotlib.dates import num2date nTAI = len(self.data) # if already UTC, we are done, no conversion if self.data.attrs['dtype'].upper() == 'UTC': UTC = self.data elif self.data.attrs['dtype'].upper() == 'ISO': self.ISO = self.data # try a few special cases that are faster than dateutil.parser try: UTC = [datetime.datetime.strptime(isot, '%Y-%m-%dT%H:%M:%S') for isot in self.data] except ValueError: try: UTC = [datetime.datetime.strptime(isot, '%Y-%m-%dT%H:%M:%SZ') for isot in self.data] except ValueError: try: UTC = [datetime.datetime.strptime(isot, '%Y-%m-%d') for isot in self.data] except ValueError: try: UTC = [datetime.datetime.strptime(isot, '%Y%m%d') for isot in self.data] except ValueError: try: UTC = [datetime.datetime.strptime(isot, '%Y%m%d %H:%M:%S') for isot in self.data] except ValueError: UTC = [dup.parse(isot) for isot in self.data] elif self.data.attrs['dtype'].upper() == 'TAI': self.TAI = self.data TAI0 = datetime.datetime(1958, 1, 1, 0, 0, 0, 0) UTC = [datetime.timedelta(seconds=float(tait)) + TAI0 for tait in self.data] # add leap seconds after UTC is created self.UTC = UTC leapsecs = self.getleapsecs() for i in np.arange(nTAI): self.UTC[i] = UTC[i] - datetime.timedelta(seconds=float(leapsecs[i])) tmpleaps = Ticktock(self.UTC[i]).leaps if tmpleaps == leapsecs[i] - 1: self.UTC[i] = self.UTC[i] + datetime.timedelta(seconds=1) elif self.data.attrs['dtype'].upper() == 'GPS': self.GPS = self.data GPS0 = datetime.datetime(1980, 1, 6, 0, 0, 0, 0) UTC = [datetime.timedelta(seconds=float(gpst)) + GPS0 for gpst in self.data] # add leap seconds after UTC is created self.UTC = UTC leapsecs = self.getleapsecs() for i in np.arange(nTAI): # there were 18 leap seconds before gps zero, need the -18 for that self.UTC[i] = UTC[i] - datetime.timedelta(seconds=float(leapsecs[i])) + \ datetime.timedelta(seconds=19) elif self.data.attrs['dtype'].upper() == 'UNX': self.UNX = self.data UNX0 = datetime.datetime(1970, 1, 1) UTC = [datetime.timedelta(seconds=unxt) + UNX0 for unxt in self.data] elif self.data.attrs['dtype'].upper() == 'RDT': self.RDT = self.data UTC = num2date(self.data) UTC = no_tzinfo(UTC) # for i in np.arange(nTAI): # UTC[i] = datetime.datetime(1,1,1) + \ # datetime.timedelta(days=np.floor(self.data[i])-1) + \ # datetime.timedelta(microseconds=(self.data[i] - \ # self.data[i])*86400000.) # roundoff the microseconds # UTC[i] = UTC[i] - datetime.timedelta(microseconds=UTC[i].microsecond) elif self.data.attrs['dtype'].upper() == 'CDF': self.CDF = self.data UTC = [datetime.timedelta(days=cdft / 86400000.) + datetime.datetime(1, 1, 1) - datetime.timedelta(days=366) for cdft in self.data] # UTC[i] = datetime.timedelta(days=np.floor(self.data[i]/86400000.), \ # milliseconds=np.mod(self.data[i],86400000)) + \ # datetime.datetime(1,1,1) - datetime.timedelta(days=366) # the following has round off errors # UTC[i] = datetime.timedelta(data[i]/86400000.-366) + datetime.datetime(1,1,1) elif self.data.attrs['dtype'].upper() in ['JD', 'MJD']: if self.data.attrs['dtype'].upper() == 'MJD': self.JD = self.data + 2400000.5 self.MJD = self.data else: self.JD = self.data UTC = [''] * nTAI for i in np.arange(nTAI): # extract partial days ja = int(np.floor(self.JD[i])) p = self.JD[i] - np.floor(self.JD[i]) # after Press: "Numerical Recipes" # http://www.rgagnon.com/javadetails/java-0506.html # only good for after 15-Oct-1582 igreg = 15 + 31 * (10 + 12 * 1582) if ja >= igreg: # after switching to Gregorian Calendar jalpha = int(((ja - 1867216) - 0.25) / 36524.25) ja = ja + 1 + jalpha - jalpha // 4 jb = ja + 1524 jc = int(6680.0 + ((jb - 2439870) - 122.1) / 365.25) jd = 365 * jc + jc // 4 je = int((jb - jd) / 30.6001) day = jb - jd - int(30.6001 * je) month = je - 1 if (month > 12): month = month - 12 year = jc - 4715 if (month > 2): year = year - 1 if (year <= 0): year = year - 1 # after http://aa.usno.navy.mil/faq/docs/JD_Formula.php # also good only for after 1582-Oct-15 # L= JD+68569 # N= 4*L/146097 # = L-(146097*N+3)/4 # I= 4000*(L+1)/1461001 # L= L-1461*I/4+31 # J= 80*L/2447 # K= L-2447*J/80 # L= J/11 # J= J+2-12*L # I= 100*(N-49)+I+L UTC[i] = datetime.datetime(year, month, int(day)) + \ datetime.timedelta(hours=12) + \ datetime.timedelta(seconds=p * 86400) if UTC[i] < datetime.datetime(1582, 10, 15): warnings.warn("WARNING: Calendar date before the switch from Julian to Gregorian\n" + "Calendar 1582-Oct-15: Use Julian Calendar dates as input") else: warnstr1 = 'Input data type {0} does not support calculation of UTC times'.format(self.data.attrs['dtype']) warnstr2 = 'Valid input dtypes are: {0}'.format( ', '.join([kk for kk in Ticktock._keylist if kk not in ['DOY', 'eDOY', 'leaps']])) raise TypeError('{0}\n{1}'.format(warnstr1, warnstr2)) UTC = spacepy.datamodel.dmarray(UTC, attrs={'dtype': 'UTC'}) self.UTC = no_tzinfo(UTC) return no_tzinfo(self.UTC)
# -----------------------------------------------
[docs] def getGPS(self): """ a.GPS or a.getGPS() return GPS epoch (0000 UT (midnight) on January 6, 1980) Returns ======== out : numpy array elapsed secs since 6Jan1980 (excludes leap secs) Examples ======== >>> a = Ticktock('2002-02-02T12:00:00', 'ISO') >>> a.GPS array([]) See Also ========= getUTC, getUNX, getRDT, getJD, getMJD, getCDF, getISO, getDOY, geteDOY """ GPS0 = datetime.datetime(1980, 1, 6, 0, 0, 0, 0) leapsec = self.getleapsecs() GPStup = [utc - GPS0 + datetime.timedelta(seconds=int(ls)) - datetime.timedelta(seconds=19) for utc, ls in zip(self.UTC, leapsec)] GPS = [gps.days * 86400 + gps.seconds + gps.microseconds / 1.e6 for gps in GPStup] self.GPS = spacepy.datamodel.dmarray(GPS, attrs={'dtype': 'GPS'}) # .astype(int) return self.GPS
# -----------------------------------------------
[docs] def getTAI(self): """ a.TAI or a.getTAI() return TAI (International Atomic Time) Returns ======= out : numpy array elapsed secs since 1958/1/1 (includes leap secs, i.e. all secs have equal lengths) Examples ======== >>> a = Ticktock('2002-02-02T12:00:00', 'ISO') >>> a.TAI array([1391342432]) See Also ======== getUTC, getUNX, getRDT, getJD, getMJD, getCDF, getISO, getDOY, geteDOY """ TAI0 = datetime.datetime(1958, 1, 1, 0, 0, 0, 0) leapsec = self.getleapsecs() TAItup = [utc - TAI0 + datetime.timedelta(seconds=int(ls)) for utc, ls in zip(self.UTC, leapsec)] TAI = [tai.days * 86400 + tai.seconds + tai.microseconds / 1.e6 for tai in TAItup] self.TAI = spacepy.datamodel.dmarray(TAI, attrs={'dtype': 'TAI'}) return self.TAI
# -----------------------------------------------
[docs] def getISO(self): """ a.ISO or a.getISO() convert dtype data into ISO string Returns ======= out : list of strings date in ISO format Examples ======== >>> a = Ticktock('2002-02-02T12:00:00', 'ISO') >>> a.ISO dmarray(['2002-02-02T12:00:00']) See Also ======== getUTC, getUNX, getRDT, getJD, getMJD, getCDF, getTAI, getDOY, geteDOY """ nTAI = len(self.data) self.TAI = self.getTAI() self.ISO = spacepy.datamodel.dmarray([utc.strftime(self._isofmt) for utc in self.UTC], attrs={'dtype': 'ISO'}) for i in range(nTAI): if self.TAI[i] in self.TAIleaps: tmptick = self.UTC[i] - datetime.timedelta(seconds=1) a, b, c = tmptick.ISO[0].split(':') cnew = c.replace('59', '60') self.ISO[i] = a + ':' + b + ':' + cnew return self.ISO
# -----------------------------------------------
[docs] def getleapsecs(self): """ a.leaps or a.getleapsecs() retrieve leapseconds from lookup table, used in getTAI Returns ======= out : numpy array leap seconds Examples ======== >>> a = Ticktock('2002-02-02T12:00:00', 'ISO') >>> a.leaps array([32]) See Also ========= getTAI """ from spacepy import DOT_FLN tup = self.UTC # so you don't have to read the file every single time global secs, year, mon, day, TAIleaps try: leaps = secs[0] except: # then we are calling this routine the 1st time # load current file fname = os.path.join(DOT_FLN, 'data', 'tai-utc.dat') with open(fname) as fh: text = fh.readlines() secs = np.zeros(len(text)) year = np.zeros(len(text)) mon = np.zeros(len(text)) day = np.zeros(len(text)) months = np.array(['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC']) for line, i in zip(text, np.arange(len(secs))): secs[i] = int(float(line.split()[6])) # truncate float seconds year[i] = int(line.split()[0]) mon[i] = int(np.where(months == line.split()[1])[0][0] + 1) day[i] = int(line.split()[2]) TAIleaps = np.zeros(len(secs)) TAItup = [''] * len(secs) TAI0 = datetime.datetime(1958, 1, 1, 0, 0, 0, 0) for i in np.arange(len(secs)): TAItup[i] = datetime.datetime(int(year[i]), int(mon[i]), int(day[i])) - TAI0 + datetime.timedelta( seconds=int(secs[i]) - 1) TAIleaps[i] = TAItup[i].days * 86400 + TAItup[i].seconds + TAItup[i].microseconds / 1.e6 # check if array: if isinstance(tup, datetime.datetime): # not an array of objects tup = [tup] nTAI = 1 aflag = False else: nTAI = len(tup) aflag = True # convert them into a time tuple and find the correct leap seconds self.TAIleaps = TAIleaps leaps = [secs[0]] * nTAI leap_dates = [datetime.datetime(int(y), int(m), int(d)) for y, m, d, s in zip(year, mon, day, secs)] for i, itup in enumerate(tup): ind = bisect.bisect_right(leap_dates, tup[i]) leaps[i] = secs[ind - 1] ## ldatetime = datetime.datetime # avoid an expensive lookup below ## for i, itup in enumerate(tup): ## for y,m,d,s in zip(year, mon, day, secs): ## if tup[i] >= ldatetime(int(y),int(m),int(d)): ## leaps[i] = s ## else: ## break # if datetime.datetime(1971,12,31) > tup[0]: # print "WARNING: date before 1972/1/1; leap seconds are by fractions off" if aflag == False: self.leaps = int(leaps[0]) return int(leaps[0]) # if you want to allow fractional leap seconds, remove 'int' here else: self.leaps = np.array(leaps, dtype=int) return self.leaps
# -----------------------------------------------
[docs] @classmethod def now(cls): """ Creates a Ticktock object with the current time, equivalent to datetime.now() Returns ======= out : ticktock Ticktock object with the current time, equivalent to datetime.now() See Also ======== datetime.datetime.now """ dt = datetime.datetime.now() return Ticktock(dt, 'UTC')
# ----------------------------------------------- @classmethod def today(cls): """ Creates a Ticktock object with the current date and time set to 00:00:00, equivalent to date.today() with time included Returns ======= out : ticktock Ticktock object with the current time, equivalent to date.today() with time included See Also ======== datetime.date.today() """ dt = datetime.datetime.now() dt = dt.replace(hour=0, minute=0, second=0, microsecond=0) return Ticktock(dt, 'UTC')
# ----------------------------------------------- # End of Ticktock class # -----------------------------------------------
[docs]def doy2date(year, doy, dtobj=False, flAns=False): """ convert integer day-of-year doy into a month and day after http://pleac.sourceforge.net/pleac_python/datesandtimes.html Parameters ========== year : int or array of int year doy : int or array of int day of year Returns ======= month : int or array of int month as integer number day : int or array of int as integer number Examples ======== >>> month, day = doy2date(2002, 186) >>> dts = doy2date([2002]*4, range(186,190), dtobj=True) See Also ======== Ticktock.getDOY """ try: n_year = len(year) except TypeError: n_year = -1 # Special case: this is a scalar try: n_doy = len(doy) except TypeError: n_doy = -1 if n_doy != n_year: raise ValueError('Day of year and year must have same length') if n_year == -1: if doy < 1: raise ValueError( 'Day-of-Year less than 1 detected: DOY starts from 1') if not flAns: dt = datetime.datetime(int(year), 1, 1) + \ datetime.timedelta(days=int(doy) - 1) else: dt = datetime.datetime(year, 1, 1) + \ datetime.timedelta(days=float(doy) - 1) if dtobj: return dt else: return dt.month, dt.day if min(doy) < 1: raise ValueError('Day-of-Year less than 1 detected: DOY starts from 1') if flAns: dateobj = spacepy.datamodel.dmarray([datetime.datetime(year[i], 1, 1) + datetime.timedelta(days=float(doy[i]) - 1) for i in range(n_year)]) else: dateobj = spacepy.datamodel.dmarray([datetime.datetime(int(year[i]), 1, 1) + datetime.timedelta(days=int(doy[i]) - 1) for i in range(n_year)]) if dtobj: return dateobj else: return (spacepy.datamodel.dmarray([dt.month for dt in dateobj]), spacepy.datamodel.dmarray([dt.day for dt in dateobj]))
# -----------------------------------------------
[docs]def tickrange(start, end, deltadays, dtype=None): """ return a Ticktock range given the start, end, and delta Parameters ========== start : string or number start time (ISO standard string and UTC/datetime do not require a dtype) end : string or number last possible time in series (excluded unless end=start+n*step for integer n) deltadays : float or timedelta step in units of days (float); or datetime timedelta object dtype : string (optional) data type for start, end; e.g. ISO, UTC, RTD, etc. see Ticktock for all options Returns ======= out : Ticktock instance ticks Examples ======== >>> ticks = st.tickrange('2002-02-01T00:00:00', '2002-02-10T00:00:00', deltadays = 1) >>> ticks Ticktock( ['2002-02-01T00:00:00', '2002-02-02T00:00:00', '2002-02-03T00:00:00', '2002-02-04T00:00:00'] , dtype=ISO) See Also ======== Ticktock """ Tstart = Ticktock(start, dtype) Tend = Ticktock(end, dtype) diff = Tend.UTC[0] - Tstart.UTC[0] dmusec, dsec = diff.microseconds / 86400000000., diff.seconds / 86400. if isinstance(deltadays, datetime.timedelta): musec, sec = deltadays.microseconds / 86400000000., deltadays.seconds / 86400. deltat = musec + sec + deltadays.days nticks = int((dmusec + dsec + diff.days) / deltat + 1) trange = [Tstart.UTC[0] + deltadays * n for n in range(nticks)] else: nticks = int((dmusec + dsec + diff.days) / float(deltadays) + 1) trange = [Tstart.UTC[0] + datetime.timedelta(days=deltadays) * n for n in range(nticks)] ticks = Ticktock(trange, 'UTC') return ticks
[docs]def sec2hms(sec, rounding=True, days=False, dtobj=False): """Convert seconds of day to hours, minutes, seconds Parameters ========== sec : float Seconds of day Other Parameters ================ rounding : boolean set for integer seconds days : boolean set to wrap around day (i.e. modulo 86400) dtobj : boolean set to return a timedelta object Returns ======= out : [hours, minutes, seconds] or datetime.timedelta """ if rounding: sec = int(round(sec)) if not days: if sec > 86400: warnings.warn("Number of seconds > seconds in day. " "Try days keyword.") else: sec %= 86400 if dtobj: # no need to do the computation return datetime.timedelta(seconds=sec) else: hours = sec // 3600 minutes = ((sec - hours * 3600) // 60) % 60 seconds = sec % 60 return [hours, minutes, seconds]
def no_tzinfo(dt): """ take in an arraylike of datetime objects and return them without any tzinfo Parameters ========== dt : iterable iterable of datetime.datetime objects Returns ======= out : list list of datetime.datetime without tzinfo """ returnclass = dt.__class__ try: retval = [val.replace(tzinfo=None) for val in dt] except TypeError: # was not an iterable, assume datetime return dt.replace(tzinfo=None) #special case: numpy ndarray - dmarray and masked array work, but not ndarray if returnclass is not np.ndarray: return returnclass(retval) else: return np.asarray(retval)
[docs]def leapyear(year, numdays=False): """ return an array of boolean leap year, a lot faster than the mod method that is normally seen Parameters ========== year : array_like array of years numdays : boolean (optional) optionally return the number of days in the year Returns ======= out : numpy array an array of boolean leap year, or array of number of days Examples ======== >>> import numpy >>> import spacepy.time >>> spacepy.time.leapyear(numpy.arange(15)+1998) [False, False, True, False, False, False, True, False, False, False, True, False, False, False, True] """ year = np.asanyarray(year) mask400 = (year % 400) == 0 mask100 = (year % 100) == 0 mask4 = (year % 4) == 0 isleap = (mask400 | mask4) & (~mask100 | mask400) if numdays: isleap = isleap.astype(int) + 365 return isleap
[docs]def randomDate(dt1, dt2, N=1, tzinfo=False, sorted=False): """ Return a (or many) random datetimes between two given dates, this is done under the convention dt <=1 rand < dt2 Parameters ========== dt1 : datetime.datetime start date for the the random date dt2 : datetime.datetime stop date for the the random date Other Parameters ================ N : int (optional) the number of random dates to generate (defualt=1) tzinfo : bool (optional) maintain the tzinfo of the input datetimes (default=False) sorted : bool (optional) return the times sorted (default=False) Returns ======= out : datetime.datetime or numpy.ndarray of datetime.datetime the new time for the next call to EventTimer Examples ======== """ from matplotlib.dates import date2num, num2date if dt1.tzinfo != dt2.tzinfo: raise (ValueError('tzinfo for the input and output datetimes must match')) dt1n = date2num(dt1) dt2n = date2num(dt2) rnd_tn = np.random.uniform(dt1n, dt2n, size=N) rnd_t = num2date(rnd_tn) if not tzinfo: tzinfo = None else: tzinfo = dt1.tzinfo rnd_t = np.asarray([val.replace(tzinfo=tzinfo) for val in rnd_t]) if sorted: rnd_t.sort() return rnd_t
def extract_YYYYMMDD(filename): """ go through a string and extract the first valid YYYYMMDD as a datetime Parameters ========== filename : str string to parse for a YYYYMMDD format Returns ======= out : (None, datetime.datetime) the datetime found in the string or None """ # return a datetime if there is one from YYYYMMDD # be picky so don't match random numbers (1950-2049) m = re.search(r"(19[5-9]|20[0-4])\d(0\d|1[0-2])([0-2]\d|3[01])", os.path.basename(filename)) if not m: return None else: return datetime.datetime.strptime(m.group(), '%Y%m%d') def valid_YYYYMMDD(inval): """ if inval is valid YYYYMMDD return True, False otherwise """ if re.search(r"(19[5-9]|20[0-4])\d(0\d|1[0-2])([0-2]\d|3[01])", inval): return True else: return False