diff --git a/gracedb/nltime.py b/gracedb/nltime.py new file mode 100755 index 0000000000000000000000000000000000000000..b6ce811d085da39bb04f0a51404f52d777da17fe --- /dev/null +++ b/gracedb/nltime.py @@ -0,0 +1,191 @@ +#!/usr/bin/python + +# Taken from +# http://pyparsing.wikispaces.com/UnderDevelopment + +from datetime import datetime, timedelta +from pyparsing import * +import calendar + +# string conversion parse actions +def convertToTimedelta(toks): + unit = toks.timeunit.lower().rstrip("s") + td = { + 'month' : timedelta(30), + 'week' : timedelta(7), + 'day' : timedelta(1), + 'hour' : timedelta(0,0,0,0,0,1), + 'minute' : timedelta(0,0,0,0,1), + 'second' : timedelta(0,1), + }[unit] + if toks.qty: + td *= int(toks.qty) + if toks.dir: + td *= toks.dir + toks["timeOffset"] = td + +def convertToDay(toks): + now = datetime.now() + if "wkdayRef" in toks: + todaynum = now.weekday() + daynames = [n.lower() for n in calendar.day_name] + nameddaynum = daynames.index(toks.wkdayRef.day.lower()) + if toks.wkdayRef.dir > 0: + daydiff = (nameddaynum + 7 - todaynum) % 7 + else: + daydiff = -((todaynum + 7 - nameddaynum) % 7) + toks["absTime"] = datetime(now.year, now.month, now.day)+timedelta(daydiff) + else: + name = toks.name.lower() + toks["absTime"] = { + "now" : now, + "today" : datetime(now.year, now.month, now.day), + "yesterday" : datetime(now.year, now.month, now.day)+timedelta(-1), + "tomorrow" : datetime(now.year, now.month, now.day)+timedelta(+1), + }[name] + +def convertToAbsTime(toks): + now = datetime.now() + if "dayRef" in toks: + day = toks.dayRef.absTime + day = datetime(day.year, day.month, day.day) + else: + day = datetime(now.year, now.month, now.day) + if "timeOfDay" in toks: + if isinstance(toks.timeOfDay,basestring): + timeOfDay = { + "now" : timedelta(0, (now.hour*60+now.minute)*60+now.second, now.microsecond), + "noon" : timedelta(0,0,0,0,0,12), + "midnight" : timedelta(), + }[toks.timeOfDay] + else: + hhmmss = toks.timeparts + if hhmmss.miltime: + hh,mm = hhmmss.miltime + ss = 0 + else: + hh,mm,ss = (hhmmss.HH % 12), hhmmss.MM, hhmmss.SS + if not mm: mm = 0 + if not ss: ss = 0 + if toks.timeOfDay.ampm == 'pm': + hh += 12 + timeOfDay = timedelta(0, (hh*60+mm)*60+ss, 0) + else: + timeOfDay = timedelta(0, (now.hour*60+now.minute)*60+now.second, now.microsecond) + toks["absTime"] = day + timeOfDay + +def calculateTime(toks): + if toks.absTime: + absTime = toks.absTime + else: + absTime = datetime.now() + if toks.timeOffset: + absTime += toks.timeOffset + toks["calculatedTime"] = absTime + +# grammar definitions +CL = CaselessLiteral +today, tomorrow, yesterday, noon, midnight, now = map( CL, + "today tomorrow yesterday noon midnight now".split()) +plural = lambda s : Combine(CL(s) + Optional(CL("s"))) +month, week, day, hour, minute, second = map( plural, + "month week day hour minute second".split()) +am = CL("am") +pm = CL("pm") +COLON = Suppress(':') + +# are these actually operators? +in_ = CL("in").setParseAction(replaceWith(1)) +from_ = CL("from").setParseAction(replaceWith(1)) +before = CL("before").setParseAction(replaceWith(-1)) +after = CL("after").setParseAction(replaceWith(1)) +ago = CL("ago").setParseAction(replaceWith(-1)) +next_ = CL("next").setParseAction(replaceWith(1)) +last_ = CL("last").setParseAction(replaceWith(-1)) + +couple = (Optional(CL("a")) + CL("couple") + Optional(CL("of"))).setParseAction(replaceWith(2)) +a_qty = CL("a").setParseAction(replaceWith(1)) +integer = Word(nums).setParseAction(lambda t:int(t[0])) +int4 = Group(Word(nums,exact=4).setParseAction(lambda t: [int(t[0][:2]),int(t[0][2:])] )) +qty = integer | couple | a_qty +dayName = oneOf( list(calendar.day_name) ) + +dayOffset = (qty("qty") + (month | week | day)("timeunit")) +dayFwdBack = (from_ + now.suppress() | ago)("dir") +weekdayRef = (Optional(next_ | last_,1)("dir") + dayName("day")) +dayRef = Optional( (dayOffset + (before | after | from_)("dir") ).setParseAction(convertToTimedelta) ) + \ + ((yesterday | today | tomorrow)("name")| + weekdayRef("wkdayRef")).setParseAction(convertToDay) +todayRef = (dayOffset + dayFwdBack).setParseAction(convertToTimedelta) | \ + (in_("dir") + qty("qty") + day("timeunit")).setParseAction(convertToTimedelta) + +dayTimeSpec = dayRef | todayRef +dayTimeSpec.setParseAction(calculateTime) + +hourMinuteOrSecond = (hour | minute | second) + +timespec = Group(int4("miltime") | + integer("HH") + + Optional(COLON + integer("MM")) + + Optional(COLON + integer("SS")) + (am | pm)("ampm") + ) +absTimeSpec = ((noon | midnight | now | timespec("timeparts"))("timeOfDay") + + Optional(dayRef)("dayRef")) +absTimeSpec.setParseAction(convertToAbsTime,calculateTime) + +relTimeSpec = qty("qty") + hourMinuteOrSecond("timeunit") + \ + (from_ | before | after)("dir") + \ + absTimeSpec("absTime") | \ + qty("qty") + hourMinuteOrSecond("timeunit") + ago("dir") | \ + in_ + qty("qty") + hourMinuteOrSecond("timeunit") +relTimeSpec.setParseAction(convertToTimedelta,calculateTime) + +nlTimeExpression = (absTimeSpec | dayTimeSpec | relTimeSpec) + +if __name__ == "__main__": + # test grammar + tests = """\ + today + tomorrow + yesterday + in a couple of days + a couple of days from now + a couple of days from today + in a day + 3 days ago + 3 days from now + a day ago + now + 10 minutes ago + 10 minutes from now + in 10 minutes + in a minute + in a couple of minutes + 20 seconds ago + in 30 seconds + 20 seconds before noon + 20 seconds before noon tomorrow + noon + midnight + noon tomorrow + 6am tomorrow + 0800 yesterday + 12:15 AM today + 3pm 2 days from today + a week from today + a week from now + 3 weeks ago + noon next Sunday + noon Sunday + noon last Sunday + 2009/12/22 12:13:14""".splitlines() + + for t in tests: + print t, "(relative to %s)" % datetime.now() + res = nlTimeExpression.parseString(t) + if "calculatedTime" in res: + print res.calculatedTime + else: + print "???" + print + diff --git a/gracedb/query.py b/gracedb/query.py index 793613c5a4002762e610a60a3a544ece825579c0..359702db580e80ba38fd519dc6e002fcf1cce21a 100644 --- a/gracedb/query.py +++ b/gracedb/query.py @@ -11,6 +11,10 @@ #import pyparsing as p +# (weak) natural language time parsing. +from nltime import nlTimeExpression as nltime_ +nltime = nltime_.setParseAction(lambda toks: toks["calculatedTime"]) + import models from django.db.models import Q @@ -80,6 +84,11 @@ hidRange = hid + Suppress("..") + hid hidQ = Optional(Suppress(Keyword("hid:"))) + (hid^hidRange) hidQ = hidQ.setParseAction(maybeRange("hid", dbname="id")) +# Created times +nltimeRange = nltime + Suppress("..") + nltime +createdQ = Optional(Suppress(Keyword("created:"))) + (nltime^nltimeRange) +createdQ = createdQ.setParseAction(maybeRange("created")) + # Labels labelNames = ["DQV", "INJ", "LUMIN_NO", "LUMIN_GO", "SWIFT_NO", "SWIFT_GO"] @@ -105,7 +114,7 @@ dateQ = (Optional(Suppress(Keyword("date:"))) + dateTime).\ setParseAction(doDate) -q = (gidQ | hidQ | atypeQ | groupQ | gpsQ | labelQ ).setName("query term") +q = (gidQ | hidQ | atypeQ | groupQ | gpsQ | labelQ | createdQ).setName("query term") def parseQuery(s): d={}