20

Outlook has some things to desire - like showing multiple month view

I decided to give it a try by pulling out the event data via Python (and then figure a way to display it nicely). Google is giving poor results.

My goals are:

  • read a shared calendar
  • read the events information like start, end, subject, creator, etc.

This is what I got together (with inspiration from this site)

import win32com.client, datetime
from dateutil.relativedelta import relativedelta

Outlook = win32com.client.Dispatch("Outlook.Application")
ns = Outlook.GetNamespace("MAPI")

appointments = namespace.GetDefaultFolder(9).Items 
# TODO: Need to figure out howto get the shared calendar instead Default [9] 
# (I have placed the shared folder into a separate folder - don't know if it matters)
# I would just like the user to select which calendar to execute on
appointments.Sort("[Start]")
appointments.IncludeRecurrences = "True"
begin = date.today().strftime("%m%d%Y")
end = (date.today() + relativedelta( months = 3 )).strftime("%m%d%Y")
appointments = appointments.Restrict("[Start] >= '" +begin+ "' AND [END] >= '" +end+ "'")

How do I loop through the events and read them?

Community
  • 1
  • 1
Norfeldt
  • 8,272
  • 23
  • 96
  • 152

1 Answers1

26

From here I need help with looping through the events and read them.

Basically, all you have to do is to follow the COM API documentation from Microsoft. For example, the Restrict() method returns AppointmentItem objects which are documented at AppointmentItem Object for Outlook 2010. So, starting with a folder, you can get and list the appointments as follows:

# Get the AppointmentItem objects
# http://msdn.microsoft.com/en-us/library/office/aa210899(v=office.11).aspx
appointments = someFolder.Items

# Restrict to items in the next 30 days (using Python 3.3 - might be slightly different for 2.7)
begin = datetime.date.today()
end = begin + datetime.timedelta(days = 30);
restriction = "[Start] >= '" + begin.strftime("%m/%d/%Y") + "' AND [End] <= '" +end.strftime("%m/%d/%Y") + "'"
restrictedItems = appointments.Restrict(restriction)

# Iterate through restricted AppointmentItems and print them
for appointmentItem in restrictedItems:
    print("{0} Start: {1}, End: {2}, Organizer: {3}".format(
          appointmentItem.Subject, appointmentItem.Start, 
          appointmentItem.End, appointmentItem.Organizer))

Note that I had to use a slightly different time format for the restriction expression ("%m/%d/%Y" instead of "%m%d%Y"). The proper solution would be to use Outlook's Format function as documented at http://msdn.microsoft.com/en-us/library/office/ff869597(v=office.14).aspx, section Date. Note also that I was using Python 3.3, so you might have to use different functions to create the dates. In any case, for testing purposes, you can use a hard coded expression like "[Start] >= '02/03/2014' AND [End] <= '03/05/2014'"

To get a shared calendar, the following code should work - This is the usual sequence found in the API documentation, however I was not able to actually get it working, but this could be due to a different backend server (not using an Exchange server):

recipient = namespace.createRecipient("User Name")
resolved = recipient.Resolve()
sharedCalendar = namespace.GetSharedDefaultFolder(recipient, 9)

To show all available folders as a tree, you can use something like

def folderTree(folders, indent = 0):
    prefix = ' ' * (indent*2)
    i = 0
    for folder in folders:
        print("{0}{1}. {2} ({3})".format(prefix, i, folder.Name, folder.DefaultItemType))
        folderTree(folder.Folders, indent + 1)
        i = i + 1

...
folderTree(namespace.Folders)

To look up a folder by its path (e.g. to find the calendar folder "Norfeld@so.com" below the "Internet Calendars" folder), you can use something like

def findFolder(folders, searchPath, level = 0):
    for folder in folders:
        if folder.Name == searchPath[level]:
            if level < len(searchPath)-1:
                # Search sub folder
                folder = findFolder(folder.folders, searchPath, level+1)
            return folder
    return None

...
sharedCalendar = findFolder(namespace.Folders, ["Internet Calendars", "Norfeld@so.com"])

See also:

Andreas Fester
  • 36,091
  • 7
  • 95
  • 123
  • 1
    Thank you for your answer - so my restriction string command was wrong. I find it hard to understand the MS API. How would I go about selecting another calendar than the default. Thinking that a loop through the names of all calendars would be nice. – Norfeldt Feb 03 '14 at 21:46
  • 2
    I agree that the COM API is not easy to read - but basically, the model is easy: There are objects which have Methods and Properties (and Events). Methods can be called and might return a result, like a reference to another object. Properties can be read (and written if not read-only), and reading might result again in references to another object. Some methods/properties return COM collections which can be iterated, which is seamlessly integrated into Python so that you can use `for ... in ...`. – Andreas Fester Feb 04 '14 at 12:17
  • It's not that I didn't try. But just can't make your folderTree work - which I need in order to grab the calendar of interest. I'm not using classes in python. – Norfeldt Feb 05 '14 at 15:32
  • `for folder in ns.Folders: print folder.Name` gives me "Internet Calendars" "SharePoint Lists" "my@mail.com" – Norfeldt Feb 05 '14 at 15:35
  • Lets say I want to grab a calendar in the "Internet Calendars" - how would I define a recipient name as a string that grabs a calendar in the "Internet Calendars" folder – Norfeldt Feb 05 '14 at 15:39
  • Please see my updated answer - I have changed the code so that it does not use classes, and I also provided a link to a standalone script which lists the folder tree, looks up a specific folder by its path (such as "Internet Calendars" => "Name@mail.com") and lists items from this calendar. I suggest that you use this script as a starting point, and see if you can get it to run in your environment . If it does not run, I suggest that you post a followup question with the specific error code / issue where you are stuck, so that the context of this question is retained – Andreas Fester Feb 11 '14 at 10:06