Hi friends,
This is my first post here. Please forgive me as I am a complete novice with Python. I have experience with HTML and CSS, but my main gig is electronics...So, please excuse me if I am out of line asking this question here!

A friend was helping me build a script using Python to integrate with my Google Calendar. I have a band, and I was wanting an app that could input one date (gig) and then several other events would auto-populate into the calendar (IE: 14 days before send out fliers, 2 days after send out thank you emails, etc).

I got Python installed and the code does the basic job. He has the Python code link to a CSV file and I open up a DOS command prompt and enter the fields and everything populates fine.

Here is the basic CSV code:

"-14","Update calendar","12:00"
"-2","Send out email","12:00"
"2","Send out thank you email to attendees","12:00"
"4","Upload drunken pictures to MySpace","1:00"

Here is the Python code that I am pretty sure relats to the above CSV (again my apologies if I am missing important bits-I'd be happy to post more if needed):

# Two helper classes to parse CSV files.
class CalendarEvent:
    def __init__(self):
        self.interval_from_start = 0
        self.description = ""
        self.hour = 0
        self.minute = 0

    def __str__(self):
        s = ""
        for k,v in self.__dict__.iteritems():
            s = s + "%s:\t%s\n" % (k,v)
        return s + "\n"


class CalendarEventFactory:
    def __init__(self, csv_file):
        self.csv_file = csv_file

    def loadEvents(self):
        # List to hold events.
        event_list = []

        # Open the file
        try:
            f = open(self.csv_file, 'r')
        except:
            raise Exception, "Unable to open file %s" % self.csv_file

        # Stuff it into the csv reader class
        input_csv = csv.reader(f)

        # For each item, create a CalendarEvent object
        for item in input_csv:
            hour, minute = item[2].split(':')

            event = CalendarEvent()

            event.interval_from_start = int(item[0])
            event.description = item[1]
            event.hour = int(hour)
            event.minute = int(minute)

            event_list.append(event)

        # Close the file
        f.close()

        # Return the list
        return event_list


# Helper function to list CSV files in a directory, without full path information
def list_csv_files(dirname):
    files = []
    csv_files = []

    try:
        files = os.listdir(dirname)
    except:
        raise Exception, "Unable to open directoy %s" % dirname

    r = re.compile(".+\.csv")

    for file in files:
        if r.match(file):
            csv_files.append(file)

    return csv_files

Now the above code works fine, but it only allows me to input the description, date, and time of the event. I know that Google calendar is able to read more CSV information on an import:
From http://www.google.com/support/calendar/bin/answer.py?hl=en&answer=45656

To add more information to your events, simply add more headers. Possible headers include: Subject, Start Date, Start Time, End Date, End Time, All Day Event, Description, Location, and Private.

So this was my long winded way of asking my question:
How can I add all these other headers into the above python code? I have read a lot on the google pages, but I just don't know enough about this stuff to understand most of what is being talked about. I'm sure the answer is somewhere HERE, but I don't know where!

Thank you for your time and your patience!

Sincerely,
Joel

I haven't fully confirmed this, but it would involve a change to this section:

# For each item, create a CalendarEvent object
        for item in input_csv:
            hour, minute = item[2].split(':')

            event = CalendarEvent()

            event.interval_from_start = int(item[0])
            event.description = item[1]
            event.hour = int(hour)
            event.minute = int(minute)

            event_list.append(event)

You could add other headers by writing something like the following into that block of code, after event.minute = int(minute) . Like this:

event.location = item[3]

and change the CSV file to have an extra value at the end of the line, so it looks like this:

"-2","Send out email","12:00","home"

In the above case, I used index 3 (indices start on zero) as the location header of the event. This is why I used item[3] in the above Python. So now this event has the location set to 'home'. I'm not sure about all the Python variables used for the various headers, but I'm guessing they follow the format of the header with spaces turned to underscores, and in all lower-case letters. I'm guessing here, but the header 'All Day Event' probably is 'all_day_event' in python.
The CSV file seems to be organized at your own whim (as far as I could find), so keep track of the order of the headers on the line, so in Python, you can call event.HEADER = item[INDEX] for in this same code block as above, where HEADER is the Python variable for a header, and INDEX is the location of it on the CSV file's line.

I've never used Google Calender, but I just glanced through those links you provided and the sample with the location header I provided seems to be logical. Tell me if it works, or if you need any other help!

Thanks for the reply shadwickman. Those changes didn't break the code, but it didn't give any results. The location did not import into the "Where" field on google calendar. I did find this-again sadly I don't really know if this info helps...Elements: "Kinds".

One thing I'm confused about though from your post is that I need to be able to edit the variables like location. If I just put "home" in the CSV, then the location would always be "home"...

So to be clear, what I need to be able to do is edit more of the fields of the "primary date", but all the secondary 'auto-populated' dates will have built in values that would be predetermined (I guess with the CSV)

Forgive me if it's too much, but I'll include all the python code to give a better description of what is happening with the DOS input. (forgive the humor that my friend included in the coding!)

#!/usr/bin/python

try:
  from xml.etree import ElementTree
except ImportError:
  from elementtree import ElementTree
import gdata.calendar.service
import gdata.service
import atom.service
import gdata.calendar
import atom
import getopt
import os
import csv
import re
import sys
import time
import datetime
from datetime import timedelta

from exceptions import Exception


# This is where the zipfile gets unpacked to.  Put your CSV files in here.
global install_root
install_root = "C:/"

# This is the name of your Google Calendar.  Please note that case must match, and you need to have created the calendar
#   in Google Calendar already.
global calendar_name
calendar_name = "Test"

# Set this to your gmail account
global gmail_account
gmail_account = 'xxxxxxxxxxxxxxxxx'

# Set this to your gmail password
global gmail_password
gmail_password = 'xxxxxxxxxxxxxxxxx'

# Two helper classes to parse CSV files.
class CalendarEvent:
    def __init__(self):
        self.interval_from_start = 0
        self.description = ""
        self.hour = 0
        self.minute = 0

    def __str__(self):
        s = ""
        for k,v in self.__dict__.iteritems():
            s = s + "%s:\t%s\n" % (k,v)
        return s + "\n"


class CalendarEventFactory:
    def __init__(self, csv_file):
        self.csv_file = csv_file

    def loadEvents(self):
        # List to hold events.
        event_list = []

        # Open the file
        try:
            f = open(self.csv_file, 'r')
        except:
            raise Exception, "Unable to open file %s" % self.csv_file

        # Stuff it into the csv reader class
        input_csv = csv.reader(f)

        # For each item, create a CalendarEvent object
        for item in input_csv:
            hour, minute = item[2].split(':')

            event = CalendarEvent()

            event.interval_from_start = int(item[0])
            event.description = item[1]
            event.hour = int(hour)
            event.minute = int(minute)
          
            event_list.append(event)

        # Close the file
        f.close()

        # Return the list
        return event_list


# Helper function to list CSV files in a directory, without full path information
def list_csv_files(dirname):
    files = []
    csv_files = []

    try:
        files = os.listdir(dirname)
    except:
        raise Exception, "Unable to open directoy %s" % dirname

    r = re.compile(".+\.csv")

    for file in files:
        if r.match(file):
            csv_files.append(file)

    return csv_files

# Helper function to show a quick and dirty menu of list items.  Returns the index of the list item picked, or exits if the user so desires.
def display_menu(menu_items):
    def print_menu():
        os.system('cls')
        print "Magical Mbira Event Calculator Version 1.0"
        print ""
        print "  Please select your desired event template"

        # Menu index
        i = 1

        for item in menu_items:
            print "\t%d - %s" % (i, item)
            i += 1

        print "\tQ - Exit program"
        print ""

    # Loop until they pick something comprehensible.
    picked = False

    while not picked:
        print_menu()
        resp = raw_input("\tPlease selecct an event template, or press Q to quit:  ")
        resp = resp.strip()

        try:
            index = int(resp)
        except:
            if resp in ('Q', 'q'):
                sys.exit()
            else:
                print "\n\tInvalid choice!!"
                time.sleep(1)
                continue

        if index>len(menu_items):
            print "\n\tInvalid choice!!"
            time.sleep(1)
        else:
            picked = True

    return index-1

# And really, isn't being a musician really just about getting dates anyway?
def get_gig_date():
    # Loop until they enter something comprehensible.  Comprehensible means a date of the format MM/DD/YYYY
    picked = False

    r = re.compile('(?P<month>\d{1,2})(/)(?P<day>\d{1,2})(/)(?P<year>\d{4,4}) (?P<hour>\d{1,2})(:)(?P<minute>\d{2,2})')

    while not picked:
        os.system('cls')
        resp = raw_input("Date/time of the gig in the format MM/DD/YYYY HH:MM (military time):   ")
        resp = resp.strip()

        mo = r.match(resp)

        if mo is None:
            print "Incorrect date format!"
            time.sleep(1)
            continue
        else:
            picked = True

    return datetime.datetime(int(mo.group('year')), int(mo.group('month')), int(mo.group('day')), int(mo.group('hour')), int(mo.group('minute')))

# Helper function to get a reference to a calendar object by name.  Really, this should be in the library...
def find_calendar_by_name(calendar_service, name):
    # Get a reference to the specified calendar
    feed = calendar_service.GetOwnCalendarsFeed()
    for i, a_calendar in enumerate(feed.entry):
        if a_calendar.title.text==calendar_name:
            return a_calendar

    return None



# First, get a list of all the CSV files in the install root directory sorted alphabetically.  Make an event factory for each.
csv_files = list_csv_files(install_root)

# Next, print a menu and get the index of the option they want.
index = display_menu(csv_files)

# Prompt for the gig date
gig_date = get_gig_date()

# Get a description of the gig
gig_description = raw_input("Short description of the gig:  ")



# Load it up via the event factory
evf = CalendarEventFactory(os.path.join(install_root, csv_files[index]))

# Authenticate to Google Calendar
try:
    calendar_service = gdata.calendar.service.CalendarService()
    calendar_service.email = gmail_account
    calendar_service.password = gmail_password
    calendar_service.source = 'Google-Calendar_Python_Sample-1.0'
    calendar_service.ProgrammaticLogin()
except:
    print "Error logging into Google Calendar service.  Please check your account settings at the top of the script."
    sys.exit(1)

# Find the named calendar
cal = find_calendar_by_name(calendar_service, calendar_name)

# Fuck, Google, could you have made getting the feed ID any more obscure?  You know, like putting it in the field marked ID???!?!?
#  Smart people my ass. If Google employees are so damn smart, why do so many of them live in IRVINE???
cal_id = cal.content.src.replace('http://www.google.com', '')

# Loop through the events, and insert them relative to the specified day
for ev in evf.loadEvents():
    event_dt = datetime.datetime(gig_date.year,
                                 gig_date.month,
                                 gig_date.day,
                                 ev.hour,
                                 ev.minute,
                                 0) + timedelta(days=ev.interval_from_start)

    end_event_dt = event_dt + timedelta(hours=1)

    start_time = event_dt.strftime('%Y-%m-%dT%H:%M:%S.000Z')
    end_time = end_event_dt.strftime('%Y-%m-%dT%H:%M:%S.000Z')

    print "Start: %s" % str(event_dt)
    print "End:   %s" % str(end_event_dt)
    print start_time
    print end_time

    event = gdata.calendar.CalendarEventEntry()
    event.title = atom.Title(text=ev.description)
    event.when.append(gdata.calendar.When(start_time=start_time, end_time=end_time))

    new_event = calendar_service.InsertEvent(event, cal_id)




# Finally, add the gig itself
event = gdata.calendar.CalendarEventEntry()
event.title = atom.Title(text=gig_description)

end_gig_date = gig_date + timedelta(hours=1)
start_time = gig_date.strftime('%Y-%m-%dT%H:%M:%S.000Z')
end_time = end_gig_date.strftime('%Y-%m-%dT%H:%M:%S.000Z')
event.when.append(gdata.calendar.When(start_time=start_time, end_time=end_time))

new_event = calendar_service.InsertEvent(event, cal_id)

Thank you for all your help!

I don't have the time to look at that massive section of code you posted, but in my above example, try changing that

event.location = item[3]

to

if len(item) >= 3:
    event.where = item[3]
else:
    event.where = "undefined location"

and tell me if that makes a difference in Google Calender!

Try this:

event.where.append(gdata.calendar.Where(value_string=item[3]))

You could try this for testing purposes so you don't have to edit your CVS structure:

event.where.append(gdata.calendar.Where(value_string="Home"))

The description part should be handled by:

event.content = atom.Content(text=description)

Other than that, the only other settings that you listed that your program doesn't do is "All Day Event", which I believe will be set if you leave off a time and date, and the visibility of the event.

Thank you Adam1122 and shadwickman for your time. I will work on these ideas this evening and report back.

Best regards,
Joel

Hi again. No luck so far:

shadwickman, when I tried that new code, the DOS stopped at that point of the code and didn't seem to like the way it was written...didn't let me get past that.

adam1122, when I put in your code, I got:

Traceback (most recent call last):
  File "calendarUpdate.py", line 226, in <module>
    for ev in evf.loadEvents():
  File "calendarUpdate.py", line 83, in loadEvents
    event.where.append(gdata.calendar.Where(value_string=item[3]))
AttributeError: CalendarEvent instance has no attribute 'where'

After sleeping on this I'm realizing that there are two different things that are needing to happen, and I don't think I was clear about the differences:

1) I enter a gig by going to the DOS run prompt and run the cualendarUpdate.py file.

