Search code examples
python-3.xevalconfigparserpython-dateutil

how to convert a string to a dateutil weekday tuple without using eval


I'm using dateutil to generate a schedule based on an input file.

my config file looks like this:

[alarm1]
time = 8:00
days = MO

[alarm2]
time = 9:00
days = TU, WE

[alarm3]
time = 22:00
days = MO, WE, FR
etc..

I'm using dateutil to generate a schedule based off this.

here's some sample code, where I omitted the control flows and import code for the sake of brevity.

from dateutil.rrule import *
from dateutil.parser import *
import datetime
import configparser


alarms = rruleset()
time = parse(alarm_parser.get(alarm_section, 'time')).time()
date = datetime.date.today()
alarm_time= datetime.datetime.combine(date, time)
days = '(' + alarm_parser.get(alarm_section, 'days') + ',)'
alarm_days = eval(days)

alarms.rrule(rrule(WEEKLY, dtstart = alarm_datetime, byweekday = alarm_days,  count = 1))

this code now works thanks to eval However, eval apparently risky.

I've tried rrulestr(), but the command doesn't seem to accept byweekday = as part of the argument.

how do I convert the string in the config file to a tuple that rrule can work with without using eval? I've tried ast.literal_eval, but that gives me the following error

ValueError: malformed node or string: <_ast.Name object at 0xb68e2430>

Solution

  • I needed a slightly more complex version of Paul's simple code (one that could handle parenthesized offsets like "FR(+2)"). Here is what I came up with:

    import re
    from dateutil.rrule import MO, TU, WE, TH, FR, SA, SU
    
    _daymap = {"MO": MO, "TU": TU, "WE": WE, "TH": TH, "FR": FR, "SA": SA, "SU": SU}
    _dayre = re.compile(r"(MO|TU|WE|TH|FR|SA|SU)(?=\(([-+]?[0-9]+)\))?")
    
    
    def parsedays(daystr):
        for day in daystr.split(","):
            m = _dayre.match(day.strip())
            if m:
                weekday, offset = m.groups()
                result = _daymap[weekday]
                if offset:
                    result = result(int(offset))
                yield result
    
    
    def dumpdays(days):
        return ",".join(str(d) for d in days)
    

    Tested using Python 3.7.3