diff --git a/core_search_facets/src/Plugin/facets/facet_source/CoreNodeSearchFacetSource.php b/core_search_facets/src/Plugin/facets/facet_source/CoreNodeSearchFacetSource.php
index d3755e1..86d850e 100644
--- a/core_search_facets/src/Plugin/facets/facet_source/CoreNodeSearchFacetSource.php
+++ b/core_search_facets/src/Plugin/facets/facet_source/CoreNodeSearchFacetSource.php
@@ -152,6 +152,11 @@ class CoreNodeSearchFacetSource extends FacetSourcePluginBase implements CoreSea
       case 'entity_reference':
         $query_types['string'] = 'core_node_search_string';
         break;
+
+      case 'created':
+        $query_types['string'] = 'core_node_search_date';
+        break;
+
     }
 
     return $query_types;
@@ -230,6 +235,7 @@ class CoreNodeSearchFacetSource extends FacetSourcePluginBase implements CoreSea
       'type' => $this->t('Content Type'),
       'uid' => $this->t('Author'),
       'langcode' => $this->t('Language'),
+      'created' => $this->t('Post date'),
     ];
   }
 
@@ -239,7 +245,7 @@ class CoreNodeSearchFacetSource extends FacetSourcePluginBase implements CoreSea
   public function getFacetQueryExtender() {
     if (!$this->facetQueryExtender) {
       $this->facetQueryExtender = db_select('search_index', 'i', array('target' => 'replica'))->extend('Drupal\core_search_facets\FacetsQuery');
-      $this->facetQueryExtender->join('node_field_data', 'n', 'n.nid = i.sid');
+      $this->facetQueryExtender->join('node_field_data', 'n', 'n.nid = i.sid AND n.langcode = i.langcode');
       $this->facetQueryExtender
          // ->condition('n.status', 1).
          ->addTag('node_access')
diff --git a/core_search_facets/src/Plugin/facets/query_type/CoreNodeSearchDate.php b/core_search_facets/src/Plugin/facets/query_type/CoreNodeSearchDate.php
new file mode 100644
index 0000000..7a83fb4
--- /dev/null
+++ b/core_search_facets/src/Plugin/facets/query_type/CoreNodeSearchDate.php
@@ -0,0 +1,196 @@
+<?php
+
+namespace Drupal\core_search_facets\Plugin\facets\query_type;
+
+use Drupal\facets\QueryType\QueryTypePluginBase;
+use Drupal\facets\Result\Result;
+use Drupal\facets\Result\ResultInterface;
+
+/**
+ * A date query type for core search.
+ *
+ * @FacetsQueryType(
+ *   id = "core_node_search_date",
+ *   label = @Translation("Date"),
+ * )
+ */
+class CoreNodeSearchDate extends QueryTypePluginBase {
+
+  /**
+   * The backend's native query object.
+   *
+   * @var \Drupal\search_api\Query\QueryInterface
+   */
+  protected $query;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function execute() {
+    /** @var \Drupal\facets\Utility\FacetsDateHandler $date_handler */
+    $date_handler = \Drupal::getContainer()->get('facets.utility.date_handler');
+
+    /** @var \Drupal\core_search_facets\Plugin\CoreSearchFacetSourceInterface $facet_source */
+    $facet_source = $this->facet->getFacetSource();
+
+    // Gets the last active date, bails if there isn't one.
+    $active_items = $this->facet->getActiveItems();
+    if (!$active_item = end($active_items)) {
+      return;
+    }
+
+    // Gets facet query and this facet's query info.
+    /** @var \Drupal\core_search_facets\FacetsQuery $facet_query */
+    $facet_query = $facet_source->getFacetQueryExtender();
+    $query_info = $facet_source->getQueryInfo($this->facet);
+    $tables_joined = [];
+
+    $active_item = $date_handler->extractActiveItems($active_item);
+
+    foreach ($query_info['fields'] as $field_info) {
+
+      // Adds join to the facet query.
+      $facet_query->addFacetJoin($query_info, $field_info['table_alias']);
+
+      // Adds join to search query, makes sure it is only added once.
+      if (isset($query_info['joins'][$field_info['table_alias']])) {
+        if (!isset($tables_joined[$field_info['table_alias']])) {
+          $tables_joined[$field_info['table_alias']] = TRUE;
+          $join_info = $query_info['joins'][$field_info['table_alias']];
+          $this->query->join($join_info['table'], $join_info['alias'], $join_info['condition']);
+        }
+      }
+
+      // Adds field conditions to the facet and search query.
+      $field = $field_info['table_alias'] . '.' . $field_info['field'];
+      $this->query->condition($field, $active_item['start']['timestamp'], '>=');
+      $this->query->condition($field, $active_item['end']['timestamp'], '<');
+      $facet_query->condition($field, $active_item['start']['timestamp'], '>=');
+      $facet_query->condition($field, $active_item['end']['timestamp'], '<');
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function build() {
+    $parent_facet_results = [];
+    /** @var \Drupal\facets\Utility\FacetsDateHandler $date_handler */
+    $date_handler = \Drupal::getContainer()->get('facets.utility.date_handler');
+
+    // Gets base facet query, adds facet field and filters.
+    /* @var \Drupal\core_search_facets\Plugin\CoreSearchFacetSourceInterface $facet_source */
+    $facet_source = $this->facet->getFacetSource();
+    $query_info = $facet_source->getQueryInfo($this->facet);
+
+    /** @var \Drupal\core_search_facets\FacetsQuery $facet_query */
+    $facet_query = $facet_source->getFacetQueryExtender();
+    $facet_query->addFacetField($query_info);
+
+    foreach ($query_info['joins'] as $table_alias => $join_info) {
+      $facet_query->addFacetJoin($query_info, $table_alias);
+    }
+
+    if ($facet_query->getSearchExpression()) {
+      // Executes query, iterates over results.
+      $result = $facet_query->execute();
+
+      foreach ($result as $record) {
+        $raw_values[$record->value] = $record->count;
+      }
+      ksort($raw_values);
+
+      // Gets active facets, starts building hierarchy.
+      $parent = NULL;
+      $gap = NULL;
+      $last_parent = NULL;
+
+      foreach ($this->facet->getActiveItems() as $value => $item) {
+        if ($active_item = $date_handler->extractActiveItems($item)) {
+          $date_gap = $date_handler->getDateGap($active_item['start']['iso'], $active_item['end']['iso']);
+          $gap = $date_handler->getNextDateGap($date_gap, $date_handler::FACETS_DATE_MINUTE);
+          $last_parent = '[' . $active_item['start']['iso'] . ' TO ' . $active_item['end']['iso'] . ']';
+          $result = new Result($last_parent, $date_handler->formatTimestamp($active_item['start']['timestamp'], $date_gap), NULL);
+          $result->setActiveState(TRUE);
+          // Sets the children for the current parent..
+          if ($parent) {
+            $parent->setChildren($result);
+          }
+          else {
+            $parent = $parent_facet_results[] = $result;
+          }
+        }
+      }
+
+      // Mind the gap! Calculates gap from min and max timestamps.
+      $timestamps = array_keys($raw_values);
+      if (is_null($parent)) {
+        if (count($raw_values) > 1) {
+          $gap = $date_handler->getTimestampGap(min($timestamps), max($timestamps));
+        }
+        else {
+          $gap = $date_handler::FACETS_DATE_HOUR;
+        }
+      }
+
+      // Converts all timestamps to dates in ISO 8601 format.
+      $dates = [];
+      foreach ($timestamps as $timestamp) {
+        $dates[$timestamp] = $date_handler->isoDate($timestamp, $gap);
+      }
+
+      // Treat each date as the range start and next date as the range end.
+      $range_end = [];
+      $previous = NULL;
+      foreach (array_unique($dates) as $date) {
+        if (!is_null($previous)) {
+          $range_end[$previous] = $date_handler->getNextDateIncrement($previous, $gap);
+        }
+        $previous = $date;
+      }
+      $range_end[$previous] = $date_handler->getNextDateIncrement($previous, $gap);
+
+      $facet_results = [];
+      foreach ($raw_values as $value => $count) {
+        $new_value = '[' . $dates[$value] . ' TO ' . $range_end[$dates[$value]] . ']';
+
+        // Avoid to repeat the last value.
+        if ($new_value === $last_parent) {
+          $this->facet->setResults($parent_facet_results);
+          return $this->facet;
+        }
+
+        // Groups dates by the range they belong to.
+        /** @var \Drupal\facets\Result\Result $last_element */
+        $last_value = end($facet_results);
+        if ($last_value) {
+          if ($new_value != $last_value->getRawValue()) {
+            $facet_results[] = new Result($new_value, $date_handler->formatTimestamp($value, $gap), $count);
+          }
+          else {
+            $last_value->setCount($last_value->getCount() + 1);
+          }
+        }
+        else {
+          $facet_results[] = new Result($new_value, $date_handler->formatTimestamp($value, $gap), $count);
+        }
+      }
+
+      // Populate the parent with children.
+      $parent = end($parent_facet_results);
+      if ($parent instanceof ResultInterface) {
+        foreach ($facet_results as $result) {
+          $parent->setChildren($result);
+          $this->facet->setResults($parent_facet_results);
+        }
+      }
+      else {
+        // Set results directly when missing parents.
+        $this->facet->setResults($facet_results);
+      }
+    }
+
+    return $this->facet;
+  }
+
+}
diff --git a/core_search_facets/src/Tests/IntegrationTest.php b/core_search_facets/src/Tests/IntegrationTest.php
index 39f9dfb..cc28fd3 100644
--- a/core_search_facets/src/Tests/IntegrationTest.php
+++ b/core_search_facets/src/Tests/IntegrationTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\core_search_facets\Tests;
 
+use Drupal\facets\Tests\ExampleContentTrait;
+
 /**
  * Tests the admin UI with the core search facet source.
  *
@@ -9,6 +11,8 @@ namespace Drupal\core_search_facets\Tests;
  */
 class IntegrationTest extends WebTestBase {
 
+  use ExampleContentTrait;
+
   /**
    * The block entities used by this test.
    *
@@ -30,22 +34,22 @@ class IntegrationTest extends WebTestBase {
 
     // Make absolutely sure the ::$blocks variable doesn't pass information
     // along between tests.
-    $this->blocks = NULL;
+    $this->blocks = [];
   }
 
   /**
    * Tests various operations via the Facets' admin UI.
    */
   public function testFramework() {
-    $facet_name = "Test Facet name";
+
     $facet_id = 'test_facet_name';
+    $facet_name = 'Test Facet Name';
 
-    // Check if the overview is empty.
+    // Check if the overview is empty. = ;
     $this->checkEmptyOverview();
 
     // Add a new facet and edit it.
-    $this->addFacet($facet_name);
-    $this->editFacet($facet_name);
+    $this->addFacet($facet_id, $facet_name, 'type');
 
     // Create and place a block for "Test Facet name" facet.
     $this->createFacetBlock($facet_id);
@@ -58,16 +62,16 @@ class IntegrationTest extends WebTestBase {
     // Verify that facet blocks appear as expected.
     $this->assertFacetBlocksAppear();
 
-    $this->setShowAmountOfResults($facet_name, TRUE);
+    $this->setShowAmountOfResults($facet_id, TRUE);
 
     // Verify that the number of results per item.
     $this->drupalGet('search/node', ['query' => ['keys' => 'test']]);
-    $this->assertLink('page (10)');
+    $this->assertLink('page (19)');
     $this->assertLink('article (10)');
 
     // Verify that the label is correct for a clicked link.
-    $this->clickLink('page (10)');
-    $this->assertLink('(-) page (10)');
+    $this->clickLink('page (19)');
+    $this->assertLink('(-) page (19)');
 
     // Do not show the block on empty behaviors.
     // Truncate the search_index table because, for the moment, we don't have
@@ -90,21 +94,106 @@ class IntegrationTest extends WebTestBase {
     $this->deleteBlock($facet_id);
 
     // Delete the facet and make sure the overview is empty again.
-    $this->deleteUnusedFacet($facet_name);
+    $this->deleteUnusedFacet($facet_id, $facet_name);
     $this->checkEmptyOverview();
+
+  }
+
+  /**
+   * Tests the date integration.
+   */
+  public function testDate() {
+
+    $facet_name =  'Tardigrade';
+    $facet_id = 'tardigrade';
+
+    $this->addFacet($facet_id, $facet_name, 'created');
+    $this->createFacetBlock($facet_id);
+    $this->setShowAmountOfResults($facet_id, TRUE);
+
+    // Assert date facets.
+    $this->drupalGet('search/node', ['query' => ['keys' => 'test']]);
+    $this->assertLink('February 2016 (9)');
+    $this->assertLink('March 2016 (10)');
+    $this->assertLink('April 2016 (10)');
+    $this->assertResponse(200);
+
+    $this->clickLink('March 2016 (10)');
+    $this->assertResponse(200);
+    $this->assertLink('March 8, 2016 (1)');
+    $this->assertLink('March 9, 2016 (2)');
+
+    $this->clickLink('March 9, 2016 (2)');
+    $this->assertResponse(200);
+    $this->assertLink('10 AM (1)');
+    $this->assertLink('12 PM (1)');
+
+    $this->drupalGet('search/node', ['query' => ['keys' => 'test']]);
+    $this->assertLink('April 2016 (10)');
+    $this->clickLink('April 2016 (10)');
+    $this->assertResponse(200);
+    $this->assertLink('April 1, 2016 (1)');
+    $this->assertLink('April 2, 2016 (1)');
+  }
+
+  /**
+   * Tests for CRUD operations.
+   */
+  public function testCrudFacet() {
+    $facet_name = "Test Facet name";
+    $facet_id = 'test_facet_name';
+
+    // Check if the overview is empty.
+    $this->checkEmptyOverview();
+
+    // Add a new facet and edit it.
+    $this->addFacetCheck($facet_id, $facet_name, 'type');
+    $this->editFacetCheck($facet_id, $facet_name);
+
+    // Create and place a block.
+    $this->createFacetBlock($facet_id);
+
+    // Delete the block.
+    $this->deleteBlock($facet_id);
+
+    // Delete the facet.
+    $this->deleteUnusedFacet($facet_id, $facet_name);
+  }
+
+  /**
+   * Creates a new facet.
+   *
+   * @param $id
+   *   The facet's id.
+   * @param $name
+   *   The facet's name.
+   * @param $type
+   *   The field type.
+   */
+  public function addFacet($id, $name, $type) {
+    $this->drupalGet('admin/config/search/facets/add-facet');
+    $form_values = [
+      'id' => $id,
+      'status' => 1,
+      'url_alias' => $id,
+      'name' => $name,
+      'weight' => 2,
+      'facet_source_id' => 'core_node_search:node_search',
+      'facet_source_configs[core_node_search:node_search][field_identifier]' => $type,
+    ];
+    $this->drupalPostForm(NULL, ['facet_source_id' => 'core_node_search:node_search'], $this->t('Configure facet source'));
+    $this->drupalPostForm(NULL, $form_values, $this->t('Save'));
   }
 
   /**
    * Configures the possibility to show the amount of results for facet blocks.
    *
-   * @param string $facet_name
-   *   The name of the facet.
+   * @param string $facet_id
+   *   The id of the facet.
    * @param bool|TRUE $show
    *   Boolean to determine if we want to show the amount of results.
    */
-  protected function setShowAmountOfResults($facet_name, $show = TRUE) {
-
-    $facet_id = $this->convertNameToMachineName($facet_name);
+  protected function setShowAmountOfResults($facet_id, $show = TRUE) {
 
     $facet_display_page = '/admin/config/search/facets/' . $facet_id . '/display';
 
@@ -157,14 +246,8 @@ class IntegrationTest extends WebTestBase {
    *   The id of the block.
    */
   protected function createFacetBlock($id) {
-    $block = [
-      'plugin_id' => 'facet_block:' . $id,
-      'settings' => [
-        'region' => 'footer',
-        'id' => str_replace('_', '-', $id),
-      ],
-    ];
-    $this->blocks[$id] = $this->drupalPlaceBlock($block['plugin_id'], $block['settings']);
+    $block_values = ['region' => 'footer', 'id' => str_replace('_', '-', $id)];
+    $this->blocks[$id] = $this->drupalPlaceBlock('facet_block:' . $id, $block_values);
   }
 
   /**
@@ -227,12 +310,14 @@ class IntegrationTest extends WebTestBase {
   /**
    * Tests adding a facet trough the interface.
    *
+   * @param string $facet_id
+   *   The id of the facet.
    * @param string $facet_name
    *   The name of the facet.
+   * @param string $type
+   *   The field type.
    */
-  protected function addFacet($facet_name) {
-    $facet_id = $this->convertNameToMachineName($facet_name);
-
+  protected function addFacetCheck($facet_id, $facet_name, $type) {
     // Go to the Add facet page and make sure that returns a 200.
     $facet_add_page = '/admin/config/search/facets/add-facet';
     $this->drupalGet($facet_add_page);
@@ -268,7 +353,7 @@ class IntegrationTest extends WebTestBase {
     // Fill in all fields and make sure the 'field is required' message is no
     // longer shown.
     $facet_source_form = [
-      'facet_source_configs[core_node_search:node_search][field_identifier]' => 'type',
+      'facet_source_configs[core_node_search:node_search][field_identifier]' => $type,
     ];
     $this->drupalPostForm(NULL, $form_values + $facet_source_form, $this->t('Save'));
     $this->assertNoText('field is required.');
@@ -283,12 +368,12 @@ class IntegrationTest extends WebTestBase {
   /**
    * Tests editing of a facet through the UI.
    *
+   * @param string $facet_id
+   *   The id of the facet.
    * @param string $facet_name
    *   The name of the facet.
    */
-  public function editFacet($facet_name) {
-    $facet_id = $this->convertNameToMachineName($facet_name);
-
+  public function editFacetCheck($facet_id,$facet_name) {
     $facet_edit_page = '/admin/config/search/facets/' . $facet_id . '/edit';
 
     // Go to the facet edit page and make sure "edit facet %facet" is present.
@@ -340,12 +425,12 @@ class IntegrationTest extends WebTestBase {
   /**
    * This deletes a facet through the UI.
    *
+   * @param string $facet_id
+   *   The id of the facet.
    * @param string $facet_name
    *   The name of the facet.
    */
-  protected function deleteUnusedFacet($facet_name) {
-    $facet_id = $this->convertNameToMachineName($facet_name);
-
+  protected function deleteUnusedFacet($facet_id, $facet_name) {
     $facet_delete_page = '/admin/config/search/facets/' . $facet_id . '/delete';
 
     // Go to the facet delete page and make the warning is shown.
diff --git a/core_search_facets/src/Tests/WebTestBase.php b/core_search_facets/src/Tests/WebTestBase.php
index a911de5..5b41816 100644
--- a/core_search_facets/src/Tests/WebTestBase.php
+++ b/core_search_facets/src/Tests/WebTestBase.php
@@ -3,6 +3,8 @@
 namespace Drupal\core_search_facets\Tests;
 
 use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\field\Entity\FieldStorageConfig;
+use Drupal\language\Entity\ConfigurableLanguage;
 use Drupal\simpletest\WebTestBase as SimpletestWebTestBase;
 
 /**
@@ -20,11 +22,11 @@ abstract class WebTestBase extends SimpletestWebTestBase {
   public static $modules = [
     'field',
     'search',
-    'entity_test',
     'node',
     'facets',
     'block',
     'core_search_facets',
+    'language',
   ];
 
   /**
@@ -58,27 +60,64 @@ abstract class WebTestBase extends SimpletestWebTestBase {
     $this->drupalCreateContentType(['type' => 'page']);
     $this->drupalCreateContentType(['type' => 'article']);
 
+    // Add a new language.
+    ConfigurableLanguage::createFromLangcode('es')->save();
+
+    // Make the body field translatable. The title is already translatable by
+    // definition. The parent class has already created the article and page
+    // content types.
+    $field_storage = FieldStorageConfig::loadByName('node', 'body');
+    $field_storage->setTranslatable(TRUE);
+    $field_storage->save();
+
     // Adding 10 pages.
-    for ($i = 0; $i < 10; $i++) {
-      $this->drupalCreateNode(array(
-        'title' => 'foo bar' . $i,
+    for ($i = 1; $i <= 9; $i++) {
+      // Adding a different created time per language to avoid to have exactly
+      // the same value per nid and langcode.
+      $created_time_en = new \DateTime('February ' . $i . ' 2016 ' . str_pad($i, 2, STR_PAD_LEFT, 0) . 'PM');
+      $created_time_es = new \DateTime('March ' . $i . ' 2016 ' . str_pad($i, 2, STR_PAD_LEFT, 0) . 'PM');
+      $node = $this->drupalCreateNode(array(
+        'title' => 'test page' . $i . ' EN',
         'body' => 'test page' . $i,
         'type' => 'page',
+        'created' => $created_time_en->format('U'),
+        'langcode' => 'en',
       ));
+
+      // Add Spanish translation to the node.
+      $node->addTranslation('es', [
+        'title' => 'test page' . $i . ' ES',
+        'created' => $created_time_es->format('U'),
+      ]);
+      $node->save();
+
     }
 
+    $created_time = new \DateTime('March 9 2016 11PM');
+    $this->drupalCreateNode(array(
+      'title' => 'test page 10 EN',
+      'body' => 'test page10',
+      'type' => 'page',
+      'created' => $created_time->format('U'),
+      'langcode' => 'en',
+    ));
+
     // Adding 10 articles.
-    for ($i = 0; $i < 10; $i++) {
+    for ($i = 1; $i <= 10; $i++) {
+      $created_time = new \DateTime('April ' . $i . ' 2016 ' . str_pad($i, 2, STR_PAD_LEFT, 0) . 'PM');
       $this->drupalCreateNode(array(
-        'title' => 'foo baz' . $i,
+        'title' => 'test article' . $i . ' EN',
         'body' => 'test article' . $i,
         'type' => 'article',
+        'created' => $created_time->format('U'),
+        'langcode' => 'en',
       ));
     }
 
     // Create the users used for the tests.
     $this->adminUser = $this->drupalCreateUser([
       'administer search',
+      'use advanced search',
       'administer facets',
       'access administration pages',
       'administer nodes',
@@ -86,6 +125,9 @@ abstract class WebTestBase extends SimpletestWebTestBase {
       'administer content types',
       'administer blocks',
       'search content',
+      'administer languages',
+      'administer site configuration',
+      'access content',
     ]);
   }
 
diff --git a/facets.services.yml b/facets.services.yml
index 1be585e..07dc307 100644
--- a/facets.services.yml
+++ b/facets.services.yml
@@ -22,3 +22,7 @@ services:
       - '@plugin.manager.facets.facet_source'
       - '@plugin.manager.facets.processor'
       - '@entity_type.manager'
+  facets.utility.date_handler:
+    class: Drupal\facets\Utility\FacetsDateHandler
+    arguments:
+      - '@date.formatter'
diff --git a/src/Plugin/facets/url_processor/QueryString.php b/src/Plugin/facets/url_processor/QueryString.php
index c37aa18..1904adb 100644
--- a/src/Plugin/facets/url_processor/QueryString.php
+++ b/src/Plugin/facets/url_processor/QueryString.php
@@ -70,6 +70,13 @@ class QueryString extends UrlProcessorPluginBase {
 
     /** @var \Drupal\facets\Result\ResultInterface[] $results */
     foreach ($results as &$result) {
+      // Flag if children filter params need to be removed.
+      $remove_children = FALSE;
+      // Sets the url for children.
+      if ($children = $result->getChildren()) {
+        $this->buildUrls($facet, $children);
+      }
+
       $filter_string = $this->urlAlias . self::SEPARATOR . $result->getRawValue();
       $result_get_params = clone $get_params;
 
@@ -78,6 +85,10 @@ class QueryString extends UrlProcessorPluginBase {
       if ($result->isActive()) {
         foreach ($filter_params as $key => $filter_param) {
           if ($filter_param == $filter_string) {
+            $remove_children = TRUE;
+            unset($filter_params[$key]);
+          }
+          elseif ($remove_children) {
             unset($filter_params[$key]);
           }
         }
@@ -142,7 +153,7 @@ class QueryString extends UrlProcessorPluginBase {
 
     // Explode the active params on the separator.
     foreach ($active_params as $param) {
-      list($key, $value) = explode(self::SEPARATOR, $param);
+      list($key, $value) = explode(self::SEPARATOR, $param, 2);
       if (!isset($this->activeFilters[$key])) {
         $this->activeFilters[$key] = [$value];
       }
diff --git a/src/Plugin/facets/widget/LinksWidget.php b/src/Plugin/facets/widget/LinksWidget.php
index 735711b..a1b78ae 100644
--- a/src/Plugin/facets/widget/LinksWidget.php
+++ b/src/Plugin/facets/widget/LinksWidget.php
@@ -6,6 +6,7 @@ use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Link;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
 use Drupal\facets\FacetInterface;
+use Drupal\facets\Result\ResultInterface;
 use Drupal\facets\Widget\WidgetInterface;
 
 /**
@@ -46,7 +47,7 @@ class LinksWidget implements WidgetInterface {
         $items[] = $text;
       }
       else {
-        $items[] = new Link($text, $result->getUrl());
+        $items[] = $this->buildListItems($result, $show_numbers);
       }
     }
 
@@ -64,6 +65,107 @@ class LinksWidget implements WidgetInterface {
   }
 
   /**
+   * Builds a renderable array of result items.
+   *
+   * @param \Drupal\facets\Result\ResultInterface $result
+   *   A result item.
+   * @param bool $show_numbers
+   *   A boolean that's true when the numbers should be shown.
+   *
+   * @return array|Link|string
+   *   A renderable array of the result or a link when the result has no
+   *   children.
+   */
+  protected function buildListItems(ResultInterface $result, $show_numbers) {
+    if ($children = $result->getChildren()) {
+      $link = $this->prepareLink($result, $show_numbers);
+
+      $children_markup = [];
+      foreach ($children as $child) {
+        $children_markup[] = $this->buildChildren($child, $show_numbers);
+      }
+
+      $items = [
+        '#markup' => $link->toString(),
+        '#wrapper_attributes' => [
+          'class' => ['expanded'],
+        ],
+        'children' => [$children_markup],
+      ];
+
+    }
+    else {
+      $items = $this->prepareLink($result, $show_numbers);
+    }
+
+    return $items;
+  }
+
+  /**
+   * Returns the text or link for an item.
+   *
+   * @param \Drupal\facets\Result\ResultInterface $result
+   *   A result item.
+   * @param bool $show_numbers
+   *   A boolean that's true when the numbers should be shown.
+   *
+   * @return Link|string
+   *   The item, can be a link or just the text.
+   */
+  protected function prepareLink(ResultInterface $result, $show_numbers) {
+    $text = $result->getDisplayValue();
+
+    if ($show_numbers && $result->getCount()) {
+      $text .= ' (' . $result->getCount() . ')';
+    }
+    if ($result->isActive()) {
+      $text = '(-) ' . $text;
+    }
+
+    if (is_null($result->getUrl())) {
+      $link = $text;
+    }
+    else {
+      $link = new Link($text, $result->getUrl());
+    }
+
+    return $link;
+  }
+
+  /**
+   * Builds a renderable array of a result.
+   *
+   * @param \Drupal\facets\Result\ResultInterface $child
+   *   A result item.
+   * @param bool $show_numbers
+   *   A boolean that's true when the numbers should be shown.
+   *
+   * @return array|Link|string
+   *   A renderable array of the result.
+   */
+  protected function buildChildren(ResultInterface $child, $show_numbers) {
+    $text = $child->getDisplayValue();
+    if ($show_numbers && $child->getCount()) {
+      $text .= ' (' . $child->getCount() . ')';
+    }
+    if ($child->isActive()) {
+      $text = '(-) ' . $text;
+    }
+
+    if (!is_null($child->getUrl())) {
+      $link = new Link($text, $child->getUrl());
+      $text = $link->toString();
+    }
+
+    return [
+      '#markup' => $text,
+      '#wrapper_attributes' => [
+        'class' => ['leaf'],
+      ],
+    ];
+  }
+
+  /**
    * {@inheritdoc}
    */
   public function buildConfigurationForm(array $form, FormStateInterface $form_state, $config) {
diff --git a/src/Result/Result.php b/src/Result/Result.php
index e841f68..7f410b2 100644
--- a/src/Result/Result.php
+++ b/src/Result/Result.php
@@ -40,6 +40,9 @@ class Result implements ResultInterface {
    */
   protected $active = FALSE;
 
+
+  protected $children = [];
+
   /**
    * Constructs a new result value object.
    *
@@ -94,6 +97,13 @@ class Result implements ResultInterface {
   /**
    * {@inheritdoc}
    */
+  public function setCount($count) {
+    $this->count = $count;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function setActiveState($active) {
     $this->active = $active;
   }
@@ -112,4 +122,18 @@ class Result implements ResultInterface {
     $this->displayValue = $display_value;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function setChildren(ResultInterface $children) {
+    $this->children[] = $children;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getChildren() {
+    return $this->children;
+  }
+
 }
diff --git a/src/Result/ResultInterface.php b/src/Result/ResultInterface.php
index a223242..2de8d03 100644
--- a/src/Result/ResultInterface.php
+++ b/src/Result/ResultInterface.php
@@ -74,4 +74,20 @@ interface ResultInterface {
    */
   public function setDisplayValue($display_value);
 
+  /**
+   * Sets children results.
+   *
+   * @param \Drupal\facets\Result\ResultInterface $children
+   *   The children to be added.
+   */
+  public function setChildren(ResultInterface $children);
+
+  /**
+   * Returns children results.
+   *
+   * @return \Drupal\facets\Result\ResultInterface $children
+   *   The children results.
+   */
+  public function getChildren();
+
 }
diff --git a/src/Utility/FacetsDateHandler.php b/src/Utility/FacetsDateHandler.php
new file mode 100644
index 0000000..a37c35d
--- /dev/null
+++ b/src/Utility/FacetsDateHandler.php
@@ -0,0 +1,427 @@
+<?php
+
+namespace Drupal\facets\Utility;
+
+use Drupal\Core\Datetime\DateFormatter;
+
+/**
+ * Dates Handler service.
+ */
+class FacetsDateHandler {
+
+  /**
+   * String that represents a time gap of a day between two dates.
+   */
+  const FACETS_DATE_DAY = 'DAY';
+
+  /**
+   * String that represents a time gap of a year between two dates.
+   */
+  const FACETS_DATE_YEAR = 'YEAR';
+
+  /**
+   * String that represents a time gap of a month between two dates.
+   */
+  const FACETS_DATE_MONTH = 'MONTH';
+
+  /**
+   * String that represents a time gap of an hour between two dates.
+   */
+  const FACETS_DATE_HOUR = 'HOUR';
+
+  /**
+   * String that represents a time gap of a minute between two dates.
+   */
+  const FACETS_DATE_MINUTE = 'MINUTE';
+
+  /**
+   * String that represents a time gap of a second between two dates.
+   */
+  const FACETS_DATE_SECOND = 'SECOND';
+
+  /**
+   * Date string for ISO 8601 date formats.
+   */
+  const FACETS_DATE_ISO8601 = 'Y-m-d\TH:i:s\Z';
+
+  /**
+   * Regex pattern for range queries.
+   */
+  const FACETS_REGEX_RANGE = '/^[\[\{](\S+) TO (\S+)[\]\}]$/';
+
+  /**
+   * Regex pattern for date queries.
+   */
+  const FACETS_REGEX_DATE = '/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z$/';
+
+  /**
+   * Regex pattern for date ranges.
+   */
+  const FACETS_REGEX_DATE_RANGE = '/^\[((\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z) TO ((\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z)\]$/';
+
+  /**
+   * The date formatting service.
+   *
+   * @var \Drupal\Core\Datetime\DateFormatter
+   */
+  protected $dateFormatter;
+
+  /**
+   * FacetsDateHandler constructor.
+   *
+   * @param \Drupal\Core\Datetime\DateFormatter $date_formatter
+   *   The date formatting service.
+   */
+  public function __construct(DateFormatter $date_formatter) {
+    $this->dateFormatter = $date_formatter;
+  }
+
+  /**
+   * Converts dates from Unix timestamps into ISO 8601 format.
+   *
+   * @param int $timestamp
+   *   An integer containing the Unix timestamp being converted.
+   * @param string $gap
+   *   A string containing the gap, see FACETS_DATE_* constants for valid
+   *   values. Defaults to FACETS_DATE_SECOND.
+   *
+   * @return string
+   *   A string containing the date in ISO 8601 format.
+   */
+  public function isoDate($timestamp, $gap = 'SECOND') {
+    switch ($gap) {
+      case static::FACETS_DATE_SECOND:
+        $format = static::FACETS_DATE_ISO8601;
+        break;
+
+      case static::FACETS_DATE_MINUTE:
+        $format = 'Y-m-d\TH:i:00\Z';
+        break;
+
+      case static::FACETS_DATE_HOUR:
+        $format = 'Y-m-d\TH:00:00\Z';
+        break;
+
+      case static::FACETS_DATE_DAY:
+        $format = 'Y-m-d\T00:00:00\Z';
+        break;
+
+      case static::FACETS_DATE_MONTH:
+        $format = 'Y-m-01\T00:00:00\Z';
+        break;
+
+      case static::FACETS_DATE_YEAR:
+        $format = 'Y-01-01\T00:00:00\Z';
+        break;
+
+      default:
+        $format = static::FACETS_DATE_ISO8601;
+        break;
+    }
+    return gmdate($format, $timestamp);
+  }
+
+  /**
+   * Return a date gap one increment smaller than the one passed.
+   *
+   * @param string $gap
+   *   A string containing the gap, see FACETS_DATE_* constants for valid
+   *   values.
+   * @param string $min_gap
+   *   A string containing the the minimum gap that can be returned, defaults to
+   *   FACETS_DATE_SECOND. This is useful for defining the smallest increment
+   *   that can be used in a date drilldown.
+   *
+   * @return string
+   *   A string containing the smaller date gap, NULL if there is no smaller
+   *   gap. See FACETS_DATE_* constants for valid values.
+   */
+  public function getNextDateGap($gap, $min_gap = self::FACETS_DATE_SECOND) {
+    // Array of numbers used to determine whether the next gap is smaller than
+    // the minimum gap allowed in the drilldown.
+    $gap_numbers = array(
+      static::FACETS_DATE_YEAR => 6,
+      static::FACETS_DATE_MONTH => 5,
+      static::FACETS_DATE_DAY => 4,
+      static::FACETS_DATE_HOUR => 3,
+      static::FACETS_DATE_MINUTE => 2,
+      static::FACETS_DATE_SECOND => 1,
+    );
+
+    // Gets gap numbers for both the gap and minimum gap, checks if the next gap
+    // is within the limit set by the $min_gap parameter.
+    $gap_num = isset($gap_numbers[$gap]) ? $gap_numbers[$gap] : 6;
+    $min_num = isset($gap_numbers[$min_gap]) ? $gap_numbers[$min_gap] : 1;
+    return ($gap_num > $min_num) ? array_search($gap_num - 1, $gap_numbers) : $min_gap;
+  }
+
+  /**
+   * Determines the best search gap to use for an arbitrary date range.
+   *
+   * Generally, we use the maximum gap that fits between the start and end date.
+   * If they are more than a year apart, 1 year; if they are more than a month
+   * apart, 1 month; etc.
+   *
+   * This function uses Unix timestamps for its computation and so is not useful
+   * for dates outside that range.
+   *
+   * @param int $start_time
+   *   A string containing the start date as an ISO date string.
+   * @param int $end_time
+   *   A string containing the end date as an ISO date string.
+   * @param string|NULL $min_gap
+   *   (Optional) The minimum gap that should be returned.
+   *
+   * @return string
+   *   A string containing the gap, see FACETS_DATE_* constants for valid
+   *   values. Returns FALSE of either of the dates cannot be converted to a
+   *   timestamp.
+   */
+  public function getTimestampGap($start_time, $end_time, $min_gap = NULL) {
+    $time_diff = $end_time - $start_time;
+    switch (TRUE) {
+      case ($time_diff >= 31536000):
+        $gap = static::FACETS_DATE_YEAR;
+        break;
+
+      case ($time_diff >= 86400 * gmdate('t', $start_time)):
+        $gap = static::FACETS_DATE_MONTH;
+        break;
+
+      case ($time_diff >= 86400):
+        $gap = static::FACETS_DATE_DAY;
+        break;
+
+      case ($time_diff >= 3600):
+        $gap = static::FACETS_DATE_HOUR;
+        break;
+
+      case ($time_diff >= 60):
+        $gap = static::FACETS_DATE_MINUTE;
+        break;
+
+      default:
+        $gap = static::FACETS_DATE_SECOND;
+        break;
+    }
+
+    // Return the calculated gap if a minimum gap was not passed of the
+    // calculated gap is a larger interval than the minimum gap.
+    if (is_null($min_gap) || $this->gapCompare($gap, $min_gap) >= 0) {
+      return $gap;
+    }
+    else {
+      return $min_gap;
+    }
+  }
+
+  /**
+   * Converts ISO date strings to Unix timestamps.
+   *
+   * Passes values to the FACETS_get_timestamp_gap() function to calculate the
+   * gap.
+   *
+   * @param string $start_date
+   *   A string containing the start date as an ISO date string.
+   * @param string $end_date
+   *   A string containing the end date as an ISO date string.
+   * @param string|NULL $min_gap
+   *   (Optional) The minimum gap that should be returned.
+   *
+   * @return string
+   *   A string containing the gap, see FACETS_DATE_* constants for valid
+   *   values. Returns FALSE of either of the dates cannot be converted to a
+   *   timestamp.
+   *
+   * @see FACETS_get_timestamp_gap()
+   */
+  public function getDateGap($start_date, $end_date, $min_gap = NULL) {
+    $range = array(strtotime($start_date), strtotime($end_date));
+    if (!in_array(FALSE, $range, TRUE)) {
+      return $this->getTimestampGap($range[0], $range[1], $min_gap);
+    }
+    return FALSE;
+  }
+
+  /**
+   * Returns a formatted date based on the passed timestamp and gap.
+   *
+   * This function assumes that gaps less than one day will be displayed in a
+   * search context in which a larger containing gap including a day is already
+   * displayed. So, HOUR, MINUTE, and SECOND gaps only display time information,
+   * without date.
+   *
+   * @param int $timestamp
+   *   An integer containing the Unix timestamp.
+   * @param string $gap
+   *   A string containing the gap, see FACETS_DATE_* constants for valid
+   *   values, defaults to YEAR.
+   *
+   * @return string
+   *   A gap-appropriate display date used in the facet link.
+   */
+  public function formatTimestamp($timestamp, $gap = self::FACETS_DATE_YEAR) {
+    switch ($gap) {
+      case static::FACETS_DATE_MONTH:
+        return $this->dateFormatter->format($timestamp, 'custom', 'F Y', 'UTC');
+
+      case static::FACETS_DATE_DAY:
+        return $this->dateFormatter->format($timestamp, 'custom', 'F j, Y', 'UTC');
+
+      case static::FACETS_DATE_HOUR:
+        return $this->dateFormatter->format($timestamp, 'custom', 'g A', 'UTC');
+
+      case static::FACETS_DATE_MINUTE:
+        return $this->dateFormatter->format($timestamp, 'custom', 'g:i A', 'UTC');
+
+      case static::FACETS_DATE_SECOND:
+        return $this->dateFormatter->format($timestamp, 'custom', 'g:i:s A', 'UTC');
+
+      default:
+        return $this->dateFormatter->format($timestamp, 'custom', 'Y', 'UTC');
+    }
+  }
+
+  /**
+   * Returns a formatted date based on the passed ISO date string and gap.
+   *
+   * @param string $date
+   *   A string containing the date as an ISO date string.
+   * @param int $gap
+   *   An integer containing the gap, see FACETS_DATE_* constants for valid
+   *   values, defaults to YEAR.
+   * @param string $callback
+   *   The formatting callback, defaults to "FACETS_format_timestamp". This is
+   *   a string that can be called as a valid callback.
+   *
+   * @return string
+   *   A gap-appropriate display date used in the facet link.
+   *
+   * @see FACETS_format_timestamp()
+   */
+  public function formatDate($date, $gap = self::FACETS_DATE_YEAR, $callback = 'facets_format_timestamp') {
+    $timestamp = strtotime($date);
+    return $callback($timestamp, $gap);
+  }
+
+  /**
+   * Returns the next increment from the given ISO date and gap.
+   *
+   * This function is useful for getting the upper limit of a date range from
+   * the given start date.
+   *
+   * @param string $date
+   *   A string containing the date as an ISO date string.
+   * @param string $gap
+   *   A string containing the gap, see FACETS_DATE_* constants for valid
+   *   values, defaults to YEAR.
+   *
+   * @return string
+   *   A string containing the date, FALSE if the passed date could not be
+   *   parsed.
+   */
+  public function getNextDateIncrement($date, $gap) {
+    if (preg_match(static::FACETS_REGEX_DATE, $date, $match)) {
+
+      // Increments the timestamp.
+      switch ($gap) {
+        case static::FACETS_DATE_MONTH:
+          $match[2] += 1;
+          break;
+
+        case static::FACETS_DATE_DAY:
+          $match[3] += 1;
+          break;
+
+        case static::FACETS_DATE_HOUR:
+          $match[4] += 1;
+          break;
+
+        case static::FACETS_DATE_MINUTE:
+          $match[5] += 1;
+          break;
+
+        case static::FACETS_DATE_SECOND:
+          $match[6] += 1;
+          break;
+
+        default:
+          $match[1] += 1;
+          break;
+
+      }
+
+      // Gets the next increment.
+      return $this->isoDate(
+        gmmktime($match[4], $match[5], $match[6], $match[2], $match[3], $match[1])
+      );
+    }
+    return FALSE;
+  }
+
+  /**
+   * Compares two timestamp gaps.
+   *
+   * @param int $gap1
+   *   An integer containing the gap, see FACETS_DATE_* constants for valid
+   *   values.
+   * @param int $gap2
+   *   An integer containing the gap, see FACETS_DATE_* constants for valid
+   *   values.
+   *
+   * @return int
+   *   Returns -1 if gap1 is less than gap2, 1 if gap1 is greater than gap2, and
+   *   0 if they are equal.
+   */
+  public function gapCompare($gap1, $gap2) {
+
+    $gap_numbers = array(
+      static::FACETS_DATE_YEAR => 6,
+      static::FACETS_DATE_MONTH => 5,
+      static::FACETS_DATE_DAY => 4,
+      static::FACETS_DATE_HOUR => 3,
+      static::FACETS_DATE_MINUTE => 2,
+      static::FACETS_DATE_SECOND => 1,
+    );
+
+    $gap1_num = isset($gap_numbers[$gap1]) ? $gap_numbers[$gap1] : 6;
+    $gap2_num = isset($gap_numbers[$gap2]) ? $gap_numbers[$gap2] : 6;
+
+    if ($gap1_num == $gap2_num) {
+      return 0;
+    }
+    else {
+      return ($gap1_num < $gap2_num) ? -1 : 1;
+    }
+  }
+
+  /**
+   * Extracts "start" and "end" dates from an active items.
+   *
+   * @param string $item
+   *   The active item to extract the dates.
+   *
+   * @return mixed
+   *   Returns FALSE if no item found and an array with the dates if the dates
+   *    were extracted as expected.
+   */
+  public function extractActiveItems($item) {
+    $active_item = [];
+    if (preg_match(static::FACETS_REGEX_DATE_RANGE, $item, $matches)) {
+
+      $active_item['start'] = [
+        'timestamp' => strtotime($matches[1]),
+        'iso' => $matches[1],
+      ];
+
+      $active_item['end'] = [
+        'timestamp' => strtotime($matches[8]),
+        'iso' => $matches[8],
+      ];
+
+      return $active_item;
+    }
+    return FALSE;
+  }
+
+}
diff --git a/tests/src/Unit/Utility/FacetsDateHandlerTest.php b/tests/src/Unit/Utility/FacetsDateHandlerTest.php
new file mode 100644
index 0000000..7a906d4
--- /dev/null
+++ b/tests/src/Unit/Utility/FacetsDateHandlerTest.php
@@ -0,0 +1,243 @@
+<?php
+
+namespace Drupal\Tests\facets\Unit\Utility;
+
+use Drupal\Core\Datetime\DateFormatter;
+use Drupal\Core\DependencyInjection\ContainerBuilder;
+use Drupal\Core\Language\Language;
+use Drupal\facets\Utility\FacetsDateHandler;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * Unit test for Date Handler Service.
+ *
+ * @group facets
+ */
+class FacetsDateHandlerTest extends UnitTestCase {
+
+  /**
+   * Timestamp used by tests: Thu, 26 Nov 1987 20:43:04 GMT.
+   */
+  const TIMESTAMP = 564957784;
+
+  /**
+   * ISO date used by tests: Thu, 26 Nov 1987 20:43:04 GMT.
+   */
+  const ISO_DATE = '1987-11-26T20:43:04Z';
+
+  /**
+   * The system under test.
+   *
+   * @var \Drupal\facets\Utility\FacetsDateHandler
+   */
+  protected $handler;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    $entity_storage = $this->getMock('Drupal\Core\Entity\EntityStorageInterface');
+
+    $em = $this->getMock('Drupal\Core\Entity\EntityManagerInterface');
+    $em->expects($this->any())
+      ->method('getStorage')
+      ->with('date_format')
+      ->willReturn($entity_storage);
+
+    $language = new Language(['id' => 'en']);
+
+    $lm = $this->getMock('Drupal\Core\Language\LanguageManagerInterface');
+    $lm->method('getCurrentLanguage')
+      ->willReturn($language);
+    $st = $this->getMock('Drupal\Core\StringTranslation\TranslationInterface');
+    $rs = $this->getMock('Symfony\Component\HttpFoundation\RequestStack');
+    $cf = $this->getConfigFactoryStub();
+
+    $config_factory = $this->getConfigFactoryStub([
+      'system.date' => ['country' => ['default' => 'GB']],
+    ]);
+    $container = new ContainerBuilder();
+    $container->set('config.factory', $config_factory);
+    \Drupal::setContainer($container);
+
+    $date_formatter = new DateFormatter($em, $lm, $st, $cf, $rs);
+
+    $this->handler = new FacetsDateHandler($date_formatter);
+  }
+
+  /**
+   * Tests the isoDate method.
+   *
+   * @dataProvider provideIsoDates
+   */
+  public function testIsoDate($iso_date, $gap) {
+    $fd = $this->handler;
+    $this->assertEquals($iso_date, $fd->isoDate(static::TIMESTAMP, $gap));
+  }
+
+  /**
+   * Tests for ::getNextDateGap.
+   */
+  public function testGetNextDateGap() {
+    $fd = $this->handler;
+
+    $gap = $fd->getNextDateGap($fd::FACETS_DATE_SECOND);
+    $this->assertEquals($fd::FACETS_DATE_SECOND, $gap);
+
+    $gap = $fd->getNextDateGap($fd::FACETS_DATE_MINUTE);
+    $this->assertEquals($fd::FACETS_DATE_SECOND, $gap);
+
+    $gap = $fd->getNextDateGap($fd::FACETS_DATE_SECOND, $fd::FACETS_DATE_MINUTE);
+    $this->assertEquals($fd::FACETS_DATE_MINUTE, $gap);
+
+    $gap = $fd->getNextDateGap($fd::FACETS_DATE_MINUTE, $fd::FACETS_DATE_MINUTE);
+    $this->assertEquals($fd::FACETS_DATE_MINUTE, $gap);
+
+    $gap = $fd->getNextDateGap($fd::FACETS_DATE_SECOND, $fd::FACETS_DATE_HOUR);
+    $this->assertEquals($fd::FACETS_DATE_HOUR, $gap);
+
+    $gap = $fd->getNextDateGap($fd::FACETS_DATE_MINUTE, $fd::FACETS_DATE_HOUR);
+    $this->assertEquals($fd::FACETS_DATE_HOUR, $gap);
+
+    $gap = $fd->getNextDateGap($fd::FACETS_DATE_HOUR, $fd::FACETS_DATE_HOUR);
+    $this->assertEquals($fd::FACETS_DATE_HOUR, $gap);
+  }
+
+  /**
+   * Tests for ::getTimestampGap.
+   */
+  public function testGetTimestampGap() {
+    $fd = $this->handler;
+
+    // The best search gap between two dates must be a year.
+    $date_gap = $this->handler->getTimestampGap(static::TIMESTAMP, static::TIMESTAMP + 31536000);
+    $this->assertEquals($fd::FACETS_DATE_YEAR, $date_gap);
+
+    // The best search gap between two dates must be a month.
+    $date_gap = $this->handler->getTimestampGap(static::TIMESTAMP, static::TIMESTAMP + 86400 * 60);
+    $this->assertEquals($fd::FACETS_DATE_MONTH, $date_gap);
+
+    // The best search gap between two dates must be a day.
+    $date_gap = $this->handler->getTimestampGap(static::TIMESTAMP, static::TIMESTAMP + 86400);
+    $this->assertEquals($fd::FACETS_DATE_DAY, $date_gap);
+
+    // The best search gap between two dates must be an hour.
+    $date_gap = $this->handler->getTimestampGap(static::TIMESTAMP, static::TIMESTAMP + 3600);
+    $this->assertEquals($fd::FACETS_DATE_HOUR, $date_gap);
+
+    // The best search gap between two dates must be a minute.
+    $date_gap = $this->handler->getTimestampGap(static::TIMESTAMP, static::TIMESTAMP + 60);
+    $this->assertEquals($fd::FACETS_DATE_MINUTE, $date_gap);
+
+    // The best search gap between two dates must be a second.
+    $date_gap = $this->handler->getTimestampGap(static::TIMESTAMP, static::TIMESTAMP + 59);
+    $this->assertEquals($fd::FACETS_DATE_SECOND, $date_gap);
+  }
+
+  /**
+   * Tests for ::getDateGap method.
+   */
+  public function testGetDateGap() {
+    $fd = $this->handler;
+
+    // Cannot convert to timestamp.
+    $this->assertFalse($fd->getDateGap(static::TIMESTAMP, static::TIMESTAMP));
+
+    // The min. gap is MONTH but the result is larger.
+    $this->assertEquals($fd::FACETS_DATE_YEAR, $fd->getDateGap('1983-03-03T20:43:04Z', '1987-11-26T20:43:04Z', $fd::FACETS_DATE_MONTH));
+
+    // The gap is YEAR.
+    $this->assertEquals($fd::FACETS_DATE_YEAR, $fd->getDateGap('1983-03-03T20:43:04Z', '1987-11-26T20:43:04Z'));
+
+    // The gap is MONTH.
+    $this->assertEquals($fd::FACETS_DATE_MONTH, $fd->getDateGap('1983-03-03T20:43:04Z', '1983-11-26T20:43:04Z'));
+
+    // The gap is DAY.
+    $this->assertEquals($fd::FACETS_DATE_DAY, $fd->getDateGap('1983-03-03T20:43:04Z', '1983-03-26T20:43:04Z'));
+
+    // The gap is HOUR.
+    $this->assertEquals($fd::FACETS_DATE_HOUR, $fd->getDateGap('1983-03-03T20:43:04Z', '1983-03-03T21:44:04Z'));
+
+    // The gap is MINUTE.
+    $this->assertEquals($fd::FACETS_DATE_MINUTE, $fd->getDateGap('1983-03-03T20:43:04Z', '1983-03-03T20:44:04Z'));
+
+    // The gap is SECOND.
+    $this->assertEquals($fd::FACETS_DATE_SECOND, $fd->getDateGap('1983-03-03T20:43:04Z', '1983-03-03T20:43:55Z'));
+  }
+
+  /**
+   * Tests for ::nextDateIncrement method.
+   *
+   * @dataProvider provideNextDateIncrementData
+   */
+  public function testNextDateIncrement($incremented_iso_date, $gap) {
+    $this->assertEquals($incremented_iso_date, $this->handler->getNextDateIncrement(static::ISO_DATE, $gap));
+  }
+
+  /**
+   * Tests for ::gapCompare method.
+   */
+  public function testGapCompare() {
+    $fd = $this->handler;
+
+    // Timestamps are equals.
+    $this->assertEquals(0, $fd->gapCompare(static::TIMESTAMP, static::TIMESTAMP));
+
+    // Timestamps are equals.
+    $this->assertEquals(0, $fd->gapCompare($fd::FACETS_DATE_YEAR, $fd::FACETS_DATE_YEAR));
+
+    // gap1 is less than gap2.
+    $this->assertEquals(-1, $fd->gapCompare($fd::FACETS_DATE_MONTH, $fd::FACETS_DATE_YEAR));
+
+    // gap1 is less than gap2.
+    $this->assertEquals(1, $fd->gapCompare($fd::FACETS_DATE_MONTH, $fd::FACETS_DATE_DAY));
+  }
+
+  /**
+   * Tests for ::formatTimestamp method.
+   */
+  public function testFormatTimestamp() {
+    $fd = $this->handler;
+
+    $year = $fd->formatTimestamp(static::TIMESTAMP);
+    $this->assertEquals(1987, $year);
+  }
+
+  /**
+   * Returns a data provider for the ::testIsoDate().
+   *
+   * @return array
+   *   Arrays with data for the test data.
+   */
+  public function provideIsoDates() {
+    return [
+      ['1987-11-26T20:43:04Z', FacetsDateHandler::FACETS_DATE_SECOND],
+      ['1987-11-26T20:43:00Z', FacetsDateHandler::FACETS_DATE_MINUTE],
+      ['1987-11-26T20:00:00Z', FacetsDateHandler::FACETS_DATE_HOUR],
+      ['1987-11-26T00:00:00Z', FacetsDateHandler::FACETS_DATE_DAY],
+      ['1987-11-01T00:00:00Z', FacetsDateHandler::FACETS_DATE_MONTH],
+      ['1987-01-01T00:00:00Z', FacetsDateHandler::FACETS_DATE_YEAR],
+      ['1987-11-26T20:43:04Z', FacetsDateHandler::FACETS_DATE_ISO8601],
+    ];
+  }
+
+  /**
+   * Returns a data provider for the ::testNextDateIncrement().
+   *
+   * @return array
+   *   Arrays with data for the test data.
+   */
+  public function provideNextDateIncrementData() {
+    return [
+      ['1987-11-26T20:43:05Z', FacetsDateHandler::FACETS_DATE_SECOND],
+      ['1987-11-26T20:44:04Z', FacetsDateHandler::FACETS_DATE_MINUTE],
+      ['1987-11-26T21:43:04Z', FacetsDateHandler::FACETS_DATE_HOUR],
+      ['1987-11-27T20:43:04Z', FacetsDateHandler::FACETS_DATE_DAY],
+      ['1987-12-26T20:43:04Z', FacetsDateHandler::FACETS_DATE_MONTH],
+      ['1988-11-26T20:43:04Z', FacetsDateHandler::FACETS_DATE_YEAR],
+    ];
+  }
+
+}
