"""
Module Docstring.,..?
"""
import copy
import logging
import sys
[docs]def find_remaining_minutes(seconds):
"""
Finds the remaining full minutes of a given number of seconds
Parameters
----------
seconds : int
The number of seconds to allocate
Returns
-------
int
The leftover seconds once new minutes have been filled.
"""
return seconds % 60
# NOTE: This actually kills the docstring, but minutes and seconds are the
# same...
find_remaining_hours = find_remaining_minutes
[docs]class Calendar(object):
"""
Class to contain various types of calendars.
Parameters
----------
calendar_type : int
The type of calendar to use.
Supported calendar types:
0
no leap years
1
proleptic greogrian calendar (default)
``n``
equal months of ``n`` days
Attributes
----------
timeunits : list of str
A list of accepted time units.
monthnames : list of str
A list of valid month names, using 3 letter English abbreviation.
Methods
-------
isleapyear(year)
Returns a boolean testing if the given year is a leapyear
day_in_year(year):
Returns the total number of days in a given year
day_in_month(year, month):
Returns the total number of days in a given month for a given year
(considering leapyears)
"""
timeunits = ["years", "months", "days", "hours", "minutes", "seconds"]
monthnames = [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec",
]
def __init__(self, calendar_type=1):
self.calendar_type = calendar_type
[docs] def isleapyear(self, year):
"""
Checks if a year is a leapyear
Parameters
----------
year : int
The year to check
Returns
-------
bool
True if the given year is a leapyear
"""
if self.calendar_type == 1:
if (year % 4) == 0:
if (year % 100) == 0:
if (year % 400) == 0:
leapyear = True
else:
leapyear = False
else:
leapyear = True
else:
leapyear = False
else:
leapyear = False
return leapyear
[docs] def day_in_year(self, year):
"""
Finds total number of days in a year, considering leapyears if the
calendar type allows for them.
Parameters
----------
year : int
The year to check
Returns
-------
int
The total number of days for this specific calendar type
"""
if self.calendar_type == 0:
number_of_days = 365
elif self.calendar_type == 1:
number_of_days = 365 + int(self.isleapyear(year))
else:
number_of_days = self.calendar_type * 12
return number_of_days
[docs] def day_in_month(self, year, month):
"""
Finds the number of days in a given month
Parameters
----------
year : int
The year to check
month : int or str
The month number or short name.
Returns
-------
int
The number of days in this month, considering leapyears if needed.
Raises
------
TypeError
Raised when you give an incorrect type for month
"""
if isinstance(month, str):
month = month.capitalize() # Clean up possible badly formated month
month = self.monthnames.index(month) + 1 # Remember, python is 0 indexed
# Make sure you gave a month from 1 to 12
if month > 12:
raise TypeError("You have given an idiotic month, please reconsider")
elif not isinstance(month, int):
raise TypeError(
"You must supply either a str with short month name, or an int!"
)
if self.calendar_type == 0 or self.calendar_type == 1:
if month in [1, 3, 5, 7, 8, 10, 12]:
return 31
if month in [4, 6, 9, 11]:
return 30
return 28 + int(self.isleapyear(year))
# I don't really like this, but if the calendar type is not 1 or 0, it
# is ``n``, with n being the number of days in equal-length months...
return self.calendar_type
def __repr__(self):
return "esm_calendar(calendar_type=%s)" % self.calendar_type
def __str__(self):
if self.calendar_type == 0:
return "esm_calender object with no leap years allowed"
if self.calendar_type == 1:
return "esm_calendar object with allowed leap years"
return (
"esm_calendar object with equal-length months of %s days"
% self.calendar_type
)
[docs]class Date(object):
"""
A class to contain dates, also compatiable with paleo (negative dates)
Parameters
----------
indate : str
The date to use.
See `pyesm.core.time_control.esm_calendar.Dateformat` for available
formatters.
calendar : ~`pyesm.core.time_control.esm_calendar.Calendar`, optional
The type of calendar to use. Defaults to a greogrian proleptic calendar
if nothing is specified.
Attributes
----------
year : int
The year
month : int
The month
day : int
The day
hour : int
The hour
minute : int
The minute
second : int
The second
_calendar : ~`pyesm.core.time_control.esm_calendar.Calendar`
The type of calendar to use
Methods
-------
"""
def __init__(self, indate, calendar=Calendar()):
printhours = True
printminutes = True
printseconds = True
ndate = ["1900", "01", "01", "00", "00", "00"]
ds = ""
ts = ""
if "T" in indate:
indate2 = indate.replace("T", "_")
ts = ":"
else:
indate2 = indate
if "_" in indate2:
date, time = indate2.split("_")
else:
date = indate2
time = ""
ts = ":"
for index in [3, 4, 5]:
if len(time) == 2:
ndate[index] = time
time = time[2:]
elif len(time) > 2:
ndate[index] = time[:2]
if len(time) > 2:
time = time[2:]
if time[0] == ":":
time = time[1:]
ts = ":"
else:
ndate[index] = "00"
if index == 3:
printhours = False
elif index == 4:
printminutes = False
elif index == 5:
printseconds = False
for index in [2, 1]:
ndate[index] = date[-2:]
date = date[:-2]
if len(date) > 0 and date[-1] == "-":
date = date[:-1]
ds = "-"
ndate[0] = date
if ds == "-" and ts == ":":
if "T" not in indate:
form = 1
else:
form = 2
elif ds == "-" and ts == "":
form = 7
elif ds == "" and ts == ":":
form = 6
elif ds == "" and ts == "":
form = 9
self.year, self.month, self.day, self.hour, self.minute, self.second = map(
int, ndate
)
self.syear, self.smonth, self.sday, self.shour, self.sminute, self.ssecond = map(
str, ndate
)
self._calendar = calendar
self.doy = self.day_of_year()
self.sdoy = str(self.day_of_year())
self._date_format = Dateformat(form, printhours, printminutes, printseconds)
@property
def sdoy(self):
return self.__sdoy
@sdoy.setter
def sdoy(self, sdoy):
self.__sdoy = str(self.doy)
@property
def syear(self):
return self.__syear
@syear.setter
def syear(self, syear):
self.__syear = str(self.year)
@property
def smonth(self):
return self.__smonth
@smonth.setter
def smonth(self, smonth):
self.__smonth = str(self.month).zfill(2)
@property
def sday(self):
return self.__sday
@sday.setter
def sday(self, sday):
self.__sday = str(self.day).zfill(2)
@property
def shour(self):
return self.__shour
@shour.setter
def shour(self, shour):
self.__shour = str(self.hour).zfill(2)
@property
def sminute(self):
return self.__sminute
@sminute.setter
def sminute(self, sminute):
self.__sminute = str(self.minute).zfill(2)
@property
def ssecond(self):
return self.__ssecond
@ssecond.setter
def ssecond(self, ssecond):
self.__ssecond = str(self.second).zfill(2)
[docs] def output(self, form="SELF"):
return self.format(form)
[docs] @classmethod
def from_list(cls, _list):
"""
Creates a new Date from a list
Parameters
----------
_list : list of ints
A list of [year, month, day, hour, minute, second]
Returns
-------
date : ~`pyesm.core.time_control.esm_calendar.Date`
A new date of year month day, hour minute, second
"""
negative = ["-" if _list[i] < 0 else "" for i in range(6)]
_list = [str(abs(element)) for element in _list]
if len(_list[0]) < 4:
_list[0] = _list[0].zfill(4)
_list[1:6] = [
element.zfill(2) if len(element) < 2 else element for element in _list[1:6]
]
_list = [negative[i] + _list[i] for i in range(6)]
indate = (
_list[0]
+ "-"
+ _list[1]
+ "-"
+ _list[2]
+ "_"
+ _list[3]
+ ":"
+ _list[4]
+ ":"
+ _list[5]
)
return cls(indate)
fromlist = from_list
def __repr__(self):
return "Date(%s-%s-%sT%s:%s:%s)" % (
self.year,
self.month,
self.day,
self.hour,
self.minute,
self.second,
)
def __getitem__(self, item):
return (self.year, self.month, self.day, self.hour, self.minute, self.second)[
item
]
def __setitem__(self, item, value):
value = int(value)
if item == 0:
self.year = value
elif item == 1:
self.month = value
elif item == 2:
self.day = value
elif item == 3:
self.hour = value
elif item == 4:
self.minute = value
elif item == 5:
self.second = value
else:
raise IndexError("You can only assign up to 5!")
def __lt__(self, other):
self_tup = (
self.year,
self.month,
self.day,
self.hour,
self.minute,
self.second,
)
other_tup = (
other.year,
other.month,
other.day,
other.hour,
other.minute,
other.second,
)
return self_tup < other_tup
def __le__(self, other):
self_tup = (
self.year,
self.month,
self.day,
self.hour,
self.minute,
self.second,
)
other_tup = (
other.year,
other.month,
other.day,
other.hour,
other.minute,
other.second,
)
return self_tup <= other_tup
def __eq__(self, other):
self_tup = (
self.year,
self.month,
self.day,
self.hour,
self.minute,
self.second,
)
other_tup = (
other.year,
other.month,
other.day,
other.hour,
other.minute,
other.second,
)
return self_tup == other_tup
def __ne__(self, other):
self_tup = (
self.year,
self.month,
self.day,
self.hour,
self.minute,
self.second,
)
other_tup = (
other.year,
other.month,
other.day,
other.hour,
other.minute,
other.second,
)
return self_tup != other_tup
def __ge__(self, other):
self_tup = (
self.year,
self.month,
self.day,
self.hour,
self.minute,
self.second,
)
other_tup = (
other.year,
other.month,
other.day,
other.hour,
other.minute,
other.second,
)
return self_tup >= other_tup
def __gt__(self, other):
self_tup = (
self.year,
self.month,
self.day,
self.hour,
self.minute,
self.second,
)
other_tup = (
other.year,
other.month,
other.day,
other.hour,
other.minute,
other.second,
)
return self_tup > other_tup
def __sub__(self, other):
if isinstance(other, Date):
return self.sub_date(other)
elif type(other) == tuple:
return self.sub_tuple(other)
else:
print("No known combination for subtraction")
sys.exit(1)
[docs] def sub_date(self, other):
d2 = copy.deepcopy(
[self.year, self.month, self.day, self.hour, self.minute, self.second]
)
d1 = copy.deepcopy(
[other.year, other.month, other.day, other.hour, other.minute, other.second]
)
diff = [0, 0, 0, 0, 0, 0]
while d1[1] > 1:
diff[1] -= 1
d1[1] -= 1
diff[2] -= self._calendar.day_in_month(d1[0], d1[1])
while d2[1] > 1:
diff[1] += 1
d2[1] -= 1
diff[2] += self._calendar.day_in_month(d2[0], d2[1])
while d1[2] > 1:
diff[2] -= 1
d1[2] -= 1
while d2[2] > 1:
diff[2] += 1
d2[2] -= 1
if diff[1] < 0:
diff[0] = diff[0] - 1
while d2[0] > d1[0]:
diff[0] += 1
diff[1] += 12
diff[2] += self._calendar.day_in_year(d1[0])
d1[0] += 1
diff[3] += diff[2] * 24
if diff[3] < 0:
diff[3] += 24
diff[4] += diff[3] * 60
if diff[4] < 0:
diff[4] += 60
diff[5] += diff[4] * 60
if diff[5] < 0:
diff[5] += 60
return diff
[docs] def time_between(self, date, outformat="seconds"):
"""
Computes the time between two dates
Parameters
----------
date : ~`pyesm.core.time_control.date`
The date to compare against.
Returns
-------
??
"""
if date > self:
diff = date - self
else:
diff = self - date
for index in range(0, 6):
if outformat == self._calendar.timeunits[index]:
# FIXME: Wouldn't this stop after the very first index that matches?
# I think that is the point, but I'm not sure.
return diff[index]
return None # ...? Or raise an error?
[docs] def day_of_year(self):
"""
Gets the day of the year, counting from Jan. 1
Returns
-------
int
The day of the current year.
"""
if self[1] == self[2] == 1:
return 1
else:
date2 = Date(str(self[0]) + "-01-01T00:00:00", self._calendar)
return self.time_between(date2, "days") + 1
def __str__(self):
return (
"-".join([str(self.year), str(self.month).zfill(2), str(self.day).zfill(2)])
+ "T"
+ ":".join(
[
str(self.hour).zfill(2),
str(self.minute).zfill(2),
str(self.second).zfill(2),
]
)
)
[docs] def makesense(self, ndate):
"""
Puts overflowed time back into the correct unit.
When manipulating the date, it might be that you have "70 seconds", or
something similar. Here, we put the overflowed time into the
appropriate unit.
"""
#ndate = copy.deepcopy(self)
ndate[4] = ndate[4] + ndate[5] // 60
ndate[5] = ndate[5] % 60
ndate[3] = ndate[3] + ndate[4] // 60
ndate[4] = ndate[4] % 60
ndate[2] = ndate[2] + ndate[3] // 24
ndate[3] = ndate[3] % 24
ndate[0] = ndate[0] + (ndate[1] - 1) // 12
ndate[1] = (ndate[1] - 1) % 12 + 1
while ndate[2] > self._calendar.day_in_month(ndate[0], ndate[1]):
ndate[2] = ndate[2] - self._calendar.day_in_month(ndate[0], ndate[1])
ndate[1] = ndate[1] + 1
ndate[0] = ndate[0] + (ndate[1] - 1) // 12
ndate[1] = (ndate[1] - 1) % 12 + 1
ndate[0] = ndate[0] + (ndate[1] - 1) // 12
ndate[1] = (ndate[1] - 1) % 12 + 1
while ndate[2] <= 0:
ndate[1] = ndate[1] - 1
if ndate[1] == 0:
ndate[1] = 12
ndate[0] = ndate[0] - 1
ndate[2] = ndate[2] + self._calendar.day_in_month(ndate[0], ndate[1])
if ndate[1] == 0:
ndate[1] = 12
ndate[0] = ndate[0] - 1
return ndate
#self.year, self.month, self.day, self.hour, self.minute, self.second = map(
# int, ndate
#)
#self.syear, self.smonth, self.sday, self.shour, self.sminute, self.ssecond = map(
# str, ndate
#)
[docs] def add(self, to_add):
"""
Adds another date to this one.
Parameters
----------
to_add : ~`pyesm.core.time_control.Date`
The other date to add to this one.
Returns
-------
new_date : ~`pyesm.core.time_control.Date`
A new date object with the added dates
"""
me = [self.year, self.month, self.day, self.hour, self.minute, self.second]
result = [me[i] + to_add[i] for i in range(6)]
result = self.makesense(result)
new_date = self.from_list(result)
return new_date
__add__ = add
[docs] def sub_tuple(self, to_sub):
"""
Adds another date to from one.
Parameters
----------
to_sub : ~`pyesm.core.time_control.Date`
The other date to sub from this one.
Returns
-------
new_date : ~`pyesm.core.time_control.Date`
A new date object with the subtracted dates
"""
me = [self.year, self.month, self.day, self.hour, self.minute, self.second]
result = [me[i] - to_sub[i] for i in range(6)]
result = self.makesense(result)
new_date = self.from_list(result)
return new_date