def dt2ymdhms(dtin, with_microsec=True):
    """
    Time conversion

    Python's Datetime => Lists of Years, Months, Days, Hours, Minutes, Seconds

    Parameters
    ----------
        dtin : datetime or list/numpy.array of datetime
            Python DateTime

        with_microsec : bool 
            if False : rounding the microsecs to the nearest sec

    Returns
    -------
        tuple with year , month , day , hour , minute , second , microsecond
    """
    if not cnv_gen.is_iterable(dtin):
        if with_microsec:
            return (dtin.year, dtin.month, dtin.day, dtin.hour, dtin.minute,
                    dtin.second, dtin.microsecond)
        else:
            dt2 = roundTime(dtin, 1)
            return (dt2.year, dt2.month, dt2.day, dt2.hour, dt2.minute,
                    dt2.second)
    else:
        return [dt2ymdhms(e, with_microsec) for e in dtin]
def dt2posix(dtin, out_array=False):
    """
    Time conversion
    
    Python's Datetime => POSIX Time

    Parameters
    ----------
    dtin : datetime or list/numpy.array of datetime.
        Datetime(s).  Can handle several datetimes in an iterable.
    out_array : bool
        if iterable as input, force the output as a Numpy array

    Returns
    -------
    L : float or list/numpy.array of floats
        POSIX Time(s)  
    """
    if not cnv_gen.is_iterable(dtin):
        D = dtin - dt.datetime(1970, 1, 1)
        return D.days * 86400 + D.seconds + D.microseconds * 10**-6
    else:
        L = [dt2posix(e) for e in dtin]
        if out_array:
            return np.array(L)
        else:
            return L
def dt_round(dtin=None, roundTo=60):
    """
    Round a datetime object to any time laps in seconds
    
    Parameters
    ----------
    dtin : datetime or list/numpy.array of datetime 
        Datetime you want to round, default now.
        Can handle several datetimes in an iterable.
            
    roundTo : int
        Closest number of seconds to round to, default 1 minute.
            
    Returns
    -------  
    dtout : datetime or list/numpy.array of datetime
        Rounded Datetime
        
    Note
    ---- 
    Based on :
    http://stackoverflow.com/questions/3463930/how-to-round-the-minute-of-a-datetime-object-python
    """
    import datetime as dtmod

    if cnv_gen.is_iterable(dtin):
        return [dt_round(e, roundTo=roundTo) for e in dtin]
    else:
        if dtin == None:
            dtin = dtmod.datetime.now()
        seconds = (dtin - dtin.min).seconds
        # // is a floor division, not a comment on following line:
        rounding = (seconds + roundTo / 2) // roundTo * roundTo
        return dtin + dtmod.timedelta(0, rounding - seconds, -dtin.microsecond)
def dt2str(dtin, str_format="%Y-%m-%d %H:%M:%S"):
    """
    Time conversion
    
    Python's Datetime => String

    Just a wrapper of strftime
    
    Parameters
    ----------
    dtin : datetime or list/numpy.array of datetime.
        Datetime(s).  Can handle several datetimes in an iterable.
        
    Returns
    -------
    L : string or list of strings
        Time as string(s)  
        
    Source
    ------
    https://stackoverflow.com/questions/7999935/python-datetime-to-string-without-microsecond-component
    
    http://www.jacksay.com/tutoriaux/bash-shell/bashshell-utilisation-commande-date.html
    """

    if cnv_gen.is_iterable(dtin):
        return [dt2str(e) for e in dtin]
    else:
        return dtin.strftime(str_format)
def dt2gpsweek_decimal(dtin, return_middle_of_day=True):
    """
    Time conversion
    
    Python's datetime => GPS week
    
    Return the GPS week in a decimal mode
    
    (Easier for plots)

    Parameters
    ----------
    dtin : datetime or list/numpy.array of datetime
        Datetime(s). Can handle several datetimes in an iterable.
        
    return_middle_of_day : bool
        if False, return the decimal part of the week at the begining of the day
    
    Returns
    -------
    GPS_week_decimal : float
        decimal GPS week
        
        Is a list of float if the input is an iterable
    """
    if cnv_gen.is_iterable(dtin):
        return np.array([dt2gpsweek_decimal(e) for e in dtin])
    else:
        if return_middle_of_day:
            mod = 0.5
        else:
            mod = 0
        week, day = dt2gpstime(dtin)
        return float(week) + (float(day + mod) / 7)
def numpy_datetime2dt(npdtin):
    """
    Time conversion
    
    Numpy Datetime => Datetime

    Parameters
    ----------
    npdtin : np.datetime64 or list/numpy.array of np.datetime64 
        Numpy Datetime.  Can handle several time in a list.
                
    Returns
    -------
    python_datetime : datetime or list/numpy.array of datetime
        Converted Datetime(s)
        
    Source
    ------
        https://stackoverflow.com/questions/29753060/how-to-convert-numpy-datetime64-into-datetime/29755657
    """
    if cnv_gen.is_iterable(npdtin):
        return [numpy_datetime2dt(e) for e in npdtin]
    else:
        python_datetime = npdtin.astype('M8[ms]').astype('O')
    return python_datetime
def jjulCNES2dt(jjulin):
    """
    Time conversion
    
    Julian Day CNES => Python's Datetime

    Parameters
    ----------
    jjulin : float/int or list/numpy.array of float/int.
        Julian Day CNES.  Can handle several float/int in an iterable.

    Returns
    -------
    L : datetime or list of datetime.
        Datetime(s)

    Note
    ----
    Julian Day CNES starts at 0h Jan 1, 1950 (JD − 2433282.5)
    
    https://www.aviso.altimetry.fr/fr/donnees/outils/jours-calendaires-ou-jours-juliens.html
    """

    if cnv_gen.is_iterable(jjulin):
        return [jjulCNES2dt(e) for e in jjulin]
    else:
        return dt.datetime(1950, 0o1, 0o1, 00, 00, 00) + dt.timedelta(
            float(jjulin))
def dt_gpstime2dt_utc(dtgpsin, out_array=False):
    """
    Time conversion
    
    Datetime in GPS Time Scale => Datetime in UTC Time Scale
    
    Correct both the Leap second and the 19sec difference between GPS Time and UTC

    Parameters
    ----------
    dtin : datetime or list/numpy.array of datetime.
        Datetime(s) in GPS Time Scale.  Can handle several datetimes in an iterable.
    out_array : bool
        if iterable as input, force the output as a Numpy array

    Returns
    -------
    L : datetime or list/numpy.array of datetime.
        Datetime(s) in UTC Time Scale
    """
    # on converti le dt gps en dt utc
    if cnv_gen.is_iterable(dtgpsin):
        Out = [dt_gpstime2dt_utc(e) for e in dtgpsin]
        if not out_array:
            return Out
        else:
            return np.array(Out)
    else:
        leapsec = find_leapsecond(dtgpsin)
        dtutc = dtgpsin + dt.timedelta(seconds=19) - dt.timedelta(
            seconds=leapsec)
        return dtutc
def tgipsy2dt(tin):
    """
    Time conversion
    
    GIPSY Time => Datetime

    Parameters
    ----------
    tin : float or list/numpy.array of float
        GIPSY time(s). Can handle several time float in a list.
                
    Returns
    -------
    dtout : datetime or list/numpy.array of datetime
        Converted Datetime(s)
        
    Note
    ----
    GIPSY time is not the 'real' J2000
    but only counted starting from 1st January 2000 at Noon
    """
    if cnv_gen.is_iterable(tin):
        return [tgipsy2dt(e) for e in tin]
    else:
        j2000 = dt.datetime(2000, 1, 1, 12, 0)
        tout = j2000 + dt.timedelta(seconds=float(tin))
        return tout

    return tout
def date_string_2_dt(strin):
    """
    Time conversion
    
    String => Python's Datetime
    
    Wrapper of dateutil.parser.parse
    
    The input string should looks like a timestamp
    
    Parameters
    ----------
    strin : string or list/numpy.array of strings.
        string(s). Can handle several datetimes in an iterable.

    Returns
    -------
    L : datetime or list of datetime.
        Datetime(s) 
    """

    import dateutil.parser
    if cnv_gen.is_iterable(strin):
        return np.array([date_string_2_dt(e) for e in strin])
    else:
        return dateutil.parser.parse(strin)
