Index: FeedsIcalDateParser.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/parser_ical/FeedsIcalDateParser.inc,v
retrieving revision 1.3
diff -u -r1.3 FeedsIcalDateParser.inc
--- FeedsIcalDateParser.inc	2 Jan 2010 11:48:41 -0000	1.3
+++ FeedsIcalDateParser.inc	14 Jan 2010 17:13:51 -0000
@@ -2,6 +2,100 @@
 // $Id: FeedsIcalDateParser.inc,v 1.3 2010/01/02 11:48:41 ekes Exp $
 
 /**
+ * Overridden version of FeedsDateTimeElement that supports iCal specific parsing and repetition.
+ *
+ * @todo repeating date storage not iCal specific, could be moved to base class.
+ */
+class FeedsIcalDateTimeElement extends FeedsDateTimeElement {
+  public $repeat_vals = NULL;
+
+  /**
+   * Construct from an iCal VEVENT date array.
+   */
+  public function __construct($feed_element) {
+    if (empty($feed_element['DTSTART']['datetime'])) {
+      return;
+    }
+    include_once(drupal_get_path('module', 'date_api') .'/date_api_ical.inc');
+
+    $timezone = $feed_element['DTSTART']['tz'];
+    if (!empty($timezone)) {
+      $timezone = new DateTimeZone($timezone);
+    }
+    $this->start = new FeedsDateTime($feed_element['DTSTART']['datetime'], $timezone);
+    if (!empty($feed_element['DTEND']) && !empty($feed_element['DTEND']['datetime'])) {
+      $this->end = new FeedsDateTime($feed_element['DTEND']['datetime'], $timezone);
+    }
+    if($feed_element['DTSTART']['all_day']){
+      // All day event; remove time granularity, set to = from
+      $this->start->setTime(0, 0, 0);
+      $this->start->removeGranularity('hour');
+      $this->start->removeGranularity('minute');
+      $this->start->removeGranularity('second');
+      $this->end = clone $this->start;
+    }
+
+    if (array_key_exists('RRULE', $feed_element) && !empty($feed_element['RRULE']) && module_exists('date_repeat')) {
+      include_once('./'. drupal_get_path('module', 'date_repeat') .'/date_repeat_calc.inc');
+      include_once('./'. drupal_get_path('module', 'date') .'/date_repeat.inc');
+      // Explode the RRULE into parts so we can analyze it.
+      $rrule = $feed_element['RRULE']['DATA'] . (!empty($feed_element['EXDATE']) ? "/n". $feed_element['EXDATE'] : "");
+      // In current API the first variable, $field, is unused--may change?
+      $form_values = date_ical_parse_rrule(NULL, $rrule);
+
+      /**
+       * Make sure we don't end up with thousands of values with RRULES 
+       * that have no UNTIL or COUNT.
+       * @todo could be adjusted or made configurable later.
+       * NOTE: This is not properly timezone converted; that's the least of its problems.
+       */
+      $max = date_now();
+      $max_repeats = 52;
+      date_modify($max, '+5 years');
+      $until = date_format($max, 'Y-m-d H:i:s');
+      if (empty($form_values['COUNT']) && (empty($form_values['UNTIL']) || $until < $form_values['UNTIL']['datetime'])) {
+        $form_values['UNTIL'] = array('datetime' => $until, 'tz' => 'UTC');
+        $form_values['COUNT'] = $max_repeats;
+      }
+      elseif (empty($form_values['COUNT'])) {
+        $form_values['COUNT'] = $max_repeats;
+      }
+      elseif (empty($form_values['UNTIL'])) {
+        $form_values['UNTIL'] = array('datetime' => $until, 'tz' => 'UTC');
+      }
+      // Save these in the form_values format, which date can convert to an rrule with date_api_ical_build_rrule()
+      $this->repeat_vals = $form_values;
+    }
+  }
+  
+  /**
+   * Overridden merge method to combine two iCal date elements.
+   * Most of the heavy lifting still handled by parent merge method.
+   */
+  public function merge(FeedsDateTimeElement $other) {
+    $ret = parent::merge($other);
+    if (($other instanceof FeedsIcalDateTimeElement) && !$ret->repeat_vals && $other->repeat_vals) {
+      $ret->repeat_vals = $other->repeat_vals; 
+    }
+    return $ret;
+  }
+
+  /**
+   * Build a node's date CCK field from our object.
+   */
+  public function buildDateField($node, $field_name) {
+    parent::buildDateField($node, $field_name);
+    if (empty($this->repeat_vals)) {
+      return;
+    }
+    $field = content_fields($field_name);
+    $node_field = $node->{$field_name}[0];
+    $values = date_repeat_build_dates(NULL, $this->repeat_vals, $field, $node_field);
+    $node->$field_name = $values;
+  }
+}
+
+/**
  * Class definition for iCal date module Parser.
  *
  * Parses iCal feeds using the iCal parser included with the date module.
Index: parser_ical.dateapi.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/parser_ical/parser_ical.dateapi.inc,v
retrieving revision 1.2
diff -u -r1.2 parser_ical.dateapi.inc
--- parser_ical.dateapi.inc	2 Jan 2010 11:48:41 -0000	1.2
+++ parser_ical.dateapi.inc	14 Jan 2010 17:13:51 -0000
@@ -44,25 +44,13 @@
       if (isset($event['DTSTAMP'])) {
         $date = date_ical_date($event['DTSTAMP']);
         if (date_is_valid($date, DATE_OBJECT)) {
-          $item->options->timestamp = date_format($date, 'U');
+          $item['timestamp'] = date_format($date, 'U');
         }
       }
 
       $item['guid'] = isset($event['UID']) ? $event['UID'] : ''; // intention
       $item['tags'] = isset($event['CATEGORIES']) ? explode(',', $event['CATEGORIES']) : array();
-
-      // Keep iCal timezone information in the feed item so we can create the right date value.
-      $date_info = array('DTSTART', 'DTEND', 'RDATE', 'EXDATE', 'DURATION', 'RRULE');
-      foreach ($date_info as $key) {
-        if (isset($event[$key])) {
-          $item['ical_date']['DATE'][$key] = $event[$key];
-          unset($event[$key]);
-        }
-      }
-      // catch-all ... could be better
-      foreach ($event as $key => $value) {
-        $item['ical_date'][$key] = $value;
-      }
+      $item['ical_date'] = new FeedsIcalDateTimeElement($event);
       $parsed_source['items'][] = $item;
     }
   }
Index: tests/feeds_mapper_ical.test
===================================================================
RCS file: tests/feeds_mapper_ical.test
diff -N tests/feeds_mapper_ical.test
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ tests/feeds_mapper_ical.test	14 Jan 2010 17:13:51 -0000
@@ -0,0 +1,114 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Test case for CCK ical date field mapper mappers/content.inc.
+ */
+
+require_once(drupal_get_path('module', 'feeds') . '/tests/feeds_mapper_test.inc');
+
+/**
+ * Class for testing Feeds <em>content</em> mapper.
+ * 
+ * @todo: Add test method iCal
+ * @todo: Add test method for end date 
+ */
+class FeedsMapperiCalTestCase extends FeedsMapperTestCase {
+
+	public static function getInfo() {
+    return array(
+      'name' => t('iCal Parser'),
+      'description' => t('Test Feeds iCal parser support and date mapping.'),
+      'group' => t('Feeds'),
+    );
+  }
+
+  public function absolutePath() {
+    return $this->absolute() . '/'. drupal_get_path('module', 'parser_ical');
+  }
+  /**
+   * Set up the test.
+   */
+  public function setUp() {
+    // Call parent setup with the required module.
+    parent::setUp('feeds', 'feeds_ui', 'ctools', 'content', 'date_api', 'date', 'parser_ical');
+
+    // Create user and login.
+    $this->drupalLogin($this->drupalCreateUser(
+        array(
+          'administer content types',
+          'administer feeds',
+          'administer nodes',
+          'administer site configuration',
+        )
+    ));
+  }
+
+  /**
+   * Basic test loading a single entry CSV file.
+   */
+  public function test() {
+    
+    // Create content type.
+  	$typename = $this->createContentType(NULL, array(
+      'eventdate' => array('type' => 'date','settings' => array('todate' => 'optional', 'tz_handling' => 'date'),),
+      'eventdate2' => array('type' => 'date', 'settings' => array('todate' => 'optional', 'tz_handling' => 'site'),),
+    ));
+
+    // Create and configure importer.
+    $this->createFeedConfiguration('iCal Feed', 'ical');
+    $this->setSettings('ical', NULL, array('content_type' => '','import_period' => FEEDS_SCHEDULE_NEVER,));
+    $this->setPlugin('ical', 'FeedsFileFetcher');
+    $this->setPlugin('ical', 'FeedsIcalDateParser');
+    $this->setSettings('ical', 'FeedsNodeProcessor', array('content_type' => $typename));
+    $this->addMappings('ical', array(
+      array(
+        'source' => 'title',
+        'target' => 'title',
+      ),
+      array(
+        'source' => 'description',
+        'target' => 'body',
+      ),
+      array(
+        'source' => 'ical_date',
+        'target' => 'field_eventdate:start',
+      ),
+      array(
+        'source' => 'ical_date',
+        'target' => 'field_eventdate2:start',
+        ),
+    ));
+    
+    // Import CSV file.
+    $this->importFile('ical', $this->absolutePath() .'/tests/feeds/School.ics');
+    $this->assertText('Created 25 '. $typename .' nodes.');
+    // Check the imported nodes.
+    $values = array(
+      '08/31/2009 - 19:30',
+      '08/31/2009 - 23:30',
+      '09/01/2009 - 19:00',
+      '09/01/2009 - 17:00',
+      '09/03/2009 - 18:00',
+      '09/03/2009 - 22:00',
+      );
+    $titles = array('Dunster Assembly', 'Advising Meeting with Ed', 'Sophomore Dinner');
+    for ($i = 1; $i <= 3; $i++) {
+      $this->drupalGet("node/$i/edit");
+      $n = node_load($i, NULL, TRUE);
+      $this->assertText($titles[$i-1]);
+      $this->assertCCKFieldValue('eventdate', $values[$i*2-2]);
+      $this->assertCCKFieldValue('eventdate2', $values[$i*2-1]);
+    }
+  }
+  
+protected function getFormFieldsNames($field_name, $index) {
+    if (in_array($field_name, array('date', 'datetime', 'datestamp', 'eventdate', 'eventdate2'))) {
+      return array("field_{$field_name}[{$index}][value][date]");
+    }
+    else {
+      return parent::getFormFieldsNames($field_name, $index);
+    }
+  }
+}
Index: tests/feeds/School.ics
===================================================================
RCS file: tests/feeds/School.ics
diff -N tests/feeds/School.ics
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ tests/feeds/School.ics	14 Jan 2010 17:13:51 -0000
@@ -0,0 +1,298 @@
+BEGIN:VCALENDAR
+METHOD:PUBLISH
+X-WR-TIMEZONE:US/Eastern
+PRODID:-//Apple Inc.//iCal 3.0//EN
+CALSCALE:GREGORIAN
+X-WR-CALNAME:School
+VERSION:2.0
+X-WR-RELCALID:3C696814-7E94-491E-862C-FA8A36DC2ECA
+X-APPLE-CALENDAR-COLOR:#E51717
+BEGIN:VEVENT
+SEQUENCE:3
+TRANSP:OPAQUE
+UID:C41732F8-ED49-4BD1-B1A0-ACF44101E653
+DTSTART;TZID=US/Eastern:20090831T193000
+DTSTAMP:20090831T043238Z
+SUMMARY:Dunster Assembly
+CREATED:20090831T043208Z
+DTEND;TZID=US/Eastern:20090831T203000
+LOCATION:Dunster Dining Hall
+END:VEVENT
+BEGIN:VEVENT
+SEQUENCE:5
+TRANSP:OPAQUE
+UID:573EC159-E38E-4A3B-97C5-565B1E23FB78
+DTSTART;TZID=Europe/Paris:20090901T190000
+DTSTAMP:20090831T131111Z
+SUMMARY:Advising Meeting with Ed
+CREATED:20090831T131037Z
+DTEND;TZID=US/Eastern:20090901T200000
+LOCATION:Dunster Dining Hall
+END:VEVENT
+BEGIN:VEVENT
+SEQUENCE:3
+TRANSP:OPAQUE
+UID:85B9AE9F-D01D-49D4-8065-FD5B951A6444
+DTSTART;TZID=US/Eastern:20090903T180000
+DTSTAMP:20090831T154244Z
+SUMMARY:Sophomore Dinner
+CREATED:20090831T154230Z
+DTEND;TZID=US/Eastern:20090903T193000
+LOCATION:Dunster Dining Hall
+END:VEVENT
+BEGIN:VEVENT
+SEQUENCE:2
+TRANSP:OPAQUE
+UID:CA0B177B-6C35-4822-A8E3-30F86476846E
+DTSTART;TZID=US/Eastern:20090905T094500
+DTSTAMP:20090831T154329Z
+SUMMARY:Dunster Outing\, boat cruise
+CREATED:20090831T154319Z
+DTEND;TZID=US/Eastern:20090905T150000
+END:VEVENT
+BEGIN:VEVENT
+SEQUENCE:3
+TRANSP:OPAQUE
+UID:6313079F-BD2F-4EC4-92A3-D9314C4B9A2A
+DTSTART;TZID=US/Eastern:20090902T100000
+DTSTAMP:20090902T120313Z
+SUMMARY:Econ 1776
+CREATED:20090902T120301Z
+DTEND;TZID=US/Eastern:20090902T110000
+LOCATION:University Hall 201
+END:VEVENT
+BEGIN:VEVENT
+SEQUENCE:3
+TRANSP:OPAQUE
+UID:0AA230CC-F858-4455-8F7C-C5604D81106B
+DTSTART;TZID=US/Eastern:20090902T160000
+DTSTAMP:20090902T120334Z
+SUMMARY:Compsci 175
+CREATED:20090902T120318Z
+DTEND;TZID=US/Eastern:20090902T173000
+LOCATION:MD G-125
+END:VEVENT
+BEGIN:VEVENT
+SEQUENCE:5
+TRANSP:OPAQUE
+UID:7CDFFB2E-074B-4177-8A60-7882C650B4A5
+DTSTART;TZID=US/Eastern:20090903T090000
+DTSTAMP:20090914T150518Z
+SUMMARY:LitArt A-64
+CREATED:20090903T033151Z
+DTEND;TZID=US/Eastern:20090903T100000
+LOCATION:Boylston 110\, Fong
+RRULE:FREQ=WEEKLY;INTERVAL=1;UNTIL=20090915T035959Z;BYDAY=TU,TH;WKST=SU
+END:VEVENT
+BEGIN:VEVENT
+SEQUENCE:6
+TRANSP:OPAQUE
+UID:75EF7CA6-90E2-4AF2-9749-54FF49DCF849
+DTSTART;TZID=US/Eastern:20090903T100000
+DTSTAMP:20091118T222221Z
+SUMMARY:Physics 143a
+EXDATE;TZID=US/Eastern:20091126T100000
+CREATED:20090903T033207Z
+DTEND;TZID=US/Eastern:20090903T113000
+LOCATION:Jefferson 256
+RRULE:FREQ=WEEKLY;INTERVAL=1;UNTIL=20091208T045959Z;BYDAY=TU,TH;WKST=SU
+END:VEVENT
+BEGIN:VEVENT
+SEQUENCE:6
+TRANSP:OPAQUE
+UID:68302B2D-3FA2-4B35-92EF-B2A08D134A64
+DTSTART;TZID=US/Eastern:20090903T113000
+DTSTAMP:20091118T222221Z
+SUMMARY:Math 122
+EXDATE;TZID=US/Eastern:20091126T113000
+CREATED:20090903T033217Z
+DTEND;TZID=US/Eastern:20090903T130000
+LOCATION:SC 507
+RRULE:FREQ=WEEKLY;INTERVAL=1;UNTIL=20091208T045959Z;BYDAY=TU,TH;WKST=SU
+END:VEVENT
+BEGIN:VEVENT
+SEQUENCE:3
+TRANSP:OPAQUE
+UID:2B1C6DCE-C49B-43DC-9E52-70A5F1D33DD2
+DTSTART;TZID=US/Eastern:20090903T130000
+DTSTAMP:20090903T033256Z
+SUMMARY:Math 115
+CREATED:20090903T033224Z
+DTEND;TZID=US/Eastern:20090903T143000
+LOCATION:SC 310
+END:VEVENT
+BEGIN:VEVENT
+SEQUENCE:6
+TRANSP:OPAQUE
+UID:349B2AF9-D3F3-49DA-BD3F-11F2F357E7A8
+DTSTART;TZID=US/Eastern:20090903T143000
+DTSTAMP:20090903T033347Z
+SUMMARY:Compsci 61
+CREATED:20090903T033230Z
+DTEND;TZID=US/Eastern:20090903T160000
+LOCATION:MD G-115
+END:VEVENT
+BEGIN:VEVENT
+SEQUENCE:5
+TRANSP:OPAQUE
+UID:A85D7912-A853-46A3-B164-58153804267F
+DTSTART;TZID=US/Eastern:20090903T133000
+DTSTAMP:20090903T033433Z
+SUMMARY:Physics 15c
+CREATED:20090903T033350Z
+DTEND;TZID=US/Eastern:20090903T150000
+LOCATION:SC D
+END:VEVENT
+BEGIN:VEVENT
+SEQUENCE:3
+TRANSP:OPAQUE
+UID:55FF8AC0-D541-405F-AE70-21BA967A4CE6
+DTSTART;TZID=US/Eastern:20090904T110000
+DTSTAMP:20090904T002315Z
+SUMMARY:APMath 105a
+CREATED:20090904T002303Z
+DTEND;TZID=US/Eastern:20090904T120000
+LOCATION:Jefferson 250
+END:VEVENT
+BEGIN:VEVENT
+SEQUENCE:7
+TRANSP:OPAQUE
+UID:F826A6FE-3EF5-49F9-9BA9-17B3040B5092
+DTSTART;TZID=US/Eastern:20090904T143000
+DTSTAMP:20091118T222208Z
+SUMMARY:Physics 210
+EXDATE;TZID=US/Eastern:20091127T143000
+EXDATE;TZID=US/Eastern:20091125T143000
+CREATED:20090904T002328Z
+DTEND;TZID=US/Eastern:20090904T160000
+LOCATION:Jefferson 453
+RRULE:FREQ=WEEKLY;INTERVAL=1;UNTIL=20091204T045959Z;BYDAY=WE,FR;WKST=SU
+END:VEVENT
+BEGIN:VEVENT
+SEQUENCE:3
+TRANSP:OPAQUE
+UID:0C34C922-7E00-4093-AAB3-61880D4553B0
+DTSTART;TZID=US/Eastern:20090904T120000
+DTSTAMP:20090904T002445Z
+SUMMARY:Math 131
+CREATED:20090904T002411Z
+DTEND;TZID=US/Eastern:20090904T130000
+LOCATION:SC 310
+END:VEVENT
+BEGIN:VEVENT
+SEQUENCE:6
+TRANSP:OPAQUE
+UID:F8F66196-5AEB-4433-9DD5-8098F76261C9
+DTSTART;TZID=US/Eastern:20090909T170000
+DTSTAMP:20091118T222221Z
+SUMMARY:Physics 143a Section
+CREATED:20090909T202701Z
+DTEND;TZID=US/Eastern:20090909T183000
+LOCATION:Lyman 425
+RRULE:FREQ=WEEKLY;INTERVAL=1;UNTIL=20091209T045959Z
+END:VEVENT
+BEGIN:VEVENT
+SEQUENCE:5
+TRANSP:OPAQUE
+UID:3292BDC1-FEDC-4179-B33B-C046F6FD4124
+DTSTART;TZID=US/Eastern:20090909T160000
+DTSTAMP:20091118T222221Z
+SUMMARY:Math 122 Section
+CREATED:20090910T181726Z
+DTEND;TZID=US/Eastern:20090909T170000
+LOCATION:SC 103B
+RRULE:FREQ=WEEKLY;INTERVAL=1;UNTIL=20091209T045959Z
+END:VEVENT
+BEGIN:VEVENT
+SEQUENCE:6
+TRANSP:OPAQUE
+UID:13EC6F84-F8ED-4A63-A732-0428E30E859F
+DTSTART;TZID=US/Eastern:20090915T090000
+DTSTAMP:20091118T222221Z
+SUMMARY:LitArt A-64
+EXDATE;TZID=US/Eastern:20091126T090000
+CREATED:20090903T033151Z
+DTEND;TZID=US/Eastern:20090915T100000
+LOCATION:Sever 211
+RRULE:FREQ=WEEKLY;INTERVAL=1;UNTIL=20091208T045959Z;BYDAY=TU,TH;WKST=SU
+END:VEVENT
+BEGIN:VEVENT
+SEQUENCE:6
+TRANSP:OPAQUE
+UID:BF9E7FC9-88BA-4B81-A5D0-C4D98184286B
+DTSTART;TZID=US/Eastern:20090918T100000
+DTSTAMP:20091118T222221Z
+SUMMARY:LitArt A-64 Section
+EXDATE;TZID=US/Eastern:20091127T100000
+CREATED:20090914T220608Z
+DTEND;TZID=US/Eastern:20090918T110000
+LOCATION:Sever 112
+RRULE:FREQ=WEEKLY;INTERVAL=1;UNTIL=20091211T045959Z
+END:VEVENT
+BEGIN:VEVENT
+SEQUENCE:0
+TRANSP:OPAQUE
+UID:E42B4CF8-615F-4D7C-9D06-95942FEF7A8C
+DTSTART;TZID=America/Montreal:20091004T180000
+DTSTAMP:20091003T234224Z
+SUMMARY:Physics 143a review
+CREATED:20100105T194938Z
+DTEND;TZID=America/Montreal:20091004T200000
+LOCATION:Jefferson 256
+END:VEVENT
+BEGIN:VEVENT
+SEQUENCE:4
+TRANSP:OPAQUE
+UID:C026968B-9DC3-4D05-B7AD-10E57484E000
+DTSTART;TZID=US/Eastern:20091004T200000
+DTSTAMP:20091004T174248Z
+SUMMARY:Physics 143a review with Jay
+CREATED:20091004T174222Z
+DTEND;TZID=US/Eastern:20091004T220000
+LOCATION:Dunster Dining Hall
+END:VEVENT
+BEGIN:VEVENT
+SEQUENCE:0
+TRANSP:OPAQUE
+UID:9A1F95AA-DF71-451A-995E-3CA2DC724B95
+DTSTART;TZID=America/Montreal:20091110T140000
+DTSTAMP:20091105T145125Z
+SUMMARY:Physics meeting
+CREATED:20100105T194938Z
+DTEND;TZID=America/Montreal:20091110T143000
+LOCATION:Lyman 238
+END:VEVENT
+BEGIN:VEVENT
+SEQUENCE:3
+TRANSP:OPAQUE
+UID:FD1E3BA4-DD40-42C6-A899-A8807EABC051
+DTSTART;TZID=US/Eastern:20091212T140000
+DTSTAMP:20091211T065353Z
+SUMMARY:LitArts A-64 Final
+CREATED:20091118T221813Z
+DTEND;TZID=US/Eastern:20091212T170000
+LOCATION:Sever 103
+END:VEVENT
+BEGIN:VEVENT
+SEQUENCE:4
+TRANSP:OPAQUE
+UID:42FF755C-AAC6-49A6-B843-25A3A199681E
+DTSTART;TZID=US/Eastern:20091216T140000
+DTSTAMP:20091211T065459Z
+SUMMARY:Math 122 Final
+CREATED:20091118T222335Z
+DTEND;TZID=US/Eastern:20091216T170000
+LOCATION:SC E
+END:VEVENT
+BEGIN:VEVENT
+SEQUENCE:2
+TRANSP:OPAQUE
+UID:9C31A2C8-7CC3-453A-A63E-D82C8DA8D9EB
+DTSTART;TZID=US/Eastern:20091212T090000
+DTSTAMP:20091118T222412Z
+SUMMARY:Physics 143a Final (bring calc!)
+CREATED:20091118T222403Z
+DTEND;TZID=US/Eastern:20091212T120000
+LOCATION:Sever 103
+END:VEVENT
+END:VCALENDAR