The DOS asks me which CSV file I am wanting to read.

The DOS then asks me for the date of the gig and the time of the gig. If I don't answer these values, it doesn't let me go further.

Then the DOS asks me for a short description of the gig.

2) THen Python adds the gig whose Info I put in onto Google calendar and then populates the other dates based on the CSV file.

So the added functionality I am needing is twofold:

1) I need to have other fields (like location, etc, etc) that can be read from the CSV,

and 2) I need other prompts requested of me from the DOS (also things like location, start and end time, etc, etc).

Thoug these two tasks seem similar, I believe they are two different areas of the above code that need to be addresse (one for the DOS and one for the CSV)

I hope this makes sense?

To be more clear: When I put a gig into the calendar, I am wanting to choose a predetermined CSV file that will have a "promotion timeline" that will be based on what type of gig it is (IE I will have a "local" and a "regional" CSV list because my promotion timeline will be different for both) These CSV lists won't need to change from gig to gig-I can edit them over time as my needs change.

But the fields that the DOS is asking me for for each specific gig need to give me more options than just the date, time, and short description...


Best regards,
Joel

You get those errors because that's your custom class and it doesn't have those fields. The code I gave you has to be done on the event object created like this:

event = gdata.calendar.CalendarEventEntry()

Hi Adam1122,