def dt2gpstime(dtin,dayinweek=True):
    
    """
    Time conversion
    
    Python's datetime => GPS time

    Parameters
    ----------
    dtin : datetime or list/numpy.array of datetime
        Datetime(s). Can handle several datetimes in an iterable.
        
    dayinweek : bool
        if True : returns  GPS week, day in GPS week
        
        if False : returns  GPS week, sec in GPS week
    Returns
    -------
    GPS_week, GPS_day/GPS_sec : tuple of int
        GPS week or GPS day/GPS sec
        
        Is a list of tuple if the input is an iterable
    """
    
    if cnv_gen.is_iterable(dt):
        return [dt2gpstime(e) for e in dtin]
        
    else:
        week , secs = utc2gpstime(dtin.year,dtin.month,dtin.day,dtin.hour,
                                  dtin.minute,dtin.second)
        if dayinweek:
            day = np.floor(np.divide(secs,86400))
            return int(week) , int(day)
        else:
            return int(week) , int(secs)
def posix2dt(posixin, out_array=False):
    """
    Time conversion
    
    POSIX Time => Python's Datetime

    Parameters
    ----------
    posixin : float or list/numpy.array of floats.
        POSIX Time.  Can handle several time in a list.
    out_array : bool
        if iterable as input, force the output as a Numpy array

    Returns
    -------
    L : datetime or list/numpy.array of datetime.
        Converted Datetime(s)
    """

    if not cnv_gen.is_iterable(posixin):
        if np.isnan(posixin):
            return dt.datetime(1970, 1, 1)
        else:
            return dt.datetime(1970, 1, 1) + dt.timedelta(seconds=posixin)
    else:
        L = [posix2dt(e) for e in posixin]
        if out_array:
            return np.array(L)
        else:
            return L
def doy2dt(year, days, hours=0, minutes=0, seconds=0):
    """
    Time conversion
    
    Day of Year Time => Python's datetime

    Parameters
    ----------
    year, days : float or list/numpy.array of floats.
        year, days of year
    hours, minutes, seconds : float or list/numpy.array of floats, optional
        hours, minutes, seconds

    Returns
    -------
    L : datetime or list/numpy.array of datetime.
        Datetime(s)
    """
    if not cnv_gen.is_iterable(year):
        # All this because Python cant handle int with a starting with 0 (like 08)
        # => SyntaxError: invalid token
        year = int(str(year))
        days = int(str(days))
        hours = float(str(hours))
        minutes = float(str(minutes))
        seconds = float(str(seconds))

        tempsecs = seconds + 60 * minutes + 3600 * hours
        #finalsecs     = np.floor(tempsecs)
        finalmicrosec = np.round(tempsecs * 10**6)

        return dt.datetime(year, 1, 1) + dt.timedelta(days - 1) + \
        dt.timedelta(microseconds=finalmicrosec)

    else:
        if not cnv_gen.is_iterable(hours):
            hours = [0] * len(year)
        if not cnv_gen.is_iterable(minutes):
            minutes = [0] * len(year)
        if not cnv_gen.is_iterable(seconds):
            seconds = [0] * len(year)

        outlis = []
        for y, d, h, m, s in zip(year, days, hours, minutes, seconds):
            outlis.append(doy2dt(y, d, h, m, s))
        return outlis
def posix2dt_in_local_timezone(posixin):
    if not cnv_gen.is_iterable(posixin):
        if np.isnan(posixin):
            return dt.datetime(1970, 1, 1)
        else:
            return dt.datetime.fromtimestamp(posixin)
    else:
        return [posix2dt_in_local_timezone(e) for e in posixin]
def gpsweek_decimal2dt(gpsweekdec_in):
    if cnv_gen.is_iterable(gpsweekdec_in):
        return [gpsweek_decimal2dt(e) for e in gpsweekdec_in]
    else:
        week_floor = np.floor(gpsweekdec_in)
        week_dec_part = gpsweekdec_in - week_floor

        dt_out = gpstime2dt(week_floor, week_dec_part * 7)
        return dt_out
def datestr_gins_filename_2_dt(datestrin):
    """
    Time conversion
    
    GINS filename time format => Python's Datetime 
    
    GINS filename time format : 'YYMMDD_HHMMSS'
    
    It is the time format used in the listing names
    
    Parameters
    ----------
    datestrin : string or list/numpy.array of string.
        GINS filename time format string(s).  
        Can handle several string in an iterable.
    
    Returns
    -------
    dtout : datetime or list of datetime.
        Datetime(s)
    """
    
    if cnv_gen.is_iterable(datestrin):
        return [datestr_gins_filename_2_dt(e) for e in datestrin]
    
    else:
        #### CASE WHERE THE DATE LOOKS LIKE 00000:00000

        yr = int(datestrin[0:2])
        mm = int(datestrin[2:4])
        dd = int(datestrin[4:6])
        
        hh   = int(datestrin[7:9])
        mmin = int(datestrin[9:11])
        ss   = int(datestrin[11:13])
    
        if yr > 50:
            year = 1900 + yr
        else:
            year = 2000 + yr
    
        return dt.datetime(year,mm,dd,hh,mmin,ss)

        
        hh   = int(datestrin[7:9])
        mmin = int(datestrin[9:11])
        ss   = int(datestrin[11:13])
    
        if yr > 50:
            year = 1900 + yr
        else:
            year = 2000 + yr
    
        return dt.datetime(year,mm,dd,hh,mmin,ss)
