Пример #1
0
    def human_time(self, full=False, preposition=True):
        assert self.reminder_time is not None
        user_tz = self.get_user().timezone
        now = util.now_utc()
        delta = self.reminder_time - now
        # Default timezone to US/Eastern TODO magic string used in a couple places
        tz = timezone(user_tz) if user_tz else timezone('US/Eastern')
        needs_date = full or delta.total_seconds() > 60 * 60 * 16  # today-ish
        needs_day = full or (needs_date and delta.days > 7)
        needs_year = full or (needs_day
                              and self.reminder_time.year != now.year)

        # now consider the repetition
        needs_dow = needs_date and self.repetition.interval in (None,
                                                                INTERVAL_WEEK)
        needs_day = needs_day and self.repetition.interval in (None,
                                                               INTERVAL_YEAR,
                                                               INTERVAL_MONTH)
        needs_month = needs_day and self.repetition.interval != INTERVAL_MONTH
        needs_year = needs_year and not self.repeats(
        )  # no repeating intervals state the year
        needs_time = self.repetition.interval not in ("hour", "minute")

        needs_date = any((needs_dow, needs_day, needs_month, needs_year))

        fmt = ""
        if needs_date and preposition:
            fmt += "on "
        if needs_dow:
            fmt += "%A "  # on Monday
        if needs_month and needs_day:
            fmt += "%B %-d "  # April 10
        if needs_day and not needs_month:
            fmt += "the {S} "  # the 10th
        if needs_year:
            fmt += "%Y "  # 2018
        if needs_time:
            if needs_date or preposition:
                fmt += "at "
            fmt += "%-I:%M %p"  # at 10:30 AM
            if not user_tz:
                fmt += " %Z"  # EDT or EST
        # TODO maybe this (or something nearby) will throw pytz.exceptions.AmbiguousTimeError
        # near DST transition?
        local = util.to_local(self.reminder_time, tz)
        formatted_time = util.strftime(fmt, local)
        if self.repeats():
            rp = "every "
            if self.repetition.nth > 1:
                rp += str(
                    self.repetition.nth) + " " + self.repetition.interval + "s"
            else:
                rp += self.repetition.interval

            if self.repetition.interval in ["hour", "minute"]:
                return rp  # exclude the "at 9:00 PM" portion
            return rp + " " + formatted_time
        return formatted_time
Пример #2
0
 def __init__(self, body, time, repetition, username, conv_id, db):
     # time is a datetime in utc
     self.reminder_time = time
     self.created_time = util.now_utc()
     self.body = body
     self.repetition = repetition if repetition else Repetition(None, None)
     self.username = username
     self.conv_id = conv_id
     self.deleted = False
     self.id = None  # when it's from the DB
     self.db = db
Пример #3
0
 def set_active(self, when=None):
     if when is None:
         when = util.now_utc()
     self.last_active_time = when
     #print "Setting last active time!", self.last_active_time
     with sqlite3.connect(self.db) as c:
         cur = c.cursor()
         cur.execute(
             'update conversations set last_active_time=? where id=?',
             (util.to_ts(self.last_active_time), self.id))
         assert cur.rowcount == 1
Пример #4
0
def get_due_reminders(db):
    reminders = []
    now_ts = util.to_ts(util.now_utc())
    with sqlite3.connect(db) as c:
        c.row_factory = sqlite3.Row
        cur = c.cursor()
        cur.execute(
            'SELECT rowid, * FROM reminders WHERE reminder_time<=? AND deleted=0 LIMIT 100',
            (now_ts, ))
        for row in cur:
            reminders.append(Reminder.from_row(row, db))
    return reminders
Пример #5
0
 def set_next_reminder(self):
     if not self.repeats():
         return
     new_time = INTERVALS[self.repetition.interval](self.reminder_time,
                                                    self.repetition.nth)
     # In case the reminder was older than now (maybe bot was offline), make sure the next reminder is in the future:
     while new_time < util.now_utc():
         new_time = INTERVALS[self.repetition.interval](new_time,
                                                        self.repetition.nth)
     new_reminder = Reminder(self.body, new_time, self.repetition,
                             self.username, self.conv_id, self.db)
     new_reminder.store()
Пример #6
0
 def get_all_reminders(self):
     reminders = []
     with sqlite3.connect(self.db) as c:
         c.row_factory = sqlite3.Row
         cur = c.cursor()
         cur.execute(
             '''select rowid, * from reminders where conv_id=?
                 and reminder_time>=?
                 and deleted=0
                 order by reminder_time''',
             (self.id, util.to_ts(util.now_utc())))
         for row in cur:
             reminders.append(Reminder.from_row(row, self.db))
     return reminders
Пример #7
0
 def is_recently_active(self):
     MINUTES = 30
     return self.last_active_time and \
             (util.now_utc() - self.last_active_time).total_seconds() < 60 * MINUTES
