Index: libraries/common_syndication_parser.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/feeds/libraries/common_syndication_parser.inc,v
retrieving revision 1.4
diff -u -r1.4 common_syndication_parser.inc
--- libraries/common_syndication_parser.inc	21 Dec 2009 00:16:22 -0000	1.4
+++ libraries/common_syndication_parser.inc	6 Jan 2010 18:47:59 -0000
@@ -417,9 +417,11 @@
     $item['author'] = $original_author;
     if (isset($news['pubDate'])) {
       $item['timestamp'] = _parser_common_syndication_parse_date($news['pubDate']);
+      $item['timestamp_raw'] = $news['pubDate'];
     }
     else {
       $item['timestamp'] = time();
+      $item['timestamp_raw'] = time();
     }
     $item['url'] = trim($original_url);
     $item['guid'] = $guid;
Index: mappers/date.inc
===================================================================
RCS file: mappers/date.inc
diff -N mappers/date.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ mappers/date.inc	6 Jan 2010 18:47:59 -0000
@@ -0,0 +1,430 @@
+<?php
+// $Id$
+/**
+ * @file
+ * On behalf implementation of Feeds mapping API for date
+ */
+ 
+/**
+ * Implementation of hook_feeds_node_processor_targets_alter().
+ *
+ * @see FeedsNodeProcessor::getMappingTargets().
+ * 
+ * @todo Only provides "end date" target if field allow it.
+ */
+function date_feeds_node_processor_targets_alter(&$targets, $content_type) {
+  
+  $sub_fields = array();
+  $sub_fields['ical'] = array('iCal VEVENT', 'date_feeds_set_target_ical', 'The !name field when set from an iCal VEVENT source.');
+  $sub_fields['start_date'] = array('Start Date (date only)');
+  $sub_fields['start_time'] = array('Start Time (time only)');
+  $sub_fields['start_datetime'] = array('Start Date (date and time)', 'date_feeds_set_target_datetime');
+  $sub_fields['start_stamp'] = array('Start Date (timestamp)', 'date_feeds_set_target_timestamp');
+  $sub_fields['end_date'] = array('End Date (date only)');
+  $sub_fields['end_time'] = array('End Time (time only)');
+  $sub_fields['end_datetime'] = array('End Date (date and time)', 'date_feeds_set_target_datetime');
+  $sub_fields['end_stamp'] = array('End Date (timestamp)', 'date_feeds_set_target_timestamp');
+  $sub_fields['all_day'] = array('All Day');
+  $sub_fields['timezone'] = array('Timezone');
+  
+  $info = content_types($content_type);
+  // The $info['fields'] holds CCK fields such as date and links.  
+  if (isset($info['fields']) && count($info['fields'])) {
+    foreach ($info['fields'] as $field_name => $field) {
+      // Check to see if the field type is a date (CCK).
+      if (in_array($field['type'], array('date', 'datestamp', 'datetime'))) {
+        // Set the name for the source label to be the label of the field or the field name if no label exists
+        $name = isset($field['widget']['label']) ? $field['widget']['label'] : $field_name;
+
+        foreach($sub_fields as $sub_field_name => $sub_field){
+          $targets[$field_name.'#'.$sub_field_name] = array(
+            'name' => $name . ': ' . $sub_field[0],
+            'callback' => $sub_field[1] ? $sub_field[1] : 'date_feeds_set_target_part',
+            'description' => t($sub_field[2] ? $sub_field[2] : 'The !subname for the !name field when set from a UNIX timestamp source.', array('!date_time' => $description, '!name' => $name, '!subname' => $sub_field[0])),
+          );
+        }
+      }
+    }
+  }
+}
+
+/**
+ * Create a date from a simple string date value.
+ */
+function date_feeds_set_target_datetime(&$node, $target, $feed_element) {
+  if (empty($feed_element)) {
+    return;
+  }
+  list($field_name, $sub_field) = split('#', $target);
+  $field = content_fields($field_name);
+  
+  $field_name = $field['field_name'];
+  $timezone = date_default_timezone_name();
+  $to_tz = date_get_timezone($field['tz_handling'], $timezone);
+
+  if (is_array($feed_element)) {
+    $feed_element = $feed_element[0];
+  }
+  // Pull offset information out of datetime and reset date to
+  // the known value, UTC. The offset is not useful here.
+  preg_match('/(([\+\-])(\d{2})?:?(\d{2})?)$/', $feed_element, $matches);
+  if (!empty($matches)) {
+    $datetime = trim(str_replace($matches[1], '', $feed_element));
+    $value = date_create($datetime, timezone_open('UTC'));
+    $direction = $matches[2];
+    $hours = $matches[3];
+    $minutes = $matches[4];
+    if ($hours == '00' && $minutes == '00') {
+        // This is UTC, no adjustment needed.
+    }
+    else {
+      // Bring the date back to UTC.
+      $direction = ($direction == '+' ? '-' : '+');
+      date_modify($value, $direction . trim($hours) .' hours');
+      date_modify($value, $direction . trim($minutes) .' minutes');       
+    }
+    // Reset it to the appropriate local timezone.
+    date_timezone_set($value, timezone_open($to_tz));
+  }
+  elseif (stripos($feed_element, "Z") != strlen($feed_element)) {
+    $value = date_create($feed_element, timezone_open($to_tz));
+  }
+  else {
+    // A date without a timezone or a non-ISO date, assume local timezone.
+    $needle = ';';
+    $pos = strripos($feed_element, $needle);
+    $feed_element = substr($feed_element, 0, $pos);
+    $value = date_create($feed_element, timezone_open($to_tz));
+  }
+
+  $date = new feeds_date_constructor();
+  $date->construct($field);
+  $date->set_value('timezone', $timezone);
+  $date->set_value($sub_field, date_format($value, DATE_FORMAT_DATETIME));
+  $date->build();
+
+  if (!isset($node->{$field_name})) {
+    $node->$field_name = array($date->value);
+  }
+  else {
+    if ($sub_field == 'start_datetime') {
+      $node->{$field_name}[0]['date'] = $date->value['date'];
+      $node->{$field_name}[0]['value'] = $date->value['value'];
+    }
+    elseif ($sub_field == 'end_datetime') {
+      $node->{$field_name}[0]['date2'] = $date->value['date2'];
+      $node->{$field_name}[0]['value2'] = $date->value['value2'];
+    }
+  }
+  return $node;
+}
+
+/**
+ * Create a date from a unix timestamp.
+ */
+function date_feeds_set_target_timestamp(&$node, $target, $feed_element) {
+  if (empty($feed_element)) {
+    return;
+  }
+  list($field_name, $sub_field) = split('#', $target);
+  $field = content_fields($field_name);
+    
+  $field_name = $field['field_name'];
+  $timezone = date_default_timezone_name();
+  $to_tz = date_get_timezone($field['tz_handling'], $timezone);
+  
+  // A date without a timezone, assume local timezone.
+  $value = date_create("@$feed_element", timezone_open('UTC'));
+  date_timezone_set($value, timezone_open($to_tz));
+
+  $date = new feeds_date_constructor();
+  $date->construct($field);
+  $date->set_value('timezone', $timezone);
+  $date->set_value($sub_field, date_format($value, DATE_FORMAT_DATETIME));
+  $date->build();
+
+  if (!isset($node->{$field_name})) {
+    $node->$field_name = array($date->value);
+  }
+  else {
+    if ($sub_field == 'start_stamp') {
+      $node->{$field_name}[0]['date'] = $date->value['date'];
+      $node->{$field_name}[0]['value'] = $date->value['value'];
+    }
+    elseif ($sub_field == 'end_stamp') {
+      $node->{$field_name}[0]['date2'] = $date->value['date2'];
+      $node->{$field_name}[0]['value2'] = $date->value['value2'];
+    }
+  }
+  return $node;
+}
+
+/**
+ * Create a date from an iCal feed array.
+ */
+function date_feeds_set_target_ical(&$node, $target, $feed_element) {
+  if (empty($feed_element)) {
+    return;
+  }
+  list($field_name, $sub_field) = split('#', $target);
+  $field = content_fields($field_name);
+  // There are lots of available elements in the feed, so do some checking.
+
+  
+  include_once(drupal_get_path('module', 'date_api') .'/date_api_ical.inc');
+
+  // If the feed element contains the top-level VEVENT, 
+  // pick out the part we need, the DATE array.
+  if (array_key_exists('DATE', $feed_element)) {
+    $feed_element = $feed_element['DATE'];
+  }
+  // If the feed element contains only the DTSTART or DTEND sub-array,
+  // we don't know which one we have, not enough information to process.
+  // If it is any other part of the VEVENT array, or the date is empty
+  // we don't have any date information and there is nothing to do.
+  if (!array_key_exists('DTSTART', $feed_element)) {
+    return;
+  }
+  elseif (empty($feed_element['DTSTART']['datetime'])) {
+    return;
+  }
+  
+  $field_name = $field['field_name'];
+  
+  $timezone = $feed_element['DTSTART']['tz'];
+  if (empty($timezone)) $timezone = date_default_timezone_name();
+  
+  $date = new feeds_date_constructor();
+  $date->construct($field);
+  $date->set_value('timezone', $timezone);
+  if (strlen($feed_element['DTSTART']['datetime'] < 11)) {
+    $date->set_value('start_date', $feed_element['DTSTART']['datetime']);
+  }
+  else {
+    $date->set_value('start_datetime', $feed_element['DTSTART']['datetime']);
+  }
+  if (!empty($feed_element['DTEND']) && !empty($feed_element['DTEND']['datetime'])) {
+    if (strlen($feed_element['DTEND']['datetime'] < 11)) {
+      $date->set_value('end_date', $feed_element['DTEND']['datetime']);
+    }
+    else {
+      $date->set_value('end_datetime', $feed_element['DTEND']['datetime']);
+    }
+  }
+  $date->set_value('all_day', $feed_element['DTSTART']['all_day']);
+  $date->build();
+  $node->$field_name = array($date->value);
+
+  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'] : "");
+    $form_values = date_ical_parse_rrule($field, $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.
+    $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');
+    }
+
+    // Reconstruct the RRULE with our changes and build the repeats.
+    $rrule = date_api_ical_build_rrule($form_values);
+    $node_field = $node->{$field_name}[0];
+    $values = date_repeat_build_dates($rrule, $form_values, $field, $node_field);
+    $node->$field_name = $values;
+  }
+}
+
+/**
+ * Create a date from a collection of date parts.
+ * The most complicated scenario since we don't know what parts
+ * we are getting or in what order. We'll create date information
+ * and retain it from one element to the next to gradually build
+ * up the date values. We use the current date as a default value
+ * when nothing else is known.
+ */
+function date_feeds_set_target_part(&$node, $target, $feed_element) {
+  if (empty($feed_element)) {
+    return;
+  }
+  list($field_name, $sub_field) = split('#', $target);
+  $field = content_fields($field_name);
+  
+  static $date;
+
+  $field_name = $field['field_name'];
+  if (empty($node->$field_name)) {
+    $date = new feeds_date_constructor();
+    $date->construct($field);
+  }
+  $date->set_value($sub_field, $feed_element);
+  $date->build();
+  $node->$field_name = array($date->value);
+}
+
+/**
+ * Construct a date from a collection of date parts
+ * that could be provided in any order, without knowing
+ * ahead of time exactly which parts will be set.
+ * 
+ * Functions by defaulting to current date for missing values.
+ * Retains previous values and continuously resets them as
+ * more information becomes known.
+ * 
+ * Call build() at any time to construct a date from the
+ * currently available values.
+ *
+ * @todo: This should live in date module.
+ * @todo: only renamed feeds_date_constructor from date_constructor to prevent error if loaded at same time as (deprecated) feed_api
+ */
+class feeds_date_constructor {
+  var $field = NULL;
+  var $timezone = NULL;
+  var $start_date = NULL;
+  var $end_date = NULL;
+  var $start_time = NULL;
+  var $end_time = NULL;
+  var $start_datetime = NULL;
+  var $end_datetime = NULL;
+  var $value = NULL;
+  var $format = NULL;
+  var $db_tz = NULL;
+  var $to_tz = NULL;
+
+  function construct($field) {
+    $now = date_now();  
+    //$this->start_date = date_format($now, DATE_FORMAT_DATE);
+    $this->start_time = '00:00:00';
+    $this->start_datetime = $this->start_date .' '. $this->start_time;
+    $this->timezone = date_default_timezone_name();
+    $this->field = $field;
+    $this->format = date_type_format($field['type']);
+  }
+
+  function set_value($key, $value) {
+    if (empty($value)) {
+      return;
+    }
+    switch ($key) {
+      case 'timezone':
+        $this->timezone = $value;
+        break;
+      case 'all_day':
+        if (!empty($value) && strtolower($value) != 'false' && $value !== FALSE) {
+          $this->start_time = '00:00:00';
+          $this->end_time = '00:00:00';
+          if (!empty($this->start_date)) {
+            $this->start_datetime = $this->start_date .' '. $this->start_time;
+          }
+          if (!empty($this->end_date)) {
+            $this->end_datetime = $this->end_date .' '. $this->end_time;
+          }
+          $this->all_day = TRUE;
+        }
+        break;
+      case 'start_date':
+        $this->start_date = $value;
+        $this->start_datetime = $this->start_date .' '. $this->start_time;
+        break;
+      case 'start_time':
+        $this->start_time = $value;
+        $this->start_datetime = $this->start_date .' '. $this->start_time;
+        break;
+      case 'end_date':
+        $this->end_date = $value;
+        if (empty($this->end_time)) {
+          $this->end_time = $this->start_time;
+        }
+        $this->end_datetime = $this->end_date .' '. $this->end_time;
+        break;
+      case 'end_time':
+        $this->end_time = $value;
+        if (empty($this->end_date)) {
+          $this->end_date = $this->start_date;
+        }
+        $this->end_datetime = $this->end_date .' '. $this->end_time;
+        break;
+      case 'start_datetime':
+        $this->start_datetime = $value;
+        $this->start_date = NULL;
+        $this->start_time = NULL;
+        break;
+      case 'end_datetime':
+        $this->end_datetime = $value;
+        $this->end_date = NULL;
+        $this->end_time = NULL;
+        break;
+    }
+  }
+
+  function set_timezone() {
+    $this->db_tz = date_get_timezone_db($this->field['tz_handling'], $this->timezone);
+    $this->to_tz = date_get_timezone($this->field['tz_handling'], $this->timezone);
+  }
+
+  /**
+   * Create date objects with the known values.
+   */
+  function build() {
+    $this->set_timezone();
+    if (empty($this->start_datetime) && !empty($this->start_date)) {
+      $this->start_datetime = trim($this->start_date .' '. $this->start_time);
+    }
+    if (empty($this->end_date) && empty($this->end_datetime)) {
+      $this->end_date = $this->start_date;
+    }
+    if (empty($this->end_time) && empty($this->end_datetime)) {
+      $this->end_time = $this->start_time;
+    }
+    if (empty($this->end_datetime) && (!empty($this->end_date) || !empty($this->end_time))) {
+      $this->end_datetime = trim($this->end_date .' '. $this->end_time);
+    }
+    elseif (empty($this->end_datetime)) {
+      $this->end_datetime = $this->start_datetime;
+    }
+
+    // We don't know exactly what format the provided date used,
+    // so use the native date_create() where available, which does a
+    // pretty good job of interpreting non-standard date values. Where the 
+    // native date_create() is not available (PHP 4, PHP 5.0 and PHP 5.1)
+    // use strtotime() as the best available interpreter of irregularly
+    // formated date values.
+    if (version_compare(PHP_VERSION, '5.2', '<')) {
+      $timestamp = strtotime($this->start_datetime);
+      $this->value['date'] = date_create("@$timestamp");
+      $timestamp = strtotime($this->end_datetime);
+      $this->value['date2'] = date_create("@$timestamp");
+    }
+    else {
+      $this->value['date'] = date_create($this->start_datetime, timezone_open($this->to_tz));
+      $this->value['date2'] = date_create($this->end_datetime, timezone_open($this->to_tz));      
+    }
+
+    if (isset($this->value['date'])) {
+      $this->value['offset'] = date_offset_get($this->value['date']);
+      date_timezone_set($this->value['date'], timezone_open($this->db_tz));
+      $this->value['value'] = date_format($this->value['date'], $this->format);
+    }
+    if (isset($this->value['date2'])) {
+      $this->value['offset2'] = date_offset_get($this->value['date2']);
+      date_timezone_set($this->value['date2'], timezone_open($this->db_tz));
+      $this->value['value2'] = date_format($this->value['date2'], $this->format);
+    }
+    $this->value['timezone'] = $this->to_tz;
+  }
+}
Index: plugins/FeedsSyndicationParser.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/feeds/plugins/FeedsSyndicationParser.inc,v
retrieving revision 1.10
diff -u -r1.10 FeedsSyndicationParser.inc
--- plugins/FeedsSyndicationParser.inc	20 Dec 2009 23:54:44 -0000	1.10
+++ plugins/FeedsSyndicationParser.inc	6 Jan 2010 18:47:59 -0000
@@ -45,6 +45,10 @@
         'name' => t('Published date'),
         'description' => t('Published date as UNIX time GMT of the feed item.'),
       ),
+      'timestamp_raw' => array(
+        'name' => t('Published date (Raw)'),
+        'description' => t('Published date as a raw string.'),
+      ),
       'url' => array(
         'name' => t('Item URL (link)'),
         'description' => t('URL of the feed item.'),
Index: tests/feeds_mapper_date.test
===================================================================
RCS file: tests/feeds_mapper_date.test
diff -N tests/feeds_mapper_date.test
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ tests/feeds_mapper_date.test	6 Jan 2010 18:47:59 -0000
@@ -0,0 +1,107 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Test case for CCK 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 FeedsMapperDateTestCase extends FeedsMapperTestCase {
+
+	public static function getInfo() {
+    return array(
+      'name' => t('Mapper: CCK Date'),
+      'description' => t('Test Feeds Mapper support for CCK date fields'),
+      'group' => t('Feeds'),
+    );
+  }
+
+  /**
+   * Set up the test.
+   */
+  public function setUp() {
+    // Call parent setup with the required module.
+    parent::setUp('feeds', 'feeds_ui', 'ctools', 'content', 'date_api', 'date');
+
+    // 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(
+      'date' => 'date',
+      'datestamp' => 'datestamp',
+      'datetime' => 'datetime',
+    ));
+
+    // Create and configure importer.
+    $this->createFeedConfiguration('Date CSV', 'csv');
+    $this->setSettings('csv', NULL, array('content_type' => '','import_period' => FEEDS_SCHEDULE_NEVER,));
+    $this->setPlugin('csv', 'FeedsFileFetcher');
+    $this->setPlugin('csv', 'FeedsCSVParser');
+    $this->setSettings('csv', 'FeedsNodeProcessor', array('content_type' => $typename));
+    $this->addMappings('csv', array(
+      array(
+        'source' => 'format',
+        'target' => 'title',
+      ),
+      array(
+        'source' => 'value',
+        'target' => 'body',
+      ),
+      array(
+        'source' => 'value',
+        'target' => 'field_date#',
+      ),
+      array(
+        'source' => 'value',
+        'target' => 'field_datetime#',
+      ),
+      array(
+        'source' => 'value',
+        'target' => 'field_datestamp#',
+      ),
+    ));
+    
+    // Import CSV file.
+    $this->importFile('csv', $this->absolutePath() .'/tests/feeds/date.csv');
+    $this->assertText('Created 13 '. $typename .' nodes.');
+    
+    // Check the imported nodes.
+    $date = '12/01/2009 - 12:41';
+    for($i = 1; $i <= 13; $i++) {
+      $this->drupalGet("node/$i/edit");
+      $this->assertCCKFieldValue('date', $date);
+      $this->assertCCKFieldValue('datetime', $date);
+      $this->assertCCKFieldValue('datestamp', $date);
+    }
+  }
+  
+protected function getFormFieldsNames($field_name, $index) {
+    if(in_array($field_name, array('date', 'datetime', 'datestamp'))) {
+      return array("field_{$field_name}[{$index}][value][date]");
+    } else {
+      return parent::getFormFieldsNames($field_name, $index);
+    }
+  }
+}