def dt2gpstime(dtin, dayinweek=True, inp_ref="utc"):
    """
    Time conversion
    
    Python's datetime => GPS time

    Parameters
    ----------
    dtin : datetime or list/numpy.array of datetime
        Datetime(s). Can handle several datetimes in an iterable.
        
    inp_ref : str
        "utc" : apply the 19 sec & leap second correction at the epoch 
        "gps" : no correction applied 
        "tai" : apply -19 sec correction
        
    dayinweek : bool
        if True : returns  GPS week, day in GPS week
        
        if False : returns  GPS week, sec in GPS week
    Returns
    -------
    GPS_week, GPS_day/GPS_sec : tuple of int
        GPS week or GPS day/GPS sec
        
        Is a list of tuple if the input is an iterable
    """

    if cnv_gen.is_iterable(dtin):
        return [dt2gpstime(e) for e in dtin]

    else:
        week_raw, secs_raw = utc2gpstime(dtin.year, dtin.month, dtin.day,
                                         dtin.hour, dtin.minute, dtin.second)

        utc_offset = find_leapsecond(dtin)

        if inp_ref == "utc":
            ### utc : utc2gpstime did the job
            week, secs = week_raw, secs_raw

        elif inp_ref == "tai":
            ### tai : utc2gpstime did the job, but needs leap sec correction again
            week, secs = week_raw, secs_raw - utc_offset

        elif inp_ref == "gps":
            ### tai : utc2gpstime did the job, but needs leap sec & 19sec correction again
            week, secs = week_raw, secs_raw + 19 - utc_offset

        if dayinweek:
            day = np.floor(np.divide(secs, 86400))
            return int(week), int(day)
        else:
            return int(week), int(secs)
def datestr_sinex_2_dt(datestrin):
    """
    Time conversion
    
    SINEX time format => Python's Datetime 
    
    SINEX time format : 'YY:DDD:SSSSS' or 'YYYY:DDD:SSSSS'
    
    Parameters
    ----------
    datestrin : string or list/numpy.array of string.
        SINEX time format string(s).  Can handle several string in an iterable.
    
    Returns
    -------
    dtout : datetime or list of datetime.
        Datetime(s)
    """

    if cnv_gen.is_iterable(datestrin):
        return [datestr_sinex_2_dt(e) for e in datestrin]
    else:
        #### CASE WHERE THE DATE LOOKS LIKE 00000:00000
        if re.search("[0-9]{5}:[0-9]{5}", datestrin):
            datestr_list = list(datestrin)
            datestr_list.insert(2, ":")
            datestrin = "".join(datestr_list)
        elif '00:000:00000' in datestrin:
            return dt.datetime(1970, 1, 1)

        dateint = [int(e) for e in datestrin.split(':')]
        yr = dateint[0]
        doy = dateint[1]
        sec = dateint[2]

        ## case for year with only 2 digits
        if re.match("[0-9]{2}:[0-9]{3}:[0-9]{5}", datestrin):
            if yr > 50:
                year = 1900 + yr
            else:
                year = 2000 + yr
        else:
            year = yr

        return doy2dt(year, doy, seconds=sec)
