Login

Generate iCal VTIMEZONE block with DAYLIGHT and STANDARD components, based on pytz zoneinfo data

Author:
pgcd
Posted:
May 14, 2016
Language:
Python
Version:
1.7
Score:
1 (after 1 ratings)

In the last few days I spent a lot of time trying to find a library or repository of some kind that could help me generate the required DAYLIGHT and STANDARD components of ical VTIMEZONE blocks. Since I couldn't find anything, I cobbled together this snippet to poke around in pytz timezone information and output the bare minimum I needed to make my ICS files compliant and useful (DST transitions for this year and the next). I promise it's (superficially) tested against "real" ICS files, but that's all.

UPDATE: Thanks to @ariannedee for a much improved version (see comment for details)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import pytz
import icalendar
import datetime

def generate_vtimezone(timezone, for_date=None):
    if not timezone or 'utc' in timezone.lower():  # UTC doesn't need a timezone definition
        return None
    if not for_date:
        for_date = now()
    try:
        z = pytz.timezone(timezone)
    except pytz.UnknownTimeZoneError:
        z = pytz.timezone('Europe/Berlin')
    if not hasattr(z, '_utc_transition_times'):
        return None
    transitions = zip(z._utc_transition_times, z._transition_info)
    try:
        dst1, std1, dst2, std2 = filter(lambda x: x[0].year in (for_date.year, for_date.year + 1),
                                        transitions)
        if dst1[1][1].seconds == 0:
            return _vtimezone_with_dst(std1, dst1, std2, dst2, timezone)
        else:
            return _vtimezone_with_dst(dst1, std1, dst2, std2, timezone)

    except:
        std = transitions[-1]
        if std[0].year > for_date.year:
            return None
        return _vtimezone_without_dst(std, timezone)


def _vtimezone_without_dst(std, timezone):
    vtimezone = icalendar.Timezone(tzid=timezone)
    standard = icalendar.TimezoneStandard()
    utc_offset, dst_offset, tz_name = std[1]
    standard.add('dtstart', std[0])
    standard.add('tzoffsetfrom', utc_offset)
    standard.add('tzoffsetto', utc_offset)
    standard.add('tzname', tz_name)
    vtimezone.add_component(standard)
    return vtimezone


def _vtimezone_with_dst(dst1, std1, dst2, std2, timezone):
    vtimezone = icalendar.Timezone(tzid=timezone)
    daylight = icalendar.TimezoneDaylight()
    utc_offset, dst_offset, tz_name = dst1[1]
    offsetfrom = std1[1][0]
    daylight.add('dtstart', dst1[0] + offsetfrom)
    daylight.add('rdate', dst1[0] + offsetfrom)
    daylight.add('rdate', dst2[0] + offsetfrom)
    daylight.add('tzoffsetfrom', offsetfrom)
    daylight.add('tzoffsetto', utc_offset)
    daylight.add('tzname', tz_name)
    vtimezone.add_component(daylight)

    standard = icalendar.TimezoneStandard()
    utc_offset, dst_offset, tz_name = std1[1]
    offsetfrom = dst1[1][0]
    standard.add('dtstart', std1[0] + offsetfrom)
    standard.add('rdate', std1[0] + offsetfrom)
    standard.add('rdate', std2[0] + offsetfrom)
    standard.add('tzoffsetfrom', offsetfrom)
    standard.add('tzoffsetto', utc_offset)
    standard.add('tzname', tz_name)
    vtimezone.add_component(standard)
    return vtimezone

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 11 months, 2 weeks ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 11 months, 3 weeks ago
  3. Serializer factory with Django Rest Framework by julio 1 year, 6 months ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 7 months ago
  5. Help text hyperlinks by sa2812 1 year, 7 months ago

Comments

ariannedee (on July 18, 2016):

Thank you for this! I extended this solution to support southern hemisphere timezones (DST starts at opposite time of year) and timezones with no DST:

def generate_vtimezone(timezone, for_date=None):
    if not timezone or 'utc' in timezone.lower():  # UTC doesn't need a timezone definition
        return None
    if not for_date:
         for_date = now()
    z = pytz.timezone(timezone)
    transitions = zip(z._utc_transition_times, z._transition_info)
    try:
        dst1, std1, dst2, std2 = filter(lambda x: x[0].year in (for_date.year, for_date.year + 1),
                                        transitions)
        if dst1[1][1].seconds == 0:
            return _vtimezone_with_dst(std1, dst1, std2, dst2, timezone)
        else:
            return _vtimezone_with_dst(dst1, std1, dst2, std2, timezone)

    except:
        std = transitions[-1]
        if std[0].year > for_date.year:
            return None
        return _vtimezone_without_dst(std, timezone)

def _vtimezone_without_dst(std, timezone):
    vtimezone = icalendar.Timezone(tzid=timezone)
    standard = icalendar.TimezoneStandard()
    utc_offset, dst_offset, tz_name = std[1]
    standard.add('dtstart', std[0])
    standard.add('tzoffsetfrom', utc_offset)
    standard.add('tzoffsetto', utc_offset)
    standard.add('tzname', tz_name)
    vtimezone.add_component(standard)
    return vtimezone


def _vtimezone_with_dst(dst1, std1, dst2, std2, timezone):
    vtimezone = icalendar.Timezone(tzid=timezone)
    daylight = icalendar.TimezoneDaylight()
    utc_offset, dst_offset, tz_name = dst1[1]
    offsetfrom = std1[1][0]
    daylight.add('dtstart', dst1[0] + offsetfrom)
    daylight.add('rdate', dst1[0] + offsetfrom)
    daylight.add('rdate', dst2[0] + offsetfrom)
    daylight.add('tzoffsetfrom', offsetfrom)
    daylight.add('tzoffsetto', utc_offset)
    daylight.add('tzname', tz_name)
    vtimezone.add_component(daylight)

    standard = icalendar.TimezoneStandard()
    utc_offset, dst_offset, tz_name = std1[1]
    offsetfrom = dst1[1][0]
    standard.add('dtstart', std1[0] + offsetfrom)
    standard.add('rdate', std1[0] + offsetfrom)
    standard.add('rdate', std2[0] + offsetfrom)
    standard.add('tzoffsetfrom', offsetfrom)
    standard.add('tzoffsetto', utc_offset)
    standard.add('tzname', tz_name)
    vtimezone.add_component(standard)
    return vtimezone

#

angelika (on December 1, 2023):

Thank you both, you saved me so much time! 7 years later... :D

#

Please login first before commenting.