<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array">
    <added>
      <filename>app/views/admin/calendars/_form.html.haml</filename>
    </added>
    <added>
      <filename>app/views/admin/calendars/edit.html.haml</filename>
    </added>
    <added>
      <filename>app/views/admin/calendars/new.html.haml</filename>
    </added>
    <added>
      <filename>app/views/admin/icals/refresh.html.haml</filename>
    </added>
    <added>
      <filename>app/views/admin/icals/refresh_all.html.haml</filename>
    </added>
    <added>
      <filename>db/migrate/20090818133511_simpler_ical_columns.rb</filename>
    </added>
    <added>
      <filename>lib/event_calendar_admin_ui.rb</filename>
    </added>
    <added>
      <filename>spec/datasets/calendar_pages_dataset.rb</filename>
    </added>
    <added>
      <filename>spec/datasets/calendar_sites_dataset.rb</filename>
    </added>
    <added>
      <filename>spec/datasets/calendars_dataset.rb</filename>
    </added>
    <added>
      <filename>spec/lib/event_calendar_page_spec.rb</filename>
    </added>
    <added>
      <filename>spec/models/calendar_spec.rb</filename>
    </added>
    <added>
      <filename>spec/models/event_spec.rb</filename>
    </added>
    <added>
      <filename>spec/models/ical_spec.rb</filename>
    </added>
  </added>
  <modified type="array">
    <modified>
      <diff>@@ -8,6 +8,8 @@ We can take feeds either from an ical file (which is your normal .ics calendar s
 
 The radius tag structure has changed. Those few people who have old event_calendar pages will need to tweak them to use the new `r:calendar` and `r:event` namespaces, of which more below.
 
+Next job is to add some proper tests.
+
 ## Requirements
 
 You need the `vpim` gem to handle event data:
@@ -41,7 +43,7 @@ Each calendar subscription will have its own address and authentication settings
 2. Find the subscription address of your calendar.
 3. Choose 'new calendar' in the radiant admin menu and enter the address and any authentication information you need to get at it. See below for notes about connecting to CalDAV. In the case of an ical file you should only need an address. Give the calendar a slug, just as you would for a page, and optionally a category. Let's say you call it 'test'.
 4. Your calendar should appear in the subscription list. Click through to browse its events and make sure everything is as it should be.
-5. Set up a new page at /calendar/ with the type 'Calendar' and fill it with something this:
+5. Set up a new page at /calendar/ with the type 'EventCalendar' and fill it with something this:
 
 	&lt;div class=&quot;event_list&quot;&gt;
 	  &lt;r:events:each year=&quot;now&quot;&gt;
@@ -68,6 +70,12 @@ Point your browser at /calendar/test and you should see a list of this year's ev
 
 Note that the `event:header` tag only shows when it changes, which in this case gives you a non-repeating date slip. For more about the available radius tags, see the extension wiki or the 'available tags' documentation.
 
+If you have another column in your layout, try adding this:
+
+	&lt;r:events:as_calendar calendar_months=&quot;6&quot; date_links=&quot;true&quot; month_links=&quot;false&quot; /&gt;
+
+For clickable thumbnails of coming months.
+
 ## Notes
 
 This is developing quite quickly at the moment but it's in production use on a couple of small sites and seems stable enough.</diff>
      <filename>README.md</filename>
    </modified>
    <modified>
      <diff>@@ -5,6 +5,7 @@ class Calendar &lt; ActiveRecord::Base
   validates_presence_of :name
   validates_uniqueness_of :name
   validates_uniqueness_of :slug, :scope =&gt; :category
+  accepts_nested_attributes_for :ical
   
   named_scope :in_category, lambda { |category| # string. needs to match exactly
     { :conditions =&gt; [ &quot;calendars.category = ?&quot;, category ] }</diff>
      <filename>app/models/calendar.rb</filename>
    </modified>
    <modified>
      <diff>@@ -57,5 +57,13 @@ class Event &lt; ActiveRecord::Base
   def allday?
     start_date.hour == 0 &amp;&amp; end_date.hour == 0
   end
+  
+  def nice_start_time
+    if start_date.min == 0
+      start_date.to_datetime.strftime(&quot;%-1I%p&quot;).downcase
+    else
+      start_date.to_datetime.strftime(&quot;%-1I:%M%p&quot;).downcase
+    end
+  end
 
 end</diff>
      <filename>app/models/event.rb</filename>
    </modified>
    <modified>
      <diff>@@ -5,31 +5,31 @@ require 'date'
 
 class Ical &lt; ActiveRecord::Base
   belongs_to :calendar
-  validates_presence_of :ical_url
+  validates_presence_of :url
 
   @@calendars_path = Radiant::Config[&quot;event_calendar.icals_path&quot;]
   
 	# Download and save the .ics calendar file, parse and save events to database
 	def refresh
-    ical_filename = RAILS_ROOT + &quot;/public/&quot; + @@calendars_path + &quot;/&quot; + self.calendar.slug + &quot;.ics&quot;
+    filename = RAILS_ROOT + &quot;/public/&quot; + @@calendars_path + &quot;/&quot; + self.calendar.slug + &quot;.ics&quot;
 
     # Retrieve calendar specified by URL and Name attributes
     begin
-      uri = URI.parse(ical_url)
+      uri = URI.parse(url)
       if authenticated?
         http = Net::HTTP.new(uri.host, uri.port)
         http.use_ssl = uri.scheme == 'https'
         req = Net::HTTP::Get.new(uri.path)
-        req.basic_auth ical_username, ical_password
+        req.basic_auth username, password
         response = http.request(req)
       else
         response = Net::HTTP.get_response(uri)
       end
-    	File.open(ical_filename, &quot;w&quot;) { |file|
+    	File.open(filename, &quot;w&quot;) { |file|
         file &lt;&lt; response.body
       }
     rescue 
-      logger.error &quot;iCal url or file error with: #{self.calendar.name} - #{ical_url} -&gt; (#{ical_filename}) -- error.&quot;
+      logger.error &quot;iCal url or file error with: #{self.calendar.name} - #{url} -&gt; (#{filename}) -- error.&quot;
       return false
     end 
     
@@ -38,7 +38,7 @@ class Ical &lt; ActiveRecord::Base
     self.calendar.events.delete_all
 
     # Open file for reading, parse and store to DB
-      File.open(ical_filename, &quot;r&quot;) do |file|
+      File.open(filename, &quot;r&quot;) do |file|
         cal = Vpim::Icalendar.decode(file).first
         event_count = 0
         cal.events.each do |parsed_event|
@@ -71,11 +71,11 @@ class Ical &lt; ActiveRecord::Base
   end
 	
 	def refresh_automatically?
-	  ical_refresh_interval.nil? || ical_refresh_interval.to_i != 0
+	  refresh_interval.nil? || refresh_interval.to_i != 0
   end
 	
 	def refresh_interval_or_default
-	  (ical_refresh_interval || Radiant::Config['event_calendar.default_refresh_interval'] || 3600).to_i.seconds
+	  (refresh_interval || Radiant::Config['event_calendar.default_refresh_interval'] || 3600).to_i.seconds
   end
   
   def needs_refreshment?
@@ -98,11 +98,11 @@ class Ical &lt; ActiveRecord::Base
 	protected
 	
   	def authenticated?
-  	  not ical_username.blank?
+  	  !username.blank?
     end
 		
 		def secured?
-  	  ical_url.match(/^https/)
+  	  url.match(/^https/)
     end
   	
 end
\ No newline at end of file</diff>
      <filename>app/models/ical.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,4 +1,5 @@
 - include_stylesheet 'admin/calendar'
+= render_region :top 
 
 %h1
   Calendar subscriptions
@@ -6,35 +7,54 @@
 %table.index
   %thead
     %tr
-      %th Name
-      %th URL (category/slug)
-      %th Last Refresh
-      %th Refresh automatically?
-      %th 
+      - render_region :thead do |thead|
+        - thead.name_header do
+          %th Name
+
+        - thead.url_header do
+          %th URL (category/slug)
+
+        - thead.refresh_header do
+          %th Last Refresh
+          %th Refresh automatically?
+
+        - thead.action_header do
+          %th 
   %tbody
     - @calendars.each do |calendar|
       %tr{:class =&gt; &quot;node level-1&quot;}
-        %td.subscription-title
-          = link_to calendar.name, admin_calendar_url(calendar)
-          = &quot;(#{calendar.ical.last_refresh_count || '0'} events)&quot;
-        %td.url
-          =h calendar.category.to_s.downcase 
-          \/
-          =h calendar.slug.downcase
-        %td.refreshed
-          =h calendar.ical.last_refresh_date.strftime(&quot;%m/%d at %H:%M&quot;) unless calendar.ical.last_refresh_date.blank?
-        %td.autorefresh
-          = calendar.ical.refresh_automatically? ? &quot;after #{distance_of_time_in_words(calendar.ical.refresh_interval_or_default.to_i.seconds)}&quot; : &quot;no&quot;
-        %td
-          = link_to 'Refresh', refresh_admin_ical_url(calendar.ical.id), :method =&gt; :put 
-          | 
-          = link_to 'Edit', edit_admin_calendar_path(calendar) 
-          |
-          = link_to 'Delete', admin_calendar_path(calendar), :confirm =&gt; 'Are you sure?', :method =&gt; :delete
-
-.footnote
-  %p
-    = link_to &quot;Refresh All&quot;, refresh_all_admin_icals_path, :method =&gt; :put
-    |
-    = link_to 'Add a calendar', new_admin_calendar_url
+
+        - render_region :tbody do |tbody|
+          - tbody.name_cell do          
+            %td.subscription-title
+              = link_to calendar.name, admin_calendar_url(calendar)
+              = &quot;(#{calendar.ical.last_refresh_count || '0'} events)&quot;
+
+          - tbody.url_cell do
+            %td.url
+              =h calendar.category.to_s.downcase 
+              \/
+              =h calendar.slug.downcase
+
+          - tbody.refresh_cell do
+            %td.refreshed
+              =h calendar.ical.last_refresh_date.strftime(&quot;%m/%d at %H:%M&quot;) unless calendar.ical.last_refresh_date.blank?
+            %td.autorefresh
+              = calendar.ical.refresh_automatically? ? &quot;after #{distance_of_time_in_words(calendar.ical.refresh_interval_or_default.to_i.seconds)}&quot; : &quot;no&quot;
+
+          - tbody.action_cell do
+            %td
+              = link_to 'Refresh', refresh_admin_ical_url(calendar.ical.id), :method =&gt; :put 
+              | 
+              = link_to 'Edit', edit_admin_calendar_path(calendar) 
+              |
+              = link_to 'Delete', admin_calendar_path(calendar), :confirm =&gt; 'Are you sure?', :method =&gt; :delete
+
+- render_region :bottom do |bottom|
+  - bottom.buttons do
+    .footnote
+      %p
+        = link_to &quot;Refresh All&quot;, refresh_all_admin_icals_path, :method =&gt; :put
+        |
+        = link_to 'Add a calendar', new_admin_calendar_url
   
\ No newline at end of file</diff>
      <filename>app/views/admin/calendars/index.html.haml</filename>
    </modified>
    <modified>
      <diff>@@ -16,15 +16,15 @@
     %li
       %strong
         address:
-      = link_to @calendar.ical.ical_url
+      = link_to @calendar.ical.url
     %li
       %strong
         username:
-      = link_to @calendar.ical.ical_username
+      = link_to @calendar.ical.username
     %li
       %strong
         password:
-      = link_to @calendar.ical.ical_password
+      = link_to @calendar.ical.password
   %p
     This may be part of a master calendar with several individual calendars. In that case there will be a different address that lets you access all the calendars at once. Consult your site administrator if unsure.
 </diff>
      <filename>app/views/admin/calendars/show.html.haml</filename>
    </modified>
    <modified>
      <diff>@@ -1,5 +1,3 @@
-&lt;%= render :partial =&gt; 'layouts/nav' %&gt;
-
 &lt;h1&gt;Events for next 6 months&lt;/h1&gt;
 &lt;% @events.each do |event| %&gt;
 	&lt;div style=&quot;border-bottom: solid 1px rgb(100,100,100); padding: 10px&quot;&gt;</diff>
      <filename>app/views/admin/events/index.html.erb</filename>
    </modified>
    <modified>
      <diff>@@ -23,8 +23,16 @@ class EventCalendarExtension &lt; Radiant::Extension
       Radiant::Config[&quot;event_calendar.icals_path&quot;] = &quot;icals&quot;
     end
 
+    unless defined? admin.calendar
+      Radiant::AdminUI.send :include, EventCalendarAdminUI
+      admin.calendar = Radiant::AdminUI.load_default_calendar_regions
+      if defined? admin.sites
+        admin.calendar.index.add :top, &quot;admin/shared/site_jumper&quot;
+      end
+    end
+    
     admin.tabs.add &quot;Calendars&quot;, EXT_ROOT + &quot;/calendars&quot;, :after =&gt; &quot;Snippets&quot;, :visibility =&gt; [:all]
-    if admin.tabs[&quot;Calendars&quot;].respond_to?(:add_link)
+    if admin.tabs[&quot;Calendars&quot;].respond_to?(:add_link)   # that is, if the submenu extension is installed
       admin.tabs[&quot;Calendars&quot;].add_link &quot;calendar list&quot;, EXT_ROOT + &quot;/calendars&quot;
       admin.tabs[&quot;Calendars&quot;].add_link &quot;new subscription&quot;, EXT_ROOT + &quot;/calendars/new&quot;
       admin.tabs[&quot;Calendars&quot;].add_link &quot;refresh all&quot;, EXT_ROOT + &quot;/icals/refresh_all&quot;</diff>
      <filename>event_calendar_extension.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,62 +1,62 @@
 class CalendarPeriod
-  attr_accessor :begin_date, :end_date, :name, :amount, :end_set
-  
-  def initialize(from=nil, to=nil)
-    @begin_date = from ? from.to_date : first_day_of_month
-    if to
-      @end_date = to.to_date
-      @end_set = true
-    else
-      @end_set = false
-      @name = &quot;month&quot;
-      @amount = 1
-    end
-  end    
+  include ActionView::Helpers::DateHelper
+  
+  attr_accessor :start, :duration
+  
+  def initialize(from=nil, length=nil)
+    @start = from ? from.to_date : Date.today.beginning_of_month
+    @duration = length || 1.month
+  end
+  
+  def self.between(from,to)
+    period = self.new(from)
+    period.finish = to
+    period
+  end
 
+  def finish
+    start + duration
+  end
+  
+  def finish=(datetime)
+    duration = datetime - start
+  end
+  
   def to_s
-    @name
+    &quot;#{distance_of_time_in_words(start, finish)} from #{start}&quot;
   end
   
   def inspect
-    self.calculate_dates!
-    %{#{@begin_date} to #{@end_date}}
+    %{#{start} to #{finish}}
   end
   
-  def begin_date
-    self.calculate_dates!
-    @begin_date
-  end
+  # to expand the period to full calendar months
+  # @period.pad
   
-  def end_date
-    self.calculate_dates!
-    @end_date
+  def pad
+    start = start.beginning_of_month
+    finish = finish.end_of_month
   end
-    
-  def end_date=(date)
-    @end_set = true
-    @end_date = date
+
+  # to shift the period forward one month
+  # @period += 1.month
+
+  def +(s)
+    CalendarPeriod.new(start + s, duration)
   end
   
-  def calculate_dates!
-    return if @end_set &amp;&amp; @end_date
-    case @name
-      when &quot;day&quot;
-        @end_date = @begin_date + (1 * @amount)
-      when &quot;week&quot;
-        @end_date = @begin_date + (7 * @amount)
-      when &quot;month&quot;
-        @end_date = @begin_date &gt;&gt; (1 * @amount)
-      when &quot;year&quot;
-        @end_date = @begin_date &gt;&gt; (12 * @amount)
-    end
-  end
+  # to shift the period back one month
+  # @period -= 1.month
   
-  def first_day_of_month(date=Date.today)
-    date.beginning_of_month
+  def -(s)
+    CalendarPeriod.new(start - s, duration)
   end
   
-  def last_day_of_month(date=Date.today)
-    date.end_of_month
+  # to extend the period by one month
+  # @period &lt;&lt; 1.month 
+  
+  def &lt;&lt;(s)
+    @duration += s
   end
   
 end</diff>
      <filename>lib/calendar_period.rb</filename>
    </modified>
    <modified>
      <diff>@@ -15,4 +15,6 @@ class EventCalendarPage &lt; Page
     end
   end
   
+  # something to munch date input into a Date object ?
+  
 end</diff>
      <filename>lib/event_calendar_page.rb</filename>
    </modified>
    <modified>
      <diff>@@ -68,84 +68,6 @@ module EventCalendarTags
     end
     result
   end
-     
-  [:from, :to, :duration, :description].each do |attribute|
-    # pass to the period
-  end
-
-  tag &quot;events:period&quot; do |tag|
-    # nice description of the enclosed period
-  end
-  
-  
-  
-  desc %{ 
-    Renders a calendar table for a single month. Like all events: tags, if no period is specified, it defaults to the present month. 
-    Usually you'll want to specify month and year attributes. An EventCalendar page will also obey month and year request parameters.
-    If a period is specified longer than a month, we just render the first month: in that case you might want to use r:events:months to get several displayed at once.
-    
-    Usage:
-    &lt;pre&gt;&lt;code&gt;&lt;r:event:month [year=&quot;&quot;] [month=&quot;&quot;] [compact=&quot;true&quot;] /&gt;&lt;/code&gt;&lt;/pre&gt; 
-    
-  }
-  tag &quot;events:month&quot; do |tag|
-    attr = tag.attr.symbolize_keys
-    tag.locals.events ||= get_events(tag)
-    compact = attr[:compact] || tag.attr[:list_events].nil?
-    table_class = compact ? 'small_month' : 'month'
-    
-    first_day = tag.locals.calendar_start ? tag.locals.calendar_start.beginning_of_month : tag.locals.period.begin_date.beginning_of_month
-    last_day = first_day.end_of_month
-    first_shown = first_day.beginning_of_week     # padding of period to show whole months
-    last_shown = last_day.end_of_week
-    previous = first_day - 1.day
-    following = last_day + 1.day
-
-    month_names = compact ? Date::ABBR_MONTHNAMES : Date::MONTHNAMES
-    day_names = compact ? Date::DAYNAMES.map {|d| d.first} : Date::DAYNAMES
-    day_names.push(day_names.shift) # Class::Date and ActiveSupport::CoreExtensions::Time::Calculations have different ideas of when is the start of the week. we've gone for the rails standard.
-    
-    cal = %(&lt;table class=&quot;#{table_class}&quot;&gt;&lt;thead&gt;&lt;tr&gt;)
-    cal &lt;&lt; %(&lt;th colspan=&quot;2&quot; class=&quot;month_link&quot;&gt;&lt;a href=&quot;?year=#{previous.year}&amp;amp;month=#{previous.month}&quot;&gt;&amp;lt; #{month_names[previous.month]}&lt;/a&gt;&lt;/th&gt;) if attr[:month_links]
-    cal &lt;&lt; %(&lt;th colspan=&quot;#{attr[:month_links] ? 3 : 7}&quot; class=&quot;month_name&quot;&gt;#{month_names[first_day.month]} #{first_day.year}&lt;/th&gt;)
-    cal &lt;&lt; %(&lt;th colspan=&quot;2&quot; class=&quot;month_link&quot;&gt;&lt;a href=&quot;?year=#{following.year}&amp;amp;month=#{following.month}&quot;&gt;#{month_names[following.month]} &amp;gt;&lt;/a&gt;&lt;/th&gt;) if attr[:month_links]
-    cal &lt;&lt; %(&lt;/tr&gt;&lt;tr class=&quot;day_name&quot;&gt;)
-    cal &lt;&lt; day_names.map { |d| &quot;&lt;th scope='col'&gt;#{d}&lt;/th&gt;&quot; }.join
-    cal &lt;&lt; &quot;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&quot;
-
-    first_shown.upto(last_shown) do |day|
-      events_today = tag.locals.events.select{ |e| e.start_date &lt;= day + 1.day &amp;&amp; e.end_date &gt;= day }
-      event_list = cell_text = date_label = &quot;&quot;
-      cell_class = &quot;day&quot;
-      cell_class += &quot; other_month&quot; if day.month != first_day.month
-      unless compact &amp;&amp; day.month != first_day.month
-        cell_class += &quot; weekend_day&quot; if weekend?(day)
-        cell_class += &quot; today&quot; if today?(day)
-        cell_class += &quot; weekend_today&quot; if weekend?(day) &amp;&amp; today?(day)
-        date_label = day.mday
- 
-        if events_today.any?
-          cell_class += &quot; eventful&quot;
-          cell_class += &quot; eventful_weekend&quot; if weekend?(day)
-          cell_class += events_today.map{|e| &quot; #{e.calendar.slug}&quot;}.join
-          if compact
-            date_label = %{&lt;a href=&quot;#event_#{events_today.first.id}&quot;&gt;#{date_label}&lt;/a&gt;}
-          else
-            event_list &lt;&lt; %{&lt;ul&gt;} &lt;&lt; events_today.map { |e| &quot;&lt;li&gt;#{e.title}&lt;/li&gt;&quot; }.join &lt;&lt; &quot;&lt;/ul&gt;&quot;
-          end
-        else
-          cell_class += &quot; uneventful&quot;
-        end
-        date_label = %{&lt;h4&gt;#{date_label}&lt;/h4&gt;} unless compact
-        cell_text = %{&lt;div class=&quot;event_holder&quot;&gt;#{date_label}#{event_list}&lt;/div&gt;}
-      end
-      cal &lt;&lt; &quot;&lt;tr&gt;&quot; if day == day.beginning_of_week
-      cal &lt;&lt; %{&lt;td class=&quot;#{cell_class}&quot;&gt;#{cell_text}&lt;/td&gt;}
-      cal &lt;&lt; &quot;&lt;/tr&gt;&quot; if day == day.end_of_week
-    end
-    cal &lt;&lt; %{&lt;/tbody&gt;&lt;/table&gt;}
-    cal
-  end
   
   desc %{ 
     This is a shortcut that returns a set of tabulated months covering the period defined. It works in the same way as r:events:each but presents the results in a familiar calendar format.
@@ -167,7 +89,7 @@ module EventCalendarTags
     
     Note that if you set 'months=3' instead of 'calendar_months=3' then it means three months from today, which actually extends over four calendar months.
     
-    Big months until the end of the year: 
+    Full size months until the end of the year: 
     
     &lt;pre&gt;&lt;code&gt;&lt;r:events:as_calendar until=&quot;12-12&quot; /&gt;&lt;/code&gt;&lt;/pre&gt;
     
@@ -175,13 +97,15 @@ module EventCalendarTags
     
   }
   tag &quot;events:as_calendar&quot; do |tag|
+    attr = parse_boolean_attributes(tag)
     tag.locals.events ||= get_events(tag)
     result = ''
     this_month = nil
-    tag.locals.period.begin_date.upto(tag.locals.period.end_date) do |day|
+    tag.locals.period.start.upto(tag.locals.period.finish) do |day|
       if day.month != this_month
         tag.locals.calendar_start = day
-        result &lt;&lt; tag.render('events:month')
+        display = attr[:compact] ? 'events:minimonth' : 'events:month'
+        result &lt;&lt; tag.render(display, attr.dup)
         this_month = day.month
       end
     end
@@ -189,30 +113,137 @@ module EventCalendarTags
   end
   
   
+  [:from, :to, :duration, :description].each do |attribute|
+
+  end
+
+  tag &quot;events:period&quot; do |tag|
+
+  end
+
+
+  # Calendars:* tags
+  # iterate over the set of calendars
+
+  desc %{
+    Loop over a set of calendars specified by the usual search conditions.
+
+    *Usage:* 
+    &lt;pre&gt;&lt;code&gt;&lt;r:calendars:each&gt;...&lt;/r:calendars:each&gt;&lt;/code&gt;&lt;/pre&gt;
+  }
+  tag 'calendars' do |tag|
+    tag.locals.calendars = Calendar.find(:all, calendars_find_options(tag))
+    tag.expand
+  end
   
-  
-  
-  
-  
-  
-  
-  
-  
-  
-  
-  
-  
-  
-  
-  
+  tag 'calendars:each' do |tag|
+    result = []
+    tag.locals.calendars.each do |cal|
+      tag.locals.calendar = cal
+      result &lt;&lt; tag.expand
+    end
+    result
+  end
+
+
+
+  # Calendar:* tags
+  # select and display attributes of a single calendar
+  # many of these are shortcuts to events: tags that set a calendar parameter and pass through.
+
+  tag 'calendar' do |tag|
+    tag.locals.calendar ||= get_calendar(tag)
+    raise TagError, &quot;No calendar&quot; unless tag.locals.calendar
+    tag.attr['calendar'] = tag.locals.calendar.slug
+    tag.expand
+  end
+
+  [:id, :name, :description, :category, :slug].each do |attribute|
+    desc %{
+      Renders the #{attribute} attribute of the current calendar.
+      
+      Usage:
+      &lt;pre&gt;&lt;code&gt;&lt;r:calendar:#{attribute} /&gt;&lt;/code&gt;&lt;/pre&gt; 
+    }
+    tag &quot;calendar:#{attribute}&quot; do |tag|
+      tag.locals.calendar.send(attribute)
+    end
+  end
+
+  [:last_refresh_date, :last_refresh_count, :url, :username, :password].each do |attribute|
+    desc %{ 
+      Renders the #{attribute} attribute of the ical subscription associated with the present calendar.
+      
+      Usage:
+      &lt;pre&gt;&lt;code&gt;&lt;r:calendar:#{attribute} /&gt;&lt;/code&gt;&lt;/pre&gt; 
+    }
+    tag &quot;calendar:#{attribute}&quot; do |tag|
+      tag.locals.calendar.ical.send(attribute)
+    end
+  end
+
+  desc %{
+    Loops over the events of the present calendar. 
+    Takes the same sort and selection options as r:events (except for those that specify which calendars to show), and within it you can use all the usual r:event and r:calendar tags.
+
+    *Usage:* 
+    &lt;pre&gt;&lt;code&gt;&lt;r:calendar:events:each&gt;...&lt;/r:calendar:events:each&gt;&lt;/code&gt;&lt;/pre&gt;
+  }
+  tag 'calendar:events' do |tag|
+    tag.locals.events = get_events(tag)
+  end
+
+  tag 'calendar:events:each' do |tag|
+    result = []
+    tag.locals.events.each do |event|
+      tag.locals.event = event
+      result &lt;&lt; tag.expand
+    end
+    result
+  end
+
+  # month, week and day tags 
+  # tabulate a period and show any events in the current set that fall into that period
+
+  [:year, :month, :week, :day].each do |period|
+    desc %{ 
+      Shortcut tag that renders a #{period} view of the current calendar with all contained events.
+      On EventCalendar pages, these tags will obey relevant year, month, week and day request parameters.
+      Any other period tags are ignored.
+      
+      Usage:
+      &lt;pre&gt;&lt;code&gt;&lt;r:calendar:#{period} [year=&quot;&quot;] [month=&quot;&quot;] [week=&quot;&quot;] [day=&quot;&quot;] /&gt;&lt;/code&gt;&lt;/pre&gt; 
+    }
+  end
+
+  tag &quot;calendar:day&quot; do |tag|
+    tag.locals.period = period_from_parts(:day =&gt; tag.attr['day'], :month =&gt; tag.attr['month'], :year =&gt; tag.attr['year'])
+    tag.render(&quot;events:day&quot;)
+  end
+
+  tag &quot;calendar:month&quot; do |tag|
+    tag.locals.period = period_from_parts(:month =&gt; tag.attr['month'], :year =&gt; tag.attr['year'])
+    tag.render(&quot;events:month&quot;)
+  end
+
+  tag &quot;calendar:week&quot; do |tag|
+    tag.locals.period = period_from_parts(:week =&gt; tag.attr['week'], :year =&gt; tag.attr['year'])
+    tag.render(&quot;events:week&quot;)
+  end
+
+  tag &quot;calendar:year&quot; do |tag|
+    tag.locals.period = period_from_parts(:year =&gt; tag.attr['year'])
+    tag.render(&quot;events:year&quot;)
+  end
+
   # Event:* tags
   # display attributes of a single event 
-  
+
   tag &quot;event&quot; do |tag|
     raise TagError, &quot;can't have r:event without an event&quot; unless tag.locals.event
     tag.expand
   end
-  
+
   desc %{ 
     We display the content between these tags only when it has changed. 
     Here this is normally used to display the date above a list of events for that day but you can also list by calendar or by week, or whatever.
@@ -237,27 +268,27 @@ module EventCalendarTags
   [:id, :title, :description, :location, :url].each do |attribute|
     desc %{ 
       Renders the #{attribute} attribute of the current event.
-      
+
       Usage:
       &lt;pre&gt;&lt;code&gt;&lt;r:event:#{attribute} /&gt;&lt;/code&gt;&lt;/pre&gt; 
     }
     tag &quot;event:#{attribute}&quot; do |tag|
       tag.locals.event.send(attribute)
     end
-    
+
     desc %{ 
       Contents are rendered if the event has a #{attribute}.
-      
+
       Usage:
       &lt;pre&gt;&lt;code&gt;&lt;r:event:if_#{attribute}&gt;...&lt;/r:event:if_#{attribute}&gt;&lt;/code&gt;&lt;/pre&gt; 
     }
     tag &quot;event:if_#{attribute}&quot; do |tag|
       tag.expand if tag.locals.event.send(attribute)
     end
-    
+
     desc %{ 
       Contents are rendered unless the event has a #{attribute}.
-      
+
       Usage:
       &lt;pre&gt;&lt;code&gt;&lt;r:event:unless_#{attribute}&gt;...&lt;/r:event:unless_#{attribute}&gt;&lt;/code&gt;&lt;/pre&gt; 
     }
@@ -265,11 +296,11 @@ module EventCalendarTags
       tag.expand unless tag.locals.event.send(attribute)
     end
   end
-  
+
   desc %{ 
     If the event has a url, renders a link to that address around the title of the event. If not, just the title without a link.
     As usual, if the tag is double the contents are used instead, and any other attributes are passed through to the link tag, if any.
-    
+
     Usage:
     &lt;pre&gt;&lt;code&gt;
       &lt;r:event:link /&gt;
@@ -287,11 +318,11 @@ module EventCalendarTags
       %{&lt;span #{attributes}&gt;#{text}&lt;/span&gt;}
     end
   end
-  
+
   [:start, :end].each do |attribute|
     desc %{ 
       Renders the #{attribute} time of the current event with the specified strftime format. Default is 24 hour hh:mm.
-      
+
       Usage:
       &lt;pre&gt;&lt;code&gt;&lt;r:event:#{attribute} [format=&quot;&quot;] /&gt;&lt;/code&gt;&lt;/pre&gt; 
     }
@@ -302,7 +333,7 @@ module EventCalendarTags
   end
 
   desc %{ 
-    Renders the start time of the current event with the specified strftime format. Unlike start and end, the default here is %m/%d/%Y
+    Renders the start time of the current event with the specified strftime format. Unlike start and end, the default here is '%d %M'
 
     Usage:
     &lt;pre&gt;&lt;code&gt;
@@ -319,7 +350,7 @@ module EventCalendarTags
   tag &quot;event:date&quot; do |tag|
     tag.locals.event.start_date.strftime(tag.attr['format'] || &quot;%m/%d/%Y&quot;)
   end
-  
+
   desc %{ 
     Prints the day-of-month of the start date of the current event.
     Equivalent to calling &lt;r:event:date format=&quot;%d&quot; /&gt; but a bit clearer.
@@ -367,7 +398,7 @@ module EventCalendarTags
   desc %{ 
     Prints the year of the start date of the current event.
     Equivalent to calling &lt;r:event:date format=&quot;%Y&quot; /&gt; but a bit clearer.
-    
+
     Usage:
     &lt;pre&gt;&lt;code&gt;&lt;r:event:year /&gt;&lt;/code&gt;&lt;/pre&gt; 
   }
@@ -394,7 +425,7 @@ module EventCalendarTags
       result = tag.locals.event.start_date.strftime(tagformat) &lt;&lt; separator &lt;&lt; tag.locals.event.end_date.strftime(format)
     end
   end
-  
+
   desc %{ 
     Contents are rendered only if this is an all-day event.
 
@@ -404,7 +435,7 @@ module EventCalendarTags
   tag &quot;event:if_all_day&quot; do |tag|
     tag.expand if tag.locals.event.allday?
   end
-  
+
   desc %{ 
     Contents are rendered only if this is not an all-day event.
 
@@ -416,222 +447,285 @@ module EventCalendarTags
   end
 
 
-
-
-
-
-
-
-
-
-  # Calendars:* tags
-  # iterate over the set of calendars
-
-  desc %{
-    Loop over a set of calendars specified by the usual search conditions.
-
-    *Usage:* 
-    &lt;pre&gt;&lt;code&gt;&lt;r:calendars:each&gt;...&lt;/r:calendars:each&gt;&lt;/code&gt;&lt;/pre&gt;
-  }
-  tag 'calendars' do |tag|
-    tag.locals.calendars = Calendar.find(:all, calendars_find_options(tag))
-    tag.expand
-  end
+  # calendar month blocks large and small. need some drying.
   
-  tag 'calendars:each' do |tag|
-    result = []
-    tag.locals.calendars.each do |cal|
-      tag.locals.calendar = cal
-      result &lt;&lt; tag.expand
-    end
-    result
-  end
-
-
-
-
-
-
-
-
-
-
-  # Calendar:* tags
-  # select and display attributes of a single calendar
-  # many of these are shortcuts to events: tags that set a calendar parameter and pass through.
-
-  tag 'calendar' do |tag|
-    tag.locals.calendar ||= get_calendar(tag)
-    raise TagError, &quot;No calendar&quot; unless tag.locals.calendar
-    tag.attr['calendar'] = tag.locals.calendar.slug
-    tag.expand
-  end
+  desc %{ 
+    Renders a minimal calendar table for a single month. Like all events: tags, if no period is specified, it defaults to the present month. 
+    Usually you'll want to specify month and year attributes. An EventCalendar page will also obey month and year request parameters.
+    If a period is specified longer than a month, we just render the first month: in that case you might want to use r:events:months to get several displayed at once.
+    
+    Usage:
+    &lt;pre&gt;&lt;code&gt;&lt;r:event:minimonth [year=&quot;&quot;] [month=&quot;&quot;] /&gt;&lt;/code&gt;&lt;/pre&gt; 
+    
+  }
+  tag &quot;events:minimonth&quot; do |tag|
+    attr = parse_boolean_attributes(tag)
+    tag.locals.events ||= get_events(tag)
+    
+    first_day = tag.locals.calendar_start ? tag.locals.calendar_start.beginning_of_month : tag.locals.period.start.beginning_of_month
+    last_day = first_day.end_of_month
+    first_shown = first_day.beginning_of_week     # padding of period to show whole months
+    last_shown = last_day.end_of_week
+    previous = first_day - 1.day
+    following = last_day + 1.day
 
-  [:id, :name, :description, :category, :slug].each do |attribute|
-    desc %{ 
-      Renders the #{attribute} attribute of the current calendar.
-      
-      Usage:
-      &lt;pre&gt;&lt;code&gt;&lt;r:calendar:#{attribute} /&gt;&lt;/code&gt;&lt;/pre&gt; 
-    }
-    tag &quot;calendar:#{attribute}&quot; do |tag|
-      tag.locals.calendar.send(attribute)
-    end
-  end
+    month_names = Date::ABBR_MONTHNAMES.dup
+    day_names = Date::DAYNAMES.dup
+    day_names.push(day_names.shift) # Class::Date and ActiveSupport::CoreExtensions::Time::Calculations have different ideas of when is the start of the week. we've gone for the rails standard.
+    
+    cal = %(&lt;table class=&quot;minimonth&quot;&gt;&lt;thead&gt;&lt;tr&gt;)
+    cal &lt;&lt; %(&lt;th colspan=&quot;2&quot; class=&quot;month_link&quot;&gt;&lt;a href=&quot;?year=#{previous.year}&amp;amp;month=#{previous.month}&quot;&gt;&amp;lt; #{month_names[previous.month]}&lt;/a&gt;&lt;/th&gt;) if attr[:month_links]
+    cal &lt;&lt; %(&lt;th colspan=&quot;#{attr[:month_links] ? 3 : 7}&quot; class=&quot;month_name&quot;&gt;#{month_names[first_day.month]} #{first_day.year}&lt;/th&gt;)
+    cal &lt;&lt; %(&lt;th colspan=&quot;2&quot; class=&quot;month_link&quot;&gt;&lt;a href=&quot;?year=#{following.year}&amp;amp;month=#{following.month}&quot;&gt;#{month_names[following.month]} &amp;gt;&lt;/a&gt;&lt;/th&gt;) if attr[:month_links]
+    cal &lt;&lt; %(&lt;/tr&gt;&lt;tr class=&quot;day_name&quot;&gt;)
+    cal &lt;&lt; day_names.map { |d| &quot;&lt;th scope='col'&gt;#{d.first}&lt;/th&gt;&quot; }.join
+    cal &lt;&lt; &quot;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&quot;
 
-  [:last_refresh_date, :last_refresh_count, :ical_url, :ical_username, :ical_password].each do |attribute|
-    desc %{ 
-      Renders the #{attribute} attribute of the ical subscription associated with the current calendar.
-      
-      Usage:
-      &lt;pre&gt;&lt;code&gt;&lt;r:calendar:#{attribute} /&gt;&lt;/code&gt;&lt;/pre&gt; 
-    }
-    tag &quot;calendar:#{attribute}&quot; do |tag|
-      tag.locals.calendar.ical.send(attribute)
+    first_shown.upto(last_shown) do |day|
+      events_today = tag.locals.events.select{ |e| e.start_date &lt;= day + 1.day &amp;&amp; e.end_date &gt;= day }
+      event_list = cell_text = date_label = &quot;&quot;
+      cell_class = &quot;day&quot;
+      cell_class += &quot; other_month&quot; if day.month != first_day.month
+      unless day.month != first_day.month
+        cell_class += &quot; weekend_day&quot; if weekend?(day)
+        cell_class += &quot; today&quot; if today?(day)
+        cell_class += &quot; weekend_today&quot; if weekend?(day) &amp;&amp; today?(day)
+        date_label = day.mday
+ 
+        if events_today.any?
+          cell_class += &quot; eventful&quot;
+          cell_class += &quot; eventful_weekend&quot; if weekend?(day)
+          cell_class += events_today.map{|e| &quot; #{e.calendar.slug}&quot;}.join
+          date_label = %{&lt;a href=&quot;#event_#{events_today.first.id}&quot;&gt;#{date_label}&lt;/a&gt;}
+        else
+          cell_class += &quot; uneventful&quot;
+        end
+        cell_text = %{&lt;div class=&quot;event_holder&quot;&gt;#{date_label}#{event_list}&lt;/div&gt;}
+      end
+      cal &lt;&lt; &quot;&lt;tr&gt;&quot; if day == day.beginning_of_week
+      cal &lt;&lt; %{&lt;td class=&quot;#{cell_class}&quot;&gt;#{cell_text}&lt;/td&gt;}
+      cal &lt;&lt; &quot;&lt;/tr&gt;&quot; if day == day.end_of_week
     end
+    cal &lt;&lt; %{&lt;/tbody&gt;&lt;/table&gt;}
+    cal
   end
-
-  desc %{
-    Loops over the events of the present calendar. 
-    Takes the same sort and selection options as r:events (except for those that specify which calendars to show), and within it you can use all the usual r:event and r:calendar tags.
-
-    *Usage:* 
-    &lt;pre&gt;&lt;code&gt;&lt;r:calendar:events:each&gt;...&lt;/r:calendar:events:each&gt;&lt;/code&gt;&lt;/pre&gt;
+  
+  desc %{ 
+    Renders a full calendar table for a single month. Like all events: tags, if no period is specified, it defaults to the present month. 
+    Usually you'll want to specify month and year attributes. An EventCalendar page will also obey month and year request parameters 
+    but only if the corresponding attributes have been specified.
+    
+    If a period is specified longer than a month, we just render the first month.
+    
+    If you use 'previous', 'now' and 'next' in your period attributes, an EventCalendar page will show the right period relative to input.
+    
+    Usage:
+    &lt;pre&gt;&lt;code&gt;&lt;r:event:month [year=&quot;&quot;] [month=&quot;&quot;] [compact=&quot;true&quot;] /&gt;&lt;/code&gt;&lt;/pre&gt; 
+    
   }
-  tag 'calendar:events' do |tag|
-    tag.locals.events = get_events(tag)
-  end
-
-  tag 'calendar:events:each' do |tag|
-    result = []
-    tag.locals.events.each do |event|
-      tag.locals.event = event
-      result &lt;&lt; tag.expand
-    end
-    result
-  end
+  tag &quot;events:month&quot; do |tag|
+    attr = parse_boolean_attributes(tag)
+    tag.locals.events ||= get_events(tag)
+    table_class = 'month'
+    
+    first_day = tag.locals.calendar_start ? tag.locals.calendar_start.beginning_of_month : tag.locals.period.start.beginning_of_month
+    first_shown = first_day.beginning_of_week     # padding of period to fill month table
+    last_day = first_day.end_of_month
+    last_shown = last_day.end_of_week
+    previous = first_day - 1.day
+    following = last_day + 1.day
 
-  # month, week and day tags 
-  # tabulate a period and show any events in the current set that fall into that period
+    month_names = Date::MONTHNAMES.dup
+    day_names = Date::DAYNAMES.dup
+    day_names.push(day_names.shift) # Class::Date and ActiveSupport::CoreExtensions::Time::Calculations have different ideas of when is the start of the week. We've gone for the rails standard.
+    
+    cal = %(&lt;table class=&quot;#{table_class}&quot;&gt;&lt;thead&gt;&lt;tr&gt;)
+    cal &lt;&lt; %(&lt;th colspan=&quot;2&quot; class=&quot;month_link&quot;&gt;&lt;a href=&quot;?year=#{previous.year}&amp;amp;month=#{previous.month}&quot;&gt;&amp;lt; #{month_names[previous.month]}&lt;/a&gt;&lt;/th&gt;) if attr[:month_links]
+    cal &lt;&lt; %(&lt;th colspan=&quot;#{attr[:month_links] ? 3 : 7}&quot; class=&quot;month_name&quot;&gt;#{month_names[first_day.month]} #{first_day.year}&lt;/th&gt;)
+    cal &lt;&lt; %(&lt;th colspan=&quot;2&quot; class=&quot;month_link&quot;&gt;&lt;a href=&quot;?year=#{following.year}&amp;amp;month=#{following.month}&quot;&gt;#{month_names[following.month]} &amp;gt;&lt;/a&gt;&lt;/th&gt;) if attr[:month_links]
+    cal &lt;&lt; %(&lt;/tr&gt;&lt;tr class=&quot;day_name&quot;&gt;)
+    cal &lt;&lt; day_names.map { |d| &quot;&lt;th scope='col'&gt;#{d}&lt;/th&gt;&quot; }.join
+    cal &lt;&lt; &quot;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&quot;
 
-  [:year, :month, :week, :day].each do |period|
-    desc %{ 
-      Shortcut tag that renders a #{period} view of the current calendar with all contained events.
-      On EventCalendar pages, these tags will obey relevant year, month, week and day request parameters.
-      Any other period tags are ignored.
+    first_shown.upto(last_shown) do |day|
+      events_today = tag.locals.events.select{ |e| e.start_date &lt;= day + 1.day &amp;&amp; e.end_date &gt;= day }
+      event_list = cell_text = date_label = &quot;&quot;
+      cell_class = &quot;day&quot;
+      cell_class += &quot; other_month&quot; if day.month != first_day.month
+      cell_class += &quot; weekend_day&quot; if weekend?(day)
+      cell_class += &quot; today&quot; if today?(day)
+      cell_class += &quot; weekend_today&quot; if weekend?(day) &amp;&amp; today?(day)
+      date_label = day.mday
+
+      if events_today.any?
+        cell_class += &quot; eventful&quot;
+        cell_class += &quot; eventful_weekend&quot; if weekend?(day)
+        cell_class += events_today.map{|e| &quot; #{e.calendar.slug}&quot;}.join
+        event_list &lt;&lt; %{&lt;ul&gt;} &lt;&lt; events_today.map { |e| %{&lt;li&gt;&lt;span class=&quot;time&quot;&gt;#{e.nice_start_time}:&lt;/span&gt; #{e.title}&lt;/li&gt;} }.join &lt;&lt; &quot;&lt;/ul&gt;&quot;
+      else
+        cell_class += &quot; uneventful&quot;
+      end
       
-      Usage:
-      &lt;pre&gt;&lt;code&gt;&lt;r:calendar:#{period} [year=&quot;&quot;] [month=&quot;&quot;] [week=&quot;&quot;] [day=&quot;&quot;] /&gt;&lt;/code&gt;&lt;/pre&gt; 
-    }
-  end
-
-  tag &quot;calendar:day&quot; do |tag|
-    tag.locals.period = period_from_parts(:day =&gt; tag.attr['day'], :month =&gt; tag.attr['month'], :year =&gt; tag.attr['year'])
-    tag.render(&quot;events:day&quot;)
-  end
-
-  tag &quot;calendar:month&quot; do |tag|
-    tag.locals.period = period_from_parts(:month =&gt; tag.attr['month'], :year =&gt; tag.attr['year'])
-    tag.render(&quot;events:month&quot;)
-  end
-
-  tag &quot;calendar:week&quot; do |tag|
-    tag.locals.period = period_from_parts(:week =&gt; tag.attr['week'], :year =&gt; tag.attr['year'])
-    tag.render(&quot;events:week&quot;)
-  end
-
-  tag &quot;calendar:year&quot; do |tag|
-    tag.locals.period = period_from_parts(:year =&gt; tag.attr['year'])
-    tag.render(&quot;events:year&quot;)
+      date_label = %{&lt;h4&gt;#{date_label}&lt;/h4&gt;}
+      cell_text = %{&lt;div class=&quot;event_holder&quot;&gt;#{date_label}#{event_list}&lt;/div&gt;}
+      cal &lt;&lt; &quot;&lt;tr&gt;&quot; if day == day.beginning_of_week
+      cal &lt;&lt; %{&lt;td class=&quot;#{cell_class}&quot;&gt;#{cell_text}&lt;/td&gt;}
+      cal &lt;&lt; &quot;&lt;/tr&gt;&quot; if day == day.end_of_week
+    end
+    cal &lt;&lt; %{&lt;/tbody&gt;&lt;/table&gt;}
+    cal
   end
   
+  
+  
+  
+  
+  
+  
+  
+  
+  
+  
+  
+  
   private
   
-    # set_period turns supplied attributes into a start and end point and returns a CalendarCalendarPeriod object
-    # and so defines most of the attribute interface
+    # parse_boolean_attributes turns &quot;true&quot; into true and everything else into false
+    # and incidentally symbolizes the keys
+    
+    def parse_boolean_attributes(tag)
+      attr = tag.attr.symbolize_keys
+      [:month_links, :event_links, :compact].each do |param|
+        attr[param] = false unless attr[param] == 'true'
+      end
+      attr
+    end
+  
+    # set_period turns supplied attributes into a start point and duration and returns a CalendarPeriod object
   
     def set_period(tag)
       attr = tag.attr.symbolize_keys
+      date_parts = [:year, :month, :week, :day]
+      interval_parts = [:months, :days, :since, :until, :from, :to]
+      relatives = {'previous' =&gt; -1, 'now' =&gt; 0, 'next' =&gt; 1}
+
+      logger.warn &quot;~~  set_period: attr is #{attr.inspect}&quot;
       
-      [:year, :month, :week, :day].each do |p|
-        case attr[p]
-        when 'now'
-          marker = Date.today
-        when 'next'
-          marker = Date.today + 1.send(p)
-        when 'previous'
-          marker = Date.today - 1.send(p)
-        end
+      # 1. fully specified period: any numeric date part found
+      #    not overridable
+
+      specific_date_parts = date_parts.select {|p| attr[p] &amp;&amp; attr[p] == attr[p].to_i.to_s}
+      return period_from_parts(attr.slice(*specific_date_parts)) if specific_date_parts.any?
+      
+      # 2. relative period: any relative date part specified and no numeric date part
+      #    overridable on an EventCalendarPage: input date parts have the effect of shifting now but leave the rest of the period calculation the same
+      #    this ought to be the most common case
+      
+      relative_date_parts = date_parts.select {|p| relatives.keys.include? attr[p]}
+      
+      logger.warn &quot;~~  set_period: date_parts is #{date_parts.inspect} and relatives.keys is #{relatives.keys.inspect} and relative_date_parts is #{relative_date_parts.inspect}&quot;
+      
+      if p = relative_date_parts.last    # if more than one - which there shouldn't be - we take the finest.
+        
+        # read date parts from tag and request
+        parts = attr.slice(*date_parts)
+        
+        if self.class == EventCalendarPage
+          params = @request.parameters.symbolize_keys
+        
+          logger.warn &quot;~~  set_period: params are #{params.inspect} and params sliced by #{date_parts.inspect} are #{params.slice(*date_parts).inspect}&quot;
         
-        # nb. containing periods might have rolled over
-        # so we must specify those too
-        if marker
-          case p
-          when :year
-            attr[:year] = marker.year
-          when :month
-            attr[:month] = marker.month
-            attr[:year] = marker.year
-          when :week
-            attr[:week] = marker.cweek
-            attr[:year] = marker.year
-          when :day
-            attr[:day] = marker.mday
-            attr[:month] = marker.month
-            attr[:year] = marker.year
-          end
+          parts.merge!( params.slice(*date_parts) )
         end
+
+        logger.warn &quot;~~  set_period: parts paramified are #{parts.inspect}&quot;
+
+        # replace any remaining magic words with the present date part
+        parts.each {|k,v| parts[k] = Date.today.send(k) unless parts[k].to_i.to_s == parts[k]}
+        
+        logger.warn &quot;~~  set_period: parts demagicated are #{parts.inspect}&quot;
+        
+        # params now holds an up to date mixture of tag attributes and input parameters
+        period = period_from_parts(parts)
+
+        logger.warn &quot;~~  set_period: period before shift is #{period}&quot;
+
+        # but the magic words are still in attr and are used to shift the period:
+        period += 1.send(p) if attr[p] == 'next'
+        period -= 1.send(p) if attr[p] == 'previous'
+
+        logger.warn &quot;~~  set_period: period after shift is #{period}&quot;
+
+        return period
       end
-            
-      # request parameters are accepted for attributes that have been specified in the tag
-      # so for a pageable calendar you would want to use &lt;r:events:month month=&quot;now&quot; /&gt; rather than relying on the default
+
+      # 3. relative interval
+      #    parameters specified in the tag can be overridden by input (on an EventCalendar page). Others are ignored.
       
-      if self.class == EventCalendarPage
-        params = @request.parameters.symbolize_keys
-        logger.warn &quot;!   reading request parameters&quot;
-        [:year, :month, :day, :years, :months, :days, :since, :until, :from, :to].select {|a| 
-          defined? attr[a]
-        }.each do |a| 
-          attr[a] = params[a] unless params[a].nil? || params[a] == 'default' || params[a] == '0'
+      specified_interval_parts = interval_parts.select {|p| !attr[p].blank?}
+      if specified_interval_parts.any?
+        parts = attr.slice(specified_interval_parts)
+        if self.class == EventCalendarPage
+          params = @request.parameters.symbolize_keys
+          parts.merge(params.slice(interval_parts))
         end
+        return period_from_interval(parts)
       end
-
-      period_from_interval(attr) || period_from_duration(attr) || period_from_parts(attr)
+      
+      # default is the present month
+      period_from_parts
     end
     
-    def period_from_interval(int={})
-      return CalendarPeriod.new(Time.now, int[:until].to_date) if int[:until]
-      return CalendarPeriod.new(int[:since].to_date, Time.now) if int[:since]
-      return CalendarPeriod.new(int[:from].to_date, int[:to].to_date) if int[:from] &amp;&amp; int[:to]
-      nil
+    def date_from_input
+      return unless self.class == EventCalendarPage
+      params = @request.parameters.symbolize_keys
+      Date.civil(params[:year], params[:month], params[:day])
     end
     
-    def period_from_duration(dur={})
-      dur[:from] ||= Date.today
-      if dur[:calendar_months]
-        from = dur[:from].beginning_of_month
-        to = from + dur[:calendar_months].to_i.months
-        return CalendarPeriod.new(from, to - 1.day)   # -1 to bring us to the end of the last month rather than the first of the one after
-      end
-      return CalendarPeriod.new(dur[:from], dur[:from] + dur[:years].to_i.years) if dur[:years]
-      return CalendarPeriod.new(dur[:from], dur[:from] + dur[:months].to_i.months) if dur[:months]
-      return CalendarPeriod.new(dur[:from], dur[:from] + dur[:weeks].to_i.days) if dur[:weeks]
-      return CalendarPeriod.new(dur[:from], dur[:from] + dur[:days].to_i.days) if dur[:days]
-      nil
-    end
     
     def period_from_parts(parts={})
-      return CalendarPeriod.new(Date.civil(parts[:year], 1, 1), Date.civil(parts[:year], -1, -1)) if parts[:year] &amp;&amp; !parts[:month]
+      parts.each {|k,v| parts[k] = parts[k].to_i}
+      
+      logger.warn &quot;~~  period_from_parts(#{parts.inspect})&quot;
+      return CalendarPeriod.new(Date.civil(parts[:year], 1, 1), 1.year) if parts[:year] and not parts[:month]
       parts[:year] ||= Date.today.year
+      return CalendarPeriod.new(Date.civil(parts[:year], parts[:month], 1), 1.month) if parts[:month] &amp;&amp; !parts[:week] &amp;&amp; !parts[:day]
       parts[:month] ||= Date.today.month
-      if parts[:day]
-        day = Date.civil(parts[:year], parts[:month], parts[:day])
-        return CalendarPeriod.new(day, day+1) 
-      end
-      return CalendarPeriod.new(Date.commercial(parts[:year], parts[:week], 1), Date.commercial(parts[:year], parts[:week], -1)) if parts[:week]
-      return CalendarPeriod.new(Date.civil(parts[:year], parts[:month], 1))
+      return CalendarPeriod.new(Date.commercial(parts[:year], parts[:week], 1), 1.week ) if parts[:week]
+      return CalendarPeriod.new(Date.civil(parts[:year], parts[:month], parts[:day]), 1.day) if parts[:day]
+      # if all defaults apply, you get the present month
+      return CalendarPeriod.new(Date.civil(parts[:year], parts[:month], 1), 1.month)
     end
     
+    def period_from_interval(parts={})
+      
+      logger.warn &quot;~~  period_from_interval(#{parts.inspect})&quot;
+      
+      # starting point defaults to now
+      parts[:from] ||= Date.today
+ 
+      # from and to fully specified (including since and until as they are always relative to the present)
+      return CalendarPeriod.between(Time.now, parts[:until].to_date) if parts[:until]
+      return CalendarPeriod.between(parts[:since].to_date, Time.now) if parts[:since]
+      return CalendarPeriod.between(parts[:from].to_date, parts[:to].to_date) if parts[:from] &amp;&amp; parts[:to]
+
+      # start moves to the first of the month if we're displaying calendar months
+      return CalendarPeriod.new(parts[:from].beginning_of_month, parts[:calendar_months].to_i.months) if parts[:calendar_months]
+      
+      # and in the end it's just a question of how much of the future to show
+      return CalendarPeriod.new(parts[:from], parts[:years].to_i.years) if parts[:years]
+      return CalendarPeriod.new(parts[:from], parts[:months].to_i.months) if parts[:months]
+      return CalendarPeriod.new(parts[:from], parts[:weeks].to_i.weeks) if parts[:weeks]
+      return CalendarPeriod.new(parts[:from], parts[:days].to_i.days) if parts[:days]  
+    end
+
+
+
+
+
+
+
+        
     def set_calendars(tag)
       attr = tag.attr.symbolize_keys
       if tag.locals.calendar  # either we're in a calendar:* shortcut or we're eaching calendars. either way, it's set for us and parameters have no effect
@@ -651,10 +745,16 @@ module EventCalendarTags
     end
     
     def get_events(tag)
+      
+      logger.warn &quot;~~ get_events: attr will be #{tag.attr.inspect}&quot;
+      
       Ical.check_refreshments
       tag.locals.period ||= set_period(tag)
+      
+      logger.warn &quot;~~ get_events: tag.locals.period is #{tag.locals.period.inspect}&quot;
+      
       tag.locals.calendars ||= set_calendars(tag)
-      event_finder = Event.between(tag.locals.period.begin_date, tag.locals.period.end_date)
+      event_finder = Event.between(tag.locals.period.start, tag.locals.period.finish)
       event_finder = event_finder.in_calendars(tag.locals.calendars) if tag.locals.calendars
 
       tag.attr[:by] ||= 'start_date'</diff>
      <filename>lib/event_calendar_tags.rb</filename>
    </modified>
  </modified>
  <removed type="array">
    <removed>
      <filename>app/views/admin/calendars/edit.html.erb</filename>
    </removed>
    <removed>
      <filename>app/views/admin/calendars/new.html.erb</filename>
    </removed>
    <removed>
      <filename>app/views/admin/icals/_edit.html.erb</filename>
    </removed>
    <removed>
      <filename>app/views/admin/icals/refresh.html.erb</filename>
    </removed>
    <removed>
      <filename>app/views/admin/icals/refresh_all.html.erb</filename>
    </removed>
  </removed>
  <parents type="array">
    <parent>
      <id>329cf1d5179fbc29ed6e424a84ee4d8958d4a350</id>
    </parent>
  </parents>
  <author>
    <name>will</name>
    <email>will@spanner.org</email>
  </author>
  <url>http://github.com/radiant/radiant-event-calendar-extension/commit/3d7dc750b9e8ed29d52cec35e3a3f21072a07d79</url>
  <id>3d7dc750b9e8ed29d52cec35e3a3f21072a07d79</id>
  <committed-date>2009-08-19T05:58:39-07:00</committed-date>
  <authored-date>2009-08-19T05:58:39-07:00</authored-date>
  <message>CalendarPeriod rewritten. tags clarified. first few tests. ical columns changed so migration required</message>
  <tree>1d0e01355937f767dc35d3a399e44dab28ce83f7</tree>
  <committer>
    <name>will</name>
    <email>will@spanner.org</email>
  </committer>
</commit>
