diff --git a/composer.json b/composer.json
index 9b317c4..6d70605 100644
--- a/composer.json
+++ b/composer.json
@@ -19,7 +19,8 @@
     "kriswallsmith/assetic": "1.1.*@alpha",
     "symfony-cmf/routing": "1.1.*@alpha",
     "easyrdf/easyrdf": "0.8.*@beta",
-    "phpunit/phpunit": "3.7.*"
+    "phpunit/phpunit": "3.7.*",
+    "zendframework/zend-feed": "2.2.*@rc"
   },
   "autoload": {
     "psr-0": {
@@ -31,5 +32,6 @@
   "config": {
     "vendor-dir": "core/vendor",
     "preferred-install": "dist"
-  }
+  },
+  "minimum-stability": "dev"
 }
diff --git a/core/core.services.yml b/core/core.services.yml
index 56142b9..1a4c86f 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -424,3 +424,57 @@ services:
   token:
     class: Drupal\Core\Utility\Token
     arguments: ['@module_handler']
+  feed.bridge.reader:
+    class: Drupal\Component\Bridge\ZfExtensionManagerSfContainer
+    calls:
+      - [setContainer, ['@service_container']]
+    arguments: ['feed.reader.']
+  feed.bridge.writer:
+    class: Drupal\Component\Bridge\ZfExtensionManagerSfContainer
+    calls:
+      - [setContainer, ['@service_container']]
+    arguments: ['feed.writer.']
+# Zend Feed reader plugins
+  feed.reader.dublincoreentry:
+    class: Zend\Feed\Reader\Extension\DublinCore\Entry
+  feed.reader.dublincorefeed:
+    class: Zend\Feed\Reader\Extension\DublinCore\Feed
+  feed.reader.contententry:
+    class: Zend\Feed\Reader\Extension\Content\Entry
+  feed.reader.atomentry:
+    class: Zend\Feed\Reader\Extension\Atom\Entry
+  feed.reader.atomfeed:
+    class: Zend\Feed\Reader\Extension\Atom\Feed
+  feed.reader.slashentry:
+    class: Zend\Feed\Reader\Extension\Slash\Entry
+  feed.reader.wellformedwebentry:
+    class: Zend\Feed\Reader\Extension\WellFormedWeb\Entry
+  feed.reader.threadentry:
+    class: Zend\Feed\Reader\Extension\Thread\Entry
+  feed.reader.podcastentry:
+    class: Zend\Feed\Reader\Extension\Podcast\Entry
+  feed.reader.podcastfeed:
+    class: Zend\Feed\Reader\Extension\Podcast\Feed
+# Zend Feed writer plugins
+  feed.writer.atomrendererfeed:
+    class: Zend\Feed\Writer\Extension\Atom\Renderer\Feed
+  feed.writer.contentrendererentry:
+    class: Zend\Feed\Writer\Extension\Content\Renderer\Entry
+  feed.writer.dublincorerendererentry:
+    class: Zend\Feed\Writer\Extension\DublinCore\Renderer\Entry
+  feed.writer.dublincorerendererfeed:
+    class: Zend\Feed\Writer\Extension\DublinCore\Renderer\Feed
+  feed.writer.itunesentry:
+    class: Zend\Feed\Writer\Extension\ITunes\Entry
+  feed.writer.itunesfeed:
+    class: Zend\Feed\Writer\Extension\ITunes\Feed
+  feed.writer.itunesrendererentry:
+    class: Zend\Feed\Writer\Extension\ITunes\Renderer\Entry
+  feed.writer.itunesrendererfeed:
+    class: Zend\Feed\Writer\Extension\ITunes\Renderer\Feed
+  feed.writer.slashrendererentry:
+    class: Zend\Feed\Writer\Extension\Slash\Renderer\Entry
+  feed.writer.threadingrendererentry:
+    class: Zend\Feed\Writer\Extension\Threading\Renderer\Entry
+  feed.writer.wellformedwebrendererentry:
+    class: Zend\Feed\Writer\Extension\WellFormedWeb\Renderer\Entry
diff --git a/core/includes/common.inc b/core/includes/common.inc
index 4f72021..97ebcce 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -12,6 +12,8 @@
 use Drupal\Core\Database\Database;
 use Drupal\Core\SystemListingInfo;
 use Drupal\Core\Template\Attribute;
+use Zend\Feed\Writer\Writer;
+use Zend\Feed\Reader\Reader;
 
 /**
  * @file
@@ -4584,6 +4586,10 @@ function _drupal_bootstrap_code() {
   // Load all enabled modules
   drupal_container()->get('module_handler')->loadAll();
 
+  // Set our bridge extension manager to Zend Feed.
+  Reader::setExtensionManager(Drupal::service('feed.bridge.reader'));
+  Writer::setExtensionManager(Drupal::service('feed.bridge.writer'));
+
   // Make sure all stream wrappers are registered.
   file_get_stream_wrappers();
 
diff --git a/core/lib/Drupal/Component/Bridge/ZfExtensionManagerSfContainer.php b/core/lib/Drupal/Component/Bridge/ZfExtensionManagerSfContainer.php
new file mode 100644
index 0000000..f33c6b8
--- /dev/null
+++ b/core/lib/Drupal/Component/Bridge/ZfExtensionManagerSfContainer.php
@@ -0,0 +1,83 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Component\Bridge\ZfExtensionManagerSfContainer
+ */
+namespace Drupal\Component\Bridge;
+
+use Symfony\Component\DependencyInjection\ContainerAwareInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Zend\Feed\Reader\ExtensionManagerInterface as ReaderManagerInterface;
+use Zend\Feed\Writer\ExtensionManagerInterface as WriterManagerInterface;
+
+/**
+ * Defines a bridge between the ZF2 service manager to Symfony container.
+ */
+class ZfExtensionManagerSfContainer implements ReaderManagerInterface, WriterManagerInterface, ContainerAwareInterface {
+
+  /**
+   * A map of characters to be replaced through strtr.
+   *
+   * @var array
+   *
+   * @see \Drupal\Component\Bridge\ZfExtensionManagerSfContainer::canonicalizeName().
+   */
+  protected $canonicalNamesReplacements = array('-' => '', '_' => '', ' ' => '', '\\' => '', '/' => '');
+
+  /**
+   * The prefix to be used when retrieving plugins from the container.
+   *
+   * @var string
+   */
+  protected $prefix = '';
+
+  /**
+   * Constructs a ZfExtensionManagerSfContainer object.
+   *
+   * @param string $prefix
+   *   The prefix to be used when retrieving plugins from the container.
+   */
+  public function __construct($prefix = '') {
+    return $this->prefix = $prefix;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function get($extension) {
+    return $this->container->get($this->prefix . $this->canonicalizeName($extension));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function has($extension) {
+    return $this->container->has($this->prefix . $this->canonicalizeName($extension));
+  }
+
+  /**
+   * Canonicalize the extension name to a service name.
+   *
+   * @param string $name
+   *   The extension name.
+   *
+   * @return string
+   *   The service name, without the prefix.
+   */
+  protected function canonicalizeName($name) {
+    if (isset($this->canonicalNames[$name])) {
+      return $this->canonicalNames[$name];
+    }
+    // This is just for performance instead of using str_replace().
+    return $this->canonicalNames[$name] = strtolower(strtr($name, $this->canonicalNamesReplacements));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setContainer(ContainerInterface $container = NULL) {
+    $this->container = $container;
+  }
+
+}
diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Plugin/aggregator/parser/DefaultParser.php b/core/modules/aggregator/lib/Drupal/aggregator/Plugin/aggregator/parser/DefaultParser.php
index eab6e9b..a295749 100644
--- a/core/modules/aggregator/lib/Drupal/aggregator/Plugin/aggregator/parser/DefaultParser.php
+++ b/core/modules/aggregator/lib/Drupal/aggregator/Plugin/aggregator/parser/DefaultParser.php
@@ -11,6 +11,9 @@
 use Drupal\aggregator\Plugin\Core\Entity\Feed;
 use Drupal\aggregator\Annotation\AggregatorParser;
 use Drupal\Core\Annotation\Translation;
+use Drupal\Core\Cache\Cache;
+use Zend\Feed\Reader\Reader;
+use Zend\Feed\Reader\Exception\ExceptionInterface;
 
 /**
  * Defines a default parser implementation.
@@ -26,353 +29,50 @@
 class DefaultParser implements ParserInterface {
 
   /**
-   * The extracted channel info.
-   *
-   * @var array
-   */
-  protected $channel = array();
-
-  /**
-   * The extracted image info.
-   *
-   * @var array
-   */
-  protected $image = array();
-
-  /**
-   * The extracted items.
-   *
-   * @var array
-   */
-  protected $items = array();
-
-  /**
-   * The element that is being processed.
-   *
-   * @var array
-   */
-  protected $element = array();
-
-  /**
-   * The tag that is being processed.
-   *
-   * @var string
-   */
-  protected $tag = '';
-
-  /**
-   * Key that holds the number of processed "entry" and "item" tags.
-   *
-   * @var int
-   */
-  protected $item;
-
-  /**
-   * Implements \Drupal\aggregator\Plugin\ParserInterface::parse().
+   * {@inheritdoc}
    */
   public function parse(Feed $feed) {
-    // Filter the input data.
-    if ($this->parseFeed($feed->source_string, $feed)) {
-
-      // Prepare the channel data.
-      foreach ($this->channel as $key => $value) {
-        $this->channel[$key] = trim($value);
-      }
-
-      // Prepare the image data (if any).
-      foreach ($this->image as $key => $value) {
-        $this->image[$key] = trim($value);
-      }
-
-      // Add parsed data to the feed object.
-      $feed->link->value = !empty($channel['link']) ? $channel['link'] : '';
-      $feed->description->value = !empty($channel['description']) ? $channel['description'] : '';
-      $feed->image->value = !empty($image['url']) ? $image['url'] : '';
-
-      // Clear the page and block caches.
-      cache_invalidate_tags(array('content' => TRUE));
-
-      return TRUE;
+    try {
+      $channel = Reader::importString($feed->source_string);
     }
+    catch (ExceptionInterface $e) {
+      watchdog_exception('aggregator', $e);
+      drupal_set_message(t('The feed from %site seems to be broken because of error "%error".', array('%site' => $feed->label(), '%error' => $e->getMessage())), 'error');
 
-    return FALSE;
-  }
-
-
-  /**
-   * Parses a feed and stores its items.
-   *
-   * @param string $data
-   *   The feed data.
-   * @param \Drupal\aggregator\Plugin\Core\Entity\Feed $feed
-   *   An object describing the feed to be parsed.
-   *
-   * @return bool
-   *   FALSE on error, TRUE otherwise.
-   */
-  protected function parseFeed(&$data, Feed $feed) {
-    // Parse the data.
-    $xml_parser = drupal_xml_parser_create($data);
-    xml_set_element_handler($xml_parser, array($this, 'elementStart'), array($this, 'elementEnd'));
-    xml_set_character_data_handler($xml_parser, array($this, 'elementData'));
-
-    if (!xml_parse($xml_parser, $data, 1)) {
-      watchdog('aggregator', 'The feed from %site seems to be broken due to an error "%error" on line %line.', array('%site' => $feed->label(), '%error' => xml_error_string(xml_get_error_code($xml_parser)), '%line' => xml_get_current_line_number($xml_parser)), WATCHDOG_WARNING);
-      drupal_set_message(t('The feed from %site seems to be broken because of error "%error" on line %line.', array('%site' => $feed->label(), '%error' => xml_error_string(xml_get_error_code($xml_parser)), '%line' => xml_get_current_line_number($xml_parser))), 'error');
       return FALSE;
     }
-    xml_parser_free($xml_parser);
-
-    // We reverse the array such that we store the first item last, and the last
-    // item first. In the database, the newest item should be at the top.
-    $this->items = array_reverse($this->items);
 
+    $feed->link->value = $channel->getLink();
+    $feed->description->value = $channel->getDescription();
+    if ($image = $channel->getImage()) {
+      $feed->image->value = $image['uri'];
+    }
     // Initialize items array.
     $feed->items = array();
-    foreach ($this->items as $item) {
-
-      // Prepare the item:
-      foreach ($item as $key => $value) {
-        $item[$key] = trim($value);
-      }
-
-      // Resolve the item's title. If no title is found, we use up to 40
-      // characters of the description ending at a word boundary, but not
-      // splitting potential entities.
-      if (!empty($item['title'])) {
-        $item['title'] = $item['title'];
-      }
-      elseif (!empty($item['description'])) {
-        $item['title'] = preg_replace('/^(.*)[^\w;&].*?$/', "\\1", truncate_utf8($item['description'], 40));
-      }
-      else {
-        $item['title'] = '';
-      }
-
-      // Resolve the items link.
-      if (!empty($item['link'])) {
-        $item['link'] = $item['link'];
+    foreach ($channel as $item) {
+      // Reset the parsed item.
+      $parsed_item = array();
+      // Move the values to an array as expected by processors.
+      $parsed_item['title'] = $item->getTitle();
+      $parsed_item['guid'] = $item->getId();
+      $parsed_item['link'] = $item->getLink();
+      $parsed_item['description'] = $item->getDescription();
+      $parsed_item['author'] = '';
+      if ($author = $item->getAuthor()) {
+        $parsed_item['author'] = $author['name'];
+      }
+      $parsed_item['timestamp'] = '';
+      if ($date = $item->getDateModified()) {
+        $parsed_item['timestamp'] = $date->getTimestamp();
       }
-      else {
-        $item['link'] = $feed->link->value;
-      }
-
-      // Atom feeds have an ID tag instead of a GUID tag.
-      if (!isset($item['guid'])) {
-        $item['guid'] = isset($item['id']) ? $item['id'] : '';
-      }
-
-      // Atom feeds have a content and/or summary tag instead of a description tag.
-      if (!empty($item['content:encoded'])) {
-        $item['description'] = $item['content:encoded'];
-      }
-      elseif (!empty($item['summary'])) {
-        $item['description'] = $item['summary'];
-      }
-      elseif (!empty($item['content'])) {
-        $item['description'] = $item['content'];
-      }
-
-      // Try to resolve and parse the item's publication date.
-      $date = '';
-      foreach (array('pubdate', 'dc:date', 'dcterms:issued', 'dcterms:created', 'dcterms:modified', 'issued', 'created', 'modified', 'published', 'updated') as $key) {
-        if (!empty($item[$key])) {
-          $date = $item[$key];
-          break;
-        }
-      }
-
-      $item['timestamp'] = strtotime($date);
-
-      if ($item['timestamp'] === FALSE) {
-        $item['timestamp'] = $this->parseW3cdtf($date); // Aggregator_parse_w3cdtf() returns FALSE on failure.
-      }
-
-      // Resolve dc:creator tag as the item author if author tag is not set.
-      if (empty($item['author']) && !empty($item['dc:creator'])) {
-        $item['author'] = $item['dc:creator'];
-      }
-
-      $item += array('author' => '', 'description' => '');
-
       // Store on $feed object. This is where processors will look for parsed items.
-      $feed->items[] = $item;
-    }
-
-    return TRUE;
-  }
-
-  /**
-   * XML parser callback: Perform an action when an opening tag is encountered.
-   *
-   * @param resource $parser
-   *   A reference to the XML parser calling the handler.
-   * @param string $name
-   *   The name of the element for which this handler is called.
-   * @param array $attributes
-   *   An associative array with the element's attributes (if any).
-   */
-  protected function elementStart($parser, $name, $attributes) {
-    $name = strtolower($name);
-    switch ($name) {
-      case 'image':
-      case 'textinput':
-      case 'summary':
-      case 'tagline':
-      case 'subtitle':
-      case 'logo':
-      case 'info':
-        $this->element = $name;
-        break;
-      case 'id':
-      case 'content':
-        if ($this->element != 'item') {
-          $this->element = $name;
-        }
-      case 'link':
-        // According to RFC 4287, link elements in Atom feeds without a 'rel'
-        // attribute should be interpreted as though the relation type is
-        // "alternate".
-        if (!empty($attributes['HREF']) && (empty($attributes['REL']) || $attributes['REL'] == 'alternate')) {
-          if ($this->element == 'item') {
-            $this->items[$this->item]['link'] = $attributes['HREF'];
-          }
-          else {
-            $this->channel['link'] = $attributes['HREF'];
-          }
-        }
-        break;
-      case 'item':
-        $this->element = $name;
-        $this->item += 1;
-        break;
-      case 'entry':
-        $this->element = 'item';
-        $this->item += 1;
-        break;
+      $feed->items[] = $parsed_item;
     }
 
-    $this->tag = $name;
-  }
-
-  /**
-   * XML parser callback: Perform an action when a closing tag is encountered.
-   *
-   * @param resource $parser
-   *   A reference to the XML parser calling the handler.
-   * @param string $name
-   *   The name of the element for which this handler is called.
-   * @param array $attributes
-   *   An associative array with the element's attributes (if any).
-   */
-  protected function elementEnd($parser, $name) {
-    switch ($name) {
-      case 'image':
-      case 'textinput':
-      case 'item':
-      case 'entry':
-      case 'info':
-        $this->element = '';
-        break;
-      case 'id':
-      case 'content':
-        if ($this->element == $name) {
-          $this->element = '';
-        }
-    }
-  }
+    // Clear the page and block caches.
+    Cache::invalidateTags(array('content' => TRUE));
 
-  /**
-   * XML parser callback: Perform an action when data is encountered.
-   *
-   * @param resource $parser
-   *   A reference to the XML parser calling the handler.
-   * @param string $name
-   *   The name of the element for which this handler is called.
-   * @param array $attributes
-   *   An associative array with the element's attributes (if any).
-   */
-  function elementData($parser, $data) {
-    $this->items += array($this->item => array());
-    switch ($this->element) {
-      case 'item':
-        $this->items[$this->item] += array($this->tag => '');
-        $this->items[$this->item][$this->tag] .= $data;
-        break;
-      case 'image':
-      case 'logo':
-        $this->image += array($this->tag => '');
-        $this->image[$this->tag] .= $data;
-        break;
-      case 'link':
-        if ($data) {
-          $this->items[$this->item] += array($tag => '');
-          $this->items[$this->item][$this->tag] .= $data;
-        }
-        break;
-      case 'content':
-        $this->items[$this->item] += array('content' => '');
-        $this->items[$this->item]['content'] .= $data;
-        break;
-      case 'summary':
-        $this->items[$this->item] += array('summary' => '');
-        $this->items[$this->item]['summary'] .= $data;
-        break;
-      case 'tagline':
-      case 'subtitle':
-        $this->channel += array('description' => '');
-        $this->channel['description'] .= $data;
-        break;
-      case 'info':
-      case 'id':
-      case 'textinput':
-        // The sub-element is not supported. However, we must recognize
-        // it or its contents will end up in the item array.
-        break;
-      default:
-        $this->channel += array($this->tag => '');
-        $this->channel[$this->tag] .= $data;
-    }
+    return TRUE;
   }
 
-  /**
-   * Parses the W3C date/time format, a subset of ISO 8601.
-   *
-   * PHP date parsing functions do not handle this format. See
-   * http://www.w3.org/TR/NOTE-datetime for more information. Originally from
-   * MagpieRSS (http://magpierss.sourceforge.net/).
-   *
-   * @param string $date_str
-   *   A string with a potentially W3C DTF date.
-   *
-   * @return int|false
-   *   A timestamp if parsed successfully or FALSE if not.
-   */
-  function parseW3cdtf($date_str) {
-    if (preg_match('/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(:(\d{2}))?(?:([-+])(\d{2}):?(\d{2})|(Z))?/', $date_str, $match)) {
-      list($year, $month, $day, $hours, $minutes, $seconds) = array($match[1], $match[2], $match[3], $match[4], $match[5], $match[6]);
-      // Calculate the epoch for current date assuming GMT.
-      $epoch = gmmktime($hours, $minutes, $seconds, $month, $day, $year);
-      if ($match[10] != 'Z') { // Z is zulu time, aka GMT
-        list($tz_mod, $tz_hour, $tz_min) = array($match[8], $match[9], $match[10]);
-        // Zero out the variables.
-        if (!$tz_hour) {
-          $tz_hour = 0;
-        }
-        if (!$tz_min) {
-          $tz_min = 0;
-        }
-        $offset_secs = (($tz_hour * 60) + $tz_min) * 60;
-        // Is timezone ahead of GMT?  If yes, subtract offset.
-        if ($tz_mod == '+') {
-          $offset_secs *= -1;
-        }
-        $epoch += $offset_secs;
-      }
-      return $epoch;
-    }
-    else {
-      return FALSE;
-    }
-  }
 }
diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Tests/FeedParserTest.php b/core/modules/aggregator/lib/Drupal/aggregator/Tests/FeedParserTest.php
index 5f4f9f9..bdbe11f 100644
--- a/core/modules/aggregator/lib/Drupal/aggregator/Tests/FeedParserTest.php
+++ b/core/modules/aggregator/lib/Drupal/aggregator/Tests/FeedParserTest.php
@@ -7,6 +7,8 @@
 
 namespace Drupal\aggregator\Tests;
 
+use Zend\Feed\Reader\Reader;
+
 /**
  * Tests feed parsing in the Aggregator module.
  */
@@ -25,6 +27,11 @@ function setUp() {
     // feeds have hardcoded dates in them (which may be expired when this test
     // is run).
     config('aggregator.settings')->set('items.expire', AGGREGATOR_CLEAR_NEVER)->save();
+    // Reset any reader cache between tests.
+    Reader::reset();
+    // Set our bridge extension manager to Zend Feed.
+    $bridge = $this->container->get('feed.bridge.reader');
+    Reader::setExtensionManager($bridge);
   }
 
   /**
