#!/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
import collections
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 pycdf import datamodel

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


# -----------------------------------------------
# Ticktock class
# -----------------------------------------------
class Ticktock(collections.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
    """
    def __init__(self, data, dtype=None):
        self._keylist = ['UTC','TAI', 'ISO', 'JD', 'MJD', 'UNX', 'RDT', 'CDF', 'GPS', 'DOY', 'eDOY', 'leaps']
        keylist_upper = [key.upper() for key in self._keylist]

        if isinstance(data, Ticktock):
            dtype = data.data.attrs['dtype']
            self.data = data.data
        else:
            try:
                datamodel.dmarray(data)[0]
            except IndexError:
                self.data = datamodel.dmarray([data])
            else:
                self.data = datamodel.dmarray(data)

            if not isinstance(dtype, collections.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'
                assert dtype.upper() in keylist_upper, "data type " + dtype +" not provided, only "+str(self._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 = self.data

        self.__isoformatstr = {'seconds': '%Y-%m-%dT%H:%M:%S', 'microseconds': '%Y-%m-%dT%H:%M:%S.%f'}
        self.__isofmt = self.__isoformatstr['seconds']

        try:
            self.data.attrs['dtype'] = dtype.upper()
        except AttributeError:
            self.data.attrs['dtype'] = str(dtype_func)
        else:
            if dtype.upper() == 'ISO':
                if self.data[0].find('Z'): #remove timezones
                    for i,v in np.ndenumerate(self.data):
                        self.data[i] = v.split('Z')[0]
                self.ISO = self.data
            self.update_items(self, 'data')
            if dtype.upper() == 'TAI': self.TAI = self.data
            if dtype.upper() == 'JD' : self.JD = self.data
            if dtype.upper() == 'MJD': self.MJD = self.data
            if dtype.upper() == 'UNX': self.UNX = self.data
            if dtype.upper() == 'RDT': self.RDT = self.data
            if dtype.upper() == 'CDF': self.CDF = self.data
            if dtype.upper() == 'UTC': self.UTC = 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')

        #del 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):
            try:
                assert (len(other)==len(self.data)) or (len(other)==1)
            except:
                raise ValueError('Ticktock lengths are mismatched, subtraction is not possible')
            else:
                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__'):
            try:
                assert isinstance(other[0], datetime.timedelta)
                assert (len(other)==len(self.data)) or (len(other)==1)
            except:
                raise TypeError("Data supplied for addition is of the wrong type or shape")
            else:
                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__'):
            try:
                assert isinstance(other[0], datetime.timedelta)
                assert (len(other)==len(self.data)) or (len(other)==1)
            except:
                raise TypeError("Data supplied for addition is of the wrong type or shape")
            else:
                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 self._keylist, "data type "+str(name)+" not provided, only "+str(self._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 eval('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 = eval('dum.{0}'.format(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]

    # -----------------------------------------------
    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

    # -----------------------------------------------
    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

    # -----------------------------------------------
    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 not fmt:
            print('Current ISO output format is %s' % self.__isofmt)
            print('Options are: {0}'.format([(k, self.__isoformatstr[k]) for k in list(self.__isoformatstr.keys())]))
        else:
            try:
                self.__isofmt = self.__isoformatstr[fmt]
                self.update_items(self, 'data')
            except KeyError:
                raise(ValueError('Not a valid option: Use {0}'.format(list(self.__isoformatstr.keys()))))

        return

    # -----------------------------------------------
    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

    # -----------------------------------------------
    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 = eval('self.'+dtype)
        return Ticktock(newdat, dtype)

    # -----------------------------------------------
    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 = eval('other.'+self.data.attrs['dtype'])
        newobj = Ticktock(np.append(self.data, otherdata), dtype=self.data.attrs['dtype'])
        return newobj

    # -----------------------------------------------
    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

    # -----------------------------------------------
    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 = datamodel.dmarray(DOY).astype(int)
        return self.DOY

    # -----------------------------------------------
    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 = datamodel.dmarray(eDOY)
        return self.eDOY


    # -----------------------------------------------
    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 = datamodel.dmarray(np.zeros(nTAI))

        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

    # -----------------------------------------------
    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

    # -----------------------------------------------
    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 = datamodel.dmarray(UNX)
        return self.UNX

    # -----------------------------------------------
    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, num2date
        #        import matplotlib.dates as mpd

        # nTAI = len(self.data)
        UTC = self.UTC
        #RDT = np.zeros(nTAI)
        RDT = datamodel.dmarray(date2num(UTC))
        #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 = RDT
        return self.RDT

    # -----------------------------------------------
    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 date2num, num2date

        nTAI = len(self.data)

        if self.data.attrs['dtype'].upper() == 'UTC':
            UTC = self.data # return

        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:
                        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 secinds 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
            # import matplotlib.dates as mpd
            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 self._keylist if kk not in ['DOY','eDOY','leaps']]))
            raise TypeError('{0}\n{1}'.format(warnstr1, warnstr2))

        UTC = datamodel.dmarray(UTC, attrs={'dtype': 'UTC'})
        self.UTC = UTC
        return self.UTC

    # -----------------------------------------------
    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

        """
        # fmt = '%Y-%m-%dT%H:%M:%S'
        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 = datamodel.dmarray(GPS)#.astype(int)
        return self.GPS


    # -----------------------------------------------
    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

        """

        fmt = '%Y-%m-%dT%H:%M:%S'
        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 = datamodel.dmarray(TAI)
        return self.TAI

    # -----------------------------------------------
    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)
        ISO = ['']*nTAI
        self.TAI = self.getTAI()
        ISO = [utc.strftime(self.__isofmt) for utc in self.UTC]
        for i in range(nTAI):
            if self.TAI[i] in self.TAIleaps:
                tmptick = Ticktock(self.UTC[i] - datetime.timedelta(seconds=1), 'UTC')
                a,b,c = tmptick.ISO[0].split(':')
                cnew = c.replace('59','60')
                ISO[i] = a+':'+b+':'+cnew

        self.ISO = datamodel.dmarray(ISO)
        return self.ISO

    # -----------------------------------------------
    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 type(tup) == type(datetime.datetime(1,1,1)): # 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

    # -----------------------------------------------
    @classmethod
    def now(self):
        """
        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(self):
        """
        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
# -----------------------------------------------

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 = [datetime.datetime(year[i], 1, 1) +
                   datetime.timedelta(days=float(doy[i]) - 1)
                   for i in range(n_year)]
    else:
        dateobj = [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 [dt.month for dt in dateobj], [dt.day for dt in dateobj]



# -----------------------------------------------
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.
    try:
        assert type(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)]
    except:
        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


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 = int(sec)//3600
        minutes = int((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
    """
    try:
        return [val.replace(tzinfo=None) for val in dt]
    except TypeError: # was not an iterable
        return dt.replace(tzinfo=None)

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]
    """
    if not isinstance(year, (tuple, np.ndarray, list)):
        year = [year]
    mask400 = [(val % 400) == 0 for val in year]   # this is a leap year
    mask100 = [(val % 100) == 0 for val in year ]   # these are not leap years
    mask4   = [(val % 4) == 0 for val in year ]   # this is a leap year
    if numdays:
        numdays=365
        ans = [numdays + ((val[0] | val[2]) & (~val[1] | val[0])) for val in zip(mask400, mask100, mask4)]
        if len(ans) == 1:
            return ans[0]
        else:
            return ans
    else:
        ans = [bool(((val[0] | val[2]) & (~val[1] | val[0]))) for val in zip(mask400, mask100, mask4)]
        if len(ans) == 1:
            return ans[0]
        else:
            return ans


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
