icalendar-2.7.1/0000755000004100000410000000000014033220500013460 5ustar www-datawww-dataicalendar-2.7.1/icalendar.gemspec0000644000004100000410000000336714033220500016760 0ustar www-datawww-datarequire 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 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 = '>= 2.4.0' s.add_dependency 'ice_cube', '~> 0.16' s.add_development_dependency 'rake', '~> 13.0' s.add_development_dependency 'bundler', '~> 2.0' # test with all groups of tzinfo dependencies # tzinfo 2.x # s.add_development_dependency 'tzinfo', '~> 2.0' # s.add_development_dependency 'tzinfo-data', '~> 1.2020' # tzinfo 1.x s.add_development_dependency 'activesupport', '~> 6.0' s.add_development_dependency 'i18n', '~> 1.8' s.add_development_dependency 'tzinfo', '~> 1.2' s.add_development_dependency 'tzinfo-data', '~> 1.2020' # tzinfo 0.x # s.add_development_dependency 'tzinfo', '~> 0.3' # end tzinfo s.add_development_dependency 'timecop', '~> 0.9' s.add_development_dependency 'rspec', '~> 3.8' s.add_development_dependency 'simplecov', '~> 0.16' end icalendar-2.7.1/.travis.yml0000644000004100000410000000040614033220500015571 0ustar www-datawww-datasudo: false before_install: - gem install bundler language: ruby rvm: - 2.7 - 2.6 - 2.5 - jruby-19mode - ruby-head - jruby-head - truffleruby-head matrix: allow_failures: - rvm: ruby-head - rvm: jruby-head script: bundle exec rake spec icalendar-2.7.1/.rspec0000644000004100000410000000003214033220500014570 0ustar www-datawww-data--color --format progress icalendar-2.7.1/README.md0000644000004100000410000002543614033220500014751 0ustar www-datawww-dataiCalendar -- Internet calendaring, Ruby style === [![Build Status](https://travis-ci.com/icalendar/icalendar.svg?branch=master)](https://travis-ci.com/icalendar/icalendar) [![Code Climate](https://codeclimate.com/github/icalendar/icalendar.png)](https://codeclimate.com/github/icalendar/icalendar) ### 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)`. Calling `.call` on the returned `DateOrDateTime` will immediately return the underlying `Date` or `DateTime` object. ```ruby event = cal.event do |e| e.dtstart = Icalendar::Values::DateOrDateTime.new('20140924') 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, 1.x, and 2.x. The `tzinfo-data` gem may also be required depending on your version of `tzinfo` and potentially your operating system. #### 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.7.1/spec/0000755000004100000410000000000014033220500014412 5ustar www-datawww-dataicalendar-2.7.1/spec/fixtures/0000755000004100000410000000000014033220500016263 5ustar www-datawww-dataicalendar-2.7.1/spec/fixtures/recurrence.ics0000644000004100000410000000074114033220500021122 0ustar www-datawww-dataBEGIN: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.7.1/spec/fixtures/single_event_bad_organizer.ics0000644000004100000410000000147214033220500024337 0ustar www-datawww-dataBEGIN: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;CN=Joe Bob\: Magician: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.7.1/spec/fixtures/tzid_search.ics0000644000004100000410000000133714033220500021266 0ustar www-datawww-dataBEGIN:VCALENDAR METHOD:PUBLISH VERSION:2.0 CALSCALE:GREGORIAN BEGIN:VTIMEZONE TZID:(GMT-05:00) Eastern Time (US & Canada) BEGIN:STANDARD DTSTART:16011104T010000 RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11 TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD BEGIN:DAYLIGHT DTSTART:16010311T030000 RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3 TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT END:VTIMEZONE BEGIN:VEVENT CREATED:20120104T225934Z UID:23D50AF6-23D4-4CB5-85C6-4826F3F19999 DTEND;TZID="(GMT-05:00) Eastern Time (US & Canada)":20180104T130000 RRULE:FREQ=WEEKLY;INTERVAL=1 TRANSP:OPAQUE SUMMARY:Recurring on Wed DTSTART;TZID="(GMT-05:00) Eastern Time (US & Canada)":20180104T100000 DTSTAMP:20120104T231637Z SEQUENCE:6 END:VEVENT END:VCALENDAR icalendar-2.7.1/spec/fixtures/two_date_time_events.ics0000644000004100000410000000273514033220500023202 0ustar www-datawww-dataBEGIN: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.7.1/spec/fixtures/two_time_events.ics0000644000004100000410000000277614033220500022212 0ustar www-datawww-dataBEGIN: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.7.1/spec/fixtures/event.ics0000644000004100000410000000113414033220500020103 0ustar www-datawww-dataBEGIN: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.7.1/spec/fixtures/nonstandard.ics0000644000004100000410000000114614033220500021300 0ustar www-datawww-dataBEGIN: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.7.1/spec/fixtures/single_event_bad_dtstart.ics0000644000004100000410000000126214033220500024021 0ustar www-datawww-dataBEGIN: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.7.1/spec/fixtures/single_event_organizer_parsed.ics0000644000004100000410000000147114033220500025066 0ustar www-datawww-dataBEGIN: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;CN=Joe Bob\:magician: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.7.1/spec/fixtures/custom_component.ics0000644000004100000410000001000214033220500022350 0ustar www-datawww-dataBEGIN:VCALENDAR PRODID:-//Atlassian Confluence//Calendar Plugin 1.0//EN VERSION:2.0 CALSCALE:GREGORIAN X-WR-CALNAME:Grimsell testkalender X-WR-CALDESC: X-WR-TIMEZONE:Europe/Stockholm X-MIGRATED-FOR-USER-KEY:true METHOD:PUBLISH BEGIN:VTIMEZONE TZID:Europe/Stockholm TZURL:http://tzurl.org/zoneinfo/Europe/Stockholm SEQUENCE:9206 BEGIN:DAYLIGHT TZOFFSETFROM:+0100 TZOFFSETTO:+0200 TZNAME:CEST DTSTART:19810329T020000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU END:DAYLIGHT BEGIN:STANDARD TZOFFSETFROM:+0200 TZOFFSETTO:+0100 TZNAME:CET DTSTART:19961027T030000 RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU END:STANDARD BEGIN:STANDARD TZOFFSETFROM:+011212 TZOFFSETTO:+010014 TZNAME:SET DTSTART:18790101T000000 RDATE:18790101T000000 END:STANDARD BEGIN:STANDARD TZOFFSETFROM:+010014 TZOFFSETTO:+0100 TZNAME:CET DTSTART:19000101T000000 RDATE:19000101T000000 END:STANDARD BEGIN:DAYLIGHT TZOFFSETFROM:+0100 TZOFFSETTO:+0200 TZNAME:CEST DTSTART:19160514T230000 RDATE:19160514T230000 RDATE:19800406T020000 END:DAYLIGHT BEGIN:STANDARD TZOFFSETFROM:+0200 TZOFFSETTO:+0100 TZNAME:CET DTSTART:19161001T010000 RDATE:19161001T010000 RDATE:19800928T030000 RDATE:19810927T030000 RDATE:19820926T030000 RDATE:19830925T030000 RDATE:19840930T030000 RDATE:19850929T030000 RDATE:19860928T030000 RDATE:19870927T030000 RDATE:19880925T030000 RDATE:19890924T030000 RDATE:19900930T030000 RDATE:19910929T030000 RDATE:19920927T030000 RDATE:19930926T030000 RDATE:19940925T030000 RDATE:19950924T030000 END:STANDARD BEGIN:STANDARD TZOFFSETFROM:+0100 TZOFFSETTO:+0100 TZNAME:CET DTSTART:19800101T000000 RDATE:19800101T000000 END:STANDARD END:VTIMEZONE BEGIN:VEVENT DTSTAMP:20151203T104347Z SUMMARY:Kaffepaus UID:20151203T101856Z-1543254179@intranet.idainfront.se DESCRIPTION: ORGANIZER;X-CONFLUENCE-USER-KEY=ff80818141e4d3b60141e4d4b75700a2;CN=Magnu s Grimsell;CUTYPE=INDIVIDUAL:mailto:magnus.grimsell@idainfront.se CREATED:20151203T101856Z LAST-MODIFIED:20151203T101856Z SEQUENCE:1 X-CONFLUENCE-SUBCALENDAR-TYPE:other TRANSP:OPAQUE STATUS:CONFIRMED DTSTART:20151203T090000Z DTEND:20151203T091500Z END:VEVENT BEGIN:X-EVENT-SERIES SUMMARY:iipax - Sprints DESCRIPTION: X-CONFLUENCE-SUBCALENDAR-TYPE:jira-agile-sprint URL:jira://8112ef9a-343e-30e6-b469-4f993bf0371d?projectKey=II&dateFieldNa me=sprint END:X-EVENT-SERIES BEGIN:VEVENT DTSTAMP:20151203T104348Z DTSTART;TZID=Europe/Stockholm:20130115T170800 DTEND;TZID=Europe/Stockholm:20130204T170800 UID:3cb4df4b-eb18-43fd-934a-16c15acfb4b1-3@jira.idainfront.se X-GREENHOPPER-SPRINT-CLOSED:false X-GREENHOPPER-SPRINT-EDITABLE:true X-JIRA-PROJECT;X-JIRA-PROJECT-KEY=II:iipax X-GREENHOPPER-SPRINT-VIEWBOARDS-URL:https://jira.idainfront.se/secure/GHG oToBoard.jspa?sprintId=3 SEQUENCE:0 X-CONFLUENCE-SUBCALENDAR-TYPE:jira-agile-sprint TRANSP:OPAQUE STATUS:CONFIRMED DESCRIPTION:https://jira.idainfront.se/secure/GHGoToBoard.jspa?sprintId=3 SUMMARY:iipax - Callisto-6 END:VEVENT BEGIN:VEVENT DTSTAMP:20151203T104348Z DTSTART;TZID=Europe/Stockholm:20130219T080000 DTEND;TZID=Europe/Stockholm:20130319T095900 UID:3cb4df4b-eb18-43fd-934a-16c15acfb4b1-5@jira.idainfront.se X-GREENHOPPER-SPRINT-CLOSED:false X-GREENHOPPER-SPRINT-EDITABLE:true X-JIRA-PROJECT;X-JIRA-PROJECT-KEY=II:iipax X-GREENHOPPER-SPRINT-VIEWBOARDS-URL:https://jira.idainfront.se/secure/GHG oToBoard.jspa?sprintId=5 SEQUENCE:0 X-CONFLUENCE-SUBCALENDAR-TYPE:jira-agile-sprint TRANSP:OPAQUE STATUS:CONFIRMED DESCRIPTION:https://jira.idainfront.se/secure/GHGoToBoard.jspa?sprintId=5 SUMMARY:iipax - Bulbasaur END:VEVENT BEGIN:VEVENT DTSTAMP:20151203T104348Z DTSTART;TZID=Europe/Stockholm:20130326T105900 DTEND;TZID=Europe/Stockholm:20130419T105900 UID:3cb4df4b-eb18-43fd-934a-16c15acfb4b1-7@jira.idainfront.se X-GREENHOPPER-SPRINT-CLOSED:true X-GREENHOPPER-SPRINT-EDITABLE:true X-JIRA-PROJECT;X-JIRA-PROJECT-KEY=II:iipax X-GREENHOPPER-SPRINT-VIEWBOARDS-URL:https://jira.idainfront.se/secure/GHG oToBoard.jspa?sprintId=7 SEQUENCE:0 X-CONFLUENCE-SUBCALENDAR-TYPE:jira-agile-sprint TRANSP:OPAQUE STATUS:CONFIRMED DESCRIPTION:https://jira.idainfront.se/secure/GHGoToBoard.jspa?sprintId=7 SUMMARY:iipax - Ivysaur END:VEVENT END:VCALENDAR icalendar-2.7.1/spec/fixtures/single_event_bad_line.ics0000644000004100000410000000132614033220500023264 0ustar www-datawww-dataBEGIN: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.7.1/spec/fixtures/two_events.ics0000644000004100000410000000164614033220500021167 0ustar www-datawww-dataBEGIN: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.7.1/spec/fixtures/nondefault_values.ics0000644000004100000410000000156614033220500022511 0ustar www-datawww-dataBEGIN: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.7.1/spec/fixtures/single_event.ics0000644000004100000410000000147314033220500021452 0ustar www-datawww-dataBEGIN: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;CN="Joe Bob: Magician":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.7.1/spec/fixtures/two_day_events.ics0000644000004100000410000000141014033220500022011 0ustar www-datawww-dataBEGIN: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.7.1/spec/fixtures/bad_wrapping.ics0000644000004100000410000000054514033220500021424 0ustar www-datawww-dataBEGIN:VCALENDAR VERSION:2.0 PRODID:manual CALSCALE:GREGORIAN BEGIN:VEVENT DTSTAMP:20200902T223352Z UID:6e7d7fe5-6735-4cdd-bfe4-761dfcecd7a7 DTSTART;VALUE=DATE:20200902 DTEND;VALUE=DATE:20200903 DESCRIPTION:Event description that puts a UTF-8 multi-octet sequence right  here. SUMMARY:UTF-8 multi-octet sequence test END:VEVENT END:VCALENDAR icalendar-2.7.1/spec/fixtures/timezone.ics0000644000004100000410000000137414033220500020622 0ustar www-datawww-dataBEGIN: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.7.1/spec/journal_spec.rb0000644000004100000410000000017014033220500017421 0ustar www-datawww-datarequire 'spec_helper' describe Icalendar::Journal do # currently no behavior in Journal not tested other places endicalendar-2.7.1/spec/tzinfo_spec.rb0000644000004100000410000000657514033220500017277 0ustar www-datawww-datarequire '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:19910915T010000 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.7.1/spec/freebusy_spec.rb0000644000004100000410000000017114033220500017574 0ustar www-datawww-datarequire 'spec_helper' describe Icalendar::Freebusy do # currently no behavior in Journal not tested other places endicalendar-2.7.1/spec/alarm_spec.rb0000644000004100000410000000614314033220500017051 0ustar www-datawww-datarequire '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.7.1/spec/calendar_spec.rb0000644000004100000410000001201014033220500017514 0ustar www-datawww-datarequire 'spec_helper' describe Icalendar::Calendar do context 'marshalling' do it 'can be de/serialized' do Marshal.load(Marshal.dump(subject)) end end 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 describe '.parse' do let(:source) { File.read File.join(File.dirname(__FILE__), 'fixtures', 'bad_wrapping.ics') } it 'correctly parses a bad file' do actual = described_class.parse(source) expect(actual[0]).to be_a(described_class) end end end icalendar-2.7.1/spec/todo_spec.rb0000644000004100000410000000075414033220500016724 0ustar www-datawww-datarequire '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.7.1/spec/values/0000755000004100000410000000000014033220500015711 5ustar www-datawww-dataicalendar-2.7.1/spec/values/date_or_date_time_spec.rb0000644000004100000410000000255114033220500022703 0ustar www-datawww-datarequire '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 describe "#to_ical" do let(:event) { Icalendar::Event.new } let(:time_stamp) { Time.now.strftime Icalendar::Values::DateTime::FORMAT } it "should call parse behind the scenes" do event.dtstart = described_class.new time_stamp, "tzid" => "UTC" expect(event.to_ical).to include "DTSTART:#{time_stamp}Z" end end end icalendar-2.7.1/spec/values/duration_spec.rb0000644000004100000410000000271414033220500021101 0ustar www-datawww-datarequire '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.7.1/spec/values/text_spec.rb0000644000004100000410000000506514033220500020242 0ustar www-datawww-datarequire '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.7.1/spec/values/period_spec.rb0000644000004100000410000000262714033220500020541 0ustar www-datawww-datarequire '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.7.1/spec/values/utc_offset_spec.rb0000644000004100000410000000165314033220500021416 0ustar www-datawww-datarequire '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.7.1/spec/values/date_time_spec.rb0000644000004100000410000000533414033220500021210 0ustar www-datawww-datarequire '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.7.1/spec/values/recur_spec.rb0000644000004100000410000000313414033220500020371 0ustar www-datawww-datarequire '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.7.1/spec/timezone_spec.rb0000644000004100000410000000567114033220500017614 0ustar www-datawww-datarequire '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 context 'marshalling' do context 'with standard/daylight components' do before do subject.standard do |standard| standard.rrule = Icalendar::Values::Recur.new("FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=10") standard.dtstart = Icalendar::Values::DateTime.new("16010101T030000") standard.tzoffsetfrom = Icalendar::Values::UtcOffset.new("+0200") standard.tzoffsetto = Icalendar::Values::UtcOffset.new("+0100") end subject.daylight do |daylight| daylight.rrule = Icalendar::Values::Recur.new("FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=3") daylight.dtstart = Icalendar::Values::DateTime.new("16010101T020000") daylight.tzoffsetfrom = Icalendar::Values::UtcOffset.new("+0100") daylight.tzoffsetto = Icalendar::Values::UtcOffset.new("+0200") end end it 'can be de/serialized' do first_standard = subject.standards.first first_daylight = subject.daylights.first expect(first_standard.valid?).to be_truthy expect(first_daylight.valid?).to be_truthy # calling previous_occurrence intializes @cached_occurrences with a time that's not handled by ruby marshaller first_occurence_for = Time.new(1601, 10, 31) standard_previous_occurrence = first_standard.previous_occurrence(first_occurence_for) expect(standard_previous_occurrence).not_to be_nil daylight_previous_occurrence = first_daylight.previous_occurrence(first_occurence_for) expect(daylight_previous_occurrence).not_to be_nil deserialized = nil expect { deserialized = Marshal.load(Marshal.dump(subject)) }.not_to raise_exception expect(deserialized.standards.first.previous_occurrence(first_occurence_for)).to eq(standard_previous_occurrence) expect(deserialized.daylights.first.previous_occurrence(first_occurence_for)).to eq(daylight_previous_occurrence) end end it 'can be de/serialized' do expect { Marshal.load(Marshal.dump(subject)) }.not_to raise_exception end end end icalendar-2.7.1/spec/spec_helper.rb0000644000004100000410000000140714033220500017232 0ustar www-datawww-datarequire '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.7.1/spec/event_spec.rb0000644000004100000410000001213014033220500017067 0ustar www-datawww-datarequire '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 "#append_custom_property" do context "with custom property" do it "appends to the custom properties hash" do subject.append_custom_property "x_my_property", "test value" expect(subject.custom_properties).to eq({"x_my_property" => ["test value"]}) end end context "with a defined property" do it "sets the proper setter" do subject.append_custom_property "summary", "event" expect(subject.summary).to eq "event" expect(subject.custom_properties).to eq({}) end end end describe "#custom_property" do it "returns a default for missing properties" do expect(subject.x_doesnt_exist).to eq([]) expect(subject.custom_property "x_doesnt_exist").to eq([]) 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.7.1/spec/parser_spec.rb0000644000004100000410000000650414033220500017252 0ustar www-datawww-datarequire '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 parsed = subject.parse expect(parsed).to be_instance_of Array expect(parsed.count).to eq 1 expect(parsed[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 parsed = subject.parse expect(parsed).to be_instance_of Array expect(parsed.count).to be 1 expect(parsed[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 context 'tzid_search.ics' do let(:fn) { 'tzid_search.ics' } it 'correctly sets the weird tzid' do parsed = subject.parse event = parsed.first.events.first expect(event.dtstart.utc).to eq Time.parse("20180104T150000Z") end end context 'custom_component.ics' do let(:fn) { 'custom_component.ics' } it 'correctly handles custom named components' do parsed = subject.parse calendar = parsed.first expect(calendar.custom_component('x_event_series').size).to eq 1 expect(calendar.custom_component('X-EVENT-SERIES').size).to eq 1 end end end describe '#parse with bad line' do let(:fn) { 'single_event_bad_line.ics' } it 'returns an array of calendars' do parsed = subject.parse expect(parsed).to be_instance_of Array expect(parsed.count).to eq 1 expect(parsed[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.7.1/spec/downcased_hash_spec.rb0000644000004100000410000000240414033220500020723 0ustar www-datawww-datarequire '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.7.1/spec/roundtrip_spec.rb0000644000004100000410000001351114033220500020000 0ustar www-datawww-datarequire '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.params_ical).to eq ";TZID=US-Mountain" end end describe 'cleanly handle facebook organizers' do let(:source) { File.read File.join(File.dirname(__FILE__), 'fixtures', 'single_event_bad_organizer.ics') } let(:source_lowered_uri) { File.read File.join(File.dirname(__FILE__), 'fixtures', 'single_event_organizer_parsed.ics') } it 'will generate the same file as it parsed' do ical = Icalendar::Calendar.parse(source).first.to_ical source_equal = ical == source # rbx-3 parses the organizer as a URI, which strips the space and lowercases everything after the first : # this is correct behavior, according to the icalendar spec, so we're not fudging the parser to accomodate # facebook not properly wrapping the CN param in dquotes source_lowered_equal = ical == source_lowered_uri expect(source_equal || source_lowered_equal).to be true 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_datetime).to eq ::DateTime.new(2014, 7, 14, 9, 0, 0, '-4') expect(events.last.dtstart.to_datetime).to eq ::DateTime.new(2014, 7, 14, 9, 1, 0, '-4') expect(events.last.dtend.to_datetime).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_datetime).to eq ::DateTime.new(2014, 7, 14, 9, 1, 0, '-4') expect(events.first.dtend.to_datetime).to eq ::DateTime.new(2014, 7, 14, 9, 59, 0, '-4') expect(events.last.dtstart.to_datetime).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_date).to eq ::Date.new(2014, 7, 14) expect(events.last.dtstart.to_datetime).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 context 'custom components' do let(:source) { File.read File.join(File.dirname(__FILE__), 'fixtures', 'custom_component.ics') } it 'can output the custom component' do ical = subject.parse.first.to_ical expect(ical).to include 'BEGIN:X-EVENT-SERIES' end end end end end icalendar-2.7.1/.gitignore0000644000004100000410000000012114033220500015442 0ustar www-datawww-data.rvmrc .ruby-version .ruby-gemset Gemfile.lock pkg/ .bundle coverage/ tags .idea icalendar-2.7.1/LICENSE0000644000004100000410000000523714033220500014474 0ustar www-datawww-dataRuby 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.7.1/History.txt0000644000004100000410000001465114033220500015671 0ustar www-datawww-data=== 2.7.1 2021-03-14 * Recover from bad line-wrapping code that splits in the middle of Unicode code points * Add a verbose option to the Parser to quiet some of the chattier log entries === 2.7.0 2020-09-12 * Handle custom component names, with and without X- prefix * Fix Component lookup to avoid namespace collisions === 2.6.1 2019-12-07 * Improve performance when generating large ICS files - Alex Balhatchet === 2.6.0 2019-11-26 * Improve performance for calculating timezone offsets - Justin Howard * Make it possible to de/serialize with Marshal - Pawel Niewiadomski * Avoid FrozenError when running with frozen_string_literal * Update minimum Ruby version to supported versions === 2.5.3 2019-03-04 * Improve parsing performance - nehresma * Support tzinfo 2.0 - Misty De Meo === 2.5.2 2018-12-08 * Remove usage of the global TimezoneStore instance, in favor of a local variable in the parser * Deprecate TimezoneStore class methods === 2.5.1 2018-10-30 * Fix usage without ActiveSupport installed. === 2.5.0 2018-09-10 * Set timezone information from VTIMEZONE components in cases that ActiveSupport can't figure it out (or isn't installed) * Prevent rewinding the Parser IO input during parsing - Niels Laukens * Update tested/supported ruby versions & documentation updates. === 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.7.1/Rakefile0000644000004100000410000000054014033220500015124 0ustar www-datawww-datarequire '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.7.1/lib/0000755000004100000410000000000014033220500014226 5ustar www-datawww-dataicalendar-2.7.1/lib/icalendar.rb0000644000004100000410000000153414033220500016500 0ustar www-datawww-datarequire '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 in 3.0. 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/marshable' 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' require 'icalendar/version' icalendar-2.7.1/lib/icalendar/0000755000004100000410000000000014033220500016150 5ustar www-datawww-dataicalendar-2.7.1/lib/icalendar/freebusy.rb0000644000004100000410000000143514033220500020324 0ustar www-datawww-datamodule 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.7.1/lib/icalendar/calendar.rb0000644000004100000410000000101614033220500020244 0ustar www-datawww-datamodule 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.7.1/lib/icalendar/version.rb0000644000004100000410000000005314033220500020160 0ustar www-datawww-datamodule Icalendar VERSION = '2.7.1' end icalendar-2.7.1/lib/icalendar/has_properties.rb0000644000004100000410000001204314033220500021524 0ustar www-datawww-datamodule 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 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 self.class.single_properties.include? property_name send "#{property_name}=", value elsif self.class.multiple_properties.include? property_name send "append_#{property_name}", value elsif 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.7.1/lib/icalendar/alarm.rb0000644000004100000410000000275614033220500017603 0ustar www-datawww-datamodule 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.7.1/lib/icalendar/has_components.rb0000644000004100000410000000510314033220500021514 0ustar www-datawww-datamodule 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 super end def add_component(c) c.parent = self yield c if block_given? send("#{c.name.downcase}s") << c c end def add_custom_component(component_name, c) c.parent = self yield c if block_given? (custom_components[component_name.downcase.gsub("-", "_")] ||= []) << c c end def custom_component(component_name) custom_components[component_name.downcase.gsub("-", "_")] || [] 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) add_custom_component(component_name, custom, &block) elsif method_name =~ /^x_/ && custom_component(method_name).size > 0 custom_component method_name else super end end def respond_to_missing?(method_name, include_private = false) string_method = method_name.to_s string_method.start_with?('add_x_') || custom_component(string_method).size > 0 || 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.7.1/lib/icalendar/marshable.rb0000644000004100000410000000142614033220500020436 0ustar www-datawww-datamodule Icalendar module Marshable def self.included(base) base.extend ClassMethods end def marshal_dump instance_variables .reject { |ivar| self.class.transient_variables.include?(ivar) } .each_with_object({}) do |ivar, serialized| serialized[ivar] = instance_variable_get(ivar) end end def marshal_load(serialized) serialized.each do |ivar, value| unless self.class.transient_variables.include?(ivar) instance_variable_set(ivar, value) end end end module ClassMethods def transient_variables @transient_variables ||= [:@transient_variables] end def transient_variable(name) transient_variables.push(name.to_sym) end end end end icalendar-2.7.1/lib/icalendar/todo.rb0000644000004100000410000000403514033220500017444 0ustar www-datawww-datamodule Icalendar class Todo < Component required_property :dtstamp, Icalendar::Values::DateTime required_property :uid # dtstart only required if duration is specified required_property :dtstart, Icalendar::Values::DateTime, ->(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.7.1/lib/icalendar/component.rb0000644000004100000410000000635714033220500020512 0ustar www-datawww-datarequire 'securerandom' module Icalendar class Component include HasProperties include HasComponents attr_reader :name attr_reader :ical_name attr_accessor :parent def self.parse(source) _parse source rescue ArgumentError source.rewind if source.respond_to?(:rewind) _parse Parser.clean_bad_wrapping(source) 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. return long_line if long_line.bytesize <= Icalendar::MAX_LINE_LENGTH 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 class << self private def _parse(source) parser = Parser.new(source) parser.component_class = self parser.parse end end end end icalendar-2.7.1/lib/icalendar/value.rb0000644000004100000410000000367614033220500017625 0ustar www-datawww-datarequire '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(/(? ddst standard.last.tzoffsetto else daylight.last.tzoffsetto end end end def standard_for(local) possible = standards.map do |std| [std.previous_occurrence(local.to_time), std] end possible.sort_by(&:first).last end def daylight_for(local) possible = daylights.map do |day| [day.previous_occurrence(local.to_time), day] end possible.sort_by(&:first).last end end end icalendar-2.7.1/lib/icalendar/event.rb0000644000004100000410000000373314033220500017624 0ustar www-datawww-datamodule 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.7.1/lib/icalendar/journal.rb0000644000004100000410000000256114033220500020153 0ustar www-datawww-datamodule 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.7.1/lib/icalendar/tzinfo.rb0000644000004100000410000001177114033220500020015 0ustar www-datawww-data=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 = (respond_to?(:local_start_at) ? local_start_at : 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 (respond_to?(:local_start_at) ? local_start_at : 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.7.1/lib/icalendar/downcased_hash.rb0000644000004100000410000000141314033220500021446 0ustar www-datawww-datarequire '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.7.1/lib/icalendar/values/0000755000004100000410000000000014033220500017447 5ustar www-datawww-dataicalendar-2.7.1/lib/icalendar/values/uri.rb0000644000004100000410000000041514033220500020573 0ustar www-datawww-datarequire '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 end icalendar-2.7.1/lib/icalendar/values/array.rb0000644000004100000410000000265314033220500021120 0ustar www-datawww-datamodule 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 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.7.1/lib/icalendar/values/utc_offset.rb0000644000004100000410000000224114033220500022134 0ustar www-datawww-datarequire '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 def to_s str = "#{behind? ? '-' : '+'}#{'%02d' % hours}:#{'%02d' % minutes}" if seconds > 0 "#{str}:#{'%02d' % seconds}" else str end end private def zero_offset? hours == 0 && minutes == 0 && seconds == 0 end def parse_fields(value) md = /\A(?[+-])(?\d{2})(?\d{2})(?\d{2})?\z/.match value.gsub(/\s+/, '') { behind: (md[:behind] == '-'), hours: md[:hours].to_i, minutes: md[:minutes].to_i, seconds: md[:seconds].to_i } end end end end icalendar-2.7.1/lib/icalendar/values/integer.rb0000644000004100000410000000032714033220500021433 0ustar www-datawww-datamodule 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.7.1/lib/icalendar/values/recur.rb0000644000004100000410000000600414033220500021114 0ustar www-datawww-datarequire '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.7.1/lib/icalendar/values/duration.rb0000644000004100000410000000233014033220500021617 0ustar www-datawww-datarequire '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.7.1/lib/icalendar/values/binary.rb0000644000004100000410000000102514033220500021256 0ustar www-datawww-datarequire '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.7.1/lib/icalendar/values/float.rb0000644000004100000410000000032514033220500021101 0ustar www-datawww-datamodule 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.7.1/lib/icalendar/values/time.rb0000644000004100000410000000117414033220500020735 0ustar www-datawww-datarequire '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.7.1/lib/icalendar/values/boolean.rb0000644000004100000410000000037014033220500021413 0ustar www-datawww-datamodule 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.7.1/lib/icalendar/values/time_with_zone.rb0000644000004100000410000000254314033220500023024 0ustar www-datawww-datarequire 'icalendar/timezone_store' begin 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' x_tz_info = params.delete 'x-tz-info' offset_value = unless params['tzid'].nil? tzid = params['tzid'].is_a?(::Array) ? params['tzid'].first : params['tzid'] if defined?(ActiveSupport::TimeZone) && defined?(ActiveSupportTimeWithZoneAdapter) && (tz = ActiveSupport::TimeZone[tzid]) ActiveSupportTimeWithZoneAdapter.new(nil, tz, value) elsif !x_tz_info.nil? offset = x_tz_info.offset_for_local(value).to_s if value.respond_to?(:change) value.change offset: offset else ::Time.new value.year, value.month, value.day, value.hour, value.min, value.sec, offset end end end super((offset_value || value), params) end def params_ical ical_params.delete 'tzid' if tz_utc super end end end end icalendar-2.7.1/lib/icalendar/values/cal_address.rb0000644000004100000410000000011714033220500022237 0ustar www-datawww-datamodule Icalendar module Values class CalAddress < Uri end end endicalendar-2.7.1/lib/icalendar/values/date_or_date_time.rb0000644000004100000410000000153714033220500023432 0ustar www-datawww-datamodule Icalendar module Values # DateOrDateTime can be used to set an attribute to either a Date or a DateTime value. # It should not be used without also invoking the `call` method. class DateOrDateTime < Value def call parsed end def value_ical parsed.value_ical end def params_ical parsed.params_ical end private def parsed @parsed ||= begin Icalendar::Values::DateTime.new value, ical_params rescue Icalendar::Values::DateTime::FormatError Icalendar::Values::Date.new value, ical_params end end def needs_value_type?(default_type) parsed.class != default_type end def value_type parsed.class.value_type end end end end icalendar-2.7.1/lib/icalendar/values/date_time.rb0000644000004100000410000000223714033220500021733 0ustar www-datawww-datarequire '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 def utc? value.respond_to?(:utc?) ? value.utc? : value.to_time.utc? end class FormatError < ArgumentError end end end end icalendar-2.7.1/lib/icalendar/values/active_support_time_with_zone_adapter.rb0000644000004100000410000000100214033220500027640 0ustar www-datawww-datamodule 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.7.1/lib/icalendar/values/text.rb0000644000004100000410000000075214033220500020764 0ustar www-datawww-datamodule 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.7.1/lib/icalendar/values/date.rb0000644000004100000410000000165414033220500020717 0ustar www-datawww-datarequire 'date' module Icalendar module Values class Date < Value FORMAT = '%Y%m%d' def initialize(value, params = {}) params.delete 'tzid' params.delete 'x-tz-info' 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.7.1/lib/icalendar/values/period.rb0000644000004100000410000000224414033220500021260 0ustar www-datawww-datamodule 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.7.1/lib/icalendar/parser.rb0000644000004100000410000001526614033220500020003 0ustar www-datawww-datarequire 'icalendar/timezone_store' module Icalendar class Parser attr_writer :component_class attr_reader :source, :strict, :timezone_store, :verbose def self.clean_bad_wrapping(source) content = if source.respond_to? :read source.read elsif source.respond_to? :to_s source.to_s else msg = 'Icalendar::Parser.clean_bad_wrapping must be called with a String or IO object' Icalendar.fatal msg fail ArgumentError, msg end encoding = content.encoding content.force_encoding(Encoding::ASCII_8BIT) content.gsub(/\r?\n[ \t]/, "").force_encoding(encoding) end def initialize(source, strict = false, verbose = 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 @verbose = verbose @timezone_store = TimezoneStore.new end def parse 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." if verbose? 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| if param_value.size > 0 param_value = param_value.gsub(/\A"|"\z/, '') params[param_name] << param_value if param_name == 'tzid' params['x-tz-info'] = timezone_store.retrieve param_value end end end end # Building the string to send to the logger is expensive. # Only do it if the logger is at the right log level. if ::Logger::DEBUG >= Icalendar.logger.level Icalendar.logger.debug "Found fields: #{parts.inspect} with params: #{params.inspect}" end { name: parts[:name].downcase.gsub('-', '_'), params: params, value: value } end end end icalendar-2.7.1/lib/icalendar/logger.rb0000644000004100000410000000036014033220500017753 0ustar www-datawww-datarequire '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.7.1/Gemfile0000644000004100000410000000004714033220500014754 0ustar www-datawww-datasource 'https://rubygems.org' gemspec