Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Dateutil to the Rescue!

Mike Pirnat
February 24, 2007

Dateutil to the Rescue!

An overview of the capabilities of dateutil, a powerful set of extensions for Python's datetime library. Presented to ClePy on February 5, 2007, and at PyCon 2007 on February 24, 2007.

Mike Pirnat

February 24, 2007
Tweet

More Decks by Mike Pirnat

Other Decks in Programming

Transcript

  1. The Challenge • New calendar/reminder web app for AmericanGreetings.com and

    BlueMountain.com (3M+ users at launch) • Replace a third-party Perl+ASP app • More sophisticated recurrence • Hands-off maintenance of recurring dates • Handle time zones properly 2
  2. What’s Dateutil? • Created by Gustavo Niemeyer • Extensions for

    Python’s datetime module • Recurrence: iCal (RFC 2445) and more • Time zones: internal and from Olson • Parse date strings in any format • Relative timedeltas 3
  3. Why Dateutil? • Works in Python 2.3 and up •

    Comfortable, Pythonic use • Time zones work right (pytz DST issues) • Recurrence is a snap • Nice extras that I didn’t know I needed • No dependence on other products (Zope) 4
  4. Parsing Dates • Turn a date string into a datetime

    instance • Can include times, time zones, and more in the parsed string • Very helpful for making usable values from some data source (database, iCal file, etc.) 5
  5. Parsing Dates >>> from datetime import datetime >>> from dateutil.parser

    import parse >>> parse('02/24/2007') datetime.datetime(2007, 2, 24, 0, 0) >>> parse('2007-02-24') datetime.datetime(2007, 2, 24, 0, 0) >>> parse('02/24/2007 15:15') datetime.datetime(2007, 2, 24, 15, 15) >>> parse('02/24/2007 3:15pm') datetime.datetime(2007, 2, 24, 15, 15) 6
  6. Two-Digit Years? >>> parse('02/24/56') datetime.datetime(2056, 2, 24, 0, 0) >>>

    parse('02/24/57') datetime.datetime(1957, 2, 24, 0, 0) The computed year is never more than 49 years from the current year... 7
  7. • Many, many more formats supported • Numerous keyword arguments

    available to fit your parsing needs • Overriding default components • Format precedence • Time zones • Alternative parsers But Wait, There’s More! 8
  8. Relative Timedeltas • Useful for getting fuzzy, human-style dates and

    datetimes (last day of next month, etc.) • Always true: dt2 + relativedelta(dt1, dt2) == dt1 9
  9. Making a Relativedelta >>> from dateutil.relativedelta import relativedelta >>> rd

    = relativedelta(dt1, dt2) • Can be made from two datetime instances • Works for date instances too 10
  10. Making a Relativedelta with Keywords • Absolute values: year, month,

    day, hour, minute, second, microsecond • Relative values: years, months, days, hours, minutes, seconds, microseconds • weekday: can specify the Nth weekday, e.g. MO(+3) is three Mondays forward >>> from dateutil.relativedelta import MO, TU, WE, TH, FR, SA, SU 11
  11. >>> now = datetime(2007, 2, 24) >>> now + relativedelta(months=2)

    datetime.datetime(2007, 4, 24, 0, 0) >>> now + relativedelta(months=2, day=31) datetime.datetime(2007, 4, 30, 0, 0) Using Relativedelta 12
  12. >>> now + relativedelta(months=+2, weekday=MO) datetime.datetime(2007, 4, 30, 0, 0)

    >>> now + relativedelta(months=+2, weekday=MO(+2)) datetime.datetime(2007, 5, 7, 0, 0) >>> now + relativedelta(months=+2, weekday=MO(-1)) datetime.datetime(2007, 4, 23, 0, 0) Using Relativedelta 13
  13. Time Zones >>> from dateutil import zoneinfo >>> zone =

    zoneinfo.gettz('US/Central') >>> dt = datetime.now(zone) >>> dt.astimezone(zoneinfo.gettz('US/Eastern')) Use dateutil.zoneinfo module for direct access to compiled time zone information 14
  14. Time Zones • Use dateutil.tz for more abstract time zone

    activity • Many ways to get a tzinfo to use: from tzfiles, TZ environment string, specified ranges using relative deltas, local machine time, fixed offset, UTC, iCal... 15
  15. >>> from dateutil import tz >>> datetime.now(tz.tzutc()) datetime.datetime(2007, 1, 6,

    0, 15, 40, 441383, tzinfo=tzutc()) >>> datetime.now(tz.tzoffset("BRST", -10800)) datetime.datetime(2007, 1, 5, 21, 19, 51, 737488, tzinfo=tzoffset('BRST', -10800)) >>> datetime.now(tz.tzlocal()) datetime.datetime(2007, 1, 6, 11, 18, 20, 466051, tzinfo=tzlocal()) UTC, Offset, Local 16
  16. Local Time Zone >>> tz.gettz('EST5') tzstr('EST5') >>> import os >>>

    os.environ['TZ'] = 'America/New York' >>> tz.gettz() tzfile('/usr/share/zoneinfo/America/New_York') >>> tz.gettz('foobar') >>> 18
  17. Simple Recurrence • rrule instances define an iCal-like recurrence rule

    • Must specify the recurrence frequency when instantiating • Set other components using keyword arguments 19
  18. rrule Keywords • freq • cache • dtstart • interval

    • wkst • count • until • bysetpos • bymonth • byday • bymonthday • byyearday • byweekno • byweekday • byhour • byminute • bysecond • byeaster 20
  19. rrule Methods • rr.before(dt, inc=False) • rr.after(dt, inc=False) • rr.between(dt1,

    dt2, inc=False) • rr.count() • Can also index and slice like a list! rr[0], rr[-1], rr[1:2], rr[::2] etc. 21
  20. rrule Examples >>> from dateutil.rrule import rrule, YEARLY >>> rr

    = rrule(YEARLY, dtstart=datetime(1948, 2, 24)) >>> rr.after(datetime(2007,2,1)) datetime.datetime(2007, 2, 24, 0, 0) Many events in our system are birthdays... (Hi, Mom!) 22
  21. rrule Examples >>> from dateutil.rrule import WEEKLY, TU >>> rr

    = rrule(WEEKLY, interval=2, byweekday=TU, dtstart=datetime(2007, 2, 6, 13)) >>> rr.between(datetime(2007,2,1), datetime(2007,3,1)) [datetime.datetime(2007, 2, 6, 13, 0), datetime.datetime(2007, 2, 20, 13, 0)] A biweekly meeting, every other Tuesday at 13:00... 23
  22. rrule Examples >>> from dateutil.rrule import MONTHLY, MO >>> rr

    = rrule(MONTHLY, byweekday=MO(+1), dtstart=datetime(2005, 6, 6, 18, 30)) >>> rr.before(datetime(2007, 2, 23)) datetime.datetime(2007, 2, 5, 18, 30) My local Python group meets on the first Monday of each month at 18:30... 24
  23. rrule Examples >>> rr = rrule(YEARLY, byeaster=-2) >>> rr[0] datetime.datetime(2007,

    4, 6, 16, 11, 3) Good Friday is a company holiday... 25
  24. rrule Examples >>> rr = rrule(YEARLY, interval=4, bymonth=11, byweekday=TU, bymonthday=(2,3,4,5,6,7,8),

    dtstart=datetime(2000, 11, 7)) >>> rr[0:3] [datetime.datetime(2000, 11, 7, 0, 0), datetime.datetime(2004, 11, 2, 0, 0), datetime.datetime(2008, 11, 4, 0, 0)] US Presidential Election Day, every four years on the first Tuesday after the first Monday of November... 26
  25. Complex Recurrence with rruleset • Mix several recurrence patterns together

    • Specific recurrence dates • Recurrence exception rules • Recurrence exception dates 27
  26. rruleset Methods • rs.rrule(rrule) • rs.rdate(dt) • rs.exrule(rrule) • rs.exdate(dt)

    • rs.before(dt, inc=False) • rs.after(dt, inc=False) • rs.between(dt1, dt2, inc=False) • rs.count() • List indexing and slicing 28
  27. Exceptions to the Rule >>> from dateutil.rrule import rruleset, SA,

    DAILY >>> rs = rruleset() >>> rs.rrule(rrule(WEEKLY, byweekday=SA, dtstart=datetime(2007,2,3,13))) >>> rs.exrule(rrule(DAILY, dtstart=datetime(2007,2,22, 13), count=4)) >>> rs[0:4] [datetime.datetime(2007, 2, 3, 13, 0), datetime.datetime(2007, 2, 10, 13, 0), datetime.datetime(2007, 2, 17, 13, 0), datetime.datetime(2007, 3, 3, 13, 0)] A weekly event that conflicts with PyCon... 29
  28. Parse Recurrence with rrulestr >>> from dateutil.rrule import rrulestr >>>

    rr = rrulestr(""" ... DTSTART:19480224 ... RRULE:FREQ=YEARLY;INTERVAL=1 ... """) >>> rr.after(datetime(2007,1,1)) datetime.datetime(2007, 2, 24, 0, 0) Use rrule.rrulestr() to make an rrule or rruleset from an iCal-style string! 30
  29. The Pirates of Penzance Dilemma What happens when your birthday

    is February 29? [spoiler warning!] ©2004 Michal Daniel 31
  30. Yearly + Feb. 29 = ? >>> rr = rrule(YEARLY,

    dtstart=datetime(2000,2,29)) >>> rr[21] datetime.datetime(2084, 2, 29, 0, 0) You’re stuck with the pirates... and you don’t get the girl (at least not right away) 32
  31. Besting the Pirate King • Build an rruleset normally •

    Figure out how much time we care about • Use a simple rrule to generate Feb. 28 datetimes for this range (or DIY) • Attach these dates as rdates to the rruleset • The rruleset now produces occurrences every calendar year 33
  32. Besting the Pirate King >>> rr = rrule(YEARLY, dtstart=datetime(2000,2,29)) >>>

    rr2 = rrule(YEARLY, dtstart=datetime(2000,2,28)) >>> rdates = [x for x in rr2[:100] if not x.year % 4 == 0] # <-- lame >>> rs = rruleset() >>> rs.rrule(rr) >>> for rd in rdates: ... rs.rdate(rd) >>> rs[4] datetime.datetime(2004, 2, 29, 0, 0) >>> rs[21] datetime.datetime(2021, 2, 28, 0, 0) 34
  33. Concluding Thoughts • Saved me much pain and hardship •

    Helped my project launch on time • Many more examples at the dateutil site • Would be nice to have in the standard library, or at least better advertised • Read your Python Cookbook! 35
  34. Acknowledgments • Gustavo Niemeyer, for dateutil and his blessing to

    present it • Michal Daniels and the Guthrie Theater, for permission to use the Pirates of Penzance image 36