{"id":870,"date":"2010-04-22T14:59:12","date_gmt":"2010-04-22T17:59:12","guid":{"rendered":"http:\/\/blog.plataformatec.com.br\/?p=870"},"modified":"2010-04-22T14:59:12","modified_gmt":"2010-04-22T17:59:12","slug":"recurring-events","status":"publish","type":"post","link":"https:\/\/blog.plataformatec.com.br\/2010\/04\/recurring-events\/","title":{"rendered":"Recurring events"},"content":{"rendered":"

We have been working in a project which deals with date events and we needed a recurrence feature in the application. An initial implementation could simply work with Rails ActiveSupport and use its Date helper methods, in order to shift by day, week, month and others.<\/p>\n

\r\n>> Date.today\r\n=> Thu, 15 Apr 2010\r\n>> Date.today.next_month\r\n=> Sat, 15 May 2010\r\n>> Date.today.next_week\r\n=> Mon, 19 Apr 2010\r\n>> Date.today.next_week(:thursday)\r\n=> Thu, 22 Apr 2010\r\n>> Date.today.advance(:days => 4)\r\n=> Mon, 19 Apr 2010\r\n<\/pre>\n

Very easy, right? But what if we now want events that occur every monday or sunday? Or events that happen at each two weeks? For each new case, we will need to add and deal with more and more logic.<\/p>\n

Instead we have been using the Recurrence<\/a> gem, created by Nando Vieira<\/a> with some contributions by Jos\u00e9 Valim<\/a> (yeah, our good fellow ;)). Recurrence works with ActiveSupport as its base for date calculation and provides a simple DSL to work with recurring events. Let me show a simple example:<\/p>\n

\r\n# events every day, considering today as Apr 15th.\r\n>> r = Recurrence.new(:starts => 2.days.ago, :every => :day, :until => Date.tomorrow)\r\n>> r.events\r\n=> [Tue, 13 Apr 2010, Wed, 14 Apr 2010, Thu, 15 Apr 2010, Fri, 16 Apr 2010]\r\n<\/pre>\n

If no :until<\/em> is determined, it runs until year 2037<\/a> (maybe the end of world? No, it’s when the unix time 32-bit overflow…). You can check the Github repository<\/a> and this blog post<\/a> (in brazilian portuguese) to learn different use cases and examples for Recurrence.<\/p>\n

Now I want to show you how we integrated it with our application. We have built an EventRecurrence class which deals internally with the recurrence logic.<\/p>\n

\r\n# app\/models\/event_recurrence.rb\r\n# It has :start_date, :every and :end_date as database columns\r\nclass EventRecurrence < ActiveRecord::Base\r\n  def dates(options={})\r\n    options = {:every => every, :starts => start_date, :until => end_date}.merge(options)\r\n    options[:on] = case options[:every]\r\n    when 'year'\r\n      [options[:starts].month, options[:starts].day]\r\n    when 'week'\r\n      options[:starts].strftime('%A').downcase.to_sym\r\n    when 'day', 'month'\r\n      options[:starts].day\r\n    end\r\n    Recurrence.new(options).events\r\n  end\r\nend\r\n<\/pre>\n

Notice we need to setup the options<\/em> hash for different cases (day, week, month and year) because the configuration options changes for each. Now, accessing all the recurring dates for one model is quite easy:<\/p>\n

\r\n>> er = EventRecurrence.new(:start_date => Date.today, :every => :month, :end_date => 6.months.from_now)\r\n>> er.dates\r\n=> [Thu, 15 Apr 2010, Sat, 15 May 2010, Tue, 15 Jun 2010,\r\nThu, 15 Jul 2010, Sun, 15 Aug 2010, Wed, 15 Sep 2010, Fri, 15 Oct 2010]\r\n<\/pre>\n

Since EventRecurrence<\/em> is a model, these options are saved in database and we can update or use them for querying freely. For instance, if we want to look for all dates in a given period, we just search for all EventRecurrence<\/em>s that are in the range and collect the dates returned by recurrence. Here you can see a raw implementation of this idea:<\/p>\n

\r\nclass EventRecurrence < ActiveRecord::Base\r\n  # Retrieves a list of all dates for a period\r\n  def self.dates_between(start_date, end_date)\r\n    # Filtering EventRecurrence on the period using a between named scope\r\n    recurrences = EventRecurrence.between(start_date, end_date)\r\n\r\n    recurrences.inject([]) do |dates, recurrence|\r\n      # Use the given dates instead of the ones in the DB\r\n      dates.concat(recurrence.dates(:starts => start_date, :until => end_date))\r\n    end\r\n  end\r\nend\r\n<\/pre>\n

This is just an initial post showing some ideas to get you going with recurring events. If you are interested, there is a lot of information related to this topic, like Recurring Event in Calendars<\/a>, an article by Martin Fowler<\/a>, and the iCal RFC2445<\/a> which includes calendar specifications.<\/p>\n

And you, have some different solutions about recurring events to share with us?<\/p>\n","protected":false},"excerpt":{"rendered":"

We have been working in a project which deals with date events and we needed a recurrence feature in the application. An initial implementation could simply work with Rails ActiveSupport and use its Date helper methods, in order to shift by day, week, month and others. >> Date.today => Thu, 15 Apr 2010 >> Date.today.next_month … \u00bb<\/a><\/p>\n","protected":false},"author":9,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"ngg_post_thumbnail":0,"footnotes":""},"categories":[1],"tags":[7,85],"aioseo_notices":[],"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/posts\/870"}],"collection":[{"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/users\/9"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/comments?post=870"}],"version-history":[{"count":29,"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/posts\/870\/revisions"}],"predecessor-version":[{"id":914,"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/posts\/870\/revisions\/914"}],"wp:attachment":[{"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/media?parent=870"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/categories?post=870"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/tags?post=870"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}