icalendar-2.4.1/0000755000175100017510000000000013317103517012467 5ustar pravipraviicalendar-2.4.1/Rakefile0000644000175100017510000000054013317103517014133 0ustar pravipravirequire 'bundler' Bundler::GemHelper.install_tasks require 'rspec/core/rake_task' RSpec::Core::RakeTask.new task default: [:spec, :build] desc "Load iCalendar in IRB" task :console do require 'irb' require 'irb/completion' $:.unshift File.join(File.dirname(__FILE__), 'lib') require 'icalendar' ARGV.clear IRB.start end icalendar-2.4.1/README.md0000644000175100017510000002611513317103517013753 0ustar pravipraviiCalendar -- Internet calendaring, Ruby style === [![Build Status](https://travis-ci.org/icalendar/icalendar.svg?branch=master)](https://travis-ci.org/icalendar/icalendar) [![Code Climate](https://codeclimate.com/github/icalendar/icalendar.png)](https://codeclimate.com/github/icalendar/icalendar) 2.x Status --- iCalendar 2.0 is under active development, and can be followed in the [master branch](https://github.com/icalendar/icalendar/tree/master). iCalendar 1.x (currently the 1.x branch) will still survive for a while, but will only be accepting bug fixes from this point forward unless someone else wants to take over more active maintainership of the 1.x series. ### 2.0 Goals ### * Implements [RFC 5545](http://tools.ietf.org/html/rfc5545) * More obvious access to parameters and values * Cleaner & easier timezone support ### Upgrade from 1.x ### Better documentation is still to come, but in the meantime the changes needed to move from 1.x to 2.0 are summarized by the [diff needed to update the README](https://github.com/icalendar/icalendar/commit/bc3701e004c915a250054030a9375d1e7618857f) DESCRIPTION --- iCalendar is a Ruby library for dealing with iCalendar files in the iCalendar format defined by [RFC-5545](http://tools.ietf.org/html/rfc5545). EXAMPLES --- ### Creating calendars and events ### ```ruby require 'icalendar' # Create a calendar with an event (standard method) cal = Icalendar::Calendar.new cal.event do |e| e.dtstart = Icalendar::Values::Date.new('20050428') e.dtend = Icalendar::Values::Date.new('20050429') e.summary = "Meeting with the man." e.description = "Have a long lunch meeting and decide nothing..." e.ip_class = "PRIVATE" end cal.publish ``` #### Or you can make events like this #### ```ruby event = Icalendar::Event.new event.dtstart = DateTime.civil(2006, 6, 23, 8, 30) event.summary = "A great event!" cal.add_event(event) event2 = cal.event # This automatically adds the event to the calendar event2.dtstart = DateTime.civil(2006, 6, 24, 8, 30) event2.summary = "Another great event!" ``` #### Support for property parameters #### ```ruby params = {"altrep" => "http://my.language.net", "language" => "SPANISH"} event = cal.event do |e| e.dtstart = Icalendar::Values::Date.new('20050428') e.dtend = Icalendar::Values::Date.new('20050429') e.summary = Icalendar::Values::Text.new "This is a summary with params.", params end event.summary.ical_params #=> {'altrep' => 'http://my.language.net', 'language' => 'SPANISH'} # or event = cal.event do |e| e.dtstart = Icalendar::Values::Date.new('20050428') e.dtend = Icalendar::Values::Date.new('20050429') e.summary = "This is a summary with params." e.summary.ical_params = params end event.summary.ical_params #=> {'altrep' => 'http://my.language.net', 'language' => 'SPANISH'} ``` #### Support for Dates or DateTimes Sometimes we don't care if an event's start or end are `Date` or `DateTime` objects. For this, we can use `DateOrDateTime.new(value).call` ```ruby event = cal.event do |e| e.dtstart = Icalendar::Values::DateOrDateTime.new('20140924').call e.dtend = Icalendar::Values::DateOrDateTime.new('20140925').call e.summary = 'This is an all-day event, because DateOrDateTime will return Dates' end ``` #### Support for URLs For clients that can parse and display a URL associated with an event, it's possible to assign one. ```ruby event = cal.event do |e| e.url = 'https://example.com' end ``` #### We can output the calendar as a string #### cal_string = cal.to_ical puts cal_string ALARMS --- ### Within an event ### ```ruby cal.event do |e| # ...other event properties e.alarm do |a| a.action = "EMAIL" a.description = "This is an event reminder" # email body (required) a.summary = "Alarm notification" # email subject (required) a.attendee = %w(mailto:me@my-domain.com mailto:me-too@my-domain.com) # one or more email recipients (required) a.append_attendee "mailto:me-three@my-domain.com" a.trigger = "-PT15M" # 15 minutes before a.append_attach Icalendar::Values::Uri.new "ftp://host.com/novo-procs/felizano.exe", "fmttype" => "application/binary" # email attachments (optional) end e.alarm do |a| a.action = "DISPLAY" # This line isn't necessary, it's the default a.summary = "Alarm notification" a.trigger = "-P1DT0H0M0S" # 1 day before end e.alarm do |a| a.action = "AUDIO" a.trigger = "-PT15M" a.append_attach "Basso" end end ``` #### Output #### # BEGIN:VALARM # ACTION:EMAIL # ATTACH;FMTTYPE=application/binary:ftp://host.com/novo-procs/felizano.exe # TRIGGER:-PT15M # SUMMARY:Alarm notification # DESCRIPTION:This is an event reminder # ATTENDEE:mailto:me-too@my-domain.com # ATTENDEE:mailto:me-three@my-domain.com # END:VALARM # # BEGIN:VALARM # ACTION:DISPLAY # TRIGGER:-P1DT0H0M0S # SUMMARY:Alarm notification # END:VALARM # # BEGIN:VALARM # ACTION:AUDIO # ATTACH;VALUE=URI:Basso # TRIGGER:-PT15M # END:VALARM #### Checking for an Alarm #### Calling the `event.alarm` method will create an alarm if one doesn't exist. To check if an event has an alarm use the `has_alarm?` method. ```ruby event.has_alarm? # => false event.alarm # => # event.has_alarm? #=> true ``` TIMEZONES --- ```ruby cal = Icalendar::Calendar.new cal.timezone do |t| t.tzid = "America/Chicago" t.daylight do |d| d.tzoffsetfrom = "-0600" d.tzoffsetto = "-0500" d.tzname = "CDT" d.dtstart = "19700308T020000" d.rrule = "FREQ=YEARLY;BYMONTH=3;BYDAY=2SU" end t.standard do |s| s.tzoffsetfrom = "-0500" s.tzoffsetto = "-0600" s.tzname = "CST" s.dtstart = "19701101T020000" s.rrule = "FREQ=YEARLY;BYMONTH=11;BYDAY=1SU" end end ``` #### Output #### # BEGIN:VTIMEZONE # TZID:America/Chicago # BEGIN:DAYLIGHT # TZOFFSETFROM:-0600 # TZOFFSETTO:-0500 # TZNAME:CDT # DTSTART:19700308T020000 # RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU # END:DAYLIGHT # BEGIN:STANDARD # TZOFFSETFROM:-0500 # TZOFFSETTO:-0600 # TZNAME:CST # DTSTART:19701101T020000 # RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU # END:STANDARD # END:VTIMEZONE iCalendar has some basic support for creating VTIMEZONE blocks from timezone information pulled from `tzinfo`. You must require `tzinfo` support manually to take advantage. iCalendar has been tested and works with `tzinfo` versions 0.3 and 1.1 #### Example #### ```ruby require 'icalendar/tzinfo' cal = Icalendar::Calendar.new event_start = DateTime.new 2008, 12, 29, 8, 0, 0 event_end = DateTime.new 2008, 12, 29, 11, 0, 0 tzid = "America/Chicago" tz = TZInfo::Timezone.get tzid timezone = tz.ical_timezone event_start cal.add_timezone timezone cal.event do |e| e.dtstart = Icalendar::Values::DateTime.new event_start, 'tzid' => tzid e.dtend = Icalendar::Values::DateTime.new event_end, 'tzid' => tzid e.summary = "Meeting with the man." e.description = "Have a long lunch meeting and decide nothing..." e.organizer = "mailto:jsmith@example.com" e.organizer = Icalendar::Values::CalAddress.new("mailto:jsmith@example.com", cn: 'John Smith') end ``` Parsing iCalendars --- ```ruby # Open a file or pass a string to the parser cal_file = File.open("single_event.ics") # Parser returns an array of calendars because a single file # can have multiple calendars. cals = Icalendar::Calendar.parse(cal_file) cal = cals.first # Now you can access the cal object in just the same way I created it event = cal.events.first puts "start date-time: #{event.dtstart}" puts "start date-time timezone: #{event.dtstart.ical_params['tzid']}" puts "summary: #{event.summary}" ``` You can also create a `Parser` instance directly, this can be used to enable strict parsing: ```ruby # Sometimes you want to strongly verify only rfc-approved properties are # used strict_parser = Icalendar::Parser.new(cal_file, true) cal = strict_parser.parse ``` Parsing Components (e.g. Events) --- ```ruby # Open a file or pass a string to the parser event_file = File.open("event.ics") # Parser returns an array of events because a single file # can have multiple events. events = Icalendar::Event.parse(event_file) event = events.first puts "start date-time: #{event.dtstart}" puts "start date-time timezone: #{event.dtstart.ical_params['tzid']}" puts "summary: #{event.summary}" ``` Finders --- Often times in web apps and other interactive applications you'll need to lookup items in a calendar to make changes or get details. Now you can find everything by the unique id automatically associated with all components. ```ruby cal = Calendar.new 10.times { cal.event } # Create 10 events with only default data. some_event = cal.events[5] # Grab it from the array of events # Use the uid as the key in your app key = some_event.uid # so later you can find it. same_event = cal.find_event(key) ``` Examples --- Check the unit tests for examples of most things you'll want to do, but please send me example code or let me know what's missing. Download --- The latest release version of this library can be found at * Installation --- It's all about rubygems: $ gem install icalendar Testing --- To run the tests: $ bundle install $ rake spec License --- This library is released under the same license as Ruby itself. Support & Contributions --- Please submit pull requests from a rebased topic branch and include tests for all bugs and features. Contributor Code of Conduct --- As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion. Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team. This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.1.0, available at [http://contributor-covenant.org/version/1/1/0/](http://contributor-covenant.org/version/1/1/0/) icalendar-2.4.1/.gitignore0000644000175100017510000000011313317103517014452 0ustar pravipravi.rvmrc .ruby-version .ruby-gemset Gemfile.lock pkg/ .bundle coverage/ tags icalendar-2.4.1/.travis.yml0000644000175100017510000000040513317103517014577 0ustar pravipravisudo: false before_install: - gem install bundler language: ruby rvm: - 2.3.1 - 2.2 - 2.1 - 2.0 - jruby-19mode - rbx-2 - ruby-head - jruby-head matrix: allow_failures: - rvm: ruby-head - rvm: jruby-head script: bundle exec rake spec icalendar-2.4.1/icalendar.gemspec0000644000175100017510000000362513317103517015764 0ustar pravipravirequire File.join File.dirname(__FILE__), 'lib', 'icalendar', 'version' Gem::Specification.new do |s| s.authors = ['Ryan Ahearn'] s.email = ['ryan.c.ahearn@gmail.com'] s.name = "icalendar" s.version = Icalendar::VERSION s.homepage = "https://github.com/icalendar/icalendar" s.platform = Gem::Platform::RUBY s.summary = "A ruby implementation of the iCalendar specification (RFC-5545)." s.description = <<-EOD Implements the iCalendar specification (RFC-5545) in Ruby. This allows for the generation and parsing of .ics files, which are used by a variety of calendaring applications. EOD s.post_install_message = <<-EOM HEADS UP! iCalendar 2.0 is not backwards-compatible with 1.x. Please see the README for the new syntax HEADS UP! icalendar 2.2.0 switches to non-strict parsing as default. Please see the README if you rely on strict parsing for information on how to enable it. ActiveSupport is required for TimeWithZone support, but not required for general use. EOM s.files = `git ls-files`.split "\n" s.test_files = `git ls-files -- {test,spec,features}/*`.split "\n" s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename f } s.require_paths = ['lib'] s.required_ruby_version = '>= 1.9.2' s.add_development_dependency 'rake', '~> 10.0' s.add_development_dependency 'bundler', '~> 1.3' # test with both groups of tzinfo dependencies # tzinfo 1.x s.add_development_dependency 'tzinfo', '~> 1.1' s.add_development_dependency 'tzinfo-data', '~> 1.2014' # tzinfo 0.x # s.add_development_dependency 'tzinfo', '~> 0.3' # end tzinfo s.add_development_dependency 'activesupport', '~> 3.2' # lock i18n to < 0.7 to maintain ruby 1.9.2 compatibility s.add_development_dependency 'i18n', '< 0.7.0' s.add_development_dependency 'timecop', '~> 0.7.0' s.add_development_dependency 'rspec', '~> 3.0' s.add_development_dependency 'simplecov', '~> 0.8' end icalendar-2.4.1/lib/0000755000175100017510000000000013317103517013235 5ustar pravipraviicalendar-2.4.1/lib/icalendar/0000755000175100017510000000000013317103517015157 5ustar pravipraviicalendar-2.4.1/lib/icalendar/alarm.rb0000644000175100017510000000275613317103517016612 0ustar pravipravimodule Icalendar class Alarm < Component required_property :action required_property :trigger, Icalendar::Values::Duration required_property :description, Icalendar::Values::Text, ->(alarm, description) { alarm.action.downcase == 'audio' || !description.nil? } required_property :summary, Icalendar::Values::Text, ->(alarm, summary) { alarm.action.downcase != 'email' || !summary.nil? } required_multi_property :attendee, Icalendar::Values::CalAddress, ->(alarm, attendees) { alarm.action.downcase != 'email' || !attendees.compact.empty? } optional_single_property :duration, Icalendar::Values::Duration optional_single_property :repeat, Icalendar::Values::Integer optional_property :attach, Icalendar::Values::Uri # not part of base spec - need better abstraction for extensions optional_single_property :uid optional_single_property :acknowledged, Icalendar::Values::DateTime def initialize super 'alarm' end def valid?(strict = false) if strict # must be part of event or todo !(parent.nil? || parent.name == 'event' || parent.name == 'todo') and return false end # either both duration and repeat or neither should be set [duration, repeat].compact.size == 1 and return false # attach must be single for audio actions action.downcase == 'audio' && attach.compact.size > 1 and return false super end end end icalendar-2.4.1/lib/icalendar/calendar.rb0000644000175100017510000000101613317103517017253 0ustar pravipravimodule Icalendar class Calendar < Component required_property :version required_property :prodid optional_single_property :calscale optional_single_property :ip_method component :timezone, :tzid component :event component :todo component :journal component :freebusy def initialize super 'calendar' self.prodid = 'icalendar-ruby' self.version = '2.0' self.calscale = 'GREGORIAN' end def publish self.ip_method = 'PUBLISH' end end end icalendar-2.4.1/lib/icalendar/logger.rb0000644000175100017510000000036013317103517016762 0ustar pravipravirequire 'delegate' require 'logger' module Icalendar class Logger < ::SimpleDelegator def initialize(sink, level = ::Logger::WARN) logger = ::Logger.new(sink) logger.level = level super logger end end end icalendar-2.4.1/lib/icalendar/value.rb0000644000175100017510000000367613317103517016634 0ustar pravipravirequire 'delegate' require 'icalendar/downcased_hash' module Icalendar class Value < ::SimpleDelegator attr_accessor :ical_params def initialize(value, params = {}) @ical_params = Icalendar::DowncasedHash(params) super value end def ical_param(key, value) @ical_params[key] = value end def value __getobj__ end def to_ical(default_type) ical_param 'value', self.value_type if needs_value_type?(default_type) "#{params_ical}:#{value_ical}" end def params_ical unless ical_params.empty? ";#{ical_params.map { |name, value| param_ical name, value }.join ';'}" end end def self.value_type name.gsub(/\A.*::/, '').gsub(/(?(todo, dtstart) { !(!todo.duration.nil? && dtstart.nil?) } optional_single_property :due, Icalendar::Values::DateTime optional_single_property :duration, Icalendar::Values::Duration mutually_exclusive_properties :due, :duration optional_single_property :ip_class optional_single_property :completed, Icalendar::Values::DateTime optional_single_property :created, Icalendar::Values::DateTime optional_single_property :description optional_single_property :geo, Icalendar::Values::Float optional_single_property :last_modified, Icalendar::Values::DateTime optional_single_property :location optional_single_property :organizer, Icalendar::Values::CalAddress optional_single_property :percent_complete, Icalendar::Values::Integer optional_single_property :priority, Icalendar::Values::Integer optional_single_property :recurrence_id, Icalendar::Values::DateTime optional_single_property :sequence, Icalendar::Values::Integer optional_single_property :status optional_single_property :summary optional_single_property :url, Icalendar::Values::Uri optional_property :rrule, Icalendar::Values::Recur, true optional_property :attach, Icalendar::Values::Uri optional_property :attendee, Icalendar::Values::CalAddress optional_property :categories optional_property :comment optional_property :contact optional_property :exdate, Icalendar::Values::DateTime optional_property :request_status optional_property :related_to optional_property :resources optional_property :rdate, Icalendar::Values::DateTime component :alarm, false def initialize super 'todo' self.dtstamp = Icalendar::Values::DateTime.new Time.now.utc, 'tzid' => 'UTC' self.uid = new_uid end end endicalendar-2.4.1/lib/icalendar/has_components.rb0000644000175100017510000000421013317103517020521 0ustar pravipravimodule Icalendar module HasComponents def self.included(base) base.extend ClassMethods base.class_eval do attr_reader :custom_components end end def initialize(*args) @custom_components = Hash.new { |h, k| h[k] = [] } super end def add_component(c) c.parent = self yield c if block_given? send("#{c.name.downcase}s") << c c end def method_missing(method, *args, &block) method_name = method.to_s if method_name =~ /^add_(x_\w+)$/ component_name = $1 custom = args.first || Component.new(component_name, component_name.upcase) custom_components[component_name] << custom yield custom if block_given? custom else super end end def respond_to_missing?(method_name, include_private = false) method_name.to_s.start_with?('add_x_') || super end module ClassMethods def components @components ||= [] end def component(singular_name, find_by = :uid, klass = nil) components = "#{singular_name}s" self.components << components component_var = "@#{components}" define_method components do if instance_variable_defined? component_var instance_variable_get component_var else instance_variable_set component_var, [] end end define_method singular_name do |c = nil, &block| if c.nil? c = begin klass ||= Icalendar.const_get singular_name.capitalize klass.new rescue NameError => ne Icalendar.logger.warn ne.message Component.new singular_name end end add_component c, &block end define_method "find_#{singular_name}" do |id| send(components).find { |c| c.send(find_by) == id } end if find_by define_method "add_#{singular_name}" do |c| send singular_name, c end define_method "has_#{singular_name}?" do !send(components).empty? end end end end end icalendar-2.4.1/lib/icalendar/downcased_hash.rb0000644000175100017510000000141313317103517020455 0ustar pravipravirequire 'delegate' module Icalendar class DowncasedHash < ::SimpleDelegator def initialize(base) super Hash.new base.each do |key, value| self[key] = value end end def []=(key, value) __getobj__[key.to_s.downcase] = value end def [](key) __getobj__[key.to_s.downcase] end def has_key?(key) __getobj__.has_key? key.to_s.downcase end alias_method :include?, :has_key? alias_method :member?, :has_key? def delete(key, &block) __getobj__.delete key.to_s.downcase, &block end end def self.DowncasedHash(base) case base when Icalendar::DowncasedHash then base when Hash then Icalendar::DowncasedHash.new(base) else fail ArgumentError end end end icalendar-2.4.1/lib/icalendar/tzinfo.rb0000644000175100017510000001162513317103517017022 0ustar pravipravi=begin Copyright (C) 2008 Sean Dague This library is free software; you can redistribute it and/or modify it under the same terms as the ruby language itself, see the file COPYING for details. =end # The following adds a bunch of mixins to the tzinfo class, with the # intent on making it very easy to load in tzinfo data for generating # ical events. With this you can do the following: # # require "icalendar/tzinfo" # # estart = DateTime.new(2008, 12, 29, 8, 0, 0) # eend = DateTime.new(2008, 12, 29, 11, 0, 0) # tstring = "America/Chicago" # # tz = TZInfo::Timezone.get(tstring) # cal = Calendar.new # # the mixins now generate all the timezone info for the date in question # timezone = tz.ical_timezone(estart) # cal.add(timezone) # # cal.event do # dtstart estart # dtend eend # summary "Meeting with the man." # description "Have a long lunch meeting and decide nothing..." # klass "PRIVATE" # end # # puts cal.to_ical # # The recurance rule calculations are hacky, and only start at the # beginning of the current dst transition. I doubt this works for non # dst areas yet. However, for a standard dst flipping zone, this # seems to work fine (tested in Mozilla Thunderbird + Lightning). # Future goal would be making this better. require 'tzinfo' begin require 'tzinfo/data' rescue LoadError Icalendar.logger.info "Could not load tzinfo/data, hopefully tzinfo is accurate (ignore for tzinfo 0.x)" end module Icalendar module TimezoneTransition def offset_from previous_offset.ical_offset end def offset_to offset.ical_offset end def offset_abbreviation offset.abbreviation.to_s end def rrule start = local_start.to_datetime # this is somewhat of a hack, but seems to work ok # assumes that no timezone transition is in law as "4th X of the month" # but only as 1st X, 2nd X, 3rd X, or Last X start_week = ((start.day - 1) / 7).to_i + 1 start_week = (start_week > 3) ? -1 : start_week [sprintf( 'FREQ=YEARLY;BYMONTH=%d;BYDAY=%d%s', start.month, start_week, start.strftime('%a').upcase[0,2] )] end def dtstart local_start.to_datetime.strftime '%Y%m%dT%H%M%S' end end module TimezoneOffset def ical_offset o = utc_total_offset sprintf '%+-2.2d%2.2d', (o / 3600).to_i, ((o / 60) % 60).to_i end end end module TZInfo class Timezone def ical_timezone(date, dst = Timezone.default_dst) period = period_for_local(date, dst) timezone = Icalendar::Timezone.new timezone.tzid = identifier if period.start_transition.nil? timezone.add_component period.single elsif period.end_transition.nil? timezone.add_component period.dst? ? period.daylight : period.standard else timezone.add_component period.daylight timezone.add_component period.standard end timezone end end if defined? TimezoneTransitionInfo class TimezoneTransitionInfo include Icalendar::TimezoneTransition end else class TimezoneTransition include Icalendar::TimezoneTransition end end if defined? TimezoneOffsetInfo class TimezoneOffsetInfo include Icalendar::TimezoneOffset end else class TimezoneOffset include Icalendar::TimezoneOffset end end class TimezonePeriod # For DST, use the start_transition, # for standard TZ, use the following period (starting from the end_transition). def daylight transition = dst? ? start_transition : end_transition day = Icalendar::Timezone::Daylight.new build_timezone(day, transition) do |tz| # rrule should not be set for the current [==DST/daylight] period # if there is no recurrence rule for the end transition if !dst? || !end_transition.nil? tz.rrule = transition.rrule end end end # For standard TZ, use the start_transition, # for DST, use the following period, (starting from the end_transition) def standard transition = dst? ? end_transition : start_transition std = Icalendar::Timezone::Standard.new build_timezone(std, transition) do |tz| if dst? || !end_transition.nil? tz.rrule = transition.rrule end end end def single Icalendar::Timezone::Standard.new.tap do |std| std.tzname = abbreviation.to_s std.tzoffsetfrom = offset.ical_offset std.tzoffsetto = offset.ical_offset std.dtstart = DateTime.new(1970).strftime '%Y%m%dT%H%M%S' end end private def build_timezone(timezone, transition) timezone.tap do |tz| tz.tzname = transition.offset_abbreviation tz.tzoffsetfrom = transition.offset_from tz.tzoffsetto = transition.offset_to tz.dtstart = transition.dtstart yield tz end end end end icalendar-2.4.1/lib/icalendar/has_properties.rb0000644000175100017510000001151013317103517020531 0ustar pravipravimodule Icalendar module HasProperties def self.included(base) base.extend ClassMethods base.class_eval do attr_reader :custom_properties end end def initialize(*args) @custom_properties = Hash.new { |h, k| h[k] = [] } super end def valid?(strict = false) self.class.required_properties.each_pair do |prop, validator| validator.call(self, send(prop)) or return false end self.class.mutex_properties.each do |mutexprops| mutexprops.map { |p| send p }.compact.size > 1 and return false end if strict self.class.suggested_single_properties.each do |single_prop| send(single_prop).size > 1 and return false end end true end def property(property_name) property_name = property_name.downcase if self.class.properties.include? property_name send property_name else custom_property property_name end end def custom_property(property_name) custom_properties[property_name.downcase] end def append_custom_property(property_name, value) property_name = property_name.downcase if value.is_a? Icalendar::Value custom_properties[property_name] << value else custom_properties[property_name] << Icalendar::Values::Text.new(value) end end def method_missing(method, *args, &block) method_name = method.to_s if method_name.start_with? 'x_' if method_name.end_with? '=' append_custom_property method_name.chomp('='), args.first else custom_property method_name end else super end end def respond_to_missing?(method, include_private = false) method.to_s.start_with?('x_') || super end module ClassMethods def properties single_properties + multiple_properties end def single_properties @single_properties ||= [] end def multiple_properties @multiple_properties ||= [] end def required_properties @required_properties ||= {} end def suggested_single_properties @suggested_single_properties ||= [] end def mutex_properties @mutex_properties ||= [] end def default_property_types @default_property_types ||= Hash.new { |h,k| Icalendar::Values::Text } end def required_property(prop, klass = Icalendar::Values::Text, validator = nil) validator ||= ->(component, value) { !value.nil? } self.required_properties[prop] = validator single_property prop, klass end def required_multi_property(prop, klass = Icalendar::Values::Text, validator = nil) validator ||= ->(component, value) { !value.compact.empty? } self.required_properties[prop] = validator multi_property prop, klass end def optional_single_property(prop, klass = Icalendar::Values::Text) single_property prop, klass end def mutually_exclusive_properties(*properties) self.mutex_properties << properties end def optional_property(prop, klass = Icalendar::Values::Text, suggested_single = false) self.suggested_single_properties << prop if suggested_single multi_property prop, klass end def single_property(prop, klass) self.single_properties << prop.to_s self.default_property_types[prop.to_s] = klass define_method prop do instance_variable_get "@#{prop}" end define_method "#{prop}=" do |value| instance_variable_set "@#{prop}", map_property_value(value, klass, false) end end def multi_property(prop, klass) self.multiple_properties << prop.to_s self.default_property_types[prop.to_s] = klass property_var = "@#{prop}" define_method "#{prop}=" do |value| mapped = map_property_value value, klass, true if mapped.is_a? Icalendar::Values::Array instance_variable_set property_var, mapped.to_a.compact else instance_variable_set property_var, [mapped].compact end end define_method prop do if instance_variable_defined? property_var instance_variable_get property_var else send "#{prop}=", nil end end define_method "append_#{prop}" do |value| send(prop) << map_property_value(value, klass, true) end end end private def map_property_value(value, klass, multi_valued) if value.nil? || value.is_a?(Icalendar::Value) value elsif value.is_a? ::Array Icalendar::Values::Array.new value, klass, {}, {delimiter: (multi_valued ? ',' : ';')} else klass.new value end end end end icalendar-2.4.1/lib/icalendar/parser.rb0000644000175100017510000001271113317103517017002 0ustar pravipravimodule Icalendar class Parser attr_writer :component_class attr_reader :source, :strict def initialize(source, strict = false) if source.respond_to? :gets @source = source elsif source.respond_to? :to_s @source = StringIO.new source.to_s, 'r' else msg = 'Icalendar::Parser.new must be called with a String or IO object' Icalendar.fatal msg fail ArgumentError, msg end read_in_data @strict = strict end def parse source.rewind read_in_data components = [] while (fields = next_fields) component = component_class.new if fields[:name] == 'begin' && fields[:value].downcase == component.ical_name.downcase components << parse_component(component) end end components end def parse_property(component, fields = nil) fields = next_fields if fields.nil? prop_name = %w(class method).include?(fields[:name]) ? "ip_#{fields[:name]}" : fields[:name] multi_property = component.class.multiple_properties.include? prop_name prop_value = wrap_property_value component, fields, multi_property begin method_name = if multi_property "append_#{prop_name}" else "#{prop_name}=" end component.send method_name, prop_value rescue NoMethodError => nme if strict? Icalendar.logger.error "No method \"#{method_name}\" for component #{component}" raise nme else Icalendar.logger.warn "No method \"#{method_name}\" for component #{component}. Appending to custom." component.append_custom_property prop_name, prop_value end end end def wrap_property_value(component, fields, multi_property) klass = get_wrapper_class component, fields if wrap_in_array? klass, fields[:value], multi_property delimiter = fields[:value].match(/(? fe raise fe if strict? fields[:params]['value'] = ['DATE'] retry end def wrap_in_array?(klass, value, multi_property) klass.value_type != 'RECUR' && ((multi_property && value =~ /(?#{NAME})(?(?:;#{PARAM})*):(?#{VALUE})" BAD_LINE = "(?#{NAME})(?(?:;#{PARAM})*)" def parse_fields(input) if parts = %r{#{LINE}}.match(input) value = parts[:value] else parts = %r{#{BAD_LINE}}.match(input) unless strict? parts or fail "Invalid iCalendar input line: #{input}" # Non-strict and bad line so use a value of empty string value = '' end params = {} parts[:params].scan %r{#{PARAM}} do |match| param_name = match[0].downcase params[param_name] ||= [] match[1].scan %r{#{PVALUE}} do |param_value| params[param_name] << param_value.gsub(/\A"|"\z/, '') if param_value.size > 0 end end Icalendar.logger.debug "Found fields: #{parts.inspect} with params: #{params.inspect}" { name: parts[:name].downcase.gsub('-', '_'), params: params, value: value } end end end icalendar-2.4.1/lib/icalendar/event.rb0000644000175100017510000000373313317103517016633 0ustar pravipravimodule Icalendar class Event < Component required_property :dtstamp, Icalendar::Values::DateTime required_property :uid # dtstart only required if calendar's method is nil required_property :dtstart, Icalendar::Values::DateTime, ->(event, dtstart) { !dtstart.nil? || !(event.parent.nil? || event.parent.ip_method.nil?) } optional_single_property :dtend, Icalendar::Values::DateTime optional_single_property :duration, Icalendar::Values::Duration mutually_exclusive_properties :dtend, :duration optional_single_property :ip_class optional_single_property :created, Icalendar::Values::DateTime optional_single_property :description optional_single_property :geo, Icalendar::Values::Float optional_single_property :last_modified, Icalendar::Values::DateTime optional_single_property :location optional_single_property :organizer, Icalendar::Values::CalAddress optional_single_property :priority, Icalendar::Values::Integer optional_single_property :sequence, Icalendar::Values::Integer optional_single_property :status optional_single_property :summary optional_single_property :transp optional_single_property :url, Icalendar::Values::Uri optional_single_property :recurrence_id, Icalendar::Values::DateTime optional_property :rrule, Icalendar::Values::Recur, true optional_property :attach, Icalendar::Values::Uri optional_property :attendee, Icalendar::Values::CalAddress optional_property :categories optional_property :comment optional_property :contact optional_property :exdate, Icalendar::Values::DateTime optional_property :request_status optional_property :related_to optional_property :resources optional_property :rdate, Icalendar::Values::DateTime component :alarm, false def initialize super 'event' self.dtstamp = Icalendar::Values::DateTime.new Time.now.utc, 'tzid' => 'UTC' self.uid = new_uid end end end icalendar-2.4.1/lib/icalendar/journal.rb0000644000175100017510000000256113317103517017162 0ustar pravipravimodule Icalendar class Journal < Component required_property :dtstamp, Icalendar::Values::DateTime required_property :uid optional_single_property :ip_class optional_single_property :created, Icalendar::Values::DateTime optional_single_property :dtstart, Icalendar::Values::DateTime optional_single_property :last_modified, Icalendar::Values::DateTime optional_single_property :organizer, Icalendar::Values::CalAddress optional_single_property :recurrence_id, Icalendar::Values::DateTime optional_single_property :sequence, Icalendar::Values::Integer optional_single_property :status optional_single_property :summary optional_single_property :url, Icalendar::Values::Uri optional_property :rrule, Icalendar::Values::Recur, true optional_property :attach, Icalendar::Values::Uri optional_property :attendee, Icalendar::Values::CalAddress optional_property :categories optional_property :comment optional_property :contact optional_property :description optional_property :exdate, Icalendar::Values::DateTime optional_property :request_status optional_property :related_to optional_property :rdate, Icalendar::Values::DateTime def initialize super 'journal' self.dtstamp = Icalendar::Values::DateTime.new Time.now.utc, 'tzid' => 'UTC' self.uid = new_uid end end endicalendar-2.4.1/lib/icalendar/timezone.rb0000644000175100017510000000262113317103517017337 0ustar pravipravimodule Icalendar class Timezone < Component module TzProperties def self.included(base) base.class_eval do required_property :dtstart, Icalendar::Values::DateTime required_property :tzoffsetfrom, Icalendar::Values::UtcOffset required_property :tzoffsetto, Icalendar::Values::UtcOffset optional_property :rrule, Icalendar::Values::Recur, true optional_property :comment optional_property :rdate, Icalendar::Values::DateTime optional_property :tzname end end end class Daylight < Component include TzProperties def initialize super 'daylight', 'DAYLIGHT' end end class Standard < Component include TzProperties def initialize super 'standard', 'STANDARD' end end required_property :tzid optional_single_property :last_modified, Icalendar::Values::DateTime optional_single_property :tzurl, Icalendar::Values::Uri component :daylight, false, Icalendar::Timezone::Daylight component :standard, false, Icalendar::Timezone::Standard def initialize super 'timezone' end def valid?(strict = false) daylights.empty? && standards.empty? and return false daylights.all? { |d| d.valid? strict } or return false standards.all? { |s| s.valid? strict } or return false super end end end icalendar-2.4.1/lib/icalendar/freebusy.rb0000644000175100017510000000143513317103517017333 0ustar pravipravimodule Icalendar class Freebusy < Component required_property :dtstamp, Icalendar::Values::DateTime required_property :uid optional_single_property :contact optional_single_property :dtstart, Icalendar::Values::DateTime optional_single_property :dtend, Icalendar::Values::DateTime optional_single_property :organizer, Icalendar::Values::CalAddress optional_single_property :url, Icalendar::Values::Uri optional_property :attendee, Icalendar::Values::CalAddress optional_property :comment optional_property :freebusy, Icalendar::Values::Period optional_property :request_status def initialize super 'freebusy' self.dtstamp = Icalendar::Values::DateTime.new Time.now.utc, 'tzid' => 'UTC' self.uid = new_uid end end endicalendar-2.4.1/lib/icalendar/component.rb0000644000175100017510000000571013317103517017511 0ustar pravipravirequire 'securerandom' module Icalendar class Component include HasProperties include HasComponents attr_reader :name attr_reader :ical_name attr_accessor :parent def self.parse(source) parser = Parser.new(source) parser.component_class = self parser.parse end def initialize(name, ical_name = nil) @name = name @ical_name = ical_name || "V#{name.upcase}" super() end def new_uid SecureRandom.uuid end def to_ical [ "BEGIN:#{ical_name}", ical_properties, ical_components, "END:#{ical_name}\r\n" ].compact.join "\r\n" end private def ical_properties (self.class.properties + custom_properties.keys).map do |prop| value = property prop unless value.nil? if value.is_a? ::Array value.map do |part| ical_fold "#{ical_prop_name prop}#{part.to_ical self.class.default_property_types[prop]}" end.join "\r\n" unless value.empty? else ical_fold "#{ical_prop_name prop}#{value.to_ical self.class.default_property_types[prop]}" end end end.compact.join "\r\n" end def ical_prop_name(prop_name) prop_name.gsub(/\Aip_/, '').gsub('_', '-').upcase end def ical_fold(long_line, indent = "\x20") # rfc2445 says: # Lines of text SHOULD NOT be longer than 75 octets, excluding the line # break. Long content lines SHOULD be split into a multiple line # representations using a line "folding" technique. That is, a long # line can be split between any two characters by inserting a CRLF # immediately followed by a single linear white space character (i.e., # SPACE, US-ASCII decimal 32 or HTAB, US-ASCII decimal 9). Any sequence # of CRLF followed immediately by a single linear white space character # is ignored (i.e., removed) when processing the content type. # # Note the useage of "octets" and "characters": a line should not be longer # than 75 octets, but you need to split between characters, not bytes. # This is challanging with Unicode composing accents, for example. chars = long_line.scan(/\P{M}\p{M}*/u) # split in graphenes folded = [''] bytes = 0 while chars.count > 0 c = chars.shift cb = c.bytes.count if bytes + cb > Icalendar::MAX_LINE_LENGTH # Split here folded.push "#{indent}" bytes = indent.bytes.count end folded[-1] += c bytes += cb end folded.join("\r\n") end def ical_components collection = [] (self.class.components + custom_components.keys).each do |component_name| components = send component_name components.each do |component| collection << component.to_ical end end collection.empty? ? nil : collection.join.chomp("\r\n") end end end icalendar-2.4.1/lib/icalendar/version.rb0000644000175100017510000000005313317103517017167 0ustar pravipravimodule Icalendar VERSION = '2.4.1' end icalendar-2.4.1/lib/icalendar/values/0000755000175100017510000000000013317103517016456 5ustar pravipraviicalendar-2.4.1/lib/icalendar/values/date_or_date_time.rb0000644000175100017510000000142413317103517022434 0ustar pravipravimodule Icalendar module Values # DateOrDateTime can be used to set an attribute to either a Date or a DateTime value. # It should not be used wihtout also invoking the `call` method. class DateOrDateTime attr_reader :value, :params, :parsed def initialize(value, params = {}) @value = value @params = params end def call @parsed ||= begin Icalendar::Values::DateTime.new value, params rescue Icalendar::Values::DateTime::FormatError Icalendar::Values::Date.new value, params end end def to_ical fail NoMethodError, 'You cannot use DateOrDateTime directly. Invoke `call` before `to_ical`' end end end end icalendar-2.4.1/lib/icalendar/values/integer.rb0000644000175100017510000000032713317103517020442 0ustar pravipravimodule Icalendar module Values class Integer < Value def initialize(value, params = {}) super value.to_i, params end def value_ical value.to_s end end end endicalendar-2.4.1/lib/icalendar/values/time_with_zone.rb0000644000175100017510000000171513317103517022033 0ustar pravipravibegin require 'active_support/time' if defined?(ActiveSupport::TimeWithZone) require 'icalendar/values/active_support_time_with_zone_adapter' end rescue LoadError # tis ok, just a bit less fancy end module Icalendar module Values module TimeWithZone attr_reader :tz_utc def initialize(value, params = {}) params = Icalendar::DowncasedHash(params) @tz_utc = params['tzid'] == 'UTC' if defined?(ActiveSupport::TimeZone) && defined?(ActiveSupportTimeWithZoneAdapter) && !params['tzid'].nil? tzid = params['tzid'].is_a?(::Array) ? params['tzid'].first : params['tzid'] zone = ActiveSupport::TimeZone[tzid] value = ActiveSupportTimeWithZoneAdapter.new nil, zone, value unless zone.nil? super value, params else super value, params end end def params_ical ical_params.delete 'tzid' if tz_utc super end end end end icalendar-2.4.1/lib/icalendar/values/float.rb0000644000175100017510000000032513317103517020110 0ustar pravipravimodule Icalendar module Values class Float < Value def initialize(value, params = {}) super value.to_f, params end def value_ical value.to_s end end end endicalendar-2.4.1/lib/icalendar/values/date.rb0000644000175100017510000000155513317103517017726 0ustar pravipravirequire 'date' module Icalendar module Values class Date < Value FORMAT = '%Y%m%d' def initialize(value, params = {}) if value.is_a? String begin parsed_date = ::Date.strptime(value, FORMAT) rescue ArgumentError => e raise FormatError.new("Failed to parse \"#{value}\" - #{e.message}") end super parsed_date, params elsif value.respond_to? :to_date super value.to_date, params else super end end def value_ical value.strftime FORMAT end def <=>(other) if other.is_a?(Icalendar::Values::Date) || other.is_a?(Icalendar::Values::DateTime) value_ical <=> other.value_ical else nil end end class FormatError < ArgumentError end end end end icalendar-2.4.1/lib/icalendar/values/array.rb0000644000175100017510000000266313317103517020130 0ustar pravipravimodule Icalendar module Values class Array < Value attr_reader :value_delimiter def initialize(value, klass, params = {}, options = {}) @value_delimiter = options[:delimiter] || ',' mapped = if value.is_a? ::Array value.map do |v| if v.is_a? Icalendar::Values::Array Icalendar::Values::Array.new v.value, klass, v.ical_params, delimiter: v.value_delimiter elsif v.is_a? ::Array Icalendar::Values::Array.new v, klass, params, delimiter: value_delimiter elsif v.is_a? Icalendar::Value v else klass.new v, params end end else [klass.new(value, params)] end super mapped, params end def params_ical value.each do |v| ical_params.merge! v.ical_params end super end def value_ical value.map do |v| v.value_ical end.join value_delimiter end def valid? klass = value.first.class !value.all? { |v| v.class == klass } end def value_type value.first.value_type end private def needs_value_type?(default_type) value.first.class != default_type end end end end icalendar-2.4.1/lib/icalendar/values/time.rb0000644000175100017510000000117413317103517017744 0ustar pravipravirequire 'date' require_relative 'time_with_zone' module Icalendar module Values class Time < Value include TimeWithZone FORMAT = '%H%M%S' def initialize(value, params = {}) if value.is_a? String params['tzid'] = 'UTC' if value.end_with? 'Z' super ::DateTime.strptime(value, FORMAT).to_time, params elsif value.respond_to? :to_time super value.to_time, params else super end end def value_ical if tz_utc "#{strftime FORMAT}Z" else strftime FORMAT end end end end endicalendar-2.4.1/lib/icalendar/values/date_time.rb0000644000175100017510000000210213317103517020731 0ustar pravipravirequire 'date' require_relative 'time_with_zone' module Icalendar module Values class DateTime < Value include TimeWithZone FORMAT = '%Y%m%dT%H%M%S' def initialize(value, params = {}) if value.is_a? String params['tzid'] = 'UTC' if value.end_with? 'Z' begin parsed_date = ::DateTime.strptime(value, FORMAT) rescue ArgumentError => e raise FormatError.new("Failed to parse \"#{value}\" - #{e.message}") end super parsed_date, params elsif value.respond_to? :to_datetime super value.to_datetime, params else super end end def value_ical if tz_utc "#{strftime FORMAT}Z" else strftime FORMAT end end def <=>(other) if other.is_a?(Icalendar::Values::Date) || other.is_a?(Icalendar::Values::DateTime) value_ical <=> other.value_ical else nil end end class FormatError < ArgumentError end end end end icalendar-2.4.1/lib/icalendar/values/recur.rb0000644000175100017510000000600413317103517020123 0ustar pravipravirequire 'ostruct' module Icalendar module Values class Recur < Value NUM_LIST = '\d{1,2}(?:,\d{1,2})*' DAYNAME = 'SU|MO|TU|WE|TH|FR|SA' WEEKDAY = "(?:[+-]?\\d{1,2})?(?:#{DAYNAME})" MONTHDAY = '[+-]?\d{1,2}' YEARDAY = '[+-]?\d{1,3}' def initialize(value, params = {}) if value.is_a? Icalendar::Values::Recur super value.value, params else super OpenStruct.new(parse_fields value), params end end def valid? return false if frequency.nil? return false if !self.until.nil? && !count.nil? true end def value_ical builder = ["FREQ=#{frequency}"] builder << "UNTIL=#{self.until}" unless self.until.nil? builder << "COUNT=#{count}" unless count.nil? builder << "INTERVAL=#{interval}" unless interval.nil? builder << "BYSECOND=#{by_second.join ','}" unless by_second.nil? builder << "BYMINUTE=#{by_minute.join ','}" unless by_minute.nil? builder << "BYHOUR=#{by_hour.join ','}" unless by_hour.nil? builder << "BYDAY=#{by_day.join ','}" unless by_day.nil? builder << "BYMONTHDAY=#{by_month_day.join ','}" unless by_month_day.nil? builder << "BYYEARDAY=#{by_year_day.join ','}" unless by_year_day.nil? builder << "BYWEEKNO=#{by_week_number.join ','}" unless by_week_number.nil? builder << "BYMONTH=#{by_month.join ','}" unless by_month.nil? builder << "BYSETPOS=#{by_set_position.join ','}" unless by_set_position.nil? builder << "WKST=#{week_start}" unless week_start.nil? builder.join ';' end private def parse_fields(value) { frequency: (value =~ /FREQ=(SECONDLY|MINUTELY|HOURLY|DAILY|WEEKLY|MONTHLY|YEARLY)/i ? $1.upcase : nil), until: (value =~ /UNTIL=([^;]*)/i ? $1 : nil), count: (value =~ /COUNT=(\d+)/i ? $1.to_i : nil), interval: (value =~ /INTERVAL=(\d+)/i ? $1.to_i : nil), by_second: (value =~ /BYSECOND=(#{NUM_LIST})(?:;|\z)/i ? $1.split(',').map { |i| i.to_i } : nil), by_minute: (value =~ /BYMINUTE=(#{NUM_LIST})(?:;|\z)/i ? $1.split(',').map { |i| i.to_i } : nil), by_hour: (value =~ /BYHOUR=(#{NUM_LIST})(?:;|\z)/i ? $1.split(',').map { |i| i.to_i } : nil), by_day: (value =~ /BYDAY=(#{WEEKDAY}(?:,#{WEEKDAY})*)(?:;|\z)/i ? $1.split(',') : nil), by_month_day: (value =~ /BYMONTHDAY=(#{MONTHDAY}(?:,#{MONTHDAY})*)(?:;|\z)/i ? $1.split(',') : nil), by_year_day: (value =~ /BYYEARDAY=(#{YEARDAY}(?:,#{YEARDAY})*)(?:;|\z)/i ? $1.split(',') : nil), by_week_number: (value =~ /BYWEEKNO=(#{MONTHDAY}(?:,#{MONTHDAY})*)(?:;|\z)/i ? $1.split(',') : nil), by_month: (value =~ /BYMONTH=(#{NUM_LIST})(?:;|\z)/i ? $1.split(',').map { |i| i.to_i } : nil), by_set_position: (value =~ /BYSETPOS=(#{YEARDAY}(?:,#{YEARDAY})*)(?:;|\z)/i ? $1.split(',') : nil), week_start: (value =~ /WKST=(#{DAYNAME})/i ? $1.upcase : nil) } end end end end icalendar-2.4.1/lib/icalendar/values/binary.rb0000644000175100017510000000102513317103517020265 0ustar pravipravirequire 'base64' module Icalendar module Values class Binary < Value def params_ical ical_param :value, 'BINARY' ical_param :encoding, 'BASE64' super end def value_ical if base64? value else Base64.strict_encode64 value end end private def base64? value.is_a?(String) && value =~ /\A(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{4}|[A-Za-z0-9+\/]{3}=|[A-Za-z0-9+\/]{2}==)\z/ end end end endicalendar-2.4.1/lib/icalendar/values/uri.rb0000644000175100017510000000041313317103517017600 0ustar pravipravirequire 'uri' module Icalendar module Values class Uri < Value def initialize(value, params = {}) parsed = URI.parse value rescue value super parsed, params end def value_ical value.to_s end end end endicalendar-2.4.1/lib/icalendar/values/cal_address.rb0000644000175100017510000000011713317103517021246 0ustar pravipravimodule Icalendar module Values class CalAddress < Uri end end endicalendar-2.4.1/lib/icalendar/values/period.rb0000644000175100017510000000224413317103517020267 0ustar pravipravimodule Icalendar module Values class Period < Value def initialize(value, params = {}) parts = value.split '/' period_start = Icalendar::Values::DateTime.new parts.first if parts.last =~ /\A[+-]?P.+\z/ period_end = Icalendar::Values::Duration.new parts.last else period_end = Icalendar::Values::DateTime.new parts.last end super [period_start, period_end], params end def value_ical value.map { |v| v.value_ical }.join '/' end def period_start first end def period_start=(v) value[0] = v.is_a?(Icalendar::Values::DateTime) ? v : Icalendar::Values::DateTime.new(v) end def explicit_end last.is_a?(Icalendar::Values::DateTime) ? last : nil end def explicit_end=(v) value[1] = v.is_a?(Icalendar::Values::DateTime) ? v : Icalendar::Values::DateTime.new(v) end def duration last.is_a?(Icalendar::Values::Duration) ? last : nil end def duration=(v) value[1] = v.is_a?(Icalendar::Values::Duration) ? v : Icalendar::Values::Duration.new(v) end end end endicalendar-2.4.1/lib/icalendar/values/duration.rb0000644000175100017510000000233013317103517020626 0ustar pravipravirequire 'ostruct' module Icalendar module Values class Duration < Value def initialize(value, params = {}) if value.is_a? Icalendar::Values::Duration super value.value, params else super OpenStruct.new(parse_fields value), params end end def past? value.past end def value_ical return "#{'-' if past?}P#{weeks}W" if weeks > 0 builder = [] builder << '-' if past? builder << 'P' builder << "#{days}D" if days > 0 builder << 'T' if time? builder << "#{hours}H" if hours > 0 builder << "#{minutes}M" if minutes > 0 builder << "#{seconds}S" if seconds > 0 builder.join end private def time? hours > 0 || minutes > 0 || seconds > 0 end def parse_fields(value) { past: (value =~ /\A([+-])P/ ? $1 == '-' : false), weeks: (value =~ /(\d+)W/ ? $1.to_i : 0), days: (value =~ /(\d+)D/ ? $1.to_i : 0), hours: (value =~ /(\d+)H/ ? $1.to_i : 0), minutes: (value =~ /(\d+)M/ ? $1.to_i : 0), seconds: (value =~ /(\d+)S/ ? $1.to_i : 0) } end end end end icalendar-2.4.1/lib/icalendar/values/boolean.rb0000644000175100017510000000037013317103517020422 0ustar pravipravimodule Icalendar module Values class Boolean < Value def initialize(value, params = {}) super value.to_s.downcase == 'true', params end def value_ical value ? 'TRUE' : 'FALSE' end end end endicalendar-2.4.1/lib/icalendar/values/active_support_time_with_zone_adapter.rb0000644000175100017510000000100213317103517026647 0ustar pravipravimodule Icalendar module Values class ActiveSupportTimeWithZoneAdapter < ActiveSupport::TimeWithZone # ActiveSupport::TimeWithZone implements a #to_a method that will cause # unexpected behavior in components with multi_property DateTime # properties when the setters for those properties are invoked with an # Icalendar::Values::DateTime that is delegating for an # ActiveSupport::TimeWithZone. To avoid this behavior, undefine #to_a. undef_method :to_a end end end icalendar-2.4.1/lib/icalendar/values/utc_offset.rb0000644000175100017510000000174513317103517021153 0ustar pravipravirequire 'ostruct' module Icalendar module Values class UtcOffset < Value def initialize(value, params = {}) if value.is_a? Icalendar::Values::UtcOffset value = value.value else value = OpenStruct.new parse_fields(value) end super value, params end def behind? return false if zero_offset? value.behind end def value_ical "#{behind? ? '-' : '+'}#{'%02d' % hours}#{'%02d' % minutes}#{'%02d' % seconds if seconds > 0}" end private def zero_offset? hours == 0 && minutes == 0 && seconds == 0 end def parse_fields(value) value.gsub!(/\s+/, '') md = /\A(?[+-])(?\d{2})(?\d{2})(?\d{2})?\z/.match value { behind: (md[:behind] == '-'), hours: md[:hours].to_i, minutes: md[:minutes].to_i, seconds: md[:seconds].to_i } end end end end icalendar-2.4.1/lib/icalendar/values/text.rb0000644000175100017510000000075213317103517017773 0ustar pravipravimodule Icalendar module Values class Text < Value def initialize(value, params = {}) value = value.gsub('\n', "\n") value.gsub!('\,', ',') value.gsub!('\;', ';') value.gsub!('\\\\') { '\\' } super value, params end def value_ical value.dup.tap do |v| v.gsub!('\\') { '\\\\' } v.gsub!(';', '\;') v.gsub!(',', '\,') v.gsub!(/\r?\n/, '\n') end end end end end icalendar-2.4.1/lib/icalendar.rb0000644000175100017510000000143313317103517015505 0ustar pravipravirequire 'icalendar/logger' module Icalendar MAX_LINE_LENGTH = 75 def self.logger @logger ||= Icalendar::Logger.new(STDERR) end def self.logger=(logger) @logger = logger end def self.parse(source, single = false) warn "**** DEPRECATION WARNING ****\nIcalendar.parse will be removed. Please switch to Icalendar::Calendar.parse." calendars = Parser.new(source).parse single ? calendars.first : calendars end end require 'icalendar/has_properties' require 'icalendar/has_components' require 'icalendar/component' require 'icalendar/value' require 'icalendar/alarm' require 'icalendar/event' require 'icalendar/todo' require 'icalendar/journal' require 'icalendar/freebusy' require 'icalendar/timezone' require 'icalendar/calendar' require 'icalendar/parser' icalendar-2.4.1/spec/0000755000175100017510000000000013317103517013421 5ustar pravipraviicalendar-2.4.1/spec/event_spec.rb0000644000175100017510000001052113317103517016100 0ustar pravipravirequire 'spec_helper' describe Icalendar::Event do describe '#dtstart' do context 'no parent' do it 'is invalid if not set' do expect(subject).to_not be_valid end it 'is valid if set' do subject.dtstart = DateTime.now expect(subject).to be_valid end end context 'with parent' do before(:each) { subject.parent = Icalendar::Calendar.new } it 'is invalid without method set' do expect(subject).to_not be_valid end it 'is valid with parent method set' do subject.parent.ip_method = 'UPDATE' expect(subject).to be_valid end end end context 'mutually exclusive values' do before(:each) { subject.dtstart = DateTime.now } it 'is invalid if both dtend and duration are set' do subject.dtend = Date.today + 1; subject.duration = 'PT15M' expect(subject).to_not be_valid end it 'is valid if dtend is set' do subject.dtend = Date.today + 1; expect(subject).to be_valid end it 'is valid if duration is set' do subject.duration = 'PT15M' expect(subject).to be_valid end end context 'suggested single values' do before(:each) do subject.dtstart = DateTime.now subject.append_rrule double('RRule').as_null_object subject.append_rrule double('RRule').as_null_object end it 'is valid by default' do expect(subject).to be_valid end it 'is invalid with strict checking' do expect(subject.valid?(true)).to be false end end context 'multi values' do describe '#comment' do it 'will return an array when set singly' do subject.comment = 'a comment' expect(subject.comment).to eq ['a comment'] end it 'can be appended' do subject.comment << 'a comment' subject.comment << 'b comment' expect(subject.comment).to eq ['a comment', 'b comment'] end it 'can be added' do subject.append_comment 'a comment' expect(subject.comment).to eq ['a comment'] end end if defined? ActiveSupport describe '#rdate' do it 'does not convert a DateTime delegating for an ActiveSupport::TimeWithZone into an Array' do timestamp = '20140130T230000Z' expected = [Icalendar::Values::DateTime.new(timestamp)] subject.rdate = timestamp expect(subject.rdate).to eq(expected) end end end end describe '.parse' do let(:source) { File.read File.join(File.dirname(__FILE__), 'fixtures', fn) } let(:fn) { 'event.ics' } it 'should return an events array' do events = Icalendar::Event.parse(source) expect(events).to be_instance_of Array expect(events.count).to be 1 expect(events.first).to be_instance_of Icalendar::Event end end describe '#find_alarm' do it 'should not respond_to find_alarm' do expect(subject.respond_to?(:find_alarm)).to be false end end describe '#has_alarm?' do context 'without a set valarm' do it { is_expected.not_to have_alarm } end context 'with a set valarm' do before { subject.alarm } it { is_expected.to have_alarm } end end describe '#to_ical' do before(:each) do subject.dtstart = "20131227T013000Z" subject.dtend = "20131227T033000Z" subject.summary = 'My event, my ical, my test' subject.geo = [41.230896,-74.411774] subject.x_custom_property = 'customize' end it { expect(subject.to_ical).to include 'DTSTART:20131227T013000Z' } it { expect(subject.to_ical).to include 'DTEND:20131227T033000Z' } it { expect(subject.to_ical).to include 'SUMMARY:My event\, my ical\, my test' } it { expect(subject.to_ical).to include 'X-CUSTOM-PROPERTY:customize' } it { expect(subject.to_ical).to include 'GEO:41.230896;-74.411774' } context 'simple organizer' do before :each do subject.organizer = 'mailto:jsmith@example.com' end it { expect(subject.to_ical).to include 'ORGANIZER:mailto:jsmith@example.com' } end context 'complex organizer' do before :each do subject.organizer = Icalendar::Values::CalAddress.new("mailto:jsmith@example.com", cn: 'John Smith') end it { expect(subject.to_ical).to include 'ORGANIZER;CN=John Smith:mailto:jsmith@example.com' } end end end icalendar-2.4.1/spec/roundtrip_spec.rb0000644000175100017510000001113413317103517017006 0ustar pravipravirequire 'spec_helper' describe Icalendar do describe 'single event round trip' do let(:source) { File.read File.join(File.dirname(__FILE__), 'fixtures', 'single_event.ics') } it 'will generate the same file as is parsed' do ical = Icalendar::Calendar.parse(source).first.to_ical expect(ical).to eq source end it 'array properties can be assigned to a new event' do event = Icalendar::Event.new parsed = Icalendar::Calendar.parse(source).first event.rdate = parsed.events.first.rdate expect(event.rdate.first).to be_kind_of Icalendar::Values::Array expect(event.rdate.first.ical_params).to eq 'tzid' => ['US-Mountain'] end end describe 'timezone round trip' do let(:source) { File.read File.join(File.dirname(__FILE__), 'fixtures', 'timezone.ics') } it 'will generate the same file as it parsed' do ical = Icalendar::Calendar.parse(source).first.to_ical expect(ical).to eq source end end describe 'non-default values' do let(:source) { File.read File.join(File.dirname(__FILE__), 'fixtures', 'nondefault_values.ics') } subject { Icalendar::Calendar.parse(source).first.events.first } it 'will set dtstart to Date' do expect(subject.dtstart.value).to eq ::Date.new(2006, 12, 15) end it 'will set dtend to Date' do expect(subject.dtend.value).to eq ::Date.new(2006, 12, 15) end it 'will output value param on dtstart' do expect(subject.dtstart.to_ical(subject.class.default_property_types['dtstart'])).to match /^;VALUE=DATE:20061215$/ end it 'will output value param on dtend' do expect(subject.dtend.to_ical(subject.class.default_property_types['dtend'])).to match /^;VALUE=DATE:20061215$/ end end describe 'sorting daily events' do let(:source) { File.read File.join(File.dirname(__FILE__), 'fixtures', 'two_day_events.ics') } subject { Icalendar::Calendar.parse(source).first.events } it 'sorts day events' do events = subject.sort_by(&:dtstart) expect(events.first.dtstart).to eq ::Date.new(2014, 7, 13) expect(events.last.dtstart).to eq ::Date.new(2014, 7, 14) end end describe 'sorting time events' do let(:source) { File.read File.join(File.dirname(__FILE__), 'fixtures', 'two_time_events.ics') } subject { Icalendar::Calendar.parse(source).first.events } it 'sorts time events by start time' do events = subject.sort_by(&:dtstart) expect(events.first.dtstart).to eq ::DateTime.new(2014, 7, 14, 9, 0, 0, '-4') expect(events.last.dtstart).to eq ::DateTime.new(2014, 7, 14, 9, 1, 0, '-4') expect(events.last.dtend).to eq ::DateTime.new(2014, 7, 14, 9, 59, 0, '-4') end it 'sorts time events by end time' do events = subject.sort_by(&:dtend) expect(events.first.dtstart).to eq ::DateTime.new(2014, 7, 14, 9, 1, 0, '-4') expect(events.first.dtend).to eq ::DateTime.new(2014, 7, 14, 9, 59, 0, '-4') expect(events.last.dtstart).to eq ::DateTime.new(2014, 7, 14, 9, 0, 0, '-4') end end describe 'sorting date / time events' do let(:source) { File.read File.join(File.dirname(__FILE__), 'fixtures', 'two_date_time_events.ics') } subject { Icalendar::Calendar.parse(source).first.events } it 'sorts time events' do events = subject.sort_by(&:dtstart) expect(events.first.dtstart).to eq ::Date.new(2014, 7, 14) expect(events.last.dtstart).to eq ::DateTime.new(2014, 7, 14, 9, 0, 0, '-4') end end describe 'non-standard values' do if defined? File::NULL before(:all) { Icalendar.logger = Icalendar::Logger.new File::NULL } after(:all) { Icalendar.logger = nil } end let(:source) { File.read File.join(File.dirname(__FILE__), 'fixtures', 'nonstandard.ics') } subject { Icalendar::Parser.new(source, strict) } context 'strict parser' do let(:strict) { true } specify { expect { subject.parse }.to raise_error(NoMethodError) } end context 'lenient parser' do let(:strict) { false } specify { expect { subject.parse }.to_not raise_error } context 'saves non-standard fields' do let(:parsed) { subject.parse.first.events.first } specify { expect(parsed.custom_property('customfield').first).to eq 'Not properly noted as custom with X- prefix.' } specify { expect(parsed.custom_property('CUSTOMFIELD').first).to eq 'Not properly noted as custom with X- prefix.' } end it 'can output custom fields' do ical = subject.parse.first.to_ical expect(ical).to include 'CUSTOMFIELD:Not properly noted as custom with X- prefix.' end end end end icalendar-2.4.1/spec/spec_helper.rb0000644000175100017510000000140713317103517016241 0ustar pravipravirequire 'simplecov' SimpleCov.start do add_filter '/spec/' end # This file was generated by the `rspec --init` command. Conventionally, all # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. # Require this file using `require "spec_helper"` to ensure that it is only # loaded once. # # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration require 'timecop' require 'icalendar' RSpec.configure do |config| config.run_all_when_everything_filtered = true config.filter_run :focus # Run specs in random order to surface order dependencies. If you find an # order dependency and want to debug it, you can fix the order by providing # the seed, which is printed after each run. # --seed 1234 config.order = 'random' end icalendar-2.4.1/spec/todo_spec.rb0000644000175100017510000000075413317103517015733 0ustar pravipravirequire 'spec_helper' describe Icalendar::Todo do describe '#dtstart' do it 'is not normally required' do subject.dtstart = nil expect(subject).to be_valid end context 'with duration set' do before(:each) { subject.duration = 'PT15M' } it 'is invalid if not set' do expect(subject).to_not be_valid end it 'is valid when set' do subject.dtstart = Date.today expect(subject).to be_valid end end end end icalendar-2.4.1/spec/downcased_hash_spec.rb0000644000175100017510000000240413317103517017732 0ustar pravipravirequire 'spec_helper' describe Icalendar::DowncasedHash do subject { described_class.new base } let(:base) { {'hello' => 'world'} } describe '#[]=' do it 'sets a new value' do subject['FOO'] = 'bar' expect(subject['foo']).to eq 'bar' end end describe '#[]' do it 'gets an already set value' do subject['foo'] = 'bar' expect(subject['FOO']).to eq 'bar' end end describe '#has_key?' do it 'correctly identifies keys in the hash' do expect(subject.has_key? 'hello').to be true expect(subject.has_key? 'HELLO').to be true end end describe '#delete' do context 'no block' do it 'removes the key' do subject.delete 'HELLO' expect(subject.has_key? 'hello').to be false end end context 'with a block' do it 'calls the block when the key is not found' do expect { |b| subject.delete 'nokey', &b }.to yield_with_args('nokey') end end end describe 'DowncasedHash()' do it 'returns self when passed an DowncasedHash' do expect(Icalendar::DowncasedHash(subject)).to be subject end it 'wraps a hash in an downcased hash' do expect(Icalendar::DowncasedHash(base)).to be_kind_of Icalendar::DowncasedHash end end end icalendar-2.4.1/spec/alarm_spec.rb0000644000175100017510000000614313317103517016060 0ustar pravipravirequire 'spec_helper' describe Icalendar::Alarm do # currently no behavior in Alarm not tested other places describe '#valid?' do subject do described_class.new.tap do |a| a.action = 'AUDIO' a.trigger = Icalendar::Values::DateTime.new(Time.now.utc) end end context 'neither duration or repeat is set' do it { should be_valid } end context 'both duration and repeat are set' do before(:each) do subject.duration = 'PT15M' subject.repeat = 4 end it { should be_valid } end context 'only duration is set' do before(:each) { subject.duration = 'PT15M' } it { should_not be_valid } end context 'only repeat is set' do before(:each) { subject.repeat = 4 } it { should_not be_valid } end context 'display action' do before(:each) { subject.action = 'DISPLAY' } it 'requires description' do expect(subject).to_not be_valid subject.description = 'Display Text' expect(subject).to be_valid end end context 'email action' do before(:each) { subject.action = 'EMAIL' } context 'requires subject and body' do before(:each) { subject.attendee = ['mailto:test@email.com'] } it 'requires description' do subject.summary = 'Email subject' expect(subject).to_not be_valid subject.description = 'Email Body' expect(subject).to be_valid end it 'requires summary' do subject.description = 'Email body' expect(subject).to_not be_valid subject.summary = 'Email subject' expect(subject).to be_valid end end context 'attendees are required' do before(:each) do subject.summary = 'subject' subject.description = 'body' end it 'must be present' do subject.attendee = nil expect(subject).to_not be_valid end it 'can be single' do subject.attendee << 'mailto:test@email.com' expect(subject).to be_valid end it 'can be multi' do subject.attendee << 'mailto:test@email.com' subject.attendee << 'mailto:email@test.com' expect(subject).to be_valid end end end context 'strict validations check parent' do subject do described_class.new.tap do |a| a.action = 'AUDIO' a.trigger = Icalendar::Values::DateTime.new(Time.now.utc) end end specify { expect(subject.valid? true).to be true } context 'with parent' do before(:each) { subject.parent = parent } context 'event' do let(:parent) { Icalendar::Event.new } specify { expect(subject.valid? true).to be true } end context 'todo' do let(:parent) { Icalendar::Todo.new } specify { expect(subject.valid? true).to be true } end context 'journal' do let(:parent) { Icalendar::Journal.new } specify { expect(subject.valid? true).to be false } end end end end end icalendar-2.4.1/spec/fixtures/0000755000175100017510000000000013317103517015272 5ustar pravipraviicalendar-2.4.1/spec/fixtures/single_event_bad_dtstart.ics0000644000175100017510000000126213317103517023030 0ustar pravipraviBEGIN:VCALENDAR VERSION:2.0 PRODID:bsprodidfortestabc123 CALSCALE:GREGORIAN BEGIN:VEVENT DTSTAMP:20050118T211523Z UID:bsuidfortestabc123 DTSTART:20050120 DTEND;TZID=US-Mountain:20050120T184500 CLASS:PRIVATE GEO:37.386013;-122.0829322 ORGANIZER:mailto:joebob@random.net PRIORITY:2 SUMMARY:This is a really long summary to test the method of unfolding lines \, so I'm just going to make it a whole bunch of lines. ATTACH:http://bush.sucks.org/impeach/him.rhtml ATTACH:http://corporations-dominate.existence.net/why.rhtml RDATE;TZID=US-Mountain:20050121T170000,20050122T170000 X-TEST-COMPONENT;QTEST="Hello, World":Shouldn't double double quotes END:VEVENT END:VCALENDAR icalendar-2.4.1/spec/fixtures/two_time_events.ics0000644000175100017510000000277613317103517021221 0ustar pravipraviBEGIN:VCALENDAR METHOD:PUBLISH VERSION:2.0 X-WR-CALNAME:TMP PRODID:-//Apple Inc.//Mac OS X 10.9.4//EN X-APPLE-CALENDAR-COLOR:#0E61B9 X-WR-TIMEZONE:America/New_York CALSCALE:GREGORIAN BEGIN:VTIMEZONE TZID:America/New_York BEGIN:DAYLIGHT TZOFFSETFROM:-0500 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU DTSTART:20070311T020000 TZNAME:EDT TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:STANDARD TZOFFSETFROM:-0400 RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU DTSTART:20071104T020000 TZNAME:EST TZOFFSETTO:-0500 END:STANDARD END:VTIMEZONE BEGIN:VEVENT STATUS:CONFIRMED CREATED:20140731T162556Z UID:7C4A5440-ABA5-4B32-A69D-AFD243B39FE1 DTEND;TZID=America/New_York:20140714T100000 TRANSP:OPAQUE SUMMARY:9am LAST-MODIFIED:20140731T162556Z DTSTAMP:20140731T164042Z DTSTART;TZID=America/New_York:20140714T090000 LOCATION: SEQUENCE:13 BEGIN:VALARM X-WR-ALARMUID:6F54CD66-F2A9-491D-8892-7E3209F6A2E4 UID:6F54CD66-F2A9-491D-8892-7E3209F6A2E4 TRIGGER;VALUE=DATE-TIME:19760401T005545Z ACTION:NONE END:VALARM END:VEVENT BEGIN:VEVENT STATUS:CONFIRMED CREATED:20140731T162556Z UID:A50FA12F-BCA9-4822-A61D-E86EB7440A16 DTEND;TZID=America/New_York:20140714T095900 TRANSP:OPAQUE SUMMARY:9:01-9:59 LAST-MODIFIED:20140731T162556Z DTSTAMP:20140731T164405Z DTSTART;TZID=America/New_York:20140714T090100 LOCATION: SEQUENCE:16 BEGIN:VALARM X-WR-ALARMUID:295F2CF7-1C48-4E76-A8FC-718A1EBEDB76 UID:295F2CF7-1C48-4E76-A8FC-718A1EBEDB76 TRIGGER;VALUE=DATE-TIME:19760401T005545Z ACTION:NONE END:VALARM END:VEVENT END:VCALENDAR icalendar-2.4.1/spec/fixtures/event.ics0000644000175100017510000000113413317103517017112 0ustar pravipraviBEGIN:VEVENT DTSTAMP:20050118T211523Z UID:bsuidfortestabc123 DTSTART;TZID=US-Mountain:20050120T170000 DTEND;TZID=US-Mountain:20050120T184500 CLASS:PRIVATE GEO:37.386013;-122.0829322 ORGANIZER:mailto:joebob@random.net PRIORITY:2 SUMMARY:This is a really long summary to test the method of unfolding lines \, so I'm just going to make it a whole bunch of lines. ATTACH:http://bush.sucks.org/impeach/him.rhtml ATTACH:http://corporations-dominate.existence.net/why.rhtml RDATE;TZID=US-Mountain:20050121T170000,20050122T170000 X-TEST-COMPONENT;QTEST="Hello, World":Shouldn't double double quotes END:VEVENT icalendar-2.4.1/spec/fixtures/nonstandard.ics0000644000175100017510000000114613317103517020307 0ustar pravipraviBEGIN:VCALENDAR VERSION:2.0 PRODID:bsprodidfortestabc123 BEGIN:VEVENT UID:bsuidfortestabc123 ORGANIZER:mailto:joebob@random.net ATTACH:http://bush.sucks.org/impeach/him.rhtml ATTACH:http://corporations-dominate.existence.net/why.rhtml SUMMARY:This is a really long summary to test the method of unfolding lines\, so I'm just going to ma ke it a whol e bunch of lines. CLASS:PRIVATE PRIORITY:2 CUSTOMFIELD:Not properly noted as custom with X- prefix. GEO:37.386013;-122.0829322 DTSTART;TZID=US-Mountain:20050120T170000 DTEND:20050120T184500 DTSTAMP:20050118T211523Z NAME:SomeName END:VEVENT END:VCALENDAR icalendar-2.4.1/spec/fixtures/two_day_events.ics0000644000175100017510000000141013317103517021020 0ustar pravipraviBEGIN:VCALENDAR METHOD:PUBLISH VERSION:2.0 X-WR-CALNAME:TMP PRODID:-//Apple Inc.//Mac OS X 10.9.4//EN X-APPLE-CALENDAR-COLOR:#0E61B9 X-WR-TIMEZONE:America/New_York CALSCALE:GREGORIAN BEGIN:VEVENT STATUS:CONFIRMED CREATED:20140731T162556Z UID:A50FA12F-BCA9-4822-A61D-E86EB7440A16 DTEND;VALUE=DATE:20140715 TRANSP:TRANSPARENT SUMMARY:Monday LAST-MODIFIED:20140731T162556Z DTSTAMP:20140731T162628Z DTSTART;VALUE=DATE:20140714 LOCATION: SEQUENCE:3 END:VEVENT BEGIN:VEVENT STATUS:CONFIRMED CREATED:20140731T162556Z UID:7C4A5440-ABA5-4B32-A69D-AFD243B39FE1 DTEND;VALUE=DATE:20140714 TRANSP:TRANSPARENT SUMMARY:Sunday LAST-MODIFIED:20140731T162556Z DTSTAMP:20140731T162611Z DTSTART;VALUE=DATE:20140713 LOCATION: SEQUENCE:5 END:VEVENT END:VCALENDAR icalendar-2.4.1/spec/fixtures/two_date_time_events.ics0000644000175100017510000000273513317103517022211 0ustar pravipraviBEGIN:VCALENDAR METHOD:PUBLISH VERSION:2.0 X-WR-CALNAME:TMP PRODID:-//Apple Inc.//Mac OS X 10.9.4//EN X-APPLE-CALENDAR-COLOR:#0E61B9 X-WR-TIMEZONE:America/New_York CALSCALE:GREGORIAN BEGIN:VTIMEZONE TZID:America/New_York BEGIN:DAYLIGHT TZOFFSETFROM:-0500 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU DTSTART:20070311T020000 TZNAME:EDT TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:STANDARD TZOFFSETFROM:-0400 RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU DTSTART:20071104T020000 TZNAME:EST TZOFFSETTO:-0500 END:STANDARD END:VTIMEZONE BEGIN:VEVENT STATUS:CONFIRMED CREATED:20140731T162556Z UID:7C4A5440-ABA5-4B32-A69D-AFD243B39FE1 DTEND;TZID=America/New_York:20140714T100000 TRANSP:OPAQUE SUMMARY:9am LAST-MODIFIED:20140731T162556Z DTSTAMP:20140731T164042Z DTSTART;TZID=America/New_York:20140714T090000 LOCATION: SEQUENCE:13 BEGIN:VALARM X-WR-ALARMUID:6F54CD66-F2A9-491D-8892-7E3209F6A2E4 UID:6F54CD66-F2A9-491D-8892-7E3209F6A2E4 TRIGGER;VALUE=DATE-TIME:19760401T005545Z ACTION:NONE END:VALARM END:VEVENT BEGIN:VEVENT STATUS:CONFIRMED CREATED:20140731T162556Z UID:A50FA12F-BCA9-4822-A61D-E86EB7440A16 DTEND;VALUE=DATE:20140715 TRANSP:TRANSPARENT SUMMARY:All Day LAST-MODIFIED:20140731T162556Z DTSTAMP:20140731T164829Z DTSTART;VALUE=DATE:20140714 LOCATION: SEQUENCE:18 BEGIN:VALARM X-WR-ALARMUID:295F2CF7-1C48-4E76-A8FC-718A1EBEDB76 UID:295F2CF7-1C48-4E76-A8FC-718A1EBEDB76 TRIGGER;VALUE=DATE-TIME:19760401T005545Z ACTION:NONE END:VALARM END:VEVENT END:VCALENDAR icalendar-2.4.1/spec/fixtures/nondefault_values.ics0000644000175100017510000000156613317103517021520 0ustar pravipraviBEGIN:VCALENDAR VERSION:2.0 CALSCALE:GREGORIAN PRODID:iCalendar-Ruby X-WR-CALNAME:Test Long Description BEGIN:VEVENT DESCRIPTION:FULL DETAILS:\nhttp://test.com/events/570\n\nCary Brothers walk s the same musical ground as Pete Yorn\, Nick Drake\, Jeff Buckley and othe rs\; crafting emotional melodies\, with strong vocals and thoughtful lyrics . Brett Dennen has "\;that thing."\; Inspired fans describe it: &qu ot\;lush shimmering vocals\, an intricately groovin'\; guitar style\, a lyrical beauty rare in a young songwriter\,"\; and "\;this soulful blend of everything that feels good."\; Rising up around him is music\; transcending genres\, genders and generations. DTEND;VALUE=DATE:20061215 DTSTAMP:20061215T114034 DTSTART;VALUE=DATE:20061215 SEQUENCE:1001 SUMMARY:DigiWorld 2006 UID:foobar URL:http://test.com/events/644 END:VEVENT END:VCALENDAR icalendar-2.4.1/spec/fixtures/two_events.ics0000644000175100017510000000164613317103517020176 0ustar pravipraviBEGIN:VEVENT DTSTAMP:20050118T211523Z UID:bsuidfortestabc123 DTSTART;TZID=US-Mountain:20050120T170000 DTEND;TZID=US-Mountain:20050120T184500 CLASS:PRIVATE GEO:37.386013;-122.0829322 ORGANIZER:mailto:joebob@random.net PRIORITY:2 SUMMARY:This is a really long summary to test the method of unfolding lines \, so I'm just going to make it a whole bunch of lines. ATTACH:http://bush.sucks.org/impeach/him.rhtml ATTACH:http://corporations-dominate.existence.net/why.rhtml RDATE;TZID=US-Mountain:20050121T170000,20050122T170000 X-TEST-COMPONENT;QTEST="Hello, World":Shouldn't double double quotes END:VEVENT BEGIN:VEVENT DTSTAMP:20110118T211523Z UID:uid-1234-uid-4321 DTSTART;TZID=US-Mountain:20110120T170000 DTEND;TZID=US-Mountain:20110120T184500 CLASS:PRIVATE GEO:37.386013;-122.0829322 ORGANIZER:mailto:jmera@jmera.human PRIORITY:2 SUMMARY:This is a very short summary. RDATE;TZID=US-Mountain:20110121T170000,20110122T170000 END:VEVENT icalendar-2.4.1/spec/fixtures/single_event.ics0000644000175100017510000000144413317103517020457 0ustar pravipraviBEGIN:VCALENDAR VERSION:2.0 PRODID:bsprodidfortestabc123 CALSCALE:GREGORIAN BEGIN:VEVENT DTSTAMP:20050118T211523Z UID:bsuidfortestabc123 DTSTART;TZID=US-Mountain:20050120T170000 DTEND;TZID=US-Mountain:20050120T184500 CLASS:PRIVATE GEO:37.386013;-122.0829322 ORGANIZER:mailto:joebob@random.net PRIORITY:2 SUMMARY:This is a really long summary to test the method of unfolding lines \, so I'm just going to make it a whole bunch of lines. With a twist: a " รถ" takes up multiple bytes\, and should be wrapped to the next line. ATTACH:http://bush.sucks.org/impeach/him.rhtml ATTACH:http://corporations-dominate.existence.net/why.rhtml RDATE;TZID=US-Mountain:20050121T170000,20050122T170000 X-TEST-COMPONENT;QTEST="Hello, World":Shouldn't double double quotes END:VEVENT END:VCALENDAR icalendar-2.4.1/spec/fixtures/single_event_bad_line.ics0000644000175100017510000000132613317103517022273 0ustar pravipraviBEGIN:VCALENDAR VERSION:2.0 PRODID:bsprodidfortestabc123 CALSCALE:GREGORIAN BEGIN:VEVENT DTSTAMP:20050118T211523Z UID:bsuidfortestabc123 DTSTART;TZID=US-Mountain:20050120T170000 DTEND;TZID=US-Mountain:20050120T184500 CLASS:PRIVATE GEO:37.386013;-122.0829322 ORGANIZER:mailto:joebob@random.net PRIORITY:2 SUMMARY:This is a really long summary to test the method of unfolding lines \, so I'm just going to make it a whole bunch of lines. ATTACH:http://bush.sucks.org/impeach/him.rhtml ATTACH:http://corporations-dominate.existence.net/why.rhtml RDATE;TZID=US-Mountain:20050121T170000,20050122T170000 X-TEST-COMPONENT;QTEST="Hello, World":Shouldn't double double quotes X-NO-VALUE END:VEVENT END:VCALENDAR icalendar-2.4.1/spec/fixtures/timezone.ics0000644000175100017510000000137413317103517017631 0ustar pravipraviBEGIN:VCALENDAR VERSION:2.0 PRODID:icalendar-ruby CALSCALE:GREGORIAN BEGIN:VTIMEZONE TZID:America/New_York BEGIN:DAYLIGHT DTSTART:20070311T020000 TZOFFSETFROM:-0500 TZOFFSETTO:-0400 RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3 TZNAME:EDT END:DAYLIGHT BEGIN:STANDARD DTSTART:20071104T020000 TZOFFSETFROM:-0400 TZOFFSETTO:-0500 RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11 TZNAME:EST END:STANDARD END:VTIMEZONE BEGIN:VEVENT DTSTAMP:20120320T014112Z UID:9B9CB907-F25F-4D27-B6F3-6DE3A3397BC2 DTSTART;TZID=America/New_York:20120323T190000 DTEND;TZID=America/New_York:20120323T230000 CREATED:20120312T191609Z LAST-MODIFIED:20120320T014105Z LOCATION:Baltimore SEQUENCE:0 STATUS:CONFIRMED SUMMARY:Eastern Event TRANSP:OPAQUE END:VEVENT END:VCALENDAR icalendar-2.4.1/spec/fixtures/recurrence.ics0000644000175100017510000000074113317103517020131 0ustar pravipraviBEGIN:VCALENDAR VERSION:2.0 PRODID:bsprodidfortestabc123 CALSCALE:GREGORIAN BEGIN:VEVENT DTSTAMP:20050118T211523Z UID:bsuidfortestabc123 DTSTART;VALUE=DATE:20050323 DTEND;VALUE=DATE:20050323 CLASS:PRIVATE ORGANIZER:mailto:joebob@random.net PRIORITY:2 SUMMARY:This is a really long summary to test the method of unfolding lines \, so I'm just going to make it a whole bunch of lines. EXDATE;VALUE=DATE:20120323,20130323 RRULE:FREQ=YEARLY END:VEVENT END:VCALENDAR icalendar-2.4.1/spec/parser_spec.rb0000644000175100017510000000524713317103517016264 0ustar pravipravirequire 'spec_helper' describe Icalendar::Parser do subject { described_class.new source, false } let(:source) { File.read File.join(File.dirname(__FILE__), 'fixtures', fn) } describe '#parse' do context 'single_event.ics' do let(:fn) { 'single_event.ics' } it 'returns an array of calendars' do expect(subject.parse).to be_instance_of Array expect(subject.parse.count).to eq 1 expect(subject.parse[0]).to be_instance_of Icalendar::Calendar end it 'properly splits multi-valued lines' do event = subject.parse.first.events.first expect(event.geo).to eq [37.386013,-122.0829322] end it 'saves params' do event = subject.parse.first.events.first expect(event.dtstart.ical_params).to eq('tzid' => ['US-Mountain']) end end context 'recurrence.ics' do let(:fn) { 'recurrence.ics' } it 'correctly parses the exdate array' do event = subject.parse.first.events.first ics = event.to_ical expect(ics).to match 'EXDATE;VALUE=DATE:20120323,20130323' end end context 'event.ics' do let(:fn) { 'event.ics' } before { subject.component_class = Icalendar::Event } it 'returns an array of events' do expect(subject.parse).to be_instance_of Array expect(subject.parse.count).to be 1 expect(subject.parse[0]).to be_instance_of Icalendar::Event end end context 'events.ics' do let(:fn) { 'two_events.ics' } before { subject.component_class = Icalendar::Event } it 'returns an array of events' do events = subject.parse expect(events.count).to be 2 expect(events.first.uid).to eq("bsuidfortestabc123") expect(events.last.uid).to eq("uid-1234-uid-4321") end end end describe '#parse with bad line' do let(:fn) { 'single_event_bad_line.ics' } it 'returns an array of calendars' do expect(subject.parse).to be_instance_of Array expect(subject.parse.count).to eq 1 expect(subject.parse[0]).to be_instance_of Icalendar::Calendar end it 'properly splits multi-valued lines' do event = subject.parse.first.events.first expect(event.geo).to eq [37.386013,-122.0829322] end it 'saves params' do event = subject.parse.first.events.first expect(event.dtstart.ical_params).to eq('tzid' => ['US-Mountain']) end end describe 'missing date value parameter' do let(:fn) { 'single_event_bad_dtstart.ics' } it 'falls back to date type for dtstart' do event = subject.parse.first.events.first expect(event.dtstart).to be_kind_of Icalendar::Values::Date end end end icalendar-2.4.1/spec/journal_spec.rb0000644000175100017510000000017013317103517016430 0ustar pravipravirequire 'spec_helper' describe Icalendar::Journal do # currently no behavior in Journal not tested other places endicalendar-2.4.1/spec/calendar_spec.rb0000644000175100017510000001120513317103517016530 0ustar pravipravirequire 'spec_helper' describe Icalendar::Calendar do context 'values' do let(:property) { 'my-value' } %w(prodid version calscale ip_method).each do |prop| it "##{prop} sets and gets" do subject.send("#{prop}=", property) expect(subject.send prop).to eq property end end it "sets and gets custom properties" do subject.x_custom_prop = property expect(subject.x_custom_prop).to eq [property] end it 'can set params on a property' do subject.prodid.ical_params = {'hello' => 'world'} expect(subject.prodid.value).to eq 'icalendar-ruby' expect(subject.prodid.ical_params).to eq('hello' => 'world') end context "required values" do it 'is not valid when prodid is not set' do subject.prodid = nil expect(subject).to_not be_valid end it 'is not valid when version is not set' do subject.version = nil expect(subject).to_not be_valid end it 'is valid when both prodid and version are set' do subject.version = '2.0' subject.prodid = 'my-product' expect(subject).to be_valid end it 'is valid by default' do expect(subject).to be_valid end end end context 'components' do let(:ical_component) { double 'Component', name: 'event', :'parent=' => nil } %w(event todo journal freebusy timezone).each do |component| it "##{component} adds a new component" do expect(subject.send "#{component}").to be_a_kind_of Icalendar::Component end it "##{component} passes a component to a block to build parts" do expect { |b| subject.send("#{component}", &b) }.to yield_with_args Icalendar::Component end it "##{component} can be passed in" do expect { |b| subject.send("#{component}", ical_component, &b) }.to yield_with_args ical_component expect(subject.send "#{component}", ical_component).to eq ical_component end end it "adds event to events list" do subject.event ical_component expect(subject.events).to eq [ical_component] end describe '#add_event' do it 'delegates to non add_ version' do expect(subject).to receive(:event).with(ical_component) subject.add_event ical_component end end describe '#find_event' do let(:ical_component) { double 'Component', uid: 'uid' } let(:other_component) { double 'Component', uid: 'other' } before(:each) do subject.events << other_component subject.events << ical_component end it 'finds by uid' do expect(subject.find_event 'uid').to eq ical_component end end describe '#find_timezone' do let(:ical_timezone) { double 'Timezone', tzid: 'Eastern' } let(:other_timezone) { double 'Timezone', tzid: 'Pacific' } before(:each) do subject.timezones << other_timezone subject.timezones << ical_timezone end it 'finds by tzid' do expect(subject.find_timezone 'Eastern').to eq ical_timezone end end it "adds reference to parent" do e = subject.event expect(e.parent).to eq subject end it 'can be added with add_x_ for custom components' do expect(subject.add_x_custom_component).to be_a_kind_of Icalendar::Component expect { |b| subject.add_x_custom_component(&b) }.to yield_with_args Icalendar::Component expect(subject.add_x_custom_component ical_component).to eq ical_component end end describe '#to_ical' do before(:each) do Timecop.freeze DateTime.new(2013, 12, 26, 5, 0, 0, '+0000') subject.event do |e| e.summary = 'An event' e.dtstart = "20140101T000000Z" e.dtend = "20140101T050000Z" e.geo = [-1.2, -2.1] end subject.freebusy do |f| f.dtstart = "20140102T080000Z" f.dtend = "20140102T100000Z" f.comment = 'Busy' end end after(:each) do Timecop.return end it 'outputs properties and components' do expected_no_uid = <<-EOICAL.gsub("\n", "\r\n") BEGIN:VCALENDAR VERSION:2.0 PRODID:icalendar-ruby CALSCALE:GREGORIAN BEGIN:VEVENT DTSTAMP:20131226T050000Z DTSTART:20140101T000000Z DTEND:20140101T050000Z GEO:-1.2;-2.1 SUMMARY:An event END:VEVENT BEGIN:VFREEBUSY DTSTAMP:20131226T050000Z DTSTART:20140102T080000Z DTEND:20140102T100000Z COMMENT:Busy END:VFREEBUSY END:VCALENDAR EOICAL expect(subject.to_ical.gsub(/^UID:.*\r\n(?: .*\r\n)*/, '')).to eq expected_no_uid end end describe '#publish' do it 'sets ip_method to "PUBLISH"' do subject.publish expect(subject.ip_method).to eq 'PUBLISH' end end end icalendar-2.4.1/spec/timezone_spec.rb0000644000175100017510000000160113317103517016610 0ustar pravipravirequire 'spec_helper' describe Icalendar::Timezone do describe "valid?" do subject { described_class.new.tap { |t| t.tzid = 'Eastern' } } context 'with both standard and daylight components' do before(:each) do subject.daylight { |d| allow(d).to receive(:valid?).and_return true } subject.standard { |s| allow(s).to receive(:valid?).and_return true } end it { should be_valid } end context 'with only standard' do before(:each) { subject.standard { |s| allow(s).to receive(:valid?).and_return true } } it { expect(subject).to be_valid } end context 'with only daylight' do before(:each) { subject.daylight { |d| allow(d).to receive(:valid?).and_return true } } it { expect(subject).to be_valid } end context 'with neither standard or daylight' do it { should_not be_valid } end end end icalendar-2.4.1/spec/tzinfo_spec.rb0000644000175100017510000000657513317103517016306 0ustar pravipravirequire 'spec_helper' require_relative '../lib/icalendar/tzinfo' describe 'TZInfo::Timezone' do let(:tz) { TZInfo::Timezone.get 'Europe/Copenhagen' } let(:date) { DateTime.new 2014 } subject { tz.ical_timezone date } describe 'daylight offset' do specify { expect(subject.daylights.first.tzoffsetto.value_ical).to eq "+0200" } specify { expect(subject.daylights.first.tzoffsetfrom.value_ical).to eq "+0100" } end describe 'standard offset' do specify { expect(subject.standards.first.tzoffsetto.value_ical).to eq "+0100" } specify { expect(subject.standards.first.tzoffsetfrom.value_ical).to eq "+0200" } end describe 'daylight recurrence rule' do specify { expect(subject.daylights.first.rrule.first.value_ical).to eq "FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3" } end describe 'standard recurrence rule' do specify { expect(subject.standards.first.rrule.first.value_ical).to eq "FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10" } end describe 'no end transition' do let(:tz) { TZInfo::Timezone.get 'Asia/Shanghai' } let(:date) { DateTime.now } it 'only creates a standard component' do expect(subject.to_ical).to eq <<-EXPECTED.gsub "\n", "\r\n" BEGIN:VTIMEZONE TZID:Asia/Shanghai BEGIN:STANDARD DTSTART:19910914T230000 TZOFFSETFROM:+0900 TZOFFSETTO:+0800 TZNAME:CST END:STANDARD END:VTIMEZONE EXPECTED end end describe 'no transition' do let(:tz) { TZInfo::Timezone.get 'UTC' } let(:date) { DateTime.now } it 'creates a standard component with equal offsets' do expect(subject.to_ical).to eq <<-EXPECTED.gsub "\n", "\r\n" BEGIN:VTIMEZONE TZID:UTC BEGIN:STANDARD DTSTART:19700101T000000 TZOFFSETFROM:+0000 TZOFFSETTO:+0000 TZNAME:UTC END:STANDARD END:VTIMEZONE EXPECTED end end describe 'dst transition' do subject { TZInfo::Timezone.get 'America/Los_Angeles' } let(:now) { subject.now } # freeze in DST transition in America/Los_Angeles before(:each) { Timecop.freeze DateTime.new(2013, 11, 03, 1, 30, 0, '-08:00') } after(:each) { Timecop.return } specify { expect { subject.ical_timezone now, nil }.to raise_error TZInfo::AmbiguousTime } specify { expect { subject.ical_timezone now, true }.not_to raise_error } specify { expect { subject.ical_timezone now, false }.not_to raise_error } context 'TZInfo::Timezone.default_dst = nil' do before(:each) { TZInfo::Timezone.default_dst = nil } specify { expect { subject.ical_timezone now }.to raise_error TZInfo::AmbiguousTime } end context 'TZInfo::Timezone.default_dst = true' do before(:each) { TZInfo::Timezone.default_dst = true } specify { expect { subject.ical_timezone now }.not_to raise_error } end context 'TZInfo::Timezone.default_dst = false' do before(:each) { TZInfo::Timezone.default_dst = false } specify { expect { subject.ical_timezone now }.not_to raise_error } end end describe 'tzname for offset' do # Check for CET/CEST correctness, which doesn't follow # the more common *ST/*DT style abbreviations. let(:tz) { TZInfo::Timezone.get 'Europe/Prague' } let(:ical_tz) { tz.ical_timezone date } describe '#daylight' do subject(:tzname) { ical_tz.daylights.first.tzname.first } it { should eql 'CEST' } end describe '#standard' do subject(:tzname) { ical_tz.standards.first.tzname.first } it { should eql 'CET' } end end end icalendar-2.4.1/spec/freebusy_spec.rb0000644000175100017510000000017113317103517016603 0ustar pravipravirequire 'spec_helper' describe Icalendar::Freebusy do # currently no behavior in Journal not tested other places endicalendar-2.4.1/spec/values/0000755000175100017510000000000013317103517014720 5ustar pravipraviicalendar-2.4.1/spec/values/text_spec.rb0000644000175100017510000000506513317103517017251 0ustar pravipravirequire 'spec_helper' describe Icalendar::Values::Text do subject { described_class.new value } let(:unescaped) { "This \\ that, semi; colons\r\nAnother line: \"why not?\"" } let(:escaped) { 'This \\\\ that\, semi\; colons\nAnother line: "why not?"' } describe '#value_ical' do let(:value) { unescaped } it 'escapes \ , ; NL' do expect(subject.value_ical).to eq escaped end end describe 'unescapes in initializer' do context 'given escaped version' do let(:unescaped_no_cr) { unescaped.gsub "\r", '' } let(:value) { escaped } it 'removes escaping' do expect(subject.value).to eq unescaped_no_cr end end context 'given unescaped version' do let(:value) { unescaped } it 'does not try to double unescape' do expect(subject.value).to eq unescaped end end end describe 'escapes parameter text properly' do subject { described_class.new escaped, {'param' => param_value} } context 'single value, no special characters' do let(:param_value) { 'HelloWorld' } it 'does not wrap param in double quotes' do expect(subject.params_ical).to eq %(;PARAM=HelloWorld) end end context 'single value, special characters' do let(:param_value) { 'Hello:World' } it 'wraps param value in double quotes' do expect(subject.params_ical).to eq %(;PARAM="Hello:World") end end context 'single value, double quotes' do let(:param_value) { 'Hello "World"' } it 'replaces double quotes with single' do expect(subject.params_ical).to eq %(;PARAM=Hello 'World') end end context 'multiple values, no special characters' do let(:param_value) { ['HelloWorld', 'GoodbyeMoon'] } it 'joins with comma' do expect(subject.params_ical).to eq %(;PARAM=HelloWorld,GoodbyeMoon) end end context 'multiple values, with special characters' do let(:param_value) { ['Hello, World', 'GoodbyeMoon'] } it 'quotes values with special characters, joins with comma' do expect(subject.params_ical).to eq %(;PARAM="Hello, World",GoodbyeMoon) end end context 'multiple values, double quotes' do let(:param_value) { ['Hello, "World"', 'GoodbyeMoon'] } it 'replaces double quotes with single' do expect(subject.params_ical).to eq %(;PARAM="Hello, 'World'",GoodbyeMoon) end end context 'nil value' do let(:param_value) { nil } it 'trats nil as blank' do expect(subject.params_ical).to eq %(;PARAM=) end end end end icalendar-2.4.1/spec/values/date_time_spec.rb0000644000175100017510000000533413317103517020217 0ustar pravipravirequire 'spec_helper' describe Icalendar::Values::DateTime do subject { described_class.new value, params } let(:value) { '20140209T194355Z' } let(:params) { {} } # not sure how to automatically test both sides of this. # For now - relying on commenting out dev dependency in gemspec # AND uninstalling gem manually if defined? ActiveSupport context 'with ActiveSupport' do it 'parses a string to a ActiveSupport::TimeWithZone instance' do expect(subject.value).to be_a_kind_of ActiveSupport::TimeWithZone expect(subject.value_ical).to eq value end context 'local time' do let(:value) { '20140209T160652' } let(:params) { {'tzid' => 'America/Denver'} } it 'parses the value as local time' do expect(subject.value.hour).to eq 16 expect(subject.value.utc_offset).to eq -25200 expect(subject.value.utc.hour).to eq 23 end end end else context 'without ActiveSupport' do it 'parses a string to a DateTime instance' do expect(subject.value).to be_a_kind_of ::DateTime end context 'local time' do let(:value) { '20140209T160652' } let(:params) { {'tzid' => 'America/Denver'} } it 'parses the value as local time' do expect(subject.value.hour).to eq 16 # TODO better offset support without ActiveSupport #expect(subject.offset).to eq Rational(-7, 24) end end end end context 'common tests' do it 'does not add any tzid parameter to output' do expect(subject.to_ical described_class).to eq ":#{value}" end context 'manually set UTC' do let(:value) { '20140209T194355' } let(:params) { {'TZID' => 'UTC'} } it 'does not add a tzid parameter, but does add a Z' do expect(subject.to_ical described_class).to eq ":#{value}Z" end end context 'local time' do let(:value) { '20140209T160652' } let(:params) { {'tzid' => 'America/Denver'} } it 'keeps value and tzid as localtime on output' do expect(subject.to_ical described_class).to eq ";TZID=America/Denver:#{value}" end end context 'floating local time' do let(:value) { '20140209T162845' } it 'keeps the value as a DateTime' do expect(subject.value).to be_a_kind_of ::DateTime end it 'does not append a Z on output' do expect(subject.to_ical described_class).to eq ":#{value}" end end context 'unparseable time' do let(:value) { 'unparseable_time' } it 'raises an error including the unparseable time' do expect { subject }.to raise_error(ArgumentError, %r{Failed to parse \"#{value}\"}) end end end end icalendar-2.4.1/spec/values/period_spec.rb0000644000175100017510000000262713317103517017550 0ustar pravipravirequire 'spec_helper' require 'ostruct' describe Icalendar::Values::Period do subject { described_class.new value } context 'date-time/date-time' do let(:value) { '19830507T000600Z/20140128T201400Z' } describe '#value_ical' do specify { expect(subject.value_ical).to eq value } end describe '#period_start' do specify { expect(subject.period_start).to eq DateTime.new(1983, 5, 7, 0, 6) } end describe '#duration' do specify { expect(subject.duration).to be_nil } end describe '#explicit_end' do specify { expect(subject.explicit_end).to eq DateTime.new(2014, 01, 28, 20, 14) } end end context 'date-time/duration' do let(:value) { '19830507T000600Z/P1604W' } let(:expected_duration) { OpenStruct.new past: false, weeks: 1604, days: 0, hours: 0, minutes: 0, seconds: 0 } describe '#value_ical' do specify { expect(subject.value_ical).to eq value } it 'allows updating duration' do subject.duration = 'PT30M' expect(subject.value_ical).to eq '19830507T000600Z/PT30M' end end describe '#period_start' do specify { expect(subject.period_start).to eq DateTime.new(1983, 5, 7, 0, 6) } end describe '#duration' do specify { expect(subject.duration).to eq expected_duration } end describe '#explicit_end' do specify { expect(subject.explicit_end).to eq nil } end end endicalendar-2.4.1/spec/values/date_or_date_time_spec.rb0000644000175100017510000000202213317103517021703 0ustar pravipravirequire 'spec_helper' describe Icalendar::Values::DateOrDateTime do subject { described_class.new value, params } let(:params) { {} } describe '#call' do context 'DateTime value' do let(:value) { '20140209T194355Z' } it 'returns a DateTime object' do expect(subject.call).to be_a_kind_of(Icalendar::Values::DateTime) end it 'has the proper value' do expect(subject.call.value).to eq DateTime.new(2014, 2, 9, 19, 43, 55) end end context 'Date value' do let(:value) { '20140209' } it 'returns a Date object' do expect(subject.call).to be_a_kind_of(Icalendar::Values::Date) end it 'has the proper value' do expect(subject.call.value).to eq Date.new(2014, 2, 9) end end context 'unparseable date' do let(:value) { '99999999' } it 'raises an error including the unparseable time' do expect { subject.call }.to raise_error(ArgumentError, %r{Failed to parse \"#{value}\"}) end end end end icalendar-2.4.1/spec/values/utc_offset_spec.rb0000644000175100017510000000165313317103517020425 0ustar pravipravirequire 'spec_helper' describe Icalendar::Values::UtcOffset do subject { described_class.new value } describe '#value_ical' do let(:value) { '-050000' } it 'does not output seconds unless required' do expect(subject.value_ical).to eq '-0500' end context 'with seconds' do let(:value) { '+023030' } specify { expect(subject.value_ical).to eq '+023030' } end end describe '#behind?' do context 'negative offset' do let(:value) { '-0500' } specify { expect(subject.behind?).to be true } end context 'positive offset' do let(:value) { '+0200' } specify { expect(subject.behind?).to be false } end context 'no offset' do let(:value) { '-0000' } specify { expect(subject.behind?).to be false } it 'does not allow override' do subject.behind = true expect(subject.behind?).to be false end end end end icalendar-2.4.1/spec/values/duration_spec.rb0000644000175100017510000000271413317103517020110 0ustar pravipravirequire 'spec_helper' describe Icalendar::Values::Duration do subject { described_class.new value } describe '#past?' do context 'positive explicit' do let(:value) { '+P15M' } specify { expect(subject.past?).to be false } end context 'positive implicit' do let(:value) { 'P15M' } specify { expect(subject.past?).to be false } end context 'negative' do let(:value) { '-P15M' } specify { expect(subject.past?).to be true } end end describe '#days' do context 'days given' do let(:value) { 'P5DT3H' } specify { expect(subject.days).to eq 5 } end context 'no days given' do let(:value) { 'P5W' } specify { expect(subject.days).to eq 0 } end end describe '#weeks' do let(:value) { 'P3W' } specify { expect(subject.weeks).to eq 3 } end describe '#hours' do let(:value) { 'PT6H' } specify { expect(subject.hours).to eq 6 } end describe '#minutes' do let(:value) { 'P2DT5H45M12S' } specify { expect(subject.minutes).to eq 45 } end describe '#seconds' do let(:value) { '-PT5M30S' } specify { expect(subject.seconds).to eq 30 } end describe '#value_ical' do let(:value) { 'P2DT4H' } specify { expect(subject.value_ical).to eq value } end describe '#days=' do let(:value) { 'P3D' } it 'can set the number of days' do subject.days = 4 expect(subject.value_ical).to eq 'P4D' end end end icalendar-2.4.1/spec/values/recur_spec.rb0000644000175100017510000000313413317103517017400 0ustar pravipravirequire 'spec_helper' describe Icalendar::Values::Recur do subject { described_class.new value } let(:value) { 'FREQ=DAILY' } describe 'parsing' do context 'multiple bydays' do let(:value) { 'FREQ=WEEKLY;COUNT=4;BYDAY=MO,WE,FR' } specify { expect(subject.frequency).to eq 'WEEKLY' } specify { expect(subject.count).to eq 4 } specify { expect(subject.by_day).to eq %w(MO WE FR) } end context 'single byday' do let(:value) { 'FREQ=YEARLY;BYDAY=2SU;BYMONTH=3' } specify { expect(subject.frequency).to eq 'YEARLY' } specify { expect(subject.by_day).to eq %w(2SU) } specify { expect(subject.by_month).to eq [3] } end context 'neverending yearly' do let(:value) { 'FREQ=YEARLY' } specify { expect(subject.frequency).to eq 'YEARLY' } it 'can be added to another event by sending' do event = Icalendar::Event.new event.send "rrule=", subject rule = event.send "rrule" expect(rule.first.frequency).to eq 'YEARLY' end end end describe '#valid?' do it 'requires frequency' do expect(subject.valid?).to be true subject.frequency = nil expect(subject.valid?).to be false end it 'cannot have both until and count' do subject.until = '20140201' subject.count = 4 expect(subject.valid?).to be false end end describe '#value_ical' do let(:value) { 'FREQ=DAILY;BYYEARDAY=1,34,56,240;BYDAY=SU,SA' } it 'outputs in spec order' do expect(subject.value_ical).to eq 'FREQ=DAILY;BYDAY=SU,SA;BYYEARDAY=1,34,56,240' end end end icalendar-2.4.1/History.txt0000644000175100017510000001221113317103517014666 0ustar pravipravi=== 2.4.1 2016-09-03 * Fix parsing multiple calendars or components in same file - Patrick Schnetger * Fix multi-byte folding bug - Niels Laukens * Fix typos across the code - Martin Edenhofer & yuuji.yaginuma === 2.4.0 2016-07-04 * Enable parsing individual ICalendar components - Patrick Schnetger * many bug fixes. Thanks to Quan Sun, Garry Shutler, Ryan Bigg, Patrick Schnetger and others * README/documentation updates. Thanks to JonMidhir and Hendrik Sollich === 2.3.0 2015-04-26 * fix value parameter for properties with multiple values * fix error when assigning Icalendar::Values::Array to a component * Fall back to Icalendar::Values::Date if Icalendar::Values::DateTime is not given a properly formatted value * Downcase the keys in ical_params to ensure we aren't assigning both tzid and TZID === 2.2.2 2014-12-27 * add a `has_#{component}?` method for testing if component exists - John Hope * add documentation & tests for organizer attribute - Ben Walding === 2.2.1 2014-12-03 * Prevent crashes when using ActiveSupport::TimeWithZone in multi-property DateTime fields - Danny (tdg5) * Ensure TimeWithZone is loaded before using, not just ActiveSupport - Jeremy Evans * Improve error message on unparseable DateTimes - Garry Shutler === 2.2.0 2014-09-23 * Default to non-strict parsing * Enable sorting events by dtstart - Mark Rickert * Better tolerate malformed lines in parser - Garry Shutler * Deduplicate timezone code - Jan Vlnas * Eliminate warnings - rochefort === 2.1.2 2014-09-10 * Fix timezone abbreviation generation - Jan Vlnas * Fix timezone repeat rules for end of month === 2.1.1 2014-07-23 * Quiet TimeWithZone support logging * Use SecureRandom.uuid - antoinelyset === 2.1.0 2014-06-17 * Enable parsing all custom properties, not just X- prefixed ones Requires non-strict parsing * Fixed bugs when using non-MRI ruby interpreters * Fix bug copying OpenStruct-backed value types === 2.0.1 2014-04-27 * Re-add support for ruby 1.9.2 === 2.0.0 2014-04-22 * Add Icalendar.logger class & logging in Parser * Support tzinfo ~> 0.3 and ~> 1.1 === 2.0.0.beta.2 2014-04-11 * Add uid & acknowledged fields from valarm extensions * Swallow NoMethodError on non-strict parsers * Expose a parse_property method on Icalendar::Parser === 2.0.0.beta.1 2014-03-30 * Rewrite for easier development going forward. === 1.5.2 2014-02-22 * Output timezone components first * Fix undefined local variable or method 'e' - Jason Stirk === 1.5.1 2014-02-27 * Check for dtend existence before setting timezone - Jonas Grau * Clean up and refactor components - Kasper Timm Hansen === 1.5.0 2013-12-06 * Support for custom x- properties - Jake Craige === 1.4.5 2013-11-14 * Fix Geo accessor methods - bouzuya * Add ical_multiline_property :related_to for Alarm * Allow using multi setters to append single values === 1.4.4 2013-11-05 * Allow user to handle TZInfo::AmbiguousTime error - David Bradford * Better handling of multiple exdate and rdate values === 1.4.3 2013-09-18 * Fix concatenation of multiple BYWEEK or BYMONTH recurrence rules === 1.4.2 2013-09-11 * Double Quote parameter values that contain forbidden characters * Update Component#respond_to? to match Ruby 2.0 - Keith Marcum === 1.4.1 2013-06-25 * Don't escape semicolon in GEO property - temirov * Allow access to various parts of RRule class === 1.4.0 2013-05-21 * Implement ACKNOWLEDGED property for VALARM - tsuzuki08 * Output VERSION property as first line after BEGIN:VCALENDAR * Check for unbounded timezone transitions in tzinfo === 1.3.0 2013-03-31 * Lenient parsing mode ignores unknown properties - David Grandinetti * VTIMEZONE positive offsets properly have "+" prepended (Fixed issue #18) - Benjamin Jorgensen (sorry for misspelling your last name) === 1.2.4 2013-03-26 * Proxy component values now frozen in Ruby 2.0 (Fixed issue #17) * Clean up gemspec for cleaner installing via bundler/git === 1.2.3 2013-03-09 * Call `super` from Component#method_missing * Clean up warnings in test suite * Add Gemfile for installing development dependencies === 1.2.2 2013-02-16 * added TZURL property to Timezone component - spacepixels * correct days in RRule ("[TU,WE]" -> "TU,WE") - Christoph Finkensiep === 1.2.1 2012-11-12 * Adds uid property to alarms to support iCloud - Jeroen Jacobs * Fix up testing docs - Eric Carty-Fickes * Fix parsing property params that have : in them - Sean Dague * Clean up warnings in test suite - Sean Dague === 1.2.0 2012-08-30 * Fix calendar handling for dates < 1000 - Ryan Ahearn * Updated license to GPL/BSD/Ruby at users option === 1.1.6 2011-03-10 * Fix todo handling (thanks to Frank Schwarz) * clean up a number of warnings during test runs === 1.1.5 2010-06-21 * Fix for windows line endings (thanks to Rowan Collins) === 1.1.4 2010-04-23 * Fix for RRULE escaping * Fix tests so they run under 1.8.7 in multiple environments * Readme fix === 1.1.3 2010-03-15 * Revert component sorting behavior that I was trying to make the tests run more consistantly on different platforms. * Added new test for multiple events in a calendar which caught that break. === 1.1.2 2010-03-10 * Convert project to newgem to make for easier publishing icalendar-2.4.1/Gemfile0000644000175100017510000000004713317103517013763 0ustar pravipravisource 'https://rubygems.org' gemspec icalendar-2.4.1/COPYING0000644000175100017510000000523713317103517013531 0ustar pravipraviRuby is copyrighted free software by Yukihiro Matsumoto . As of Ruby 1.9.3, the Ruby Language went from a Dual GPL/Ruby license to Dual BSD/Ruby license. The intent of the icalendar license is that it is provided under the same terms as Ruby itself. The way we're going to interpret this is the software can be redistributed under any of the 3 licenses, GPL, BSD, or the conditions below, at your option: 1. You may make and give away verbatim copies of the source form of the software without restriction, provided that you duplicate all of the original copyright notices and associated disclaimers. 2. You may modify your copy of the software in any way, provided that you do at least ONE of the following: a) place your modifications in the Public Domain or otherwise make them Freely Available, such as by posting said modifications to Usenet or an equivalent medium, or by allowing the author to include your modifications in the software. b) use the modified software only within your corporation or organization. c) give non-standard binaries non-standard names, with instructions on where to get the original software distribution. d) make other distribution arrangements with the author. 3. You may distribute the software in object code or binary form, provided that you do at least ONE of the following: a) distribute the binaries and library files of the software, together with instructions (in the manual page or equivalent) on where to get the original distribution. b) accompany the distribution with the machine-readable source of the software. c) give non-standard binaries non-standard names, with instructions on where to get the original software distribution. d) make other distribution arrangements with the author. 4. You may modify and include the part of the software into any other software (possibly commercial). But some files in the distribution are not written by the author, so that they are not under these terms. For the list of those files and their copying conditions, see the file LEGAL. 5. The scripts and library files supplied as input to or produced as output from the software do not automatically fall under the copyright of the software, but belong to whomever generated them, and may be sold commercially, and may be aggregated with this software. 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. icalendar-2.4.1/.rspec0000644000175100017510000000003213317103517013577 0ustar pravipravi--color --format progress