* Added utils parse_timedelta, format_timedelta * Added to json_handler for de-serializing timedelta objectsversion-14
@@ -1,4 +1,4 @@ | |||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||||
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors | |||||
# License: MIT. See LICENSE | # License: MIT. See LICENSE | ||||
import functools | import functools | ||||
@@ -104,11 +104,17 @@ def get_timedelta(time: Optional[str] = None) -> Optional[datetime.timedelta]: | |||||
datetime.timedelta: Timedelta object equivalent of the passed `time` string | datetime.timedelta: Timedelta object equivalent of the passed `time` string | ||||
""" | """ | ||||
from dateutil import parser | from dateutil import parser | ||||
from dateutil.parser import ParserError | |||||
time = time or "0:0:0" | time = time or "0:0:0" | ||||
try: | try: | ||||
t = parser.parse(time) | |||||
try: | |||||
t = parser.parse(time) | |||||
except ParserError as e: | |||||
if "day" in e.args[1]: | |||||
from frappe.utils import parse_timedelta | |||||
return parse_timedelta(time) | |||||
return datetime.timedelta( | return datetime.timedelta( | ||||
hours=t.hour, minutes=t.minute, seconds=t.second, microseconds=t.microsecond | hours=t.hour, minutes=t.minute, seconds=t.second, microseconds=t.microsecond | ||||
) | ) | ||||
@@ -332,7 +338,7 @@ def get_time(time_str): | |||||
return time_str | return time_str | ||||
else: | else: | ||||
if isinstance(time_str, datetime.timedelta): | if isinstance(time_str, datetime.timedelta): | ||||
time_str = str(time_str) | |||||
return format_timedelta(time_str) | |||||
return parser.parse(time_str).time() | return parser.parse(time_str).time() | ||||
def get_datetime_str(datetime_obj): | def get_datetime_str(datetime_obj): | ||||
@@ -1678,3 +1684,30 @@ class UnicodeWithAttrs(str): | |||||
def __init__(self, text): | def __init__(self, text): | ||||
self.toc_html = text.toc_html | self.toc_html = text.toc_html | ||||
self.metadata = text.metadata | self.metadata = text.metadata | ||||
def format_timedelta(o: datetime.timedelta) -> str: | |||||
# mariadb allows a wide diff range - https://mariadb.com/kb/en/time/ | |||||
# but frappe doesnt - i think via babel : only allows 0..23 range for hour | |||||
total_seconds = o.total_seconds() | |||||
hours, remainder = divmod(total_seconds, 3600) | |||||
minutes, seconds = divmod(remainder, 60) | |||||
rounded_seconds = round(seconds, 6) | |||||
int_seconds = int(seconds) | |||||
if rounded_seconds == int_seconds: | |||||
seconds = int_seconds | |||||
else: | |||||
seconds = rounded_seconds | |||||
return "{:01}:{:02}:{:02}".format(int(hours), int(minutes), seconds) | |||||
def parse_timedelta(s: str) -> datetime.timedelta: | |||||
# ref: https://stackoverflow.com/a/21074460/10309266 | |||||
if 'day' in s: | |||||
m = re.match(r"(?P<days>[-\d]+) day[s]*, (?P<hours>\d+):(?P<minutes>\d+):(?P<seconds>\d[\.\d+]*)", s) | |||||
else: | |||||
m = re.match(r"(?P<hours>\d+):(?P<minutes>\d+):(?P<seconds>\d[\.\d+]*)", s) | |||||
return datetime.timedelta(**{key: float(val) for key, val in m.groupdict().items()}) |
@@ -3,9 +3,11 @@ | |||||
import frappe | import frappe | ||||
import datetime | import datetime | ||||
from frappe.utils import formatdate, fmt_money, flt, cstr, cint, format_datetime, format_time, format_duration | |||||
from frappe.utils import formatdate, fmt_money, flt, cstr, cint, format_datetime, format_time, format_duration, format_timedelta | |||||
from frappe.model.meta import get_field_currency, get_field_precision | from frappe.model.meta import get_field_currency, get_field_precision | ||||
import re | import re | ||||
from dateutil.parser import ParserError | |||||
def format_value(value, df=None, doc=None, currency=None, translated=False, format=None): | def format_value(value, df=None, doc=None, currency=None, translated=False, format=None): | ||||
'''Format value based on given fieldtype, document reference, currency reference. | '''Format value based on given fieldtype, document reference, currency reference. | ||||
@@ -47,7 +49,10 @@ def format_value(value, df=None, doc=None, currency=None, translated=False, form | |||||
return format_datetime(value) | return format_datetime(value) | ||||
elif df.get("fieldtype")=="Time": | elif df.get("fieldtype")=="Time": | ||||
return format_time(value) | |||||
try: | |||||
return format_time(value) | |||||
except ParserError: | |||||
return format_timedelta(value) | |||||
elif value==0 and df.get("fieldtype") in ("Int", "Float", "Currency", "Percent") and df.get("print_hide_if_no_value"): | elif value==0 and df.get("fieldtype") in ("Int", "Float", "Currency", "Percent") and df.get("print_hide_if_no_value"): | ||||
# this is required to show 0 as blank in table columns | # this is required to show 0 as blank in table columns | ||||
@@ -1,4 +1,4 @@ | |||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||||
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors | |||||
# License: MIT. See LICENSE | # License: MIT. See LICENSE | ||||
import json | import json | ||||
@@ -16,7 +16,7 @@ from werkzeug.local import LocalProxy | |||||
from werkzeug.wsgi import wrap_file | from werkzeug.wsgi import wrap_file | ||||
from werkzeug.wrappers import Response | from werkzeug.wrappers import Response | ||||
from werkzeug.exceptions import NotFound, Forbidden | from werkzeug.exceptions import NotFound, Forbidden | ||||
from frappe.utils import cint | |||||
from frappe.utils import cint, format_timedelta | |||||
from urllib.parse import quote | from urllib.parse import quote | ||||
from frappe.core.doctype.access_log.access_log import make_access_log | from frappe.core.doctype.access_log.access_log import make_access_log | ||||
@@ -122,12 +122,14 @@ def make_logs(response = None): | |||||
def json_handler(obj): | def json_handler(obj): | ||||
"""serialize non-serializable data for json""" | """serialize non-serializable data for json""" | ||||
# serialize date | |||||
import collections.abc | |||||
from collections.abc import Iterable | |||||
if isinstance(obj, (datetime.date, datetime.timedelta, datetime.datetime, datetime.time)): | |||||
if isinstance(obj, (datetime.date, datetime.datetime, datetime.time)): | |||||
return str(obj) | return str(obj) | ||||
elif isinstance(obj, datetime.timedelta): | |||||
return format_timedelta(obj) | |||||
elif isinstance(obj, decimal.Decimal): | elif isinstance(obj, decimal.Decimal): | ||||
return float(obj) | return float(obj) | ||||
@@ -138,7 +140,7 @@ def json_handler(obj): | |||||
doc = obj.as_dict(no_nulls=True) | doc = obj.as_dict(no_nulls=True) | ||||
return doc | return doc | ||||
elif isinstance(obj, collections.abc.Iterable): | |||||
elif isinstance(obj, Iterable): | |||||
return list(obj) | return list(obj) | ||||
elif type(obj)==type or isinstance(obj, Exception): | elif type(obj)==type or isinstance(obj, Exception): | ||||