summaryrefslogtreecommitdiff
path: root/examples/delta_time.py
diff options
context:
space:
mode:
Diffstat (limited to 'examples/delta_time.py')
-rw-r--r--examples/delta_time.py312
1 files changed, 186 insertions, 126 deletions
diff --git a/examples/delta_time.py b/examples/delta_time.py
index e079094..237414f 100644
--- a/examples/delta_time.py
+++ b/examples/delta_time.py
@@ -39,22 +39,34 @@ __all__ = ["time_expression"]
# basic grammar definitions
def make_integer_word_expr(int_name, int_value):
return pp.CaselessKeyword(int_name).addParseAction(pp.replaceWith(int_value))
-integer_word = pp.MatchFirst(make_integer_word_expr(int_str, int_value)
- for int_value, int_str
- in enumerate("one two three four five six seven eight nine ten"
- " eleven twelve thirteen fourteen fifteen sixteen"
- " seventeen eighteen nineteen twenty".split(), start=1))
+
+
+integer_word = pp.MatchFirst(
+ make_integer_word_expr(int_str, int_value)
+ for int_value, int_str in enumerate(
+ "one two three four five six seven eight nine ten"
+ " eleven twelve thirteen fourteen fifteen sixteen"
+ " seventeen eighteen nineteen twenty".split(),
+ start=1,
+ )
+)
integer = pp.pyparsing_common.integer | integer_word
CK = pp.CaselessKeyword
CL = pp.CaselessLiteral
-today, tomorrow, yesterday, noon, midnight, now = map(CK, "today tomorrow yesterday noon midnight now".split())
+today, tomorrow, yesterday, noon, midnight, now = map(
+ CK, "today tomorrow yesterday noon midnight now".split()
+)
+
+
def plural(s):
- return CK(s) | CK(s + 's').addParseAction(pp.replaceWith(s))
+ return CK(s) | CK(s + "s").addParseAction(pp.replaceWith(s))
+
+
week, day, hour, minute, second = map(plural, "week day hour minute second".split())
am = CL("am")
pm = CL("pm")
-COLON = pp.Suppress(':')
+COLON = pp.Suppress(":")
in_ = CK("in").setParseAction(pp.replaceWith(1))
from_ = CK("from").setParseAction(pp.replaceWith(1))
@@ -66,51 +78,59 @@ last_ = CK("last").setParseAction(pp.replaceWith(-1))
at_ = CK("at")
on_ = CK("on")
-couple = (pp.Optional(CK("a")) + CK("couple") + pp.Optional(CK("of"))).setParseAction(pp.replaceWith(2))
+couple = (pp.Optional(CK("a")) + CK("couple") + pp.Optional(CK("of"))).setParseAction(
+ pp.replaceWith(2)
+)
a_qty = (CK("a") | CK("an")).setParseAction(pp.replaceWith(1))
the_qty = CK("the").setParseAction(pp.replaceWith(1))
qty = pp.ungroup(integer | couple | a_qty | the_qty)
-time_ref_present = pp.Empty().addParseAction(pp.replaceWith(True))('time_ref_present')
+time_ref_present = pp.Empty().addParseAction(pp.replaceWith(True))("time_ref_present")
+
def fill_24hr_time_fields(t):
- t['HH'] = t[0]
- t['MM'] = t[1]
- t['SS'] = 0
- t['ampm'] = ('am','pm')[t.HH >= 12]
+ t["HH"] = t[0]
+ t["MM"] = t[1]
+ t["SS"] = 0
+ t["ampm"] = ("am", "pm")[t.HH >= 12]
+
def fill_default_time_fields(t):
- for fld in 'HH MM SS'.split():
+ for fld in "HH MM SS".split():
if fld not in t:
t[fld] = 0
+
weekday_name_list = list(calendar.day_name)
weekday_name = pp.oneOf(weekday_name_list)
-_24hour_time = pp.Word(pp.nums, exact=4).addParseAction(lambda t: [int(t[0][:2]),int(t[0][2:])],
- fill_24hr_time_fields)
+_24hour_time = pp.Word(pp.nums, exact=4).addParseAction(
+ lambda t: [int(t[0][:2]), int(t[0][2:])], fill_24hr_time_fields
+)
_24hour_time.setName("0000 time")
ampm = am | pm
-timespec = (integer("HH")
- + pp.Optional(CK("o'clock")
- |
- COLON + integer("MM")
- + pp.Optional(COLON + integer("SS"))
- )
- + (am | pm)("ampm")
- ).addParseAction(fill_default_time_fields)
+timespec = (
+ integer("HH")
+ + pp.Optional(
+ CK("o'clock") | COLON + integer("MM") + pp.Optional(COLON + integer("SS"))
+ )
+ + (am | pm)("ampm")
+).addParseAction(fill_default_time_fields)
absolute_time = _24hour_time | timespec
absolute_time_of_day = noon | midnight | now | absolute_time
+
def add_computed_time(t):
- if t[0] in 'now noon midnight'.split():
- t['computed_time'] = {'now': datetime.now().time().replace(microsecond=0),
- 'noon': time(hour=12),
- 'midnight': time()}[t[0]]
+ if t[0] in "now noon midnight".split():
+ t["computed_time"] = {
+ "now": datetime.now().time().replace(microsecond=0),
+ "noon": time(hour=12),
+ "midnight": time(),
+ }[t[0]]
else:
- t['HH'] = {'am': int(t['HH']) % 12,
- 'pm': int(t['HH']) % 12 + 12}[t.ampm]
- t['computed_time'] = time(hour=t.HH, minute=t.MM, second=t.SS)
+ t["HH"] = {"am": int(t["HH"]) % 12, "pm": int(t["HH"]) % 12 + 12}[t.ampm]
+ t["computed_time"] = time(hour=t.HH, minute=t.MM, second=t.SS)
+
absolute_time_of_day.addParseAction(add_computed_time)
@@ -119,42 +139,49 @@ absolute_time_of_day.addParseAction(add_computed_time)
# | qty time_units 'ago'
# | 'in' qty time_units
time_units = hour | minute | second
-relative_time_reference = (qty('qty') + time_units('units') + ago('dir')
- | qty('qty') + time_units('units')
- + (from_ | before | after)('dir')
- + pp.Group(absolute_time_of_day)('ref_time')
- | in_('dir') + qty('qty') + time_units('units')
- )
+relative_time_reference = (
+ qty("qty") + time_units("units") + ago("dir")
+ | qty("qty")
+ + time_units("units")
+ + (from_ | before | after)("dir")
+ + pp.Group(absolute_time_of_day)("ref_time")
+ | in_("dir") + qty("qty") + time_units("units")
+)
+
def compute_relative_time(t):
- if 'ref_time' not in t:
- t['ref_time'] = datetime.now().time().replace(microsecond=0)
+ if "ref_time" not in t:
+ t["ref_time"] = datetime.now().time().replace(microsecond=0)
else:
- t['ref_time'] = t.ref_time.computed_time
- delta_seconds = {'hour': 3600,
- 'minute': 60,
- 'second': 1}[t.units] * t.qty
- t['time_delta'] = timedelta(seconds=t.dir * delta_seconds)
+ t["ref_time"] = t.ref_time.computed_time
+ delta_seconds = {"hour": 3600, "minute": 60, "second": 1}[t.units] * t.qty
+ t["time_delta"] = timedelta(seconds=t.dir * delta_seconds)
+
relative_time_reference.addParseAction(compute_relative_time)
time_reference = absolute_time_of_day | relative_time_reference
+
+
def add_default_time_ref_fields(t):
- if 'time_delta' not in t:
- t['time_delta'] = timedelta()
+ if "time_delta" not in t:
+ t["time_delta"] = timedelta()
+
+
time_reference.addParseAction(add_default_time_ref_fields)
# absolute_day_reference ::= 'today' | 'tomorrow' | 'yesterday' | ('next' | 'last') weekday_name
# day_units ::= 'days' | 'weeks'
day_units = day | week
-weekday_reference = pp.Optional(next_ | last_, 1)('dir') + weekday_name('day_name')
+weekday_reference = pp.Optional(next_ | last_, 1)("dir") + weekday_name("day_name")
+
def convert_abs_day_reference_to_date(t):
now = datetime.now().replace(microsecond=0)
# handle day reference by weekday name
- if 'day_name' in t:
+ if "day_name" in t:
todaynum = now.weekday()
daynames = [n.lower() for n in weekday_name_list]
nameddaynum = daynames.index(t.day_name.lower())
@@ -168,84 +195,111 @@ def convert_abs_day_reference_to_date(t):
else:
name = t[0]
t["abs_date"] = {
- "now" : now,
- "today" : datetime(now.year, now.month, now.day),
- "yesterday" : datetime(now.year, now.month, now.day) + timedelta(days=-1),
- "tomorrow" : datetime(now.year, now.month, now.day) + timedelta(days=+1),
- }[name]
+ "now": now,
+ "today": datetime(now.year, now.month, now.day),
+ "yesterday": datetime(now.year, now.month, now.day) + timedelta(days=-1),
+ "tomorrow": datetime(now.year, now.month, now.day) + timedelta(days=+1),
+ }[name]
-absolute_day_reference = today | tomorrow | yesterday | now + time_ref_present | weekday_reference
+
+absolute_day_reference = (
+ today | tomorrow | yesterday | now + time_ref_present | weekday_reference
+)
absolute_day_reference.addParseAction(convert_abs_day_reference_to_date)
# relative_day_reference ::= 'in' qty day_units
# | qty day_units 'ago'
# | 'qty day_units ('from' | 'before' | 'after') absolute_day_reference
-relative_day_reference = (in_('dir') + qty('qty') + day_units('units')
- | qty('qty') + day_units('units') + ago('dir')
- | qty('qty') + day_units('units') + (from_ | before | after)('dir')
- + absolute_day_reference('ref_day')
- )
+relative_day_reference = (
+ in_("dir") + qty("qty") + day_units("units")
+ | qty("qty") + day_units("units") + ago("dir")
+ | qty("qty")
+ + day_units("units")
+ + (from_ | before | after)("dir")
+ + absolute_day_reference("ref_day")
+)
+
def compute_relative_date(t):
now = datetime.now().replace(microsecond=0)
- if 'ref_day' in t:
- t['computed_date'] = t.ref_day
+ if "ref_day" in t:
+ t["computed_date"] = t.ref_day
else:
- t['computed_date'] = now.date()
- day_diff = t.dir * t.qty * {'week': 7, 'day': 1}[t.units]
- t['date_delta'] = timedelta(days=day_diff)
+ t["computed_date"] = now.date()
+ day_diff = t.dir * t.qty * {"week": 7, "day": 1}[t.units]
+ t["date_delta"] = timedelta(days=day_diff)
+
+
relative_day_reference.addParseAction(compute_relative_date)
# combine expressions for absolute and relative day references
day_reference = relative_day_reference | absolute_day_reference
+
+
def add_default_date_fields(t):
- if 'date_delta' not in t:
- t['date_delta'] = timedelta()
+ if "date_delta" not in t:
+ t["date_delta"] = timedelta()
+
+
day_reference.addParseAction(add_default_date_fields)
# combine date and time expressions into single overall parser
-time_and_day = (time_reference + time_ref_present + pp.Optional(pp.Optional(on_) + day_reference)
- | day_reference + pp.Optional(at_ + absolute_time_of_day + time_ref_present))
+time_and_day = time_reference + time_ref_present + pp.Optional(
+ pp.Optional(on_) + day_reference
+) | day_reference + pp.Optional(at_ + absolute_time_of_day + time_ref_present)
# parse actions for total time_and_day expression
def save_original_string(s, l, t):
# save original input string and reference time
- t['original'] = ' '.join(s.strip().split())
- t['relative_to'] = datetime.now().replace(microsecond=0)
+ t["original"] = " ".join(s.strip().split())
+ t["relative_to"] = datetime.now().replace(microsecond=0)
+
def compute_timestamp(t):
# accumulate values from parsed time and day subexpressions - fill in defaults for omitted parts
now = datetime.now().replace(microsecond=0)
- if 'computed_time' not in t:
- t['computed_time'] = t.ref_time or now.time()
- if 'abs_date' not in t:
- t['abs_date'] = now
+ if "computed_time" not in t:
+ t["computed_time"] = t.ref_time or now.time()
+ if "abs_date" not in t:
+ t["abs_date"] = now
# roll up all fields and apply any time or day deltas
- t['computed_dt'] = (
- t.abs_date.replace(hour=t.computed_time.hour, minute=t.computed_time.minute, second=t.computed_time.second)
+ t["computed_dt"] = (
+ t.abs_date.replace(
+ hour=t.computed_time.hour,
+ minute=t.computed_time.minute,
+ second=t.computed_time.second,
+ )
+ (t.time_delta or timedelta(0))
+ (t.date_delta or timedelta(0))
)
# if time just given in terms of day expressions, zero out time fields
if not t.time_ref_present:
- t['computed_dt'] = t.computed_dt.replace(hour=0, minute=0, second=0)
+ t["computed_dt"] = t.computed_dt.replace(hour=0, minute=0, second=0)
# add results name compatible with previous version
- t['calculatedTime'] = t.computed_dt
+ t["calculatedTime"] = t.computed_dt
# add time_offset fields
- t['time_offset'] = t.computed_dt - t.relative_to
+ t["time_offset"] = t.computed_dt - t.relative_to
+
def remove_temp_keys(t):
# strip out keys that are just used internally
all_keys = list(t.keys())
for k in all_keys:
- if k not in ('computed_dt', 'original', 'relative_to', 'time_offset', 'calculatedTime'):
+ if k not in (
+ "computed_dt",
+ "original",
+ "relative_to",
+ "time_offset",
+ "calculatedTime",
+ ):
del t[k]
+
time_and_day.addParseAction(save_original_string, compute_timestamp, remove_temp_keys)
@@ -304,50 +358,56 @@ if __name__ == "__main__":
last Sunday at 2pm
"""
- time_of_day = timedelta(hours=current_time.hour,
- minutes=current_time.minute,
- seconds=current_time.second)
+ time_of_day = timedelta(
+ hours=current_time.hour,
+ minutes=current_time.minute,
+ seconds=current_time.second,
+ )
expected = {
- 'now' : timedelta(0),
- '10 minutes ago': timedelta(minutes=-10),
- '10 minutes from now': timedelta(minutes=10),
- 'in 10 minutes': timedelta(minutes=10),
- 'in a minute': timedelta(minutes=1),
- 'in a couple of minutes': timedelta(minutes=2),
- '20 seconds ago': timedelta(seconds=-20),
- 'in 30 seconds': timedelta(seconds=30),
- 'in an hour': timedelta(hours=1),
- 'in a couple hours': timedelta(hours=2),
- 'a week from now': timedelta(days=7),
- '3 days from now': timedelta(days=3),
- 'a couple of days from now': timedelta(days=2),
- 'an hour ago': timedelta(hours=-1),
- 'in a couple days': timedelta(days=2) - time_of_day,
- 'a week from today': timedelta(days=7) - time_of_day,
- 'three weeks ago': timedelta(days=-21) - time_of_day,
- 'a day ago': timedelta(days=-1) - time_of_day,
- 'in a couple of days': timedelta(days=2) - time_of_day,
- 'a couple of days from today': timedelta(days=2) - time_of_day,
- '2 weeks after today': timedelta(days=14) - time_of_day,
- 'in 2 weeks': timedelta(days=14) - time_of_day,
- 'the day after tomorrow': timedelta(days=2) - time_of_day,
- 'tomorrow': timedelta(days=1) - time_of_day,
- 'the day before yesterday': timedelta(days=-2) - time_of_day,
- 'yesterday': timedelta(days=-1) - time_of_day,
- 'today': -time_of_day,
- 'midnight': -time_of_day,
- 'in a day': timedelta(days=1) - time_of_day,
- '3 days ago': timedelta(days=-3) - time_of_day,
- 'noon tomorrow': timedelta(days=1) - time_of_day + timedelta(hours=12),
- '6am tomorrow': timedelta(days=1) - time_of_day + timedelta(hours=6),
- '0800 yesterday': timedelta(days=-1) - time_of_day + timedelta(hours=8),
- '1700 tomorrow': timedelta(days=1) - time_of_day + timedelta(hours=17),
- '12:15 AM today': -time_of_day + timedelta(minutes=15),
- '3pm 2 days from today': timedelta(days=2) - time_of_day + timedelta(hours=15),
- 'ten seconds before noon tomorrow': timedelta(days=1) - time_of_day
- + timedelta(hours=12) + timedelta(seconds=-10),
- '20 seconds before noon': -time_of_day + timedelta(hours=12) + timedelta(seconds=-20),
- 'in 3 days at 5pm': timedelta(days=3) - time_of_day + timedelta(hours=17),
+ "now": timedelta(0),
+ "10 minutes ago": timedelta(minutes=-10),
+ "10 minutes from now": timedelta(minutes=10),
+ "in 10 minutes": timedelta(minutes=10),
+ "in a minute": timedelta(minutes=1),
+ "in a couple of minutes": timedelta(minutes=2),
+ "20 seconds ago": timedelta(seconds=-20),
+ "in 30 seconds": timedelta(seconds=30),
+ "in an hour": timedelta(hours=1),
+ "in a couple hours": timedelta(hours=2),
+ "a week from now": timedelta(days=7),
+ "3 days from now": timedelta(days=3),
+ "a couple of days from now": timedelta(days=2),
+ "an hour ago": timedelta(hours=-1),
+ "in a couple days": timedelta(days=2) - time_of_day,
+ "a week from today": timedelta(days=7) - time_of_day,
+ "three weeks ago": timedelta(days=-21) - time_of_day,
+ "a day ago": timedelta(days=-1) - time_of_day,
+ "in a couple of days": timedelta(days=2) - time_of_day,
+ "a couple of days from today": timedelta(days=2) - time_of_day,
+ "2 weeks after today": timedelta(days=14) - time_of_day,
+ "in 2 weeks": timedelta(days=14) - time_of_day,
+ "the day after tomorrow": timedelta(days=2) - time_of_day,
+ "tomorrow": timedelta(days=1) - time_of_day,
+ "the day before yesterday": timedelta(days=-2) - time_of_day,
+ "yesterday": timedelta(days=-1) - time_of_day,
+ "today": -time_of_day,
+ "midnight": -time_of_day,
+ "in a day": timedelta(days=1) - time_of_day,
+ "3 days ago": timedelta(days=-3) - time_of_day,
+ "noon tomorrow": timedelta(days=1) - time_of_day + timedelta(hours=12),
+ "6am tomorrow": timedelta(days=1) - time_of_day + timedelta(hours=6),
+ "0800 yesterday": timedelta(days=-1) - time_of_day + timedelta(hours=8),
+ "1700 tomorrow": timedelta(days=1) - time_of_day + timedelta(hours=17),
+ "12:15 AM today": -time_of_day + timedelta(minutes=15),
+ "3pm 2 days from today": timedelta(days=2) - time_of_day + timedelta(hours=15),
+ "ten seconds before noon tomorrow": timedelta(days=1)
+ - time_of_day
+ + timedelta(hours=12)
+ + timedelta(seconds=-10),
+ "20 seconds before noon": -time_of_day
+ + timedelta(hours=12)
+ + timedelta(seconds=-20),
+ "in 3 days at 5pm": timedelta(days=3) - time_of_day + timedelta(hours=17),
}
def verify_offset(instring, parsed):
@@ -355,9 +415,9 @@ if __name__ == "__main__":
if instring in expected:
# allow up to a second time discrepancy due to test processing time
if (parsed.time_offset - expected[instring]) <= time_epsilon:
- parsed['verify_offset'] = 'PASS'
+ parsed["verify_offset"] = "PASS"
else:
- parsed['verify_offset'] = 'FAIL'
+ parsed["verify_offset"] = "FAIL"
print("(relative to %s)" % datetime.now())
time_expression.runTests(tests, postParse=verify_offset)