fix: Handle parsing and formatting timedeltas

* Added utils parse_timedelta, format_timedelta
* Added to json_handler for de-serializing timedelta objects
This commit is contained in:
Gavin D'souza 2022-01-28 18:33:03 +05:30
parent 9091b2a037
commit 8037866dc1
4 changed files with 51 additions and 11 deletions

View file

@ -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

View file

@ -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()})

View file

@ -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

View file

@ -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):