From e4c1b7991e1a757f45e0baa77b432a8ffdd51dd2 Mon Sep 17 00:00:00 2001 From: Daniel Tsvetkov Date: Thu, 13 May 2021 11:13:54 +0200 Subject: [PATCH] time diff calculation --- README.md | 25 ++++++++--------- tww/tokenizer.py | 58 ++++++++++++++++++++++++++++++++++---- tww/web.py | 72 ------------------------------------------------ 3 files changed, 63 insertions(+), 92 deletions(-) diff --git a/README.md b/README.md index 59ec770..6dc27ec 100644 --- a/README.md +++ b/README.md @@ -22,12 +22,22 @@ python tww QUERY [--debug] [--full] - `time between 2012-03-14 and 2012-04-26` - `time since 09:00` - `time until end of workday` + - Calculate time differences: ` (?\+|\-|plus|minus) ` or ` (before|from)
` + - `12-12-2019 + 2 weeks` + - `05:23 - 150 minutes` + - `3 days from next Friday` + - `2 hours before 15:00` - (Approximate) workdays calculation (assumes monday-friday are work days - ignores public/local holidays (for now)): `work days/hours since/until ` or ` - `workdays since 2021-01-05` - `work hours until Friday` - Milliseconds since epoch: `(time/(milli)seconds) since epoch` or datetime to epoch: ` to epoch` or `(milli)seconds since /epoch` - `2021-01:01 to epoch` - `milliseconds since epoch` + - Day of the week + - `what day is today` + - `2021-05-10 day of week` + - Find timezones + - `timezone in Brazil` Few more notes: @@ -101,20 +111,7 @@ $ tww 3/14 15 9:26:53 PST to sofia ## TODO -* Calculate time differences: - - parse `timedelta`: - - `(%d) (?year|month|week|day|hour|minute|second)[s]?` - - calculate: - - ` (?\+|\-|plus|minus) ` - - `12-12-2019 + 2 weeks` - - `05:23 - 150 minutes` - - ` (before|from)
` - - `3 days from next Friday` - - `2 hours before 15:00` -* Day of the week - - `what day is
` * Return Time range - - parse relative to now: - - `(previous|last|this|next) (year|month|week|day|hour|minute|second|Monday|Tuesday|...|Sunday)` + * [Countries names in their own languages](https://www.worldatlas.com/articles/names-of-countries-in-their-own-languages.html), [list of countries in various languages](https://en.wikipedia.org/wiki/List_of_country_names_in_various_languages_(A%E2%80%93C)) \ No newline at end of file diff --git a/tww/tokenizer.py b/tww/tokenizer.py index 966b7e4..4efb871 100644 --- a/tww/tokenizer.py +++ b/tww/tokenizer.py @@ -25,9 +25,14 @@ r_time_in_epoch_ms_now = re.compile('(?:milliseconds since epoch)', flags=re.IGN r_time_in_epoch_ms2 = re.compile('(.*)?\s*(?:in|to)\s*(?:ms|milliseconds|miliseconds)', flags=re.IGNORECASE) r_time_in_epoch_ms3 = re.compile('(?:ms|milliseconds|miliseconds)?\s*since\s*(.*)', flags=re.IGNORECASE) r_time_in = re.compile('(?:time)?\s*in\s*(.*)', flags=re.IGNORECASE) -r_time_since = re.compile('(?:time)?\s*since\s*(.*)', flags=re.IGNORECASE) -r_time_until = re.compile('(?:time)?\s*until\s*(.*)', flags=re.IGNORECASE) -r_time_between = re.compile('(?:time)?\s*between\s*(.*)\s*and\s*(.*)', flags=re.IGNORECASE) +r_time_since = re.compile('(?:time|year|month|week|day|hour|minute|second)?(?:s)?\s*since\s*(.*)', flags=re.IGNORECASE) +r_time_until = re.compile('(?:time|year|month|week|day|hour|minute|second)?(?:s)?\s*until\s*(.*)', flags=re.IGNORECASE) +r_time_between = re.compile('(?:time|year|month|week|day|hour|minute|second)?(?:s)?\s*between\s*(.*)\s*and\s*(.*)', flags=re.IGNORECASE) +r_time_plus = re.compile('(.*)\s*(?:plus|\+|after|from)\s*(.*)', flags=re.IGNORECASE) +r_time_minus = re.compile('(.*)\s*(?:minus|\-)\s*(.*)', flags=re.IGNORECASE) +r_time_before = re.compile('(.*)\s*(?:before|\-)\s*(.*)', flags=re.IGNORECASE) +r_weekday_pre = re.compile('(?:day of week|weekday|week day|what day is|what day of week is)\s*(.*)', flags=re.IGNORECASE) +r_weekday_post = re.compile('(.*)\s*(?:day of week|weekday|week day|what day|what day of week)', flags=re.IGNORECASE) r_workdays_since = re.compile('(?:workdays|work days)\s*since\s*(.*)', flags=re.IGNORECASE) r_workdays_until = re.compile('(?:workdays|work days)\s*until\s*(.*)', flags=re.IGNORECASE) r_workdays_between = re.compile('(?:workdays|work days)\s*between\s*(.*)\s*and\s*(.*)', flags=re.IGNORECASE) @@ -99,7 +104,7 @@ def handler_workhours_since_until(start_dt_s: str): return handler_workhours_diff(dateparser_parse_dt(start_dt_s), get_local_now()) -def timezone_mangle(dt_s: str, timezone_like_s: str) -> dict: +def timezone_mangle(dt_s: str, timezone_like_s: str): logger.debug("Timezone translation between {} | {}".format(dt_s, timezone_like_s)) if dt_s.lower().strip() == "time": dt_s = "now" @@ -108,7 +113,7 @@ def timezone_mangle(dt_s: str, timezone_like_s: str) -> dict: tz = resolve_timezone(timezone_like_s) logger.debug("Destination timezone: {}".format(tz)) if not tz: - tz, dst_dt = {}, src_dt + tz, dst_dt, offset = {}, src_dt, {} else: offset = tz.get('tz_offset') return src_dt, offset, tz @@ -146,6 +151,37 @@ def handler_timezone(timezone_s: str): return resolve_timezone(timezone_s) +def get_timedelta(td: str, direction='future'): + if direction in ['future']: + prefix = 'in {}' + else: + prefix = '{} ago' + other_dt = dateparser_parse_dt(prefix.format(td)) + now = get_local_now() + if direction in ['future']: + return other_dt - now + else: + return now - other_dt + + +def handler_time_plus(dt_s: str, td: str): + dt = dateparser_parse_dt(dt_s) + td = get_timedelta(td) + return dt + td + + +def handler_time_minus(dt_s: str, td: str): + dt = dateparser_parse_dt(dt_s) + td = get_timedelta(td, direction='past') + return dt - td + + +def handler_time_before(td: str, dt_s: str): + dt = dateparser_parse_dt(dt_s) + td = get_timedelta(td, direction='past') + return dt - td + + QUERY_TYPE_DT_TR = "datetime_translation" QUERY_TYPE_DT = "datetime_details" QUERY_TYPE_TZ = "timezone" @@ -159,6 +195,7 @@ h_time_in = 'dt->hh:mm' h_translation = 'dt->iso8601_full' h_default_dt = 'dt->iso8601_full' h_default_td = 'timedelta->diff->duration_human' +h_day_of_week = 'dt->locale_day_of_week' regex_handlers = [ (r_time_in_epoch_s_now, handler_time_now_local, QUERY_TYPE_DT, h_unix_s), @@ -174,6 +211,11 @@ regex_handlers = [ (r_time_since, handler_time_since_until, QUERY_TYPE_TD, h_default_td), (r_time_until, handler_time_since_until, QUERY_TYPE_TD, h_default_td), (r_time_between, handler_time_diff, QUERY_TYPE_TD, h_default_td), + (r_time_plus, handler_time_plus, QUERY_TYPE_DT, h_default_dt), + (r_time_minus, handler_time_minus, QUERY_TYPE_DT, h_default_dt), + (r_time_before, handler_time_before, QUERY_TYPE_DT, h_default_dt), + (r_weekday_pre, handler_generic_parser, QUERY_TYPE_DT, h_day_of_week), + (r_weekday_post, handler_generic_parser, QUERY_TYPE_DT, h_day_of_week), (r_workdays_since, handler_workdays_since_until, QUERY_TYPE_TD, h_default_td), (r_workdays_until, handler_workdays_since_until, QUERY_TYPE_TD, h_default_td), (r_workdays_between, handler_workdays_diff, QUERY_TYPE_TD, h_default_td), @@ -224,7 +266,7 @@ def pretty_print_dict(obj): return colorful_json -def show_magic_results(obj, args): +def show_magic_results(obj, args, results=1): rv = [] for solution in obj['solutions']: entry_proxy = Cut(solution, sep='->') @@ -232,6 +274,7 @@ def show_magic_results(obj, args): try: highlight_result = entry_proxy[highlight_entry] except Exception as e: + logger.debug("Exception from magic result: {} -> {}".format(highlight_entry, e)) continue if args.handlers: to_print = "{} -> {}".format(solution['handler'], highlight_result) @@ -239,8 +282,11 @@ def show_magic_results(obj, args): to_print = highlight_result rv.append(to_print) print(to_print) + if len(rv) >= results: + break return rv + def dt_pretty(dt): rv = {} global custom_locale diff --git a/tww/web.py b/tww/web.py index 9274d68..2d68e1a 100644 --- a/tww/web.py +++ b/tww/web.py @@ -5,9 +5,6 @@ from tokenizer import resolve_query, QUERY_TYPE_DT, QUERY_TYPE_DT_TR, QUERY_TYPE app = Flask(__name__) -import dateparser -from tww.lib import resolve_timezone - IN_KW = " in " TO_KW = " to " NO_TZ_FORMAT = '%Y-%m-%dT%H:%M:%S' @@ -17,75 +14,6 @@ TIME_FORMAT = '%H:%M:%S' TZ_OFFSET_FORMAT = '%z' -def parse_query(q): - fmt = NO_TZ_FORMAT - to_split = q.split(TO_KW) - in_dt_resolve_fmt, to_dt_resolve_fmt = {}, {} - in_tz_resolve, to_tz_resolve = {}, {} - error = "" - if len(to_split) >= 2: - # 18:00 to Zurich - in_q, to_tz = to_split[0], TO_KW.join(to_split[1:]) - else: - # 18:00 - in_q = q - to_tz = "" - in_split = in_q.split(IN_KW) - if len(in_split) >= 2: - # 18:00 in Zurich - in_dt, in_tz = in_split[0], IN_KW.join(in_split[1:]) - else: - # in 2 hours / 19:00 CET - in_dt = in_q - in_tz = "" - try: - in_dt_resolve = dateparser.parse(in_dt, settings={ - 'RETURN_AS_TIMEZONE_AWARE': True}) - if not in_tz: - in_tz = in_dt_resolve.strftime("%Z") - if in_tz: - isofmt = in_dt_resolve.isoformat() - in_tz_resolve = resolve_timezone(in_tz) - in_dt_resolve = dateparser.parse(isofmt, settings={ - 'TO_TIMEZONE': in_tz_resolve}) - to_dt_resolve, to_tz_resolve = in_dt_resolve, in_tz_resolve - if in_dt_resolve: - in_dt_resolve_fmt = { - 'iso': in_dt_resolve.strftime(ISO_FORMAT), - 'fmt': in_dt_resolve.strftime(fmt), - 'date': in_dt_resolve.strftime(DATE_FORMAT), - 'time': in_dt_resolve.strftime(TIME_FORMAT), - 'tz_offset': in_dt_resolve.strftime(TZ_OFFSET_FORMAT), - } - if to_tz: - isofmt = in_dt_resolve.isoformat() - to_tz_resolve = resolve_timezone(to_tz) - to_dt_resolve = dateparser.parse(isofmt, settings={ - 'TO_TIMEZONE': to_tz_resolve}) - if to_dt_resolve: - to_dt_resolve_fmt = { - 'iso': to_dt_resolve.strftime(ISO_FORMAT), - 'fmt': to_dt_resolve.strftime(fmt), - 'date': to_dt_resolve.strftime(DATE_FORMAT), - 'time': to_dt_resolve.strftime(TIME_FORMAT), - 'tz_offset': to_dt_resolve.strftime(TZ_OFFSET_FORMAT), - } - except Exception as e: - error = str(e) - return { - 'error': error, - 'query': q, - 'fmt': fmt, - 'in_dt': in_dt, - 'in_tz_resolve': in_tz_resolve, - 'in_dt_resolve': in_dt_resolve_fmt, - 'to_dt_resolve': to_dt_resolve_fmt, - 'in_tz': in_tz, - 'to_tz': to_tz, - 'to_tz_resolve': to_tz_resolve, - } - - @app.route("/") def home(): ctx = dict(all_tz=pytz.all_timezones)