Skip to content

Commit

Permalink
TimePoint: change all remaining get() -> @Property and fix #184
Browse files Browse the repository at this point in the history
Fix rounding bug for decimal time quantities
  • Loading branch information
MetRonnie committed Oct 16, 2020
1 parent 8e147b0 commit 658b31e
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 70 deletions.
117 changes: 57 additions & 60 deletions metomi/isodatetime/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -1112,28 +1112,49 @@ def num_expanded_year_digits(self): return self._num_expanded_year_digits
def year(self): return self._year

@property
def month_of_year(self): return self._month_of_year
def month_of_year(self):
if self._month_of_year is None:
return self.get_calendar_date()[1]
return self._month_of_year

@property
def week_of_year(self): return self._week_of_year
def week_of_year(self):
if self._week_of_year is None:
return self.get_week_date()[1]
return self._week_of_year

@property
def day_of_year(self): return self._day_of_year
def day_of_year(self):
if self._day_of_year is None:
return self.get_ordinal_date()[1]
return self._day_of_year

@property
def day_of_month(self): return self._day_of_month
def day_of_month(self):
if self._day_of_month is None:
return self.get_calendar_date()[2]
return self._day_of_month

@property
def day_of_week(self): return self._day_of_week
def day_of_week(self):
if self._day_of_week is None:
return self.get_week_date()[2]
return self._day_of_week

@property
def hour_of_day(self): return self._hour_of_day

@property
def minute_of_hour(self): return self._minute_of_hour
def minute_of_hour(self):
if self._minute_of_hour is None:
return self.get_hour_minute_second()[1]
return self._minute_of_hour

@property
def second_of_minute(self): return self._second_of_minute
def second_of_minute(self):
if self._second_of_minute is None:
return self.get_hour_minute_second()[2]
return self._second_of_minute

@property
def time_zone(self): return self._time_zone
Expand Down Expand Up @@ -1226,6 +1247,18 @@ def year_of_decade(self): return abs(self._year) % 10
def decade_of_century(self):
return (abs(self._year) % 100 - abs(self._year) % 10) // 10

@property
def hour_of_day_decimal_string(self):
return self._decimal_string("hour_of_day")

@property
def minute_of_hour_decimal_string(self):
return self._decimal_string("minute_of_hour")

@property
def second_of_minute_decimal_string(self):
return self._decimal_string("second_of_minute")

@property
def time_zone_minute_abs(self): return abs(self._time_zone._minutes)

Expand All @@ -1247,58 +1280,22 @@ def seconds_since_unix_epoch(self):
return str(int(CALENDAR.SECONDS_IN_DAY * days + seconds))

def get(self, property_name):
"""Return a calculated value for property name."""
if property_name == "month_of_year":
if self._month_of_year is not None:
return self._month_of_year
return self.get_calendar_date()[1]
if property_name == "day_of_year":
if self._day_of_year is not None:
return self._day_of_year
return self.get_ordinal_date()[1]
if property_name == "day_of_month":
if self._day_of_month is not None:
return self._day_of_month
return self.get_calendar_date()[2]
if property_name == "week_of_year":
if self._week_of_year is not None:
return self._week_of_year
return self.get_week_date()[1]
if property_name == "day_of_week":
if self._day_of_week is not None:
return self._day_of_week
return self.get_week_date()[2]
if property_name == "minute_of_hour":
if self._minute_of_hour is None:
return self.get_hour_minute_second()[1]
return int(self._minute_of_hour)
if property_name == "hour_of_day":
return int(self._hour_of_day)
if property_name == "hour_of_day_decimal_string":
string = "%f" % (float(self._hour_of_day) - int(self._hour_of_day))
string = string.replace("0.", "", 1).rstrip("0")
if not string:
return "0"
return string
if property_name == "minute_of_hour_decimal_string":
string = "%f" % (float(self._minute_of_hour) -
int(self._minute_of_hour))
string = string.replace("0.", "", 1).rstrip("0")
if not string:
return "0"
return string
if property_name == "second_of_minute":
if self._second_of_minute is None:
return self.get_hour_minute_second()[2]
return int(self._second_of_minute)
if property_name == "second_of_minute_decimal_string": # FIXME: #184
string = "%f" % (float(self._second_of_minute) -
int(self._second_of_minute))
string = string.replace("0.", "", 1).rstrip("0")
if not string:
return "0"
return string
raise NotImplementedError(property_name)
"""Obsolete method for returning calculated value for property name."""
raise NotImplementedError(
"The method TimePoint.get('{0}') is obsolete; use TimePoint.{0} "
"or getattr() instead".format(property_name))
# return getattr(self, property_name)