I'm very new to this but trying hard to keep up. Here is where I put it:

old code:

# For each item, create a CalendarEvent object
        for item in input_csv:
            hour, minute = item[2].split(':')

            event = CalendarEvent()

            event.interval_from_start = int(item[0])
            event.description = item[1]
            event.hour = int(hour)
            event.minute = int(minute)
          
            event_list.append(event)

        # Close the file
        f.close()

        # Return the list
        return event_list

New code:

# For each item, create a CalendarEvent object
        for item in input_csv:
            hour, minute = item[2].split(':')

            event = CalendarEvent()

            event.interval_from_start = int(item[0])
            event.description = item[1]
            event.hour = int(hour)
            event.minute = int(minute)
            event.where.append(gdata.calendar.Where(value_string=item[3]))
            event_list.append(event)

        # Close the file
        f.close()

        # Return the list
        return event_list

I still had put the extra "home" attribute on the CSV files thinking that the above new code would relate to it...

If there is somewhere else or something different I should put the code, I'm not understanding where-sorry!!

This is the final part of your code:

event = gdata.calendar.CalendarEventEntry()
    event.title = atom.Title(text=ev.description)
    event.when.append(gdata.calendar.When(start_time=start_time, end_time=end_time))
    event.where.append(gdata.calendar.Where(value_string=item[3]))

    new_event = calendar_service.InsertEvent(event, cal_id)




# Finally, add the gig itself
event = gdata.calendar.CalendarEventEntry()
event.title = atom.Title(text=gig_description)

end_gig_date = gig_date + timedelta(hours=1)
start_time = gig_date.strftime('%Y-%m-%dT%H:%M:%S.000Z')
end_time = end_gig_date.strftime('%Y-%m-%dT%H:%M:%S.000Z')
event.when.append(gdata.calendar.When(start_time=start_time, end_time=end_time))
event.where.append(gdata.calendar.Where(value_string="LOCATION"))
new_event = calendar_service.InsertEvent(event, cal_id)

I would suggest you try this with your old CSV structure until you are sure it's working but that's up to you.
You would probably want to alter your custom CalendarEvent to include a where field after getting this to work.

OK, so I added those last two changes. This is the CSV file:

"-14","Update calendar","12:00"
"-2","Send out email","12:00"
"2","Send out thank you email to attendees","12:00"
"4","Upload drunken pictures to MySpace","1:00"

I'm still getting an error, but I'm confused what do I put in this bit of code (I feel like it is so close!!!):

# For each item, create a CalendarEvent object
        for item in input_csv:
            hour, minute = item[2].split(':')

            event = CalendarEvent()

            event.interval_from_start = int(item[0])
            event.description = item[1]
            event.hour = int(hour)
            event.minute = int(minute)
            WHAT GOES HERE????
            event_list.append(event)

        # Close the file
        f.close()

        # Return the list
        return event_list

Again, my humble apologies-I really am trying to learn how to fish here and not just have you give me a fish.

It looks like you are using your original CSV. In that case don't put anything there (for now). Change the code I gave you last time to this:

event = gdata.calendar.CalendarEventEntry()
    event.title = atom.Title(text=ev.description)
    event.when.append(gdata.calendar.When(start_time=start_time, end_time=end_time))
    event.where.append(gdata.calendar.Where(value_string="LOCATION OF PROMOTIONS"))

    new_event = calendar_service.InsertEvent(event, cal_id)




# Finally, add the gig itself
event = gdata.calendar.CalendarEventEntry()
event.title = atom.Title(text=gig_description)

end_gig_date = gig_date + timedelta(hours=1)
start_time = gig_date.strftime('%Y-%m-%dT%H:%M:%S.000Z')
end_time = end_gig_date.strftime('%Y-%m-%dT%H:%M:%S.000Z')
event.when.append(gdata.calendar.When(start_time=start_time, end_time=end_time))
event.where.append(gdata.calendar.Where(value_string="LOCATION OF GIG"))
new_event = calendar_service.InsertEvent(event, cal_id)

What is the error? Always post the actual error.

Excellent! That worked! The "where" field is populating for the gig (LOCATION OF GIG) and the "where" field of the promotions is (LOCATION OF PROMOTIONS).

Thank you!!

So how do I get the DOS prompt to ask me for the location of the gig when I input the date and times, etc?

I'm hoping that with this information I will be able to wrestle the other needed fields into the calendar.

You need to alter your CalendarEvent object to include the location:

class CalendarEvent:
    def __init__(self):
        self.interval_from_start = 0
        self.description = ""
        self.hour = 0
        self.minute = 0
        self.location = ""

Grab the location field from the CSV file:

class CalendarEventFactory:
    def __init__(self, csv_file):
        self.csv_file = csv_file

    def loadEvents(self):
        # List to hold events.
        event_list = []

        # Open the file
        try:
            f = open(self.csv_file, 'r')
        except:
            raise Exception, "Unable to open file %s" % self.csv_file

        # Stuff it into the csv reader class
        input_csv = csv.reader(f)

        # For each item, create a CalendarEvent object
        for item in input_csv:
            hour, minute = item[2].split(':')

            event = CalendarEvent()

            event.interval_from_start = int(item[0])
            event.description = item[1]
            event.hour = int(hour)
            event.minute = int(minute)
            event.location = item[3]

            event_list.append(event)

You need to add a location field to the end of each CSV line for this to work.

So that takes care of locations for promotional events.

Now you need to ask for a gig location:

# Prompt for the gig date
gig_date = get_gig_date()

# Get a location of the gig
gig_location = raw_input("Location of the gig:  ")

# Get a description of the gig
gig_description = raw_input("Short description of the gig:  ")

Finally, set the location and upload the gig event:

# Finally, add the gig itself
event = gdata.calendar.CalendarEventEntry()
event.title = atom.Title(text=gig_description)

end_gig_date = gig_date + timedelta(hours=1)
start_time = gig_date.strftime('%Y-%m-%dT%H:%M:%S.000Z')
end_time = end_gig_date.strftime('%Y-%m-%dT%H:%M:%S.000Z')
event.when.append(gdata.calendar.When(start_time=start_time, end_time=end_time))
event.where.append(gdata.calendar.Where(value_string=gig_location))
new_event = calendar_service.InsertEvent(event, cal_id)

OK, getting there, I think...

After adding that code, I'm still getting "LOCATION OF PROMOTIONS" populating the where field even though I have added the last CSV field with "home".

Now, I do get the DOS prompt asking for the gig location, but ne matter what I fill in, "gig_location" is populating the field in the calendar...

No errors are displaying though, so that is good!

Yes, forgot one thing:

event = gdata.calendar.CalendarEventEntry()
    event.title = atom.Title(text=ev.description)
    event.when.append(gdata.calendar.When(start_time=start_time, end_time=end_time))
    event.where.append(gdata.calendar.Where(value_string=ev.location))

    new_event = calendar_service.InsertEvent(event, cal_id)




# Finally, add the gig itself
event = gdata.calendar.CalendarEventEntry()
event.title = atom.Title(text=gig_description)

end_gig_date = gig_date + timedelta(hours=1)
start_time = gig_date.strftime('%Y-%m-%dT%H:%M:%S.000Z')
end_time = end_gig_date.strftime('%Y-%m-%dT%H:%M:%S.000Z')
event.when.append(gdata.calendar.When(start_time=start_time, end_time=end_time))
event.where.append(gdata.calendar.Where(value_string=gig_location))
new_event = calendar_service.InsertEvent(event, cal_id)

As for "gig_location" showing up, it sounds like you may have done this:

event.where.append(gdata.calendar.Where(value_string="gig_location"))

Which is not right. There should be no quotation marks.

I wrote a long reply, but I did it in the quick reply box and unfortunately got logged out before I could send it...

In short...IT WORKS!!!

Not only does it work, but your teaching enabled me to also create another field for "content" to add a longer description using the atom:content.

Thank you!

I'm hesitant to ask, and you can please feel free to tell me to take a hike...there are a couple other things that I'm not understanding.

1) for some reason the times I am entering in the DOS and CSV are being pushed back 6 hours on Google calendar (some kind of time-change thing?) Also related to the time, I don't have this code set up to get both a start and end time-as it is now it just gives the start time and makes the event one hour long...

2) This seems tricky to me, but would be pretty important...How could I link the date or title of the gig I am entering with the CSV events? The reason is because if we have something like 20 gigs in the month, we will need to know which gig we are promoting when the reminder pops up. The easiest way from a user standpoint would be to have the date or the title (description) of the gig in the description of the promotion event. Any idea on that one?

Again, thank you SO MUCH for your help! I have been struggling with this for weeks and weeks...you have really taught me a lot!

Joel

1) for some reason the times I am entering in the DOS and CSV are being pushed back 6 hours on Google calendar (some kind of time-change thing?) Also related to the time, I don't have this code set up to get both a start and end time-as it is now it just gives the start time and makes the event one hour long...

Let me guess, you are in the US Central timezone? If so, try this:

# Loop through the events, and insert them relative to the specified day
for ev in evf.loadEvents():
    event_dt = datetime.datetime(gig_date.year,
                                 gig_date.month,
                                 gig_date.day,
                                 ev.hour,
                                 ev.minute,
                                 0) + timedelta(days=ev.interval_from_start)

    end_event_dt = event_dt + timedelta(hours=1)

    start_time = event_dt.strftime('%Y-%m-%dT%H:%M:%S.000Z')
    end_time = end_event_dt.strftime('%Y-%m-%dT%H:%M:%S.000Z')

    print "Start: %s" % str(event_dt)
    print "End:   %s" % str(end_event_dt)
    print start_time
    print end_time

    event = gdata.calendar.CalendarEventEntry()
    event.timezone = gdata.calendar.Timezone(value='America/Chicago')
    event.title = atom.Title(text=ev.description)
    event.when.append(gdata.calendar.When(start_time=start_time, end_time=end_time))

    new_event = calendar_service.InsertEvent(event, cal_id)

To get an end time, modify CalendarEvent like this:

class CalendarEvent:
    def __init__(self):
        self.interval_from_start = 0
        self.description = ""
        self.hour = 0
        self.minute = 0
        self.end_hour = 0
        self.end_minute = 0
        self.location = ""

Then ask the user for input, like we did when adding the location field.
You then set end_time near the end of the script to equal this time that you requested from the user.

2) This seems tricky to me, but would be pretty important...How could I link the date or title of the gig I am entering with the CSV events? The reason is because if we have something like 20 gigs in the month, we will need to know which gig we are promoting when the reminder pops up. The easiest way from a user standpoint would be to have the date or the title (description) of the gig in the description of the promotion event. Any idea on that one?

You said you added "content" for longer descriptions. You could modify that like this:

time = gig_date.strftime('%Y-%m-%dT%H:%M:%S.000Z')
ev.content += '\n\n' + gig_description + '(' + time + ')'

Again, thank you SO MUCH for your help! I have been struggling with this for weeks and weeks...you have really taught me a lot!

Joel

No problem. Good to hear. It's my way of procrastinating as I work on my own projects. Let me know if you need more help.

Comments
I noticed the amazing help you were giving on GoogleCalender

Well I got cocky too quickly...

Adding the time change didn't seem to do anything. i remember my friend who originally built this saying he couldn't figure out how to get the time right...he seemed to think it was a Google bug...I don't know.

As for the end times:

To get an end time, modify CalendarEvent like this:

Help with Code Tags
(Toggle Plain Text)

class CalendarEvent:
def __init__(self):
self.interval_from_start = 0
self.description = ""
self.hour = 0
self.minute = 0
self.end_hour = 0
self.end_minute = 0
self.location = ""

OK, mine looks like this now (I have that content field in now and changed 'description' to the more accurate 'title':

class CalendarEvent:
    def __init__(self):
        self.interval_from_start = 0
        self.title = ""
        self.hour = 0
        self.minute = 0
        self.end_hour = 0
        self.end_minute = 0
        self.location = ""
        self.content = ""

Then ask the user for input, like we did when adding the location field.

OK, that all looks like this now:

# First, get a list of all the CSV files in the install root directory sorted alphabetically.  Make an event factory for each.
csv_files = list_csv_files(install_root)

# Next, print a menu and get the index of the option they want.
index = display_menu(csv_files)

# Prompt for the gig date
gig_date = get_gig_date()

# Get the end time of the gig
end_time = raw_input("End Time:  ")

# Get a location of the gig
gig_location = raw_input("Location of the gig:  ")


# Get a title for the gig
gig_title = raw_input("Title of the event:  ")

# Get a long description for the gig
gig_content = raw_input("Details:  ")

You then set end_time near the end of the script to equal this time that you requested from the user.

OK, you lost me...

I know it's in here, but I see some values already done for the end time, so I'm not sure if anything needs to go:

# Loop through the events, and insert them relative to the specified day
for ev in evf.loadEvents():
    event_dt = datetime.datetime(gig_date.year,
                                 gig_date.month,
                                 gig_date.day,
                                 ev.hour,
                                 ev.minute,
                                 0) + timedelta(days=ev.interval_from_start)

    end_event_dt = event_dt + timedelta(hours=1)

    start_time = event_dt.strftime('%Y-%m-%dT%H:%M:%S.000Z')
    end_time = end_event_dt.strftime('%Y-%m-%dT%H:%M:%S.000Z')

    print "Start: %s" % str(event_dt)
    print "End:   %s" % str(end_event_dt)
    print start_time
    print end_time

    event = gdata.calendar.CalendarEventEntry()
    event.timezone = gdata.calendar.Timezone(value='America/Chicago')
    event.title = atom.Title(text=ev.title)
    event.when.append(gdata.calendar.When(start_time=start_time, end_time=end_time))

    new_event = calendar_service.InsertEvent(event, cal_id)


# Finally, add the gig itself
event = gdata.calendar.CalendarEventEntry()
event.title = atom.Title(text=gig_title)
event.content = atom.Content(text=gig_content)

end_gig_date = gig_date + timedelta(hours=1)
start_time = gig_date.strftime('%Y-%m-%dT%H:%M:%S.000Z')
end_time = end_gig_date.strftime('%Y-%m-%dT%H:%M:%S.000Z')
event.when.append(gdata.calendar.When(start_time=start_time, end_time=end_time))
event.where.append(gdata.calendar.Where(value_string=gig_location))
new_event = calendar_service.InsertEvent(event, cal_id)

You said you added "content" for longer descriptions. You could modify that like this:

time = gig_date.strftime('%Y-%m-%dT%H:%M:%S.000Z')
ev.content += '\n\n' + gig_description + '(' + time + ')'

Where do I do this?

Happy to help with the procrastination ;-)

Joel

I'll have to get back with you later. Did that fix the time offset issue? Might be worth posting the entire code at this point.

Hey, no problem at all...again thanks for your help!

Here is the full code:

#!/usr/bin/python

try:
  from xml.etree import ElementTree
except ImportError:
  from elementtree import ElementTree
import gdata.calendar.service
import gdata.service
import atom.service
import gdata.calendar
import atom
import getopt
import os
import csv
import re
import sys
import time
import datetime
from datetime import timedelta

from exceptions import Exception


# This is where the zipfile gets unpacked to.  Put your CSV files in here.
global install_root
install_root = "C:/"

# This is the name of your Google Calendar.  Please note that case must match, and you need to have created the calendar
#   in Google Calendar already.
global calendar_name
calendar_name = "Test"

# Set this to your gmail account
global gmail_account
gmail_account = 'xxxxxxxxxxxxx'

# Set this to your gmail password
global gmail_password
gmail_password = 'xxxxxxxxxxxxx'

# Two helper classes to parse CSV files.
class CalendarEvent:
    def __init__(self):
        self.interval_from_start = 0
        self.title = ""
        self.hour = 0
        self.minute = 0
        self.end_hour = 0
        self.end_minute = 0
        self.location = ""
        self.content = ""

    def __str__(self):
        s = ""
        for k,v in self.__dict__.iteritems():
            s = s + "%s:\t%s\n" % (k,v)
        return s + "\n"


class CalendarEventFactory:
    def __init__(self, csv_file):
        self.csv_file = csv_file

    def loadEvents(self):
        # List to hold events.
        event_list = []

        # Open the file
        try:
            f = open(self.csv_file, 'r')
        except:
            raise Exception, "Unable to open file %s" % self.csv_file

        # Stuff it into the csv reader class
        input_csv = csv.reader(f)

        # For each item, create a CalendarEvent object
        for item in input_csv:
            hour, minute = item[2].split(':')

            event = CalendarEvent()

            event.interval_from_start = int(item[0])
            event.title = item[1]
            event.hour = int(hour)
            event.minute = int(minute)
            event.location = item[3]
            event.content = item[4]

            event_list.append(event)

        # Close the file
        f.close()

        # Return the list
        return event_list


# Helper function to list CSV files in a directory, without full path information
def list_csv_files(dirname):
    files = []
    csv_files = []

    try:
        files = os.listdir(dirname)
    except:
        raise Exception, "Unable to open directoy %s" % dirname

    r = re.compile(".+\.csv")

    for file in files:
        if r.match(file):
            csv_files.append(file)

    return csv_files

# Helper function to show a quick and dirty menu of list items.  Returns the index of the list item picked, or exits if the user so desires.
def display_menu(menu_items):
    def print_menu():
        os.system('cls')
        print "Magical Mbira Event Calculator Version 1.0"
        print ""
        print "  Please select your desired event template"

        # Menu index
        i = 1

        for item in menu_items:
            print "\t%d - %s" % (i, item)
            i += 1

        print "\tQ - Exit program"
        print ""

    # Loop until they pick something comprehensible.
    picked = False

    while not picked:
        print_menu()
        resp = raw_input("\tPlease selecct an event template, or press Q to quit:  ")
        resp = resp.strip()

        try:
            index = int(resp)
        except:
            if resp in ('Q', 'q'):
                sys.exit()
            else:
                print "\n\tInvalid choice!!"
                time.sleep(1)
                continue

        if index>len(menu_items):
            print "\n\tInvalid choice!!"
            time.sleep(1)
        else:
            picked = True

    return index-1

# And really, isn't being a musician really just about getting dates anyway?
def get_gig_date():
    # Loop until they enter something comprehensible.  Comprehensible means a date of the format MM/DD/YYYY
    picked = False

    r = re.compile('(?P<month>\d{1,2})(/)(?P<day>\d{1,2})(/)(?P<year>\d{4,4}) (?P<hour>\d{1,2})(:)(?P<minute>\d{2,2})')

    while not picked:
        os.system('cls')
        resp = raw_input("Date/time of the gig in the format MM/DD/YYYY HH:MM (military time):   ")
        resp = resp.strip()

        mo = r.match(resp)

        if mo is None:
            print "Incorrect date format!"
            time.sleep(1)
            continue
        else:
            picked = True

    return datetime.datetime(int(mo.group('year')), int(mo.group('month')), int(mo.group('day')), int(mo.group('hour')), int(mo.group('minute')))

# Helper function to get a reference to a calendar object by name.  Really, this should be in the library...
def find_calendar_by_name(calendar_service, name):
    # Get a reference to the specified calendar
    feed = calendar_service.GetOwnCalendarsFeed()
    for i, a_calendar in enumerate(feed.entry):
        if a_calendar.title.text==calendar_name:
            return a_calendar

    return None



# First, get a list of all the CSV files in the install root directory sorted alphabetically.  Make an event factory for each.
csv_files = list_csv_files(install_root)

# Next, print a menu and get the index of the option they want.
index = display_menu(csv_files)

# Prompt for the gig date
gig_date = get_gig_date()

# Get the end time of the gig
end_time = raw_input("End Time:  ")

# Get a location of the gig
gig_location = raw_input("Location of the gig:  ")


# Get a title for the gig
gig_title = raw_input("Title of the event:  ")

# Get a long description for the gig
gig_content = raw_input("Details:  ")



# Load it up via the event factory
evf = CalendarEventFactory(os.path.join(install_root, csv_files[index]))

# Authenticate to Google Calendar
try:
    calendar_service = gdata.calendar.service.CalendarService()
    calendar_service.email = gmail_account
    calendar_service.password = gmail_password
    calendar_service.source = 'Google-Calendar_Python_Sample-1.0'
    calendar_service.ProgrammaticLogin()
except:
    print "Error logging into Google Calendar service.  Please check your account settings at the top of the script."
    sys.exit(1)

# Find the named calendar
cal = find_calendar_by_name(calendar_service, calendar_name)

# Fuck, Google, could you have made getting the feed ID any more obscure?  You know, like putting it in the field marked ID???!?!?
#  Smart people my ass. If Google employees are so damn smart, why do so many of them live in IRVINE???
cal_id = cal.content.src.replace('http://www.google.com', '')

# Loop through the events, and insert them relative to the specified day
for ev in evf.loadEvents():
    event_dt = datetime.datetime(gig_date.year,
                                 gig_date.month,
                                 gig_date.day,
                                 ev.hour,
                                 ev.minute,
                                 0) + timedelta(days=ev.interval_from_start)

    end_event_dt = event_dt + timedelta(hours=1)

    start_time = event_dt.strftime('%Y-%m-%dT%H:%M:%S.000Z')
    end_time = end_event_dt.strftime('%Y-%m-%dT%H:%M:%S.000Z')

    print "Start: %s" % str(event_dt)
    print "End:   %s" % str(end_event_dt)
    print start_time
    print end_time

    event = gdata.calendar.CalendarEventEntry()
    event.timezone = gdata.calendar.Timezone(value='America/Chicago')
    event.title = atom.Title(text=ev.title)
    event.when.append(gdata.calendar.When(start_time=start_time, end_time=end_time))

    new_event = calendar_service.InsertEvent(event, cal_id)





# Finally, add the gig itself
event = gdata.calendar.CalendarEventEntry()
event.title = atom.Title(text=gig_title)
event.content = atom.Content(text=gig_content)

end_gig_date = gig_date + timedelta(hours=1)
start_time = gig_date.strftime('%Y-%m-%dT%H:%M:%S.000Z')
end_time = end_gig_date.strftime('%Y-%m-%dT%H:%M:%S.000Z')
event.when.append(gdata.calendar.When(start_time=start_time, end_time=end_time))
event.where.append(gdata.calendar.Where(value_string=gig_location))
new_event = calendar_service.InsertEvent(event, cal_id)

And the CSV file:

"-42","inquire about interviews","10:00","location","long description"
"-14","Update calendar","11:00","location","long description"
"-2","Send out email","12:00","location","long description"
"2","Send out thank you email to attendees","14:00","location","long description"
"4","Upload pictures to MySpace","15:00","location","long description"

Best regards,
Joel

Thanks again for all your help adam1122.

I didn't answer your question. The time issue is not resolved. Worse case, I can offset things on my end by 6 hours as a work around. The big question I have (and hopefully would be a fast and easy response) is with this:

You said you added "content" for longer descriptions. You could modify that like this:

time = gig_date.strftime('%Y-%m-%dT%H:%M:%S.000Z')
ev.content += '\n\n' + gig_description + '(' + time + ')'

I don't understand where in the code I need to put that.

Thank you!
Joel

Was I right in assuming you are in the central time zone? If so, I'm sure the time is just being stored as GMT. I have some use for a more generic library so I'll figure it out then and post it here.

This should add the gig description and time to the end of the long description of each promotional event:

ev.content += '\n\n' + gig_description + ' (' + str(event_dt) + ' - ' + str(end_event_dt) + ')'

    event = gdata.calendar.CalendarEventEntry()
    event.timezone = gdata.calendar.Timezone(value='America/Chicago')
    event.title = atom.Title(text=ev.title)
    event.content = atom.Content(text=ev.content)
    event.when.append(gdata.calendar.When(start_time=start_time, end_time=end_time))

    new_event = calendar_service.InsertEvent(event, cal_id)





# Finally, add the gig itself
event = gdata.calendar.CalendarEventEntry()
event.title = atom.Title(text=gig_title)
event.content = atom.Content(text=gig_content)

end_gig_date = gig_date + timedelta(hours=1)
start_time = gig_date.strftime('%Y-%m-%dT%H:%M:%S.000Z')
end_time = end_gig_date.strftime('%Y-%m-%dT%H:%M:%S.000Z')
event.when.append(gdata.calendar.When(start_time=start_time, end_time=end_time))
event.where.append(gdata.calendar.Where(value_string=gig_location))
new_event = calendar_service.InsertEvent(event, cal_id)

Hi,
You're correct that I am in central timezone.

For some reason, I posted code with errors above. The working code is below (less the changes you just posted-which I will do now).

I had an issue with the start and stop time. You can see in the below code that my friend had put the date and the time of the gig as one line requested by the DOS. Thanks to your help, I know how to break the DOS request into two lines, but I don't know how to separate out the code to handle them as two separate events. I'm thinking that might be what is hindering the end_time request? I highlighted the bits that I'm talking about.

I understand that I may well just be asking too much from a stranger on a forum in which I am a newbie, so I really do apologies and thank you for your time.

#!/usr/bin/python

try:
  from xml.etree import ElementTree
except ImportError:
  from elementtree import ElementTree
import gdata.calendar.service
import gdata.service
import atom.service
import gdata.calendar
import atom
import getopt
import os
import csv
import re
import sys
import time
import datetime
from datetime import timedelta

from exceptions import Exception


# This is where the zipfile gets unpacked to.  Put your CSV files in here.
global install_root
install_root = "C:/"

# This is the name of your Google Calendar.  Please note that case must match, and you need to have created the calendar
#   in Google Calendar already.
global calendar_name
calendar_name = "Test"

# Set this to your gmail account
global gmail_account
gmail_account = 'xxxxxxxxxxxxx'

# Set this to your gmail password
global gmail_password
gmail_password = 'xxxxxxxxxxxxxx'

# Two helper classes to parse CSV files.
class CalendarEvent:
    def __init__(self):
        self.interval_from_start = 0
        self.title = ""
        self.hour = 0
        self.minute = 0
        self.end_hour = 0
        self.end_minute = 0
        self.location = ""
        self.content = ""

    def __str__(self):
        s = ""
        for k,v in self.__dict__.iteritems():
            s = s + "%s:\t%s\n" % (k,v)
        return s + "\n"


class CalendarEventFactory:
    def __init__(self, csv_file):
        self.csv_file = csv_file

    def loadEvents(self):
        # List to hold events.
        event_list = []

        # Open the file
        try:
            f = open(self.csv_file, 'r')
        except:
            raise Exception, "Unable to open file %s" % self.csv_file

        # Stuff it into the csv reader class
        input_csv = csv.reader(f)

        # For each item, create a CalendarEvent object
        for item in input_csv:
            hour, minute = item[2].split(':')

            event = CalendarEvent()

            event.interval_from_start = int(item[0])
            event.title = item[1]
            event.hour = int(hour)
            event.minute = int(minute)
            event.location = item[3]
            event.content = item[4]

            event_list.append(event)

        # Close the file
        f.close()

        # Return the list
        return event_list


# Helper function to list CSV files in a directory, without full path information
def list_csv_files(dirname):
    files = []
    csv_files = []

    try:
        files = os.listdir(dirname)
    except:
        raise Exception, "Unable to open directoy %s" % dirname

    r = re.compile(".+\.csv")

    for file in files:
        if r.match(file):
            csv_files.append(file)

    return csv_files

# Helper function to show a quick and dirty menu of list items.  Returns the index of the list item picked, or exits if the user so desires.
def display_menu(menu_items):
    def print_menu():
        os.system('cls')
        print "Magical Mbira Event Calculator Version 1.0"
        print ""
        print "  Please select your desired event template"

        # Menu index
        i = 1

        for item in menu_items:
            print "\t%d - %s" % (i, item)
            i += 1

        print "\tQ - Exit program"
        print ""

    # Loop until they pick something comprehensible.
    picked = False

    while not picked:
        print_menu()
        resp = raw_input("\tPlease selecct an event template, or press Q to quit:  ")
        resp = resp.strip()

        try:
            index = int(resp)
        except:
            if resp in ('Q', 'q'):
                sys.exit()
            else:
                print "\n\tInvalid choice!!"
                time.sleep(1)
                continue

        if index>len(menu_items):
            print "\n\tInvalid choice!!"
            time.sleep(1)
        else:
            picked = True

    return index-1

# And really, isn't being a musician really just about getting dates anyway?
def get_gig_date():
    # Loop until they enter something comprehensible.  Comprehensible means a date of the format MM/DD/YYYY
    picked = False

    r = re.compile('(?P<month>\d{1,2})(/)(?P<day>\d{1,2})(/)(?P<year>\d{4,4}) (?P<hour>\d{1,2})(:)(?P<minute>\d{2,2})')

    while not picked:
        os.system('cls')
        resp = raw_input("Date/time of the gig in the format MM/DD/YYYY HH:MM (military time):   ")
        resp = resp.strip()

        mo = r.match(resp)

        if mo is None:
            print "Incorrect date format!"
            time.sleep(1)
            continue
        else:
            picked = True

    return datetime.datetime(int(mo.group('year')), int(mo.group('month')), int(mo.group('day')), int(mo.group('hour')), int(mo.group('minute')))

# Helper function to get a reference to a calendar object by name.  Really, this should be in the library...
def find_calendar_by_name(calendar_service, name):
    # Get a reference to the specified calendar
    feed = calendar_service.GetOwnCalendarsFeed()
    for i, a_calendar in enumerate(feed.entry):
        if a_calendar.title.text==calendar_name:
            return a_calendar

    return None



# First, get a list of all the CSV files in the install root directory sorted alphabetically.  Make an event factory for each.
csv_files = list_csv_files(install_root)

# Next, print a menu and get the index of the option they want.
index = display_menu(csv_files)

# Prompt for the gig date
gig_date = get_gig_date()

# Get the end time of the gig
end_time = raw_input("End Time:  ")THIS SHOULD NOT BE RAW_INPUT I'M SURE... 

# Get a location of the gig
gig_location = raw_input("Location of the gig:  ")


# Get a title for the gig
gig_title = raw_input("Title of the event:  ")

# Get a long description for the gig
gig_content = raw_input("Details:  ")



# Load it up via the event factory
evf = CalendarEventFactory(os.path.join(install_root, csv_files[index]))

# Authenticate to Google Calendar
try:
    calendar_service = gdata.calendar.service.CalendarService()
    calendar_service.email = gmail_account
    calendar_service.password = gmail_password
    calendar_service.source = 'Google-Calendar_Python_Sample-1.0'
    calendar_service.ProgrammaticLogin()
except:
    print "Error logging into Google Calendar service.  Please check your account settings at the top of the script."
    sys.exit(1)

# Find the named calendar
cal = find_calendar_by_name(calendar_service, calendar_name)


cal_id = cal.content.src.replace('http://www.google.com', '')

# Loop through the events, and insert them relative to the specified day
for ev in evf.loadEvents():
    event_dt = datetime.datetime(gig_date.year,
                                 gig_date.month,
                                 gig_date.day,
                                 ev.hour,
                                 ev.minute,
                                 0) + timedelta(days=ev.interval_from_start)

    end_event_dt = event_dt + timedelta(hours=1)

    start_time = event_dt.strftime('%Y-%m-%dT%H:%M:%S.000Z')
    end_time = end_event_dt.strftime('%Y-%m-%dT%H:%M:%S.000Z')



    print "Start: %s" % str(event_dt)
    print "End:   %s" % str(end_event_dt)
    print start_time
    print end_time

    event = gdata.calendar.CalendarEventEntry()
    event.content = atom.Content(text=ev.content)
    event.title = atom.Title(text=ev.title)
    event.when.append(gdata.calendar.When(start_time=start_time, end_time=end_time))
    event.where.append(gdata.calendar.Where(value_string=ev.location))
    new_event = calendar_service.InsertEvent(event, cal_id)





# Finally, add the gig itself
event = gdata.calendar.CalendarEventEntry()
event.title = atom.Title(text=gig_title)
event.content = atom.Content(text=gig_content)

end_gig_date = gig_date + timedelta(hours=1)
start_time = gig_date.strftime('%Y-%m-%dT%H:%M:%S.000Z')
end_time = end_gig_date.strftime('%Y-%m-%dT%H:%M:%S.000Z')
event.when.append(gdata.calendar.When(start_time=start_time, end_time=end_time))
event.where.append(gdata.calendar.Where(value_string=gig_location))
new_event = calendar_service.InsertEvent(event, cal_id)

dang, I'm so close here!

There are a couple things I'm trying to iron out here to make this useable...

Adam1122, you have been SO HELPFUL in getting me to where I am! Thank you so much! I'm wondering if anyone will be able to help carry me through this last little stretch?

THere are two main issues:
1) I haven't figured out how to make a gig event have a start and end time. Also the times are all shifted back 6 hours from what in input on the CSV and the DOS.

2) I have set the calendar up so that when I put an event in the calendar, it automatically adds a reminder to email me when that event happens. I have a filter set up in my gmail inbox to forward that specific event title to one of my bandmates to do the task. It works absolutely beautiful, the ONLY PROBLEM is for some reason the events added via Python are not getting that reminder automatically added? Any event I manually input does get one...

Hoping for a miracle!

Sincerely,
Joel

dang, I'm so close here!

There are a couple things I'm trying to iron out here to make this useable...

Adam1122, you have been SO HELPFUL in getting me to where I am! Thank you so much! I'm wondering if anyone will be able to help carry me through this last little stretch?

THere are two main issues:
1) I haven't figured out how to make a gig event have a start and end time. Also the times are all shifted back 6 hours from what in input on the CSV and the DOS.

2) I have set the calendar up so that when I put an event in the calendar, it automatically adds a reminder to email me when that event happens. I have a filter set up in my gmail inbox to forward that specific event title to one of my bandmates to do the task. It works absolutely beautiful, the ONLY PROBLEM is for some reason the events added via Python are not getting that reminder automatically added? Any event I manually input does get one...

Hoping for a miracle!

Sincerely,
Joel

I didn't entirely forget about you. I might get a chance tomorrow to check into your problems. I don't know if someone else can help you in the meantime.

As for the time offset issue, change instances of %Y-%m-%dT%H:%M:%S.000Z to %Y-%m-%dT%H:%M:%S . That should make all event times right for the time zone you have set for that calendar.

Thanks Adam1122. I didn't see your response till now. Unfortunately that didn't affect the time shift problem. Still doing the same thing...

This article has been dead for over six months. Start a new discussion instead.