Question

I'm trying to calculate the next point in time which complies with some simple rules.

Only include Weekdays mentioned in a list (in the example below, Mon-Fri). Return only hours which are listed (12:00, 18:00).

I'd like to get the first such point, starting from the After date provided.

I've got as far as this but it's returning an 'unrounded' time which just seems to be a constant offset from the current time

def NextRunTime(After=django.utils.timezone.now()):
    Sched = {'Hours': [12,18], 'Days': [0,1,2,3,4]}
    rule = DateRule.rrule(DateRule.HOURLY,
                byweekday=Sched['Days'],
                byhour=Sched['Hours'],
                dtstart=After,
                count=1)
    return rule[0]

This is currently returning 2013-05-01 18:53:39+01 which seems to be relative to the current time of day.

Can someone tell me how I can get rrule to use times relative to midnight but return dates after After?

Explicit examples of desired output

After           Output
Mon 10:11:12    Mon 12:00:00
Mon 11:22:33    Mon 12:00:00
Mon 12:00:01    Mon 18:00:00
Fri 12:00:01    Fri 18:00:00
Fri 18:00:01    Mon 12:00:00 (Following Monday)
Was it helpful?

Solution

I think you are looking for bysetpos = 1. This parameter causes rrule to return datetimes which are at "position 1" in a set of matches. "position 1" is relative to the byweekday, byhour, byminute, bysecond, etc. So by setting byminute=0 and bysecond=0, and bysetpos=1, rrule always returns hour 12 or hour 18:

import dateutil.rrule as DateRule
import datetime as DT

def NextRunTime(After):
    Sched = {'Hours': [12,18], 'Days': [0,1,2,3,4]}
    rule = DateRule.rrule(DateRule.HOURLY,
                          byweekday=Sched['Days'],
                          byhour=Sched['Hours'],
                          byminute=0,
                          bysecond=0,
                          bysetpos=1,
                          # dtstart=After means rule might return After
                          dtstart=After,
                          # To ensure NextRunTime returns something after After, use
                          # dtstart=After+DT.timedelta(seconds=1), 
                          count=1)
    return rule[0]

tests = [
    (DT.datetime(2013,4,29,10,11,12),
     DT.datetime(2013,4,29,12,0,0)),
    (DT.datetime(2013,4,29,11,22,33),
     DT.datetime(2013,4,29,12,0,0)),
    (DT.datetime(2013,4,29,12,0,1),
     DT.datetime(2013,4,29,18,0,0)),
    (DT.datetime(2013,5,3,12,0,1),
     DT.datetime(2013,5,3,18,0,0)),
    (DT.datetime(2013,5,3,18,0,1),
     DT.datetime(2013,5,6,12,0,0)),
    (DT.datetime(2013,4,29,12,0,0),  # Note that NextRunTime(After) returns After
     DT.datetime(2013,4,29,12,0,0)),
    ]

for After, answer in tests:
    result = NextRunTime(After)
    try:
        assert result == answer
    except AssertionError:
        print('''\
result:
{result}
answer:
{answer}
'''.format(**locals()))
        raise

OTHER TIPS

You should either use dtstart with minutes=0 and seconds=0 or chop the time from the returned value yourself.

Because this is how rrule is expected to work - it adds some intervals to the passed dtstart, which in your case includes minutes and seconds.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top