class AddToCalendarLinks::URLs

Attributes

add_url_to_description[RW]
description[RW]
end_datetime[RW]
last_modified[RW]
location[RW]
organizer[RW]
sequence[RW]
start_datetime[RW]
strip_html[RW]
timezone[RW]
title[RW]
uid[RW]
url[RW]

Public Class Methods

new(start_datetime:, end_datetime: nil, title:, timezone:, location: nil, url: nil, description: nil, add_url_to_description: true, organizer: nil, strip_html: false, sequence: nil, last_modified: Time.now.utc, uid:) click to toggle source
# File lib/add_to_calendar_links.rb, line 16
def initialize(start_datetime:, end_datetime: nil, title:, timezone:, location: nil, url: nil, description: nil, add_url_to_description: true, organizer: nil, strip_html: false, sequence: nil, last_modified: Time.now.utc, uid:)
  @start_datetime = start_datetime
  @end_datetime = end_datetime
  @title = title
  @timezone = TZInfo::Timezone.get(timezone)
  @location = location
  @url = url
  @description = description
  @add_url_to_description = add_url_to_description
  @organizer = organizer if organizer
  @strip_html = strip_html
  @sequence = sequence
  @uid = uid
  @last_modified = last_modified
  validate_attributes
end

Public Instance Methods

android_url() click to toggle source
# File lib/add_to_calendar_links.rb, line 204
def android_url
  ical_url
end
apple_url() click to toggle source
# File lib/add_to_calendar_links.rb, line 196
def apple_url
  ical_url
end
google_url() click to toggle source
# File lib/add_to_calendar_links.rb, line 33
def google_url
  # Eg. https://www.google.com/calendar/render?action=TEMPLATE&text=Holly%27s%208th%20Birthday!&dates=20200615T180000/20200615T190000&ctz=Europe/London&details=Join%20us%20to%20celebrate%20with%20lots%20of%20games%20and%20cake!&location=Apartments,%20London&sprop=&sprop=name:
  calendar_url = "https://www.google.com/calendar/render?action=TEMPLATE"
  params = {}
  params[:text] = url_encode(title)
  if end_datetime
    params[:dates] = "#{format_date_google(start_datetime)}/#{format_date_google(end_datetime)}"
  else
    params[:dates] = "#{format_date_google(start_datetime)}/#{format_date_google(start_datetime + 60*60)}" # end time is 1 hour later
  end
  params[:ctz] = timezone.identifier
  params[:location] = url_encode(location) if location
  params[:details] = url_encode(description) if description
  if add_url_to_description && url
    if params[:details]
      params[:details] << url_encode("\n\n#{url}")
    else
      params[:details] = url_encode(url)
    end
  end
  
  params.each do |key, value|
    calendar_url << "&#{key}=#{value}"
  end
  
  return calendar_url
end
ical_file() click to toggle source
# File lib/add_to_calendar_links.rb, line 103
def ical_file
  calendar_url = "BEGIN:VCALENDAR\nVERSION:2.0\nMETHOD:REQUEST\nBEGIN:VEVENT"
  
  params = {}
  params[:DTSTART] = utc_datetime(start_datetime)
  if end_datetime
    params[:DTEND] = utc_datetime(end_datetime)
  else
    params[:DTEND] = utc_datetime(start_datetime + 60*60) # 1 hour later
  end
  params[:SUMMARY] = strip_html_tags(title) #ical doesnt support html so remove all markup. Optional for other formats
  params[:URL] = url if url
  description.gsub!(/\n/, "\\n")
  params[:DESCRIPTION] = strip_html_tags(description).strip if description
  if add_url_to_description && url
    if params[:DESCRIPTION]
      params[:DESCRIPTION] << "\\n\\n#{url}"
    else
      params[:DESCRIPTION] = url
    end
  end
  params[:LOCATION] = strip_html_tags(location) if location
  if uid
    params[:UID] = uid
  else
    params[:UID] = "-#{urlc}" if url
    params[:UID] = "-#{utc_datetime(start_datetime)}-#{title}" unless params[:UID] # set uid based on starttime and title only if url is unavailable
  end
  params[:ORGANIZER] = organizer if organizer
  params[:SEQUENCE] = sequence if sequence
  params["LAST-MODIFIED"] = format_date_google(last_modified) if last_modified
  params[:METHOD] = "REQUEST"

  params.each do |key, value|
    if key == :ORGANIZER
      calendar_url << "\n#{key}#{value}"
    else
      calendar_url << "\n#{key}:#{value}"
    end
  end

  calendar_url << "\nEND:VEVENT\nEND:VCALENDAR"

  return calendar_url
