from datetime import datetime, timedelta, timezone
import numpy as np
from .tools.misc import fillgaps
def _fullyear(year):
    if year > 100:
        return year
    year += 1900 + 100 * (year < 90)
    return year
[docs]
def epoch2dt64(ep_time):
    """
    Convert from epoch time (seconds since 1/1/1970 00:00:00) to
    numpy.datetime64 array
    Parameters
    ----------
    ep_time : xarray.DataArray
      Time coordinate data-array or single time element
    Returns
    -------
    time : numpy.datetime64
      The converted datetime64 array
    """
    # assumes t0=1970-01-01 00:00:00
    out = np.array(ep_time.astype("int")).astype("datetime64[s]")
    out = out + ((ep_time % 1) * 1e9).astype("timedelta64[ns]")
    return out 
[docs]
def dt642epoch(dt64):
    """
    Convert numpy.datetime64 array to epoch time
    (seconds since 1/1/1970 00:00:00)
    Parameters
    ----------
    dt64 : numpy.datetime64
      Single or array of datetime64 object(s)
    Returns
    -------
    time : float
      Epoch time (seconds since 1/1/1970 00:00:00)
    """
    return dt64.astype("datetime64[ns]").astype("float") / 1e9 
[docs]
def date2dt64(dt):
    """
    Convert numpy.datetime64 array to list of datetime objects
    Parameters
    ----------
    time : datetime.datetime
      The converted datetime object
    Returns
    -------
    dt64 : numpy.datetime64
      Single or array of datetime64 object(s)
    """
    return np.array(dt).astype("datetime64[ns]") 
[docs]
def dt642date(dt64):
    """
    Convert numpy.datetime64 array to list of datetime objects
    Parameters
    ----------
    dt64 : numpy.datetime64
      Single or array of datetime64 object(s)
    Returns
    -------
    time : datetime.datetime
      The converted datetime object
    """
    return epoch2date(dt642epoch(dt64)) 
[docs]
def epoch2date(ep_time, offset_hr=0, to_str=False):
    """
    Convert from epoch time (seconds since 1/1/1970 00:00:00) to a list
    of datetime objects
    Parameters
    ----------
    ep_time : xarray.DataArray
      Time coordinate data-array or single time element
    offset_hr : int
      Number of hours to offset time by (e.g. UTC -7 hours = PDT)
    to_str : logical
      Converts datetime object to a readable string
    Returns
    -------
    time : datetime.datetime
      The converted datetime object or list(strings)
    Notes
    -----
    The specific time instance is set during deployment, usually sync'd to the
    deployment computer. The time seen by DOLfYN is in the timezone of the
    deployment computer, which is unknown to DOLfYN.
    """
    try:
        ep_time = ep_time.values
    except AttributeError:
        pass
    if isinstance(ep_time, (np.ndarray)) and ep_time.ndim == 0:
        ep_time = [ep_time.item()]
    elif not isinstance(ep_time, (np.ndarray, list)):
        ep_time = [ep_time]
    ######### IMPORTANT #########
    # Note the use of `utcfromtimestamp` here, rather than `fromtimestamp`
    # This is CRITICAL! See the difference between those functions here:
    #    https://docs.python.org/3/library/datetime.html#datetime.datetime.fromtimestamp
    # Long story short: `fromtimestamp` used system-specific timezone
    # info to calculate the datetime object, but returns a
    # timezone-agnostic object.
    if offset_hr != 0:
        delta = timedelta(hours=offset_hr)
        time = [datetime.utcfromtimestamp(t) + delta for t in ep_time]
    else:
        time = [datetime.utcfromtimestamp(t) for t in ep_time]
    if to_str:
        time = date2str(time)
    return time 
[docs]
def date2str(dt, format_str=None):
    """
    Convert list of datetime objects to legible strings
    Parameters
    ----------
    dt : datetime.datetime
      Single or list of datetime object(s)
    format_str : string
      Timestamp string formatting. Default is '%Y-%m-%d %H:%M:%S.%f'
      See datetime.strftime documentation for timestamp string formatting.
    Returns
    -------
    time : string
      Converted timestamps
    """
    if format_str is None:
        format_str = "%Y-%m-%d %H:%M:%S.%f"
    if not isinstance(dt, list):
        dt = [dt]
    return [t.strftime(format_str) for t in dt] 
[docs]
def date2epoch(dt):
    """
    Convert list of datetime objects to epoch time
    Parameters
    ----------
    dt : datetime.datetime
      Single or list of datetime object(s)
    Returns
    -------
    time : float
      Datetime converted to epoch time (seconds since 1/1/1970 00:00:00)
    """
    if not isinstance(dt, list):
        dt = [dt]
    return [t.replace(tzinfo=timezone.utc).timestamp() for t in dt] 
[docs]
def date2matlab(dt):
    """
    Convert list of datetime objects to MATLAB datenum
    Parameters
    ----------
    dt : datetime.datetime
      List of datetime objects
    Returns
    -------
    time : float
      List of timestamps in MATLAB datnum format
    """
    time = list()
    for i in range(len(dt)):
        mdn = dt[i] + timedelta(days=366)
        frac_seconds = (
            dt[i] - datetime(dt[i].year, dt[i].month, dt[i].day, 0, 0, 0)
        ).seconds / (24 * 60 * 60)
        frac_microseconds = dt[i].microsecond / (24 * 60 * 60 * 1000000)
        time.append(mdn.toordinal() + frac_seconds + frac_microseconds)
    return time 
[docs]
def matlab2date(matlab_dn):
    """
    Convert MATLAB datenum to list of datetime objects
    Parameters
    ----------
    matlab_dn : float
      List of timestamps in MATLAB datnum format
    Returns
    -------
    dt : datetime.datetime
      List of datetime objects
    """
    time = list()
    for i in range(len(matlab_dn)):
        day = datetime.fromordinal(int(matlab_dn[i]))
        dayfrac = timedelta(days=matlab_dn[i] % 1) - timedelta(days=366)
        time.append(day + dayfrac)
        # Datenum is precise down to 100 microseconds - add difference to round
        us = int(round(time[i].microsecond / 100, 0)) * 100
        time[i] = time[i].replace(microsecond=time[i].microsecond) + timedelta(
            microseconds=us - time[i].microsecond
        )
    return time 
def _fill_time_gaps(epoch, sample_rate_hz):
    """
    Fill gaps (NaN values) in the timeseries by simple linear
    interpolation.  The ends are extrapolated by stepping
    forward/backward by 1/sample_rate_hz.
    """
    # epoch is seconds since 1970
    dt = 1.0 / sample_rate_hz
    epoch = fillgaps(epoch)
    if np.isnan(epoch[0]):
        i0 = np.nonzero(~np.isnan(epoch))[0][0]
        delta = np.arange(-i0, 0, 1) * dt
        epoch[:i0] = epoch[i0] + delta
    if np.isnan(epoch[-1]):
        # Search backward through the array to get the 'negative index'
        ie = -np.nonzero(~np.isnan(epoch[::-1]))[0][0] - 1
        delta = np.arange(1, -ie, 1) * dt
        epoch[(ie + 1) :] = epoch[ie] + delta
    return epoch