calendar_generator_controller.rb 3.17 KB
Newer Older
Zac Wood's avatar
Zac Wood committed
1 2 3
require 'icalendar'
require 'time'

4
# Contains functionality for generating schedules.
5
class CalendarGeneratorController < ApplicationController
6 7 8
  # Render an iCal file containing the schedules of all the 
  # course sections with the given CRNs.
  def new
Zac Wood's avatar
Zac Wood committed
9
    cal = Icalendar::Calendar.new
10 11

    # the intended format for the json is a list of CRNs
12
    params[:_json].each do |crn| # for each CRN sent by the post request
Zac Wood's avatar
Zac Wood committed
13
      section = CourseSection.find_by_crn(crn)
Zac Wood's avatar
Zac Wood committed
14 15 16 17
      event = generate_event_from_section(section)
      cal.add_event(event)
    end

18
    render plain: cal.to_ical # render a plaintext iCal file
Zac Wood's avatar
Zac Wood committed
19 20 21 22
  end

  private

23
  # Configures a calendar event from a given section
Zac Wood's avatar
Zac Wood committed
24
  # @param section [CourseSection]
Zac Wood's avatar
Zac Wood committed
25 26
  def generate_event_from_section(section)
    event = Icalendar::Event.new
Zac Wood's avatar
Zac Wood committed
27

Zac Wood's avatar
Zac Wood committed
28 29
    event.summary = section.name
    event.description = section.title
Zac Wood's avatar
Zac Wood committed
30
    event.location = section.location
Zac Wood's avatar
Zac Wood committed
31 32
    event.dtstart = Icalendar::Values::DateTime.new(formatted_datetime_str(section.start_date, section.start_time))
    event.dtend = Icalendar::Values::DateTime.new(formatted_datetime_str(section.start_date, section.end_time))
Zac Wood's avatar
Zac Wood committed
33
    event.rrule = Icalendar::Values::Recur.new(recurrence_rule_str(section))
Zac Wood's avatar
Zac Wood committed
34
    event.exdate = exdates_for_section(section)
Zac Wood's avatar
Zac Wood committed
35 36 37 38

    event
  end

39 40 41 42
  # Format a DateTime string based on a given date and time
  # @param date [String]
  # @param time [String]
  # @return [String]
Zac Wood's avatar
Zac Wood committed
43 44 45
  def formatted_datetime_str(date, time)
    formatted_date = date.to_s.tr('-', '')
    formatted_time = Time.parse(time).strftime("%H%M%S")
Zac Wood's avatar
Zac Wood committed
46

Zac Wood's avatar
Zac Wood committed
47 48 49
    "#{formatted_date}T#{formatted_time}"
  end

50
  # Mapping of days as represented by GMU to the iCal standard
Zac Wood's avatar
Zac Wood committed
51 52 53 54 55 56 57 58 59 60
  DAYS = {
    "M" => "MO",
    "T" => "TU",
    "W" => "WE",
    "R" => "TH",
    "F" => "FR",
    "S" => "SA",
    "U" => "SU"
  }.freeze

61 62
  # Generates a recurrence rule string descripting which day the class event
  # should take place on
Zac Wood's avatar
Zac Wood committed
63
  # @param section [CourseSection]
64
  # @return [String]
Zac Wood's avatar
Zac Wood committed
65
  def recurrence_rule_str(section)
Zac Wood's avatar
Zac Wood committed
66 67 68 69 70 71
    days = section.days.split("").map do |day|
      DAYS[day]
    end

    "FREQ=WEEKLY;UNTIL=#{formatted_datetime_str(section.end_date, section.end_time)};BYDAY=#{days.join(',')}"
  end
Zac Wood's avatar
Zac Wood committed
72

73
  # Get all dates that should excluded from the schedule
Zac Wood's avatar
Zac Wood committed
74
  # @param section [CourseSection]
75
  # @return [Array]
Zac Wood's avatar
Zac Wood committed
76
  def exdates_for_section(section)
77 78
    # Generate exdates for all closures in a semester
    exdates = Closure.where(semester: section.course.semester).map { |closure| 
Zac Wood's avatar
Zac Wood committed
79
      generate_exdate(closure.date.to_formatted_s(:number), section.start_time)
Zac Wood's avatar
Zac Wood committed
80
    }
Zac Wood's avatar
Zac Wood committed
81 82 83

    # Every section's start_date is the first Monday of the semester.
    # So we need to add an exclusion for that day unless the class is held on Mondays
Zac Wood's avatar
Zac Wood committed
84 85 86
    unless section.days.start_with? "M"
      exdates << generate_exdate(
        section.start_date.to_formatted_s(:number),
Zac Wood's avatar
Zac Wood committed
87
        section.start_time
Zac Wood's avatar
Zac Wood committed
88 89
      )
    end
Zac Wood's avatar
Zac Wood committed
90 91

    exdates
Zac Wood's avatar
Zac Wood committed
92 93
  end

94 95 96 97
  # Generate a DataTime to use as an exdate
  # @param date [String]
  # @param time [String]
  # @return [Icalendar::Values::DateTime]
Zac Wood's avatar
Zac Wood committed
98
  def generate_exdate(date, time)
99
    # format the time for use in a DateTime
Zac Wood's avatar
Zac Wood committed
100 101 102
    formatted_time = Time.parse(time).strftime("%H%M%S")
    Icalendar::Values::DateTime.new("#{date}T#{formatted_time}")
  end
103
end