end
ical_url() click to toggle source
# File lib/add_to_calendar_links.rb, line 149
def ical_url
  # Downloads a *.ics file provided as a data-uri
  # Eg. "data:text/calendar;charset=utf8,BEGIN:VCALENDAR%0AVERSION:2.0%0ABEGIN:VEVENT%0ADTSTART:20200512T123000Z%0ADTEND:20200512T160000Z%0ASUMMARY:Holly%27s%208th%20Birthday%21%0AURL:https%3A%2F%2Fwww.example.com%2Fevent-details%0ADESCRIPTION:Come%20join%20us%20for%20lots%20of%20fun%20%26%20cake%21\\n\\nhttps%3A%2F%2Fwww.example.com%2Fevent-details%0ALOCATION:Flat%204%5C%2C%20The%20Edge%5C%2C%2038%20Smith-Dorrien%20St%5C%2C%20London%5C%2C%20N1%207GU%0AUID:-https%3A%2F%2Fwww.example.com%2Fevent-details%0AEND:VEVENT%0AEND:VCALENDAR"
  calendar_url = "data:text/calendar;charset=utf8,BEGIN:VCALENDAR%0AVERSION:2.0%0AMETHOD:REQUEST%0ABEGIN:VEVENT"

  params = {}
  params[:DTSTART] = utc_datetime(start_datetime)
  if end_datetime
    params[:DTEND] = utc_datetime(end_datetime)
  else
    params[:DTEND] = utc_datetime(start_datetime + 60*60) # 1 hour later
  end
  params[:SUMMARY] = url_encode_ical(title, strip_html: true) #ical doesnt support html so remove all markup. Optional for other formats
  params[:URL] = url_encode(url) if url
  params[:DESCRIPTION] = url_encode_ical(strip_html_tags(description, line_break_seperator: "\n")) if description
  if add_url_to_description && url
    if params[:DESCRIPTION]
      params[:DESCRIPTION] << "\\n\\n#{url_encode(url)}"
    else
      params[:DESCRIPTION] = url_encode(url)
    end
  end
  params[:LOCATION] = url_encode_ical(location) if location
  if uid
    params[:UID] = uid
  else
    params[:UID] = "-#{url_encode(url)}" if url
    params[:UID] = "-#{utc_datetime(start_datetime)}-#{url_encode_ical(title)}" unless params[:UID] # set uid based on starttime and title only if url is unavailable
  end
  params[:ORGANIZER] = organizer if organizer
  params[:SEQUENCE] = sequence if sequence
  params["LAST-MODIFIED"] = format_date_google(last_modified) if last_modified

  new_line = "%0A"
  params.each do |key, value|
    if key == :ORGANIZER
      calendar_url << "\n#{key}#{value}"
    else
      calendar_url << "\n#{key}:#{value}"
    end
  end

  calendar_url << "%0AEND:VEVENT%0AEND:VCALENDAR"

  return calendar_url
end
office365_url() click to toggle source
# File lib/add_to_calendar_links.rb, line 93
def office365_url
  # Eg. https://outlook.live.com/calendar/0/deeplink/compose?path=/calendar/action/compose&rru=addevent&subject=Holly%27s%208th%20Birthday%21&startdt=2020-05-12T12:30:00Z&enddt=2020-05-12T16:00:00Z&body=Come%20join%20us%20for%20lots%20of%20fun%20%26%20cake%21%0A%0Ahttps%3A%2F%2Fwww.example.com%2Fevent-details&location=Flat%204%2C%20The%20Edge%2C%2038%20Smith-Dorrien%20St%2C%20London%2C%20N1%207GU
  microsoft("office365")
