class Tilia::VObject::Component::VCalendar
The VCalendar
component.
This component adds functionality to a component, specific for a VCALENDAR.
Public Instance Methods
Returns the first component that is not a VTIMEZONE, and does not have an RECURRENCE-ID.
If there is no such component, null will be returned.
@param [String] component_name filter by component name
@return [Component, nil]
# File lib/tilia/v_object/component/v_calendar.rb, line 185 def base_component(component_name = nil) is_base_component = lambda do |component| return false unless component.is_a?(Component) return false if component.name == 'VTIMEZONE' return false if component.key?('RECURRENCE-ID') true end if component_name select(component_name).each do |child| return child if is_base_component.call(child) end return nil end children.each do |child_group| child_group.each do |child| return child if is_base_component.call(child) end end nil end
Returns a list of all 'base components'. For instance, if an Event has a recurrence rule, and one instance is overridden, the overridden event will have the same UID, but will be excluded from this list.
VTIMEZONE components will always be excluded.
@param [String] component_name filter by component name
@return [Array<Component>]
# File lib/tilia/v_object/component/v_calendar.rb, line 146 def base_components(component_name = nil) is_base_component = lambda do |component| return false unless component.is_a?(Component) return false if component.name == 'VTIMEZONE' return false if component.key?('RECURRENCE-ID') true end if component_name # Early exit return select(component_name).select is_base_component end components = [] children.each do |child_group| do_skip = false child_group.each do |child| unless child.is_a?(Component) # If one child is not a component, they all are so we skip # the entire group. do_skip = true break end components << child if is_base_component.call(child) end next if do_skip end components end
Returns all components with a specific UID value.
@return [array]
# File lib/tilia/v_object/component/v_calendar.rb, line 422 def by_uid(uid) components.select do |item| item_uid = item.select('UID') if item_uid.empty? false else item_uid = item_uid.first.value uid == item_uid end end end
Returns the current document type.
@return [Fixnum]
# File lib/tilia/v_object/component/v_calendar.rb, line 133 def document_type ICALENDAR20 end
Expand all events in this VCalendar
object and return a new VCalendar
with the expanded events.
If this calendar object, has events with recurrence rules, this method can be used to expand the event into multiple sub-events.
Each event will be stripped from it's recurrence information, and only the instances of the event in the specified timerange will be left alone.
In addition, this method will cause timezone information to be stripped, and normalized to UTC.
@param [Time] start @param [Time] end @param [ActiveSupport::TimeZone, nil] time_zone reference timezone for floating dates and
times.
@return [VCalendar]
# File lib/tilia/v_object/component/v_calendar.rb, line 228 def expand(start, ending, time_zone = nil) new_children = [] recurring_events = {} time_zone = ActiveSupport::TimeZone.new('UTC') unless time_zone strip_timezones = lambda do |component| component.children.each do |component_child| if component_child.is_a?(Property::ICalendar::DateTime) && component_child.time? dt = component_child.date_times(time_zone) # We only need to update the first timezone, because # setDateTimes will match all other timezones to the # first. dt[0] = dt[0].in_time_zone(ActiveSupport::TimeZone.new('UTC')) component_child.date_times = dt elsif component_child.is_a?(Component) strip_timezones.call(component_child) end end component end children.each do |child| if child.is_a?(Property) && child.name != 'PRODID' # We explictly want to ignore PRODID, because we want to # overwrite it with our own. new_children << child.clone elsif child.is_a?(Component) && child.name != 'VTIMEZONE' # We're also stripping all VTIMEZONE objects because we're # converting everything to UTC. if child.name == 'VEVENT' && (child.key?('RECURRENCE-ID') || child.key?('RRULE') || child.key?('RDATE')) # Handle these a bit later. uid = child['UID'].to_s fail InvalidDataException, 'Every VEVENT object must have a UID property' if uid.blank? if recurring_events.key?(uid) recurring_events[uid] << child.clone else recurring_events[uid] = [child.clone] end elsif child.name == 'VEVENT' && child.in_time_range?(start, ending) new_children << strip_timezones.call(child.clone) end end end recurring_events.each do |_uid, events| begin it = Recur::EventIterator.new(events, time_zone) rescue Recur::NoInstancesException # This event is recurring, but it doesn't have a single # instance. We are skipping this event from the output # entirely. next end it.fast_forward(start) while it.valid && it.dt_start < ending new_children << strip_timezones.call(it.event_object) if it.dt_end > start it.next end end self.class.new(new_children) end
(see Component#validate
)
# File lib/tilia/v_object/component/v_calendar.rb, line 326 def validate(options = 0) warnings = super(options) ver = self['VERSION'] if ver unless ver.to_s == '2.0' warnings << { 'level' => 3, 'message' => 'Only iCalendar version 2.0 as defined in rfc5545 is supported.', 'node' => self } end end uid_list = {} components_found = 0 component_types = [] children.each do |child| next unless child.is_a?(Component) components_found += 1 next unless ['VEVENT', 'VTODO', 'VJOURNAL'].include?(child.name) component_types << child.name uid = child['UID'].to_s is_master = child.key?('RECURRENCE-ID') ? 0 : 1 if uid_list.key?(uid) uid_list[uid]['count'] += 1 if is_master == 1 && uid_list[uid]['hasMaster'] > 0 warnings << { 'level' => 3, 'message' => "More than one master object was found for the object with UID #{uid}", 'node' => self } end uid_list[uid]['hasMaster'] += is_master else uid_list[uid] = { 'count' => 1, 'hasMaster' => is_master } end end if components_found == 0 warnings << { 'level' => 3, 'message' => 'An iCalendar object must have at least 1 component.', 'node' => self } end if options & PROFILE_CALDAV > 0 if uid_list.size > 1 warnings << { 'level' => 3, 'message' => 'A calendar object on a CalDAV server may only have components with the same UID.', 'node' => self } end if component_types.size == 0 warnings << { 'level' => 3, 'message' => 'A calendar object on a CalDAV server must have at least 1 component (VTODO, VEVENT, VJOURNAL).', 'node' => self } end if component_types.uniq.size > 1 warnings << { 'level' => 3, 'message' => 'A calendar object on a CalDAV server may only have 1 type of component (VEVENT, VTODO or VJOURNAL).', 'node' => self } end if key?('METHOD') warnings << { 'level' => 3, 'message' => 'A calendar object on a CalDAV server MUST NOT have a METHOD property.', 'node' => self } end end warnings end
(see Component#validation_rules
)
# File lib/tilia/v_object/component/v_calendar.rb, line 315 def validation_rules { 'PRODID' => 1, 'VERSION' => 1, 'CALSCALE' => '?', 'METHOD' => '?' } end
Protected Instance Methods
This method should return a list of default property values.
@return [Hash]
# File lib/tilia/v_object/component/v_calendar.rb, line 304 def defaults { 'VERSION' => '2.0', 'PRODID' => "-//Tilia//Tilia VObject #{Version::VERSION}//EN", 'CALSCALE' => 'GREGORIAN' } end