def MJD2dt(mjd_in, seconds=None):
    # cf http://en.wikipedia.org/wiki/Julian_day
    """
    Time conversion
    
    Modified Julian Day  => Python's Datetime

    Parameters
    ----------
    mjd_in : float/int or list/numpy.array of float/int.
        Modified Julian Day.  Can handle several float/int in an iterable.

    Returns
    -------
    L : datetime or list of datetime.
        Datetime(s)

    Note
    ----
    Modified Julian Day starts at 0h Nov 17, 1858  (JD − 2400000.5)
    
    https://en.wikipedia.org/wiki/Julian_day
    """

    # ITERABLE CASE
    if cnv_gen.is_iterable(mjd_in):
        if seconds:
            if len(seconds) != len(mjd_in):
                print("ERR : MJD2dt : len(seconds) != len(mjd_in) !!")
                raise Exception
        else:
            seconds = np.zeros(len(mjd_in))

        return [
            dt.datetime(1858, 11, 17) + dt.timedelta(days=m, seconds=sec)
            for m, sec in zip(mjd_in, seconds)
        ]

    # NON ITERABLE / FLOAT CASE
    else:
        if not seconds:
            seconds = 0
        return dt.datetime(1858, 11, 17) + dt.timedelta(days=mjd_in,
                                                        seconds=seconds)
def dt2fracday(dtin):
    """
    Python's datetime => Seconds in days

    Parameters
    ----------
    dtin : datetime or list/numpy.array of datetime
        Datetime(s). Can handle several datetimes in an iterable.
        
    Returns
    -------
    fraction_day : float
        Fractional of the day
        Is a list of int if the input is an iterable
    """
    if cnv_gen.is_iterable(dtin):
        return [dt2fracday(e) for e in dtin]
    else:
        return dtin.hour / 24 + dtin.minute / (60*24) + dtin.second / (60*60*24)
def dt2year_decimal(dtin):
    """
    Time conversion
    
    Python's Datetime => Decimal Year

    Parameters
    ----------
    dtin : datetime or list/numpy.array of datetime.
        Datetime(s).  Can handle several datetimes in an iterable.

    Returns
    -------
    L : float or list/numpy.array of floats
        Decimal Year(s)  
    """
    if cnv_gen.is_iterable(dtin):
        return np.array([dt2year_decimal(e) for e in dtin])
    else:
        return toYearFraction(dtin)
def year_decimal2dt(yearin):
    """
    Time conversion
    
    Decimal Year => Python's Datetime 

    Parameters
    ----------
    yearin : float or list/numpy.array of floats.
        Decimal year(s).  Can handle several floats in an iterable.

    Returns
    -------
    L : datetime or list of datetime.
        Datetime(s)  
    """
    if cnv_gen.is_iterable(yearin):
        return [year_decimal2dt(e) for e in yearin]
    else:
        return convert_partial_year(yearin)
def dt2secinday(dtin):
    """
    Time conversion
    
    Python's datetime => Seconds in days

    Parameters
    ----------
    dtin : datetime or list/numpy.array of datetime
        Datetime(s). Can handle several datetimes in an iterable.
        
    Returns
    -------
    secinday : int
        Seconds in the day
        Is a list of int if the input is an iterable
    """
    if cnv_gen.is_iterable(dtin):
        return [dt2secinday(e) for e in dtin]
    else:
        return dtin.hour * 3600 + dtin.minute * 60 + dtin.second
def matlab_time2dt(matlab_datenum):
    """
    Time conversion
    
    MATLAB Time => Datetime

    Parameters
    ----------
    matlab_datenum : float or list/numpy.array of float
        MATLAB time(s).  Can handle several time floats in a list.
                
    Returns
    -------
    python_datetime : datetime or list/numpy.array of datetime
        Converted Datetime(s)
    """
    if cnv_gen.is_iterable(matlab_datenum):
        return [matlab_time2dt(e) for e in matlab_datenum]
    else:
        python_datetime = dt.datetime.fromordinal(int(matlab_datenum)) + \
        dt.timedelta(days=matlab_datenum%1) - dt.timedelta(days = 366)
    return python_datetime
def dt2doy_year(dtin, outputtype=str):
    """
    Time conversion
    
    Python's datetime => Day of Year, Year

    Parameters
    ----------
    dtin : datetime or list/numpy.array of datetime
        Datetime(s). Can handle several datetimes in an iterable.
        
    Returns
    -------
    doy , year : tuple of int
        Day of Year and Year
        Is a list of tuple if the input is an iterable
    """

    if cnv_gen.is_iterable(dtin):
        return [dt2doy_year(e, outputtype=outputtype) for e in dtin]

    else:
        return outputtype(dtin.strftime('%j')), outputtype(dtin.strftime('%Y'))
def dt_in_local_timezone2posix(dtin):
    if not cnv_gen.is_iterable(dtin):
        return time.mktime(dtin.timetuple()) + dtin.microsecond * 0.000001
    else:
        return [dt_in_local_timezone2posix(e) for e in dtin]