def _decimal_string(self, attr):
"""Return the decimal digits (after the '.') of the specified attribute
as a string. Rounds to 6 d.p."""
string = "%f" % (
float(getattr(self, attr)) - int(getattr(self, attr)))
# Note: 0.9999999 rounds up to 1.0; this is handled in TimePointDumper
string = string.split(".", 1)[1].rstrip("0")
if not string:
return "0"
return string

def get_second_of_day(self):
"""Return the seconds elapsed since the start of the day."""
Expand Down Expand Up @@ -1447,7 +1444,7 @@ def get_truncated_properties(self):
for attr in ["month_of_year", "week_of_year", "day_of_year",
"day_of_month", "day_of_week", "hour_of_day",
"minute_of_hour", "second_of_minute"]:
value = getattr(self, attr)
value = getattr(self, "_{0}".format(attr))
if value is not None:
props.update({attr: value})
return props
Expand Down
13 changes: 12 additions & 1 deletion metomi/isodatetime/dumpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ class TimePointDumper(object):
"""

FLOAT_DECIMAL_PLACES = 6

def __init__(self, num_expanded_year_digits=2):
self._timepoint_parser = None
self._rec_formats = {"date": [], "time": [], "time_zone": []}
Expand Down Expand Up @@ -144,7 +146,7 @@ def _dump_expression_with_properties(self, timepoint, expression,
timepoint = timepoint.to_time_zone(new_time_zone)
property_map = {}
for property_ in properties:
property_map[property_] = timepoint.get(property_)
property_map[property_] = getattr(timepoint, property_)
if (property_ == "century" and
("expanded_year_digits" not in properties or
not self.num_expanded_year_digits)):
Expand All @@ -153,6 +155,15 @@ def _dump_expression_with_properties(self, timepoint, expression,
elif property_ == "expanded_year_digits":
max_value = (10 ** (self.num_expanded_year_digits + 4)) - 1
min_value = -max_value
elif property_ in ("hour_of_day", "minute_of_hour",
"second_of_minute"):
# Handle cases where decimal is rounded up
prop_decimal_str = "{0}_decimal_string".format(property_)
if prop_decimal_str in properties:
fp_value = property_map[property_]
rounded = int(round(fp_value, self.FLOAT_DECIMAL_PLACES))
property_map[property_] = rounded
continue
else:
continue
value = timepoint.year
Expand Down
18 changes: 9 additions & 9 deletions metomi/isodatetime/parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -590,17 +590,17 @@ def parse(self, expression):
if timepoint.get_is_week_date():
raise ISO8601SyntaxError("duration", expression)
result_map = {}
result_map["years"] = timepoint.year
result_map["years"] = timepoint._year
if timepoint.get_is_calendar_date():
result_map["months"] = timepoint.month_of_year
result_map["days"] = timepoint.day_of_month
result_map["months"] = timepoint._month_of_year
result_map["days"] = timepoint._day_of_month
if timepoint.get_is_ordinal_date():
result_map["days"] = timepoint.day_of_year
result_map["hours"] = timepoint.hour_of_day
if timepoint.minute_of_hour is not None:
result_map["minutes"] = timepoint.minute_of_hour
if timepoint.second_of_minute is not None:
result_map["seconds"] = timepoint.second_of_minute
result_map["days"] = timepoint._day_of_year
result_map["hours"] = timepoint._hour_of_day
if timepoint._minute_of_hour is not None:
result_map["minutes"] = timepoint._minute_of_hour
if timepoint._second_of_minute is not None:
result_map["seconds"] = timepoint._second_of_minute
return data.Duration(**result_map)
raise ISO8601SyntaxError("duration", expression)

Expand Down

0 comments on commit 658b31e

Please sign in to comment.