-
-
Save tekkub/429662 to your computer and use it in GitHub Desktop.
GCal ruby lib
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'net/http' | |
require 'uri' | |
require 'time' | |
class Time | |
def self.gcalschema(tzid) # We may not be handling Time Zones in the best way... | |
tzid =~ /(\d\d\d\d)(\d\d)(\d\d)T(\d\d)(\d\d)(\d\d)Z/ ? # yyyymmddThhmmss | |
# Strange, sometimes it's 4 hours ahead, sometimes 4 hours behind. Need to figure out the timezone piece of ical. | |
# Time.xmlschema("#{$1}-#{$2}-#{$3}T#{$4}:#{$5}:#{$6}") - 4*60*60 : | |
Time.xmlschema("#{$1}-#{$2}-#{$3}T#{$4}:#{$5}:#{$6}") : | |
nil | |
end | |
end | |
# include CalendarReader | |
# g = Calendar.new('http://www.google.com/calendar/ical/example%40gmail.com/public/basic.ics') | |
module CalendarReader | |
class Calendar | |
attr_accessor :url, :ical, :xml, :product_id, :version, :scale, :method, :time_zone_name, :time_zone_offset, :events | |
def initialize(cal_url=nil) | |
self.events = [] | |
if !cal_url.blank? | |
self.url = cal_url | |
self.parse! | |
end | |
end | |
def add_event(event, sortit=true) | |
self.events = [] unless self.events.is_a?(Array) | |
self.events << event | |
@events.sort! {|a,b| a.start_time <=> b.start_time } if sortit | |
event | |
end | |
def self.parse(cal_url) | |
self.new(cal_url) | |
end | |
def parse! | |
self.url =~ /\.ics(?:\?.+)?$/ ? self.parse_from_ical! : self.parse_from_xml! | |
end | |
def parse | |
self.dup.parse! | |
end | |
def parse_from_xml! | |
return false # THIS IS NOT IMPLEMENTED YET!! | |
end | |
def parse_from_xml | |
self.dup.parse_from_xml! | |
end | |
def parse_from_ical! | |
rawdata = self.calendar_raw_data | |
return nil unless rawdata | |
self.ical = ICal.new(rawdata) | |
self.version = self.ical.hash['VCALENDAR']['VERSION'] | |
self.scale = self.ical.hash['VCALENDAR']['CALSCALE'] | |
self.method = self.ical.hash['VCALENDAR']['METHOD'] | |
self.product_id = self.ical.hash['VCALENDAR']['PRODID'] | |
# These aren't needed for my implementations. | |
# self.time_zone_name = self.ical.hash['VCALENDAR']['VTIMEZONE']['TZID'] | |
# puts "Time Zone: #{self.time_zone_name}" | |
# self.time_zone_offset = self.ical.hash['VCALENDAR']['VTIMEZONE']['STANDARD']['TZOFFSETTO'] | |
# puts "Time Zone offset: #{self.time_zone_offset}" | |
self.ical.hash['VCALENDAR']['VEVENT'] = [self.ical.hash['VCALENDAR']['VEVENT']] unless self.ical.hash['VCALENDAR']['VEVENT'].is_a?(Array) | |
self.ical.hash['VCALENDAR']['VEVENT'].each do |e| | |
# DTSTART;VALUE=DATE # format of yyyymmdd | |
# DTSTART;TZID=America/Chicago # format of yyyymmddThhmmss | |
# DTEND;VALUE=DATE # format of yyyymmdd | |
# DTEND;TZID=America/Chicago # format of yyyymmddThhmmss | |
# DTSTAMP # format of yyyymmddThhmmssZ - today's date and time! | |
# TRANSP # disreguard - transparency: opaque | |
# LOCATION # location string | |
# LAST-MODIFIED # format of yyyymmddThhmmssZ | |
# SEQUENCE # integer - not sure what it is | |
# UID # [email protected] - not sure what it's for, but they're all unique | |
# CATEGORIES # in gcal, all = 'http' | |
# SUMMARY # summary/title string | |
# CLASS # in gcal = PUBLIC or PRIVATE? | |
# STATUS # in gcal = CONFIRMED | |
# ORGANIZER;CN=Moody Campus # in gcal, all = MAILTO | |
# CREATED # format of yyyymmddThhmmssZ | |
# DESCRIPTION # description string | |
# ATTENDEE;CUTYPE=GROUP;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;CN=Moody Campu\ns;X-NUM-GUESTS=0 # so far always nil | |
# COMMENT;X-COMMENTER=MAILTO # someone's email address, perhaps if they commented on the event. | |
# RRULE # Recurrance Rule - string like 'FREQ=WEEKLY' | |
if !e.nil? | |
tzadjust = Time.gcalschema("#{e["DTSTART;TZID=#{self.time_zone_name}"] || "#{e['DTSTART;VALUE=DATE']}T000000"}Z").nil? ? -4*3600 : 0 | |
st = (Time.gcalschema("#{e["DTSTART;TZID=#{self.time_zone_name}"] || "#{e['DTSTART;VALUE=DATE']}T000000"}Z") || Time.gcalschema(e['DTSTART'])) + tzadjust | |
et = (Time.gcalschema("#{e["DTEND;TZID=#{self.time_zone_name}"] || "#{e['DTEND;VALUE=DATE']}T000000"}Z") || Time.gcalschema(e['DTEND'])) + tzadjust | |
# DTSTART;TZID=America/New_York:20070508T070000 | |
self.add_event(Event.new( | |
:start_time => st, | |
:end_time => et, | |
:location => e['LOCATION'], | |
:created_at => Time.gcalschema(e['CREATED']), | |
:updated_at => Time.gcalschema(e['LAST-MODIFIED']), | |
:summary => e['SUMMARY'], | |
:description => e['DESCRIPTION'], | |
:recurrance_rule => e['RRULE'] | |
), false) # (disable sorting until done) | |
@events.reject! {|e| e.start_time.nil?} | |
@events.sort! {|a,b| a.start_time <=> b.start_time } | |
end | |
end | |
end | |
def parse_from_ical | |
self.dup.parse_from_ical | |
end | |
def source_format | |
self.ical ? 'ical' : (self.xml ? 'xml' : nil) | |
end | |
def future_events | |
t = Time.now | |
events.inject([]) {|future,e| e.start_time > t ? future.push(e) : future} | |
end | |
def past_events | |
t = Time.now | |
events.inject([]) {|past,e| e.start_time < t ? past.push(e) : past} | |
end | |
def events_in_range(start_time, end_time) | |
events.inject([]) {|in_range,e| e.start_time < end_time && e.end_time > start_time ? in_range.push(e) : in_range} | |
end | |
def calendar_raw_data | |
# If you need to use a proxy: | |
# Net::HTTP::Proxy(proxy_host, proxy_port, proxy_user, proxy_pass).start('www.google.com', 80) do |http| | |
Net::HTTP.start('www.google.com', 80) do |http| | |
response, data = http.get(self.url) | |
case response | |
when Net::HTTPSuccess, Net::HTTPRedirection | |
return data | |
else | |
# response.error! | |
return nil | |
end | |
end | |
end | |
class Event | |
attr_accessor :start_time, :end_time, :location, :created_at, :updated_at, :summary, :description, :recurrance_rule | |
def initialize(attributes={}) | |
attributes.each do |key, value| | |
self.send("#{key.to_s}=", value) | |
end | |
end | |
end | |
end | |
class ICal | |
attr_accessor :hash, :raw | |
def initialize(ical_data) | |
self.raw = ical_data | |
self.hash = self.parse_ical_data(self.raw) | |
end | |
def parse_ical_data(data) | |
data.gsub!(/\\\n/, "\\n") | |
data.gsub!(/[\n\r]+ /, "\\n") | |
lines = data.split(/[\n\r]+/) | |
structure = [{}] | |
keys_path = [] | |
last_is_array = false | |
lines.each do |line| | |
line.gsub!(/\\n/, "\n") | |
pair = line.split(':') | |
name = pair.shift | |
value = pair.join(':') | |
case name | |
when 'BEGIN' #Begin Section | |
if structure[-1].has_key?(value) | |
if structure[-1][value].is_a?(Array) | |
structure[-1][value].push({}) | |
last_is_array = true | |
else | |
structure[-1][value] = [structure[-1][value], {}] | |
last_is_array = true | |
end | |
else | |
structure[-1][value] = {} | |
end | |
keys_path.push(value) | |
structure.push({}) | |
when 'END' #End Section | |
if last_is_array | |
structure[-2][keys_path.pop][-1] = structure.pop | |
last_is_array = false | |
else | |
structure[-2][keys_path.pop] = structure.pop | |
end | |
else #Within last Section | |
structure[-1][name] = value | |
end | |
end | |
structure[0] | |
end | |
end | |
end | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment