time diff calculation

This commit is contained in:
Daniel Tsvetkov 2021-05-13 11:13:54 +02:00
parent 000d0ca81f
commit e4c1b7991e
3 changed files with 63 additions and 92 deletions

View File

@ -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: `<dt-like> (?\+|\-|plus|minus) <timedelta>` or `<timedelta> (before|from) <dt>`
- `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 <datetime-like>` or `
- `workdays since 2021-01-05`
- `work hours until Friday`
- Milliseconds since epoch: `(time/(milli)seconds) since epoch` or datetime to epoch: `<datetime-like> to epoch` or `(milli)seconds since <datetime-like>/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:
- `<dt-like> (?\+|\-|plus|minus) <timedelta>`
- `12-12-2019 + 2 weeks`
- `05:23 - 150 minutes`
- `<timedelta> (before|from) <dt>`
- `3 days from next Friday`
- `2 hours before 15:00`
* Day of the week
- `what day is <dt>`
* 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))

View File

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

View File

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