* 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 | |||
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 | |||
""" | |||
from dateutil import parser | |||
from dateutil.parser import ParserError | |||
time = time or "0:0:0" | |||
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( | |||
hours=t.hour, minutes=t.minute, seconds=t.second, microseconds=t.microsecond | |||
) | |||
@@ -332,7 +338,7 @@ def get_time(time_str): | |||
return time_str | |||
else: | |||
if isinstance(time_str, datetime.timedelta): | |||
time_str = str(time_str) | |||
return format_timedelta(time_str) | |||
return parser.parse(time_str).time() | |||
def get_datetime_str(datetime_obj): | |||
@@ -1678,3 +1684,30 @@ class UnicodeWithAttrs(str): | |||
def __init__(self, text): | |||
self.toc_html = text.toc_html | |||
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 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 | |||
import re | |||
from dateutil.parser import ParserError | |||
def format_value(value, df=None, doc=None, currency=None, translated=False, format=None): | |||
'''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) | |||
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"): | |||
# 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 | |||
import json | |||
@@ -16,7 +16,7 @@ from werkzeug.local import LocalProxy | |||
from werkzeug.wsgi import wrap_file | |||
from werkzeug.wrappers import Response | |||
from werkzeug.exceptions import NotFound, Forbidden | |||
from frappe.utils import cint | |||
from frappe.utils import cint, format_timedelta | |||
from urllib.parse import quote | |||
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): | |||
"""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) | |||
elif isinstance(obj, datetime.timedelta): | |||
return format_timedelta(obj) | |||
elif isinstance(obj, decimal.Decimal): | |||
return float(obj) | |||
@@ -138,7 +140,7 @@ def json_handler(obj): | |||
doc = obj.as_dict(no_nulls=True) | |||
return doc | |||
elif isinstance(obj, collections.abc.Iterable): | |||
elif isinstance(obj, Iterable): | |||
return list(obj) | |||
elif type(obj)==type or isinstance(obj, Exception): | |||