Пример #8
0
def try_parse_when(when, user):
    def fixup_times(when_str, relative_base):
        # When there is no explicit AM/PM assume the next upcoming one
        # H:MM (AM|PM)?

        def hhmm_explicit_ampm(when_str, relative_base):
            # looks for HH:MM without an AM or PM and adds 12 to the HH if necessary.
            # Returns str if the returned str is good to go, None if it's not fixed.
            time_with_minutes = regex(
                '(?:[^\w]|^)(\d\d?(:\d\d))(\s?[ap]\.?m)?')
            results = re.findall(time_with_minutes, when_str)
            if len(results) != 1:
                # I don't expect to find more than one time. Rather not do anything.
                return None
            time_match, mins_match, ampm_match = results[0]
            if ampm_match:
                # AM/PM is explicit
                return when_str

            time = datetime.strptime(time_match, '%I:%M')

            if time.hour > 12:
                return when_str  # you explicitly are after noon e.g. 23:00

            if relative_base.hour > time.hour:
                new_hour = str(time.hour + 12)
                return when_str.replace(time_match, new_hour + mins_match)

            return None

        def at_hh_explicit_ampm(when_str, relative_base):
            # looks for "at HH" without AM/PM and adds 12 to HH and :00 if necessary.
            at_hh_regex = regex(
                '(?:(?:^|\s)at\s|^)(\d\d?)($|\s?[ap]\.?m\.?(?:$|[^\w]))')
            results = re.findall(at_hh_regex, when_str)
            if len(results) != 1:
                # I don't expect to find more than one time. Rather not do anything.
                return None
            hour_match, ampm_match = results[0]
            when_with_minutes = when_str.replace(hour_match,
                                                 hour_match + ":00")
            if ampm_match:
                # AM/PM is explicit
                return when_with_minutes

            time = datetime.strptime(hour_match, '%I')

            if time.hour > 12:
                return when_with_minutes  # you explicitly are after noon e.g. 23:00

            if relative_base.hour > time.hour:
                new_hour = str(time.hour + 12)
                return when_with_minutes.replace(hour_match, new_hour)

            return when_with_minutes

        new_when = hhmm_explicit_ampm(when_str, relative_base)
        if new_when:
            return new_when
        new_when = at_hh_explicit_ampm(when_str, relative_base)
        if new_when:
            return new_when
        # else... none of the fixes worked
        return when_str

    def extract_repetition(when_str):
        # Returns a new when_str, Repetition
        if not "every" in when_str:
            return when_str, Repetition(None, None)
        when_str = when_str.replace("week day", "weekday")
        days = set([
            "sunday", "monday", "tuesday", "wednesday", "thursday", "friday",
            "saturday"
        ])
        intervals_day = set(
            ["day", "night", "evening", "morning", "afternoon"])
        intervals = set(INTERVALS)
        # 0: nth; 1: interval; 2: rest (e.g. "at 2pm")
        regexes = [
            regex("(?:on )?every (?:(?P<nth>\w+) )?(?P<interval>" + i +
                  ")s?(?:$|(?: (?P<rest>.+)))")
            for i in days | intervals_day | intervals
        ]

        for r in regexes:
            match = r.search(when_str)
            if match:
                nth_text = match.group('nth')
                interval = match.group('interval')
                rest = match.group('rest')
                if nth_text:
                    nth_text = nth_text.lower()
                    nths = {"other": 2, "second": 2, "third": 3, "fourth": 4}
                    if nth_text in nths:
                        nth = nths[nth_text]
                    else:
                        nth_regex = regex("(\d+)[a-z]*")
                        nth_match = nth_regex.search(nth_text)
                        if nth_match:
                            nth = int(match.group(1))
                        else:
                            # an nth that I don't understand.
                            # TODO this is what posting to debug channel is for
                            nth = 1
                else:
                    nth = 1
                if interval:
                    interval = interval.lower()
                if interval in intervals_day:
                    interval = "day"
                if interval in days:
                    if rest:
                        rest = interval + " " + rest
                    else:
                        rest = "on " + interval
                    interval = "week"
                if not rest:
                    # TODO "weekday" doesn't work well here.
                    rest = "in " + str(nth) + " " + interval + ("s" if nth > 1
                                                                else "")
                return rest, Repetition(interval, nth)
        return when_str, Repetition(None, None)

    # include RELATIVE_BASE explicitly so we can mock now in tests
    local_timezone_str = user.timezone if user.timezone else 'US/Eastern'
    relative_base = util.now_local(local_timezone_str).replace(tzinfo=None)
    when = fixup_times(when, relative_base)
    when, repetition = extract_repetition(when)
    parse_date_settings = {
        'PREFER_DATES_FROM': 'future',
        'PREFER_DAY_OF_MONTH': 'first',
        'TO_TIMEZONE': 'UTC',
        'TIMEZONE': local_timezone_str,
        'RETURN_AS_TIMEZONE_AWARE': True,
        'RELATIVE_BASE': relative_base
    }
    dt = dateparser.parse(when, settings=parse_date_settings)
    if dt != None and (dt - util.now_utc()).total_seconds() < 0:
        return None, None
    return dt, repetition