# -*- coding: utf-8 -*-
"""
Basic time variable manipulation functions.
"""
#Functions for manipulating time variables associated with *in situ* measurment
#and modelled data.
#
#Chris Old
#IES, School of Engineering, University of Edinburgh
#Jan 2020
# ----------------------------------------------------------------------------
# IMPORTS
# ----------------------------------------------------------------------------
# Standard Python Dependencies
from datetime import datetime, timedelta
import numpy as np
# Non-Standard Python Dependencies
# Local Module Dependencies
# Other Dependencies
# ----------------------------------------------------------------------------
# GLOBAL VARIABLES
# ----------------------------------------------------------------------------
stdTimeUnits = 'days since 0001-01-01 00:00:00'
stdTimeFmt = "%Y-%m-%d %H:%M:%S"
decTimeFmt = "%Y-%m-%d %H:%M:%S.%f" # Include fractions of a second
stdCalendar = 'gregorian'
stdEpochStr = "0001-01-01 00:00:00"
julianDay_Offset = 2400000.500
# ----------------------------------------------------------------------------
# CLASS DEFINITIONS
# ----------------------------------------------------------------------------
[docs]
class temporalCoverage:
"""Object used to determine if any part of a time record lies within a time bound
defined by timeStart and timeEnd
"""
def __init__(self, timeStart, timeEnd):
self.timeRangeStr = [timeStart, timeEnd]
self.timeRangeNum = [dateNumFromDateStr(timeStart),
dateNumFromDateStr(timeEnd)]
self.units = stdTimeUnits
self.format = stdTimeFmt
self.calendar = stdCalendar
def timeOverlap(self, tRec):
if len(tRec) > 0:
t0 = tRec[0]
t1 = tRec[-1]
case01 = (t0 >= min(self.timeRangeNum)) & \
(t1 <= max(self.timeRangeNum))
case02 = (t0 < min(self.timeRangeNum)) & \
(t1 >= min(self.timeRangeNum))
case03 = (t0 < max(self.timeRangeNum)) & \
(t1 >= max(self.timeRangeNum))
case04 = (t0 < min(self.timeRangeNum)) & \
(t1 > max(self.timeRangeNum))
overlaps = case01 or case02 or case03 or case04
if overlaps:
tindx0 = np.where(tRec >= self.timeRangeNum[0])[0][0]
tindx1 = np.where(tRec <= self.timeRangeNum[1])[0][-1]
indices = [tindx0, tindx1]
else:
indices = [None, None]
else:
overlaps = False
indices = [None, None]
return overlaps, indices
class dateTime:
nparray = type(np.asarray([]))
def __init__(self,dt,tfmt=stdTimeFmt):
self.dt = dt
self.dt_type = type(dt)
self.timeFmt = tfmt
def dateStr(self):
if self.dt_type in [list,tuple,self.nparray]:
ds = self.dt.copy()
for i,d in enumerate(self.dt):
ds[i] = self.__dt2ds(d)
else:
ds = self.__dt2ds(self.dt)
return ds
def dateNum(self):
if self.dt_type in [list,tuple,self.nparray]:
dn = self.dt.copy()
for i,d in enumerate(self.dt):
dn[i] = self.__dt2dn(d)
else:
dn = self.__dt2dn(self.dt)
return dn
def __dt2ds(self,dt):
ds = dt.strftime(self.timeFmt)
return ds
def __dt2dn(self,dt):
dn = dt.toordinal() + (dt-datetime.fromordinal(dt.toordinal())).total_seconds()/(24*60*60)
return dn
class dateString:
nparray = type(np.asarray([]))
def __init__(self,ds,tfmt=stdTimeFmt):
self.ds = ds
self.ds_type = type(ds)
self.timeFmt = tfmt
def dateTime(self):
if self.ds_type in [list,tuple,self.nparray]:
dt = self.ds.copy()
for i,d in enumerate(self.ds):
dt[i] = self.__ds2dt(d)
else:
dt = self.__ds2dt(self.ds)
return dt
def dateNum(self):
if self.ds_type in [list,tuple,self.nparray]:
dn = self.ds.copy()
for i,d in enumerate(self.ds):
dt = self.__ds2dt(d)
dn[i] = self.__dt2dn(dt)
else:
dt = self.__ds2dt(self.ds)
dn = self.__dt2dn(dt)
return dn
def __ds2dt(self,ds):
dt = datetime.strptime(ds,self.timeFmt)
return dt
def __dt2dn(self,dt):
dn = dt.toordinal() + (dt-datetime.fromordinal(dt.toordinal())).total_seconds()/(24*60*60)
return dn
class dateNumber:
nparray = type(np.asarray([]))
def __init__(self,dn,tfmt=stdTimeFmt):
self.dn = dn
self.dn_type = type(dn)
self.timeFmt = tfmt
def dateTime(self):
if self.dn_type in [list,tuple,self.nparray]:
dt = self.dn.copy()
for i,d in enumerate(self.dn):
dt[i] = self.__dn2dt(d)
else:
dt = self.__dn2dt(self.dn)
return dt
def dateStr(self):
if self.dn_type in [list,tuple,self.nparray]:
ds = self.dn.copy()
for i,d in enumerate(self.dn):
dt = self.__dn2dt(d)
ds[i] = self.__dt2ds(dt)
else:
dt = self.__dn2dt(self.dn)
ds = self.__dt2ds(dt)
return ds
def __dn2dt(self,dn):
dt = datetime.fromordinal(int(dn)) + timedelta(days=(dn%1))
return dt
def __dt2ds(self,dt):
ds = dt.strftime(self.timeFmt)
return ds
# ----------------------------------------------------------------------------
# FUNCTION DEFINITIONS
# ----------------------------------------------------------------------------
[docs]
def datetimeFromStr(dateStr, timeFmt=stdTimeFmt):
"""Generates a python datetime object from a date string with a format defined
by timeFmt.
"""
dt = datetime.strptime(dateStr, timeFmt)
return dt
[docs]
def datetimeFromNum(dateNum):
"""Generates a python datetime object from a date number assuming the python default
epoch 0001-01-01 00:00:00.
"""
dt = datetime.fromordinal(int(dateNum)) + timedelta(days=(dateNum%1))
return dt
def datetimeFromJulianDay(julian):
dt = datetime(1858, 11, 17) + timedelta(julian-julianDay_Offset)
return dt
[docs]
def dateStrFromDatetime(dtime, timeFmt=stdTimeFmt):
"""Generates a date string from a python datetime object with the format
defined by timeFmt.
"""
ds = dtime.strftime(timeFmt)
return ds
[docs]
def dateNumFromDatetime(dtime):
"""Generates a date number from a python datetime object assuming the python default
epoch of 0001-01-01 00:00:00.
"""
dn = dtime.toordinal() + (dtime-datetime.fromordinal(dtime.toordinal())).total_seconds()/(24*60*60)
return dn
def dateNumFromJulianDay(julian):
dt = datetimeFromJulianDay(julian)
dn = dateNumFromDatetime(dt)
return dn
[docs]
def dateStrFromDateNum(dateNum, timeFmt=stdTimeFmt):
"""Generates a date string from a date number assuming the python default epoch of
0001-01-01 00:00:00, using the fromat defined by timeFmt.
"""
dt = datetimeFromNum(dateNum)
ds = dateStrFromDatetime(dt, timeFmt)
return ds
def dateStrFromJulianDay(julian, timeFmt=stdTimeFmt):
dt = datetimeFromJulianDay(julian)
ds = dateStrFromDatetime(dt, timeFmt=timeFmt)
return ds
[docs]
def dateNumFromDateStr(dateStr, timeFmt=stdTimeFmt):
"""Generates a date number from a date string using the format defined by timeFmt,
and python default epoch 0001-01-01 00:00:00.
"""
dt = datetimeFromStr(dateStr, timeFmt)
dn = dateNumFromDatetime(dt)
return dn
[docs]
def dateNumFromEpoch(dateNum, epochStr=stdEpochStr):
"""Converts a date number from the default python epoch 0001-01-01 00:00:00 to days
from the epoch define in epochStr.
"""
nepoch = dateNumFromDateStr(epochStr)
dn = dateNum - nepoch
return dn
def matlab2std(dateNum, isLeapYear=False):
dn = dateNum - 366.0
return dn
def std2matlab(dateNum, isLeapYear=False):
dn = dateNum + 366.0
return dn
[docs]
def matlab2python(dateNum):
"""Converts a date number based on the matlab default epoch 0000-01-01 00:00:00 to the
python default epoch 0001-01-01 00:00:00.
"""
dn = dateNum - 366.0
return dn
[docs]
def python2matlab(dateNum):
"""Converts a date number based on the python default epoch 0001-01-01 00:00:00 to the
matlab default epoch 0000-01-01 00:00:00.
"""
dn = dateNum + 366.0
return dn
def isLeapYear(year):
if year % 400 == 0:
return True
if year % 100 == 0:
return False
if year % 4 == 0:
return True
else:
return False