A custom iterator implementation to help rendering a calendar

I recently found myself implementing a calendar view and bemoaning the various complexities of doing so. Rendering the first day in the middle of the week is one constraint I found I could abstract out with a little custom iterator in my presenter class. A custom iterator might be a bit of an over-reach for this application, but I’m satisfied with the result.

It came out like this:

module Presenter
  class BroadcastCalendar
    def initialize(@first_of_month : Time)
    end

    def days_of_the_month
      RenderableMonthIterator.new(@first_of_month)
    end

    def in_month?(date : Time) : Bool
      date.month == @first_of_month.month
    end

    def month_name : String
      @first_of_month.to_s "%Y %B"
    end
  end

  # Iterates the days of the month, and including the "padding" days both
  # before the start of the month and after the end which are needed to
  # visually render a calendar.
  class RenderableMonthIterator
    include Iterator(Time)

    def initialize(@first_of_month : Time)
      @current_day = @first_of_month.at_beginning_of_week
      @last_day = @first_of_month.at_end_of_month.at_end_of_week
      @first = true
    end

    def next
      if @first
        @first = false
        return @current_day
      end

      @current_day = @current_day + 1.day
      if @current_day > @last_day
        stop
      else
        @current_day
      end
    end
  end
end

I haven’t traditionally found a use for a custom iterator, but I’ve found a use for them twice in the last month (the other, a bit more traditional on @jgaskins aws/s3 shard). This could have been implemented within the display logic, but calendar display logic is already so complicated – html is verbose, css is verbose, and rendering data itself is verbose. I figure anything I can do to abstract out a component of that logic into another file is worth it.

I wrote some more notes about it over on my website, but I won’t paste another 50 lines of code into this post.

Anyway, cheers! Hope you find this as interesting as I found it satisfying.

4 Likes

I can’t remember if you can use a range of Time and using the step method, so you could do (beginning…ending).step(by: 1.day)

2 Likes