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 12 Jan 2010 23:33:00 -0000 @@ -0,0 +1,79 @@ + $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; + $targets[$field_name.'#start'] = array( + 'name' => $name . ': Start', + 'callback' => 'date_feeds_set_target', + 'description' => t('The start date for the !name field. Also use if mapping both start and end.', array('!name' => $name)), + ); + $targets[$field_name.'#end'] = array( + 'name' => $name . ': End', + 'callback' => 'date_feeds_set_target', + 'description' => t('The end date for the !name field.', array('!name' => $name)), + ); + } + } + } +} + +/** + * Implementation of hook_feeds_set_target(). + * + * @param $node + * The target node. + * @param $field_name + * The name of field on the target node to map to. + * @param $feed_element + * The value to be mapped. Should be either a (flexible) date string + * or a FeedsDateTimeField object. + * + * @todo Support array of values for dates. + */ +function date_feeds_set_target($node, $target, $feed_element) { + if (empty($feed_element)) { + return; + } + list($field_name, $sub_field) = split('#', $target); + if ($feed_element instanceof FeedsDateTimeField) { + // The wrapper object should only be used if you know both end and start. + $feed_element->buildField($node, $field_name); + return; + } + + $field = content_fields($field_name); + $timezone = date_default_timezone_name(); + // The timezone to assume this is entered in, unless it has a timezone as part of the date itself. + $to_tz = date_get_timezone($field['tz_handling'], $timezone); + + if (is_array($feed_element)) { + $feed_element = $feed_element[0]; + } + // Parse the value. If it doesn't contain a timezone, it will be set to the field timezone by default. + $value = new FeedsDateTime($feed_element, timezone_open($to_tz)); + if($sub_field == 'end') { + $se_dt = new FeedsDateTimeField(NULL, $value); + } else { + $se_dt = new FeedsDateTimeField($value, NULL); + } + $se_dt->buildField($node, $field_name); +} \ No newline at end of file Index: plugins/FeedsParser.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/plugins/FeedsParser.inc,v retrieving revision 1.5 diff -u -r1.5 FeedsParser.inc --- plugins/FeedsParser.inc 12 Jan 2010 16:28:02 -0000 1.5 +++ plugins/FeedsParser.inc 12 Jan 2010 23:33:00 -0000 @@ -95,4 +95,235 @@ public function getSourceElement($item, $element_key) { return isset($item[$element_key]) ? $item[$element_key] : ''; } +} + +class FeedsDateTime extends DateTime { + public $granularity = array(); + protected static $allgranularity = array('year', 'month', 'day', 'hour', 'minute', 'second', 'tz_id'); + + public function __construct($time, $tz) { + if(is_numeric($time)) { + //assume timestamp + $time = "@".$time; + } + // PHP <5.3 doesn't like the GMT- notation for parsing timezones + $time = str_replace("GMT-", "-", $time); + $time = str_replace("GMT+", "+", $time); + + parent::__construct($time, $tz); + $this->setGranularityFromTime($time, $tz); + } + + public function hasTime() { + return in_array('hour', $this->granularity); + } + + /** + * This function will keep this object's values by default. + * Only one timezone is ever respected; ie one tz was WRONG or IRRELEVANT. + * Examples: + * One has date info and the other time + * the one with time will set TZ, and date will stay the same in new TZ + * One has tz_id granularity + * that tz will override all others, all elements will stay same in new TZ + * + */ + public function merge(FeedsDateTime $dt) { + $old_dttz = $dt->getTimezone(); + $old_tz = $this->getTimezone(); + $gran_tz = FALSE; + // Figure out which timezone to use, with preferential order. + if (in_array('tz_id', $this->granularity)) { + // Use our timezone preferentially + $use_tz = $old_tz; + $gran_tz = TRUE; + } else if (in_array('tz_id', $dt->granularity)) { + // They specifically set tz, use it + $use_tz = $old_dttz; + $gran_tz = TRUE; + } else if($this->hasTime()) { + // We have time set, use ours + $use_tz = $old_tz; + } else if($dt->hasTime()) { + // They have time set, use it + $use_tz = $old_dttz; + } else { + // no relevant timezone; doesn't matter. + $use_tz = $old_tz; + } + // Convert both to the working tz, without doing conversion, + // The printed date will stay the same, timestamp changes. + $this->setTimezone($use_tz, TRUE); + $dt->setTimezone($use_tz, TRUE); + $val = $this->toArray(); + $otherval = $dt->toArray(); + $gran = $this->granularity; + foreach(self::$allgranularity as $g) { + if (in_array($g, $dt->granularity) && !in_array($g, $this->granularity)) { + // The other class has a property we don't; steal it. + $gran[] = $g; + $val[$g] = $otherval[$g]; + } + } + // Convert back, once again not actually changing literal, printed values. + $dt->setTimezone($old_dttz, TRUE); + $this->setTimezone($old_tz, TRUE); + return FeedsDateTime::createFromArray($val, $gran, $use_tz); + } + + /** + * Output in the format date module looks for for storage. + * + * @param boolean $iso + * Whether it should be output in pseudo-iso format, used for 'date', supports granularity. + */ + public function toField($iso = FALSE) { + $format = "Y-m-d H:i:s"; + if ($iso) { + $format = "Y-m-d\TH:i:s"; + $format = date_limit_format($format, $this->granularity); + } + return $this->format($format); + } + + /** + * Overrides default DateTime function. Only changes output values if actually had + * time granularity. + */ + public function setTimezone(DateTimeZone $dt, $force = FALSE) { + if(!$this->hasTime() || $force) { + // this has no time granularity, so timezone doesn't mean much + // We set the timezone using the method, which will change the day/hour, but then we switch back + $arr = $this->toArray(); + parent::setTimezone($dt); + $this->setTime($arr['hour'], $arr['minute'], $arr['second']); + $this->setDate($arr['year'], $arr['month'], $arr['day']); + return; + } + parent::setTimezone($dt); + } + + public function addGranularity($g) { + $this->granularity[] = $g; + $this->granularity = array_unique($this->granularity); + } + + public function removeGranularity($g) { + if($key = array_search($g, $this->granularity)) { + unset($this->granularity[$key]); + } + } + + /** + * Create FeedsDateTime from arrays of time and granularity. + * + * @return + * A FeedsDateTime object. + */ + public static function createFromArray($time, $gran, $tz) { + $ret = new FeedsDateTime($time['year'].'-'.$time['month'].'-'.$time['day'].' '.$time['hour'].':'.$time['minute'].':'.$time['second'] .' '. $time['tz_id'], $tz); + $ret->granularity = $gran; + return $ret; + } + + protected function setGranularityFromTime($time, $tz) { + $this->granularity = array(); + $temp = date_parse($time); + // This PHP method currently doesn't have resolution down to seconds, so if there is some time, all will be set. + foreach(self::$allgranularity AS $g) { + if(is_numeric($temp[$g]) || ($g == 'tz_id' && !empty($temp[$g]))) { + $this->granularity[] = $g; + } + } + } + + protected function toArray() { + return array('year' => $this->format('Y'), 'month' => $this->format('m'), 'day' => $this->format('d'), 'hour' => $this->format('H'), 'minute' => $this->format('i'), 'second' => $this->format('s'), 'tz_id' => $this->format('e')); + } +} + +/** + * Defines a parsed date (including ranges, repeat) wrapper, to be passed to mappers. + */ +class FeedsDateTimeField extends FeedsElement { + protected $value2; + public function __construct($s = NULL, $e = NULL) { + $this->value = $s; + $this->value2 = $e; + } + + /** + * Merge this field with another. Most stuff goes down when + * merging the two sub-dates. + * + * @see FeedsDateTime + */ + public function merge(FeedsDateTimeField $fsedt, $byparts = TRUE) { + $rs = $this->value ? $this->value : $fsedt->value; + $re = $this->value2 ? $this->value2 : $fsedt->value2; + if ($byparts) { + $rs = $this->value && $fsedt->value ? $this->value->merge($fsedt->value) : clone $rs; + $re = $this->value2 && $fsedt->value2 ? $this->value2->merge($fsedt->value2) : clone $re; + } + return new FeedsDateTimeField($rs, $re); + } + + /** + * Build a node's date CCK field from our object. + */ + public function buildField($node, $field_name) { + $field = content_fields($field_name); + // Put together the whole object we're dealing with, including if if already exists in some fashion on the field. + if($node->{$field_name}[0]['date']) { + // already has some date data + if($this->value) { + $use_start = $this->value->merge($node->{$field_name}[0]['date']); + } else { + $use_start = $node->{$field_name}[0]['date']; + } + } else if ($this->value) { + $use_start = clone $this->value; + } else { + $use_start = NULL; + } + if($node->{$field_name}[0]['date2']) { + // already has some date data + if($this->value) { + $use_end = $this->value2->merge($node->{$field_name}[0]['date2']); + } else { + $use_end = $node->{$field_name}[0]['date2']; + } + } else if ($this->value2) { + $use_end = clone $this->value2; + } else { + $use_end = NULL; + } + + if ($use_start) { + $db_tz = date_get_timezone_db($field['tz_handling'], $use_start->getTimezone()->getName()); + } elseif ($use_end) { + $db_tz = date_get_timezone_db($field['tz_handling'], $use_end->getTimezone()->getName()); + } else { + return; + } + $db_tz = new DateTimeZone($db_tz); + + if (!isset($node->{$field_name})) { + $node->{$field_name} = array(); + } + if ($use_start) { + $node->{$field_name}[0]['timezone'] = $use_start->getTimezone()->getName(); + $node->{$field_name}[0]['offset'] = $use_start->getOffset(); + $use_start->setTimezone($db_tz); + $node->{$field_name}[0]['date'] = $use_start; + $node->{$field_name}[0]['value'] = $use_start->toField($field['type'] == 'date'); + } + if ($use_end) { + // Don't ever use end to set timezone (for now) + $node->{$field_name}[0]['offset2'] = $use_end->getOffset(); + $use_end->setTimezone($db_tz); + $node->{$field_name}[0]['date2'] = $use_end; + $node->{$field_name}[0]['value2'] = $use_end->toField($field['type'] == 'date'); + } + } } \ No newline at end of file