end
outlook_com_url() click to toggle source
# File lib/add_to_calendar_links.rb, line 98
def outlook_com_url
  # Eg. https://outlook.live.com/calendar/0/deeplink/compose?path=/calendar/action/compose&rru=addevent&subject=Holly%27s%208th%20Birthday%21&startdt=2020-05-12T12:30:00Z&enddt=2020-05-12T16:00:00Z&body=Come%20join%20us%20for%20lots%20of%20fun%20%26%20cake%21%0A%0Ahttps%3A%2F%2Fwww.example.com%2Fevent-details&location=Flat%204%2C%20The%20Edge%2C%2038%20Smith-Dorrien%20St%2C%20London%2C%20N1%207GU
  microsoft("outlook.com")
end
outlook_url() click to toggle source
# File lib/add_to_calendar_links.rb, line 200
def outlook_url
  ical_url
end
yahoo_url() click to toggle source
# File lib/add_to_calendar_links.rb, line 61
def yahoo_url
  # Eg. https://calendar.yahoo.com/?v=60&view=d&type=20&title=Holly%27s%208th%20Birthday!&st=20200615T170000Z&dur=0100&desc=Join%20us%20to%20celebrate%20with%20lots%20of%20games%20and%20cake!&in_loc=7%20Apartments,%20London
  calendar_url = "https://calendar.yahoo.com/?v=60&VIEW=d&TYPE=20"
  params = {}
  params[:TITLE] = url_encode(title)
  params[:ST] = utc_datetime(start_datetime)
  if end_datetime
    # params[:ET] = utc_datetime(end_datetime)
    seconds = duration_seconds(start_datetime, end_datetime)
    params[:DUR] = seconds_to_hours_minutes(seconds)
  else
    # params[:ET] = utc_datetime(start_datetime + 60*60)
    params[:DUR] = "0100" 
  end
  params[:DESC] = url_encode(strip_html_tags(description)).truncate(3000) if description

  if add_url_to_description && url
    if params[:DESC]
      params[:DESC] << url_encode("\n\n#{url}")
    else
      params[:DESC] = url_encode(url)
    end
  end
  params[:IN_LOC] = url_encode(location) if location

  params.each do |key, value|
    calendar_url << "&#{key}=#{value}"
  end
  
  return calendar_url
end

Private Instance Methods

duration_seconds(start_time, end_time) click to toggle source
# File lib/add_to_calendar_links.rb, line 304
def duration_seconds(start_time, end_time)
  (start_time.to_i - end_time.to_i).abs
end
format_date_google(start_datetime) click to toggle source
# File lib/add_to_calendar_links.rb, line 300
def format_date_google(start_datetime)
  start_datetime.strftime('%Y%m%dT%H%M%S')
end
microsoft(service) click to toggle source
# File lib/add_to_calendar_links.rb, line 234
def microsoft(service)
  # Eg.
  calendar_url = case service
  when "outlook.com"
    "https://outlook.live.com/calendar/0/deeplink/compose?path=/calendar/action/compose&rru=addevent"
  when "office365"
    "https://outlook.office.com/calendar/0/deeplink/compose?path=/calendar/action/compose&rru=addevent"
  else
    raise MicrosoftServiceError, ":service must be 'outlook.com' or 'office365'. '#{service}' given"
  end

  params = {}
  params[:subject] = url_encode(title.gsub(' & ', ' and '))
  params[:startdt] = utc_datetime_microsoft(start_datetime)
  if end_datetime
    params[:enddt] = utc_datetime_microsoft(end_datetime)
  else
    params[:enddt] = utc_datetime_microsoft(start_datetime + 60*60) # 1 hour later
  end
  params[:body] = url_encode(newlines_to_html_br(description)) if description
  if add_url_to_description && url
    if params[:body]
      params[:body] << url_encode(newlines_to_html_br("\n\n#{url}"))
    else
      params[:body] = url_encode(url)
    end
  end
  params[:location] = url_encode(location) if location
  
  params.each do |key, value|
    calendar_url << "&#{key}=#{value}"
  end
    
  return calendar_url
end
newlines_to_html_br(string) click to toggle source
# File lib/add_to_calendar_links.rb, line 312
def newlines_to_html_br(string)
  string.gsub(/(?:\n\r?|\r\n?)/, '<br>')
end
seconds_to_hours_minutes(sec) click to toggle source
# File lib/add_to_calendar_links.rb, line 308
def seconds_to_hours_minutes(sec)
  "%02d%02d" % [sec / 3600, sec / 60 % 60]
end
strip_html_tags(description, line_break_seperator: "\\n") click to toggle source
# File lib/add_to_calendar_links.rb, line 333
def strip_html_tags(description, line_break_seperator: "\\n")
  string = description.dup
  string.gsub!("<br>", line_break_seperator)
  string.gsub!("<p>", line_break_seperator)
  string.gsub!("</p>", line_break_seperator)
  string.gsub!("&amp;", "and")
  string.gsub!("&nbsp;", " ")
  string.gsub!(/<\/?[^>]*>/, "")
  string.gsub!(/(\\n){2,}/, "\\n\\n")
  string.strip
end
url_encode_ical(s, strip_html: @strip_html) click to toggle source
# File lib/add_to_calendar_links.rb, line 316
def url_encode_ical(s, strip_html: @strip_html)
  # per https://tools.ietf.org/html/rfc5545#section-3.3.11
  string = s.dup # don't modify original input
  if strip_html
    string = strip_html_tags(string)
  end
  string.gsub!("\\", "\\\\\\") # \ >> \\     --yes, really: https://stackoverflow.com/questions/6209480/how-to-replace-backslash-with-double-backslash
  string.gsub!("\r\n", "\n") # so can handle all newlines the same
  string.split("\n").map { |e|
    if e.empty?
      e
    else
      url_encode(e)
    end
  }.compact.join("\\n").gsub(/(\\n){2,}/, "\\n\\n").strip
end
utc_datetime(datetime) click to toggle source
# File lib/add_to_calendar_links.rb, line 270
def utc_datetime(datetime)
  t = timezone.local_to_utc(
    Time.new(
      datetime.strftime("%Y").to_i, 
      datetime.strftime("%m").to_i, 
      datetime.strftime("%d").to_i, 
      datetime.strftime("%H").to_i, 
      datetime.strftime("%M").to_i, 
      datetime.strftime("%S").to_i
    )
  )

  return t.strftime('%Y%m%dT%H%M%SZ')
end
utc_datetime_microsoft(datetime) click to toggle source
# File lib/add_to_calendar_links.rb, line 285
def utc_datetime_microsoft(datetime)
  t = timezone.local_to_utc(
    Time.new(
      datetime.strftime("%Y").to_i, 
      datetime.strftime("%m").to_i, 
      datetime.strftime("%d").to_i, 
      datetime.strftime("%H").to_i, 
      datetime.strftime("%M").to_i, 
      datetime.strftime("%S").to_i
    )
  )

  return t.strftime('%Y-%m-%dT%H:%M:%SZ')
end
validate_attributes() click to toggle source
# File lib/add_to_calendar_links.rb, line 209
def validate_attributes
  # msg =  "- Object must be a DateTime or Time object."
  msg =  "- Object must be a Time object."
  raise(ArgumentError, ":start_datetime #{msg} #{start_datetime.class} given") unless start_datetime.kind_of? Time
  if end_datetime
    raise(ArgumentError, ":end_datetime #{msg} #{end_datetime.class} given") unless end_datetime.kind_of? Time
    raise(ArgumentError, ":end_datetime must be greater than :start_datetime") unless end_datetime > start_datetime
  end
  
  raise(ArgumentError, ":title must be a string") unless self.title.kind_of? String
  raise(ArgumentError, ":title must not be blank") if self.title.strip.empty? # strip first, otherwise " ".empty? #=> false

  if location
    raise(ArgumentError, ":location must be a string") unless self.location.kind_of? String
  end

  if description
    raise(ArgumentError, ":description must be a string") unless self.description.kind_of? String
  end

  if organizer
    raise(ArgumentError, ":organizer must be a string") unless self.organizer.kind_of? String
  end
end