Das hat echt was Nerven gekostet, aber ich bin mehr als zufrieden mit dem Resultat.
Ich darf vorstellen: Wenn man seinen Kalender in Mac OS X (z. B. per Webdav) auf seinen Server lädt (bzw. synchronisiert) und auf diesem Server auch Ruby on Rails läuft, dann kann man seinen Kalender jetzt auf seiner Webseite veröffentlichen.
In fact I just realize I should better speak english, because someone’s German might be somewhat rusty :)
So again: You have ics files on your server (e.g. via webdav) and Ruby on Rails is running? Great, let’s publish your calendar. The idea came from the great PHPicalendar script.
This is what it will somewhat look like:
![]()
I am sorry to not have made a plugin out of this yet, but, hey, the basics are there, help yourself :) If you have any questions feel free to comment.
Requirements:
- Vpim plugin with
sudo gem install vpim
Features:
- Read several ICS files from a directory on the server
- Parse all the ical events in them
- Cache the current calendar in yaml files
- (The cache will be refreshed when a ICS file was updated meanwhile)
- HTML will be presented for the calendar
- Currently you can only choose a date and see the next X days
A word on recurrence of events
- It does do most of the recurrence rules!
- Specifically: All that VPIM supports
- PLUS: EXDATE is also supported!
The Code
Initializer (config/initializers/any_filename.rb)
1 2 3 4 5 | # Path to icalendar *.ics files on your server PATH_ICS = "#{RAILS_ROOT}/private/calendars/" PATH_ICS_CACHE = "#{RAILS_ROOT}/tmp/calendars/" FileUtils::mkdir_p(PATH_ICS) unless File.exists?(PATH_ICS) FileUtils::mkdir_p(PATH_ICS_CACHE) unless File.exists?(PATH_ICS_CACHE) |
Controller (app/controllers/calendars_controller.rb)
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 | require 'vpim' class CalendarsController < ApplicationController def index # Load parameters if submitted session[:date_year] = params[:options]["date(1i)"] if !params[:options].blank? && !params[:options]["date(1i)"].empty? session[:date_month] = params[:options]["date(2i)"] if !params[:options].blank? && !params[:options]["date(2i)"].empty? session[:date_day] = params[:options]["date(3i)"] if !params[:options].blank? && !params[:options]["date(3i)"].empty? # Load standard if nothing submitted session[:date_year] = Time.now.year if session[:date_year].blank? session[:date_month] = Time.now.month if session[:date_month].blank? session[:date_day] = Time.now.day if session[:date_day].blank? # Set variables @scope = 7 @events = [] @today = Time.gm(session[:date_year], session[:date_month], session[:date_day]) cachefile = File.join(PATH_ICS_CACHE, "#{@today.to_s(:ical)}_#{@scope}.yml") # Kill cache if outdated if File.exists?(cachefile) killcache = false Dir.glob(File.join(PATH_ICS, '*.ics')).each do |file| killcache = true if File.mtime(file) > File.mtime(cachefile) end if killcache Dir.glob(File.join(PATH_ICS_CACHE, '*.*')).each do |file| File.delete(file) end end end # Load calendar from cache if File.exists?(cachefile) @events = YAML.load_file cachefile else # No cache, parse each icalendar *.ics file in PATH_ICS and check for event occurences Dir.glob(File.join(PATH_ICS, '*.ics')).each do |file| category = File.basename(file, '.ics') Vpim::Icalendar.decode(File.open(file)).each do |calendar| calendar.components do |event| for day in 0..@scope if start = event.occurs_in?(@today+(60*60*24*day), @today+(60*60*24)+(60*60*24*day)) myend = start + (event.dtend - event.dtstart) @events < < { 'category' => category, 'day' => day, 'start' => start, 'duration' => ((event.dtend - event.dtstart) / 60).round, # In minutes 'end' => myend, 'allday' => start.hour == 0 && start.min == 0 && start.sec == 0 && myend.hour == 0 && myend.min == 0 && myend.sec == 0, 'data' => event } end end end end end @events = @events.uniq # Just in case :) # Save cache File.open(cachefile, 'w') { |f| YAML.dump(@events, f) } end end end |
Single View (view/calendars/index.html.erb)
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 68 69 70 71 72 73 74 75 76 77 | < %
width = 110 # Width of one day
height = 6 # height of 15 minutes in pixels
buffer = 28 # free space for dates in day column (at top of each day)
minh = 20 # Minimum of event height
cutmornings = 120 # I don't have events between 0:00 and 6:00, cut these pixels off
%>
<div id="action">
< % for day in 0...@scope + 1 do
today = (@today+(60*60*24*day))
case today.wday
when 0
dayclass = 'class="sunday"'
when 6
dayclass = 'class="saturday"'
else
dayclass = 'class="otherday"'
end
%>
<div id="ical_day" <%= dayclass %>
style=" width: < %= width %>px;
height: < %= height*96 + buffer - cutmornings %>px;
left: < %= day*width + day*10 %>px;">
< % if today.year == Time.now.year && today.month == Time.now.month && today.day == Time.now.day %>
<b>< %= 'Today' %></b>
< % else %>
< %= _(today.strftime("%a")) +', '+ today.to_s(:date) %>
< % end %>
</div>
< % end %>
< % tops = Array.new(@scope + 1, '')
@events.each do |event|
# Exclude recurrence rule hack
today = (@today+(60*60*24*event['day']))
exme = false
event['data'].propvaluearray('EXDATE').each do |exdate|
exdate = exdate.to_time
exme = true if today.year == exdate.year && today.mon == exdate.mon && today.day == exdate.day
end
next if exme # Skip this event
if event['allday']
# All-day events will be inserted later
tops[event['day']] += ' ' + event['data'].summary.to_s + '<br/>'
else
eventheight = ((event['duration']/15)*height).round
eventheight = minh if eventheight < minh
%>
<div id="ical_event"
style=" background: #<%= eventcolor(event['category']) %>;
width: < %= width-2 %>px;
height: < %= eventheight.to_s %>px;
top: < %= event['start'].hour*height*4 + (event['start'].min/15)*6 + buffer - cutmornings %>px;
left: < %= event['day']*width + event['day']*10 %>px;">
< %= '<b>'+ event['start'].to_s(:time) +' - '+ event['end'].to_s(:time) +'<br />' %>
< %= event['data'].summary %>
</div>
< % end %>
< % end %>
< % tops.each_with_index do |top, day| %>
<div id="ical_event_top"
style=" width: <%= width-2 %>px;
height: < %= height %>px;
top: < %= buffer %>px;
left: < %= day * width + day * 10 %>px;">
< %= top %>
</div>
< % end %>
<div id="vertical_spacer" style="height: <%= height*96 + buffer*2 - cutmornings %>px;"> </div>
</div> |
Layout View (views/layouts/calendars.html.erb)
< % form_tag :controller => 'calendars', :action => nil, :id => nil do |f| %> < %= date_select("options", "date", :default => @today, :order => [:day, :month, :year]) %> < %= submit_tag 'Show' + ' »', :class => 'date_button' %> < % end %> |
Helper (for coloring events) (app/helpers/calendar_helper.rb)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | module CalendarsHelper def eventcolor(category) case category when 'Wichtig' # This is the name of the .ics file return 'f66' when 'Sonstiges' return '4f4' when 'Studium' return 'fb4' when 'Privat' return '77f' when 'Freunde' return 'f4f' else return 'fb4' end end end |
Stylesheet (public/stylesheets/calendar.css)
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 | /**************** DIVs ***************************/ div#action { position: absolute; margin-left: 10px; margin-top: 10px; padding: 4px; border-left: 0px; } div#ical_day { position: absolute; padding-top: 5px; text-align: center; font-size: 10px; } .otherday { background: #ddd; } .sunday { background: #fdd; } .saturday { background: #ddf; } #ical_event { position: absolute; border: 1px #555 solid; background: #fb4; font-size: 8px; text-align: left; } #ical_event_top { position: absolute; border: 0px; font-size: 9px; font-weight: bold; text-align: left; } /**************** Fonts ***************************/ #title { float:right; color: #ddd; margin-top: 30px; margin-right: 10px; line-height: 11px; font-size: 16px; text-align: right; } #title .description { color: #777; font-size: 10px; margin: 0; } |
Routes (could be optional) (config/routes.rb)
1 2 | # CALENDAR Controllers map.connect 'calendar', :controller => 'calendars' |

Ich heiße Captain Future und meine Leidenschaft ist es die Brücke zwischen Menschen und Technik zu schlagen.
nice job.
this yaml cache is an awesome idee, who the fuck had it ;-)
i haven’t tried it but i’m sure it works quite well.
Hi I had done all wat u say ..
And I runned the server Then I got “LOAD ERROR”
LoadError
Expected /home/hasmukh/hafeez/newacal/app/helpers/calendar_helper.rb to define CalendarHelper
How to rectify this
Hi hafeez,
I’m sorry, I can’t recall that error. In fact, I am currently working on a new version to run on Rails 2.3
It will come out in a few months. However, your error doesn’t look like it’s a big thing. You should try to copy and paste all this code from the helper:
def eventcolor(category)
case category
when ‘Wichtig’ # This is the name of the .ics file
return ‘f66′
when ‘Sonstiges’
return ’4f4′
when ‘Studium’
return ‘fb4′
when ‘Privat’
return ’77f’
when ‘Freunde’
return ‘f4f’
else
return ‘fb4′
end
end
into the application_helper.rb
That could solve your problem. Let us know!
Greets, Marius
hi captain
i am not getting the full display of index action and how i will display the ics files using this date_select helper
Hi!
1. What do you mean by “I am not getting the full display”? Can you not see the code or is your application not having any output, or..?
2. Do you see these lines:
# Set variables
@scope = 7
@events = []
@today = Time.gm(session[:date_year], session[:date_month], session[:date_day])
The ics file (in the PATH_ICS path) will be displayed according to the date in @today. So when you set @today manually it will show you what happens on that date and the next 7 days (@scope). However, the date_select will make you have a dropdown box to select any date you want right on the website.
Do I understand you correctly? :)
i have fixed this i was only getting the date_select submit form not proper calendar for ics file on PATH_ICS path
thanks for reply
Wonderful.
Ganz schön formuliert – super.