Blame it on Caesar!

Lennart Regebro

PyCon US 2013, Santa Clara, Grand Ballroom AB







Grand Ballroom AB, eh?


Why not in the "Great America" Ballroom?


I heard America was great.


It was smaller than I expected.


Didn't look that special either.




Calendaring

Measuring time in celestial cycles

Roman calendars

You name 'em, they had 'em

Calendar of Romulus

Lunar calendars

Lunisolar calendars

Villain #1

Gaius Julius Caesar

Solar calendars

My calendar brings all the boys to the yard!

And they're like, it's better than yours.

Damn right, it's better than yours!

I could teach you, but I'd have to charge.

Spot the patterns

31 28 31 30 31 30 31 31 30 31 30 31

31 28 31 30 31 30 31 31 30 31 30 31

31 28 31 30 31 30 31 31 30 31 30 31

31 29 31 30 31 30 31 31 30 31 30 31

Leap Day!

February 24th!

Don't ask why

It's the 29th now

Except in Hungary

Weeks

Egyptian calendar math

def day_of_year(month, day):
    return (month - 1) * 30 + day_of_month

def day_of_week(day):
    return ((day - 1) % 10) + 1

def weekno(month, day):
    return ((day_of_year(month, day) - 1) // 10) + 1

Julian calendar math

MONTH_LENGHTS_STD = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]
MONTH_LENGHTS_LYR = [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335]
MONTH_DAY_OF_WEEK = [0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4]

def day_of_year(year, month, day):
    if not year % 4 and year % 100:
        d = MONTH_LENGHTS_LYR[month-1]
    else:
        d = MONTH_LENGHTS_STD[month-1]
    return d + day

def day_of_week(year, month, day):
    if month < 3:
        year -= 1
    dow = (year + year//4 - year//100 + year//400 +
           MONTH_DAY_OF_WEEK[month-1] + day) % 7
    if dow == 0:
        dow = 7
    return dow

def _weekno(year, month, day):
    return (day_of_year(year, month, day) -
            day_of_week(year, month, day) + 10) // 7

def weekno(year, month, day):
    week = _weekno(year, month, day)
    if week == 0:
        week = _weekno(year - 1, 12, 31)
    if week == 53:
        if _weekno(year + 1, 1, 1) == 1:
            week = 1
    return week

Calendaring and you

There is calendaring

bug born every day!

Always wear a library!

Python

datetime

Javascript

Javascript has zero-indexed months

January is 0

December is 11

Villain #2

James Gosling; created Java

You put the first day

into the week end?

Python and weekdays

weekday() uses 0 for Monday

ISO and weekdays

isoweekday() uses 1 for Monday

Always use 1 for Monday

And 7 for Sunday

Or possibly 0

But never ever 6

Time zones

A brief history of something that doesn't exist

Villain #3

William Willet; invented DST

Midnight DST change

You are scaring the computer

>>>> from datetime import date
>>> date(2011,  2, 29)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: day is out of range for month
js> new Date(2011, 1, 29);
Tue Mar 01 2011 00:00:00 GMT+0100 (CET)
js> y = 2011; m = 1; d = 29;
js> dt = new Date(y, m, d);
js> if (dt.getFullYear() !== y ||
        dt.getMonth() !== m ||
        dt.getDay() !== d) {
  > (Insert error handling here)
  > }
$ TZ='Europe/Rome' js
Rhino 1.7 release 3 2012 02 16
js> new Date(1972, 4, 28);
Sat May 27 1972 23:00:00 GMT+0100 (CET)
js> new Date(y, m, d, 12);
Sun May 28 1972 12:00:00 GMT+0200 (CEST)

Python & Timezones

Python 2.3 - 3.1

Abstract class tzinfo only

Python 3.2 - 3.3

timezone class for fixed offsets

Python 3.4?

PEP-431: Full zoneinfo support

pytz or dateutil?

Let's have a shootout!

pytz has the latest zoneinfo database

pytz 15 - 0 dateutil

dateutil will use the OS database

pytz 15 - 15 dateutil

But not on Windows

pytz 30 - 15 dateutil

dateutil can parse dates with time zones

pytz 30 - 30 dateutil

Which half past two do you mean?

>>> rome = pytz.timezone('Europe/Rome')
>>> rome.localize(datetime(1972, 5, 28, 0, 0), is_dst=None)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python2.7/dist-packages/pytz/tzinfo.py",
    line 322, in localize
    raise NonExistentTimeError(dt)
pytz.exceptions.NonExistentTimeError: 1972-05-28 00:00:00
>>>

pytz has better DST handling

pytz 40 - 30 dateutil

dateutil has better local time zone support

pytz 40 - 40 dateutil

tzlocal

Local time zone support for pytz

http://pypi.python.org/pypi/tzlocal

Advantage pytz

PEP-431 is based on pytz

But not the API

Parsing timezones

RFC 822 format

>>> from dateutil.parser import parse

>>> parse('2003-09-25 12:45 -0300')
datetime.datetime(2003, 9, 25, 12, 45,
        tzinfo=tzoffset(None, -10800))

POSIX format

>>> parse('2003-09-25 12:45 GMT-0300')
datetime.datetime(2003, 9, 25, 12, 45,
        tzinfo=tzoffset(None, 10800))

Wait what?

iCalendar

RFC 5545 / RFC 2445

The Python Secret Underground meeting schedule

First meeting: Saturday 28th of August 1999

Every Last Saturday of the month, except in August, when it's the 28th. And also, every 13th of every month, unless it's a Friday.

SECONDLY

MINUTELY

HOURLY

DAILY

WEEKLY

MONTHLY

YEARLY

BYMONTH

Limit

Limit

Limit

Limit

Limit

Limit

Expand

BYWEEKNO

N/A

N/A

N/A

N/A

N/A

N/A

Expand

BYYEARDAY

Limit

Limit

Limit

N/A

N/A

N/A

Expand

BYMONTHDAY

Limit

Limit

Limit

Limit

N/A

Expand

Expand

BYDAY

Limit

Limit

Limit

Limit

Expand

Note 1

Note 2

BYHOUR

Limit

Limit

Limit

Expand

Expand

Expand

Expand

BYMINUTE

Limit

Limit

Expand

Expand

Expand

Expand

Expand

BYSECOND

Limit

Expand

Expand

Expand

Expand

Expand

Expand

BYSETPOS

Limit

Limit

Limit

Limit

Limit

Limit

Limit

dateutil.rrule

Implements all this so you don't have to!

Using a timezone in iCalendar

DTSTART;TZID=America/New_York:19980119T020000

You must define the zone

BEGIN:VTIMEZONE
TZID:America/New_York
LAST-MODIFIED:20050809T050000Z
BEGIN:DAYLIGHT
DTSTART:19670430T020000
RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=-1SU;UNTIL=19730429T070000Z
TZOFFSETFROM:-0500
TZOFFSETTO:-0400
TZNAME:EDT
END:DAYLIGHT
BEGIN:STANDARD
DTSTART:19671029T020000
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=20061029T060000Z
TZOFFSETFROM:-0400
TZOFFSETTO:-0500
TZNAME:EST
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:19740106T020000
RDATE:19750223T020000
TZOFFSETFROM:-0500
TZOFFSETTO:-0400
TZNAME:EDT
END:DAYLIGHT
BEGIN:DAYLIGHT
DTSTART:19760425T020000
RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=-1SU;UNTIL=19860427T070000Z
TZOFFSETFROM:-0500
TZOFFSETTO:-0400
TZNAME:EDT
END:DAYLIGHT
BEGIN:DAYLIGHT
DTSTART:19870405T020000
RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=1SU;UNTIL=20060402T070000Z
TZOFFSETFROM:-0500
TZOFFSETTO:-0400
TZNAME:EDT
END:DAYLIGHT
BEGIN:DAYLIGHT
DTSTART:20070311T020000
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
TZOFFSETFROM:-0500
TZOFFSETTO:-0400
TZNAME:EDT
END:DAYLIGHT
BEGIN:STANDARD
DTSTART:20071104T020000
RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
TZOFFSETFROM:-0400
TZOFFSETTO:-0500
TZNAME:EST
END:STANDARD
END:VTIMEZONE

Good enough

DTSTART:19980119T080000Z
DTSTART:19980119T020000

The icalendar module

Further considerations

User interface

Date inputs: The text field

The date picker

The drop down list

jquery.recurrenceinput

https://github.com/collective/jquery.recurrenceinput.js

http://regebro.wordpress.com/

Q?

Creaditus Generalus

Presentation software:

Hovercraft

https://github.com/regebro/hovercraft

Fonts:

Cinzel by Natanael Gama

DejaVu Mono Book

SpaceForward
Left, Down, Page DownNext slide
Right, Up, Page UpPrevious slide
POpen presenter console
HToggle this help