Index: Solr_Base_Query.php
===================================================================
RCS file: /cvs/drupal/contributions/modules/apachesolr/Solr_Base_Query.php,v
retrieving revision 1.1.4.9
diff -u -r1.1.4.9 Solr_Base_Query.php
--- Solr_Base_Query.php	8 Nov 2008 11:27:31 -0000	1.1.4.9
+++ Solr_Base_Query.php	14 Nov 2008 08:08:14 -0000
@@ -37,6 +37,14 @@
       // The preg_replace removes beginning and trailing quotations.
       return preg_replace('/^"|"$/', '', $matches[2]);
     }
+    // Extract query range field (for date field).
+    // See http://lucene.apache.org/java/2_4_0/queryparsersyntax.html#Range%20Searches
+    $pattern = '/(^| )'. $option .':(\[[^\]]+\s+TO\s+[^\[]+\])/i';
+    if (preg_match_all($pattern, $keys, $matches)) {
+      if (!empty($matches[2])) {
+        return $matches[2];
+      }
+    }
     $pattern = '/(^| )'. $option .':([^ ]*)/i';
     if (preg_match_all($pattern, $keys, $matches)) {
       if (!empty($matches[2])) {
@@ -70,7 +78,15 @@
     }
     else {
       // if the field value has spaces in it, wrap it in double quotes.
-      if (count(explode(' ', $values['#value'])) > 1) {
+      // If the field value is looks like date field, wrap it in double quotes too
+      // (to avoid escaping of ':' and '-').
+      if (
+        count(explode(' ', $values['#value'])) > 1
+        &&
+        !preg_match('|^\[[^\]]+\s+TO\s+[^\[]+\]$|', $values['#value'])
+        ||
+        preg_match('|^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$|', $values['#value'])
+      ) {
         $values['#value'] = '"'. $values['#value']. '"';
       }
       return $values['#name']. ':'. $values['#value'];
@@ -145,7 +161,17 @@
     $this->rebuild_query();
   }
 
-  function has_field($name, $value) {
+  function has_field($name, $value = NULL) {
+    // We can check a field without value checking.
+    if (empty($value)) {
+      foreach ($this->_fields as $pos => $values) {
+        if (!empty($values['#name']) && $values['#name'] == $name) {
+          return TRUE;
+        }
+      }
+      return FALSE;
+    }
+    
     foreach ($this->_fields as $pos => $values) {
       if (!empty($values['#name']) && !empty($values['#value']) && $values['#name'] == $name && $values['#value'] == $value) {
         return TRUE;
@@ -272,7 +298,7 @@
     foreach ($this->_fields as $pos => $values) {
       $fields[] = Solr_Base_Query::make_field($values);
     }
-    $join_delim = $this->_field_operator == 'AND' ? ' ' : ' OR ';    
+    $join_delim = $this->_field_operator == 'AND' ? ' ' : ' OR ';
     $this->_query = trim(implode($join_delim, array_filter($fields, 'trim')));
     foreach ($this->_subqueries as $id => $data) {
       $operator = $data['#operator'];
Index: apachesolr.module
===================================================================
RCS file: /cvs/drupal/contributions/modules/apachesolr/apachesolr.module,v
retrieving revision 1.1.2.12.2.48
diff -u -r1.1.2.12.2.48 apachesolr.module
--- apachesolr.module	11 Nov 2008 14:59:15 -0000	1.1.2.12.2.48
+++ apachesolr.module	14 Nov 2008 09:01:13 -0000
@@ -360,6 +360,7 @@
           $document->body  = $node->body;
           $document->type  = $node->type;
           $document->changed = $node->changed;
+          $document->changed_date = date('Y-m-d\TH:i:s\Z', $node->changed);
           $document->comment_count = $node->comment_count;
           $document->name = $node->name;
           $document->language = $node->language;
@@ -493,7 +494,7 @@
 }
 
 function apachesolr_apachesolr_facets() {
-  return array('type');
+  return array('type' => array('type' => 'field'));
 }
 
 /**
@@ -616,6 +617,94 @@
   return NULL;
 }
 
+
+function apachesolr_facet_date_block($response, $query, $delta, $filter_by) {
+  if (is_object($response->facet_counts->facet_dates->$delta)) {
+    $items = array();
+    // Get the first facet data to find out parents.
+    $first_facet = each($response->facet_counts->facet_dates->$delta);
+    $items = apachesolr_facet_date_block_parents($response->facet_counts->facet_dates->$delta->gap, $query, $delta, strtotime($first_facet['key']));
+    foreach ($response->facet_counts->facet_dates->$delta as $date => $count) {
+      // Skip empty facets and non-date keys
+      if (empty($count) || in_array($date , array('gap', 'end', 'before', 'after', 'between'))) {
+        continue;
+      }
+      $timestamp = strtotime($date);
+      switch ($response->facet_counts->facet_dates->$delta->gap) {
+        case '+1YEAR':
+          $name = date('Y', $timestamp);
+          $period = date('Y', $timestamp);
+          break;
+        case '+1MONTH':
+          $name = date('Y F', $timestamp);
+          $period = date('Y-m', $timestamp);
+          break;
+        case '+1DAY': // for now the limit is a 'DAY'
+          $name = date('Y F d', $timestamp);
+          $period = date('Y-m-d', $timestamp);
+          break;
+      }
+      if (isset($name)) {
+        $new_query = drupal_clone($query);
+        $new_query->remove_field('changed_date');
+        $changed_date = apachesolr_facet_date_range('period', $period);
+        $new_query->add_field('changed_date', $changed_date);
+        $path = 'search/' . arg(1) . '/' . $new_query->get_query();
+        $items[] = theme('apachesolr_facet_item', $name, $count, $path, FALSE, NULL, $response->numFound);
+      }
+    }
+    $output = theme('apachesolr_facet_list', $items);
+    return array('subject' => t('Filter by @filter_by', array('@filter_by' => $filter_by)), 'content' => $output);
+  }
+  return NULL;
+}
+/**
+ * Helper function to find out parents of date facet.
+ */
+function apachesolr_facet_date_block_parents($gap, $query, $delta, $first_facet_date) {
+  $parents = array();
+  $items = array();
+  switch ($gap) {
+    case '+1YEAR':
+      break;
+    case '+1MONTH':
+      $parents[] = array('name' => date('Y', $first_facet_date), 'field' => NULL);
+      break;
+    case '+1DAY':
+      $parents[] = array('name' => date('Y', $first_facet_date), 'field' => NULL);
+      $parents[] = array('name' => date('Y F', $first_facet_date), 'field' => 'Y');
+      break;
+    case '+1HOUR':
+      $parents[] = array('name' => date('Y', $first_facet_date), 'field' => NULL);
+      $parents[] = array('name' => date('Y F', $first_facet_date), 'field' => 'Y');
+      $parents[] = array('name' => date('Y F d', $first_facet_date), 'field' => 'Y-m');
+      break;
+    case '+1MINUTE':
+      $parents[] = array('name' => date('Y', $first_facet_date), 'field' => NULL);
+      $parents[] = array('name' => date('Y F', $first_facet_date), 'field' => 'Y');
+      $parents[] = array('name' => date('Y F d', $first_facet_date), 'field' => 'Y-m');
+      $parents[] = array('name' => date('Y F d, H', $first_facet_date), 'field' => 'Y-m-d');
+      break;
+    case '+1SECOND':
+      $parents[] = array('name' => date('Y', $first_facet_date), 'field' => NULL);
+      $parents[] = array('name' => date('Y F', $first_facet_date), 'field' => 'Y');
+      $parents[] = array('name' => date('Y F d', $first_facet_date), 'field' => 'Y-m');
+      $parents[] = array('name' => date('Y F d, H', $first_facet_date), 'field' => 'Y-m-d');
+      $parents[] = array('name' => date('Y F d, H:i', $first_facet_date), 'field' => 'Y-m-d\TH');
+      break;
+  }
+  foreach ($parents as $parent) {
+    $new_query = drupal_clone($query);
+    $new_query->remove_field($delta);
+    if (!empty($parent['field'])) {
+      $new_query->add_field($delta, apachesolr_facet_date_range('period', date($parent['field'], $first_facet_date)));
+    }
+    $path = 'search/' . arg(1) . '/' . $new_query->get_query();
+    $unclick_link = theme('apachesolr_unclick_link', $path);
+    $items[] = theme('apachesolr_facet_item', $parent['name'], NULL, $path, TRUE, $unclick_link);
+  }
+  return $items;
+}
 /**
  * Callback function for the 'Filter by type' facet block.
  */
@@ -849,6 +938,97 @@
 }
 
 /**
+ * Takes a part of ISO 8601 date and return a range for start, end or a whole period.
+ * It is used for date facets.
+ * 
+ * Example:
+ *  - apachesolr_date_range('start', '2004') returns '2004-01-01T00:00:00Z'
+ *  - apachesolr_date_range('end', '2004-03') returns '2004-03-31T23:59:59Z'
+ *  - apachesolr_date_range('period', '2004-03-10') returns '[2004-03-10T00:00:00Z TO 2004-03-10T23:59:59Z]'
+ */
+function apachesolr_facet_date_range($op = 'start', $date_string) {
+  if (preg_match('|^(\d{4})(?:-(\d{2}))?(?:-(\d{2}))?(?:T(\d{2}))?(?::(\d{2}))?(?::(\d{2}))?Z?$|', $date_string, $matches)) {
+    $year_start = $year_end = 0;
+    $month_start = $month_end = $day_start = $day_end = 1;
+    $hour_start = $hour_end = $minute_start = $minute_end = $second_start = $second_end = 0;
+
+    // To avoid many 'if-else' statement we use logical operators.
+    ((isset($matches[1]) && ($year_start = $year_end = $matches[1])))                        &&
+    ((isset($matches[2]) && ($month_start = $month_end = $matches[2]))   || !($year_end++))  &&
+    ((isset($matches[3]) && ($day_start = $day_end = $matches[3]))       || !($month_end++)) &&
+    ((isset($matches[4]) && ($hour_start = $hour_end = $matches[4]))     || !($day_end++))   &&
+    ((isset($matches[5]) && ($minute_start = $minute_end = $matches[5])) || !($hour_end++))  &&
+    ((isset($matches[6]) && ($second_start = $second_end = $matches[6])) || !($minute_end++));
+    
+    switch ($op) {
+      case 'start':
+        $result = date('Y-m-d\TH:i:s\Z', mktime($hour_start, $minute_start, $second_start, $month_start, $day_start, $year_start));
+        break;
+      case 'end':
+        $result = date('Y-m-d\TH:i:s\Z', mktime($hour_end, $minute_end, $second_end, $month_end, $day_end, $year_end) - 1);
+        break;
+      case 'period':
+        $result = '[';
+        $result .= apachesolr_facet_date_range('start', $date_string);
+        $result .= ' TO ';
+        $result .= apachesolr_facet_date_range('end', $date_string);
+        $result .= ']';
+        break;
+    }
+  }
+  return $result;
+}
+
+/**
+ * Obtains a gap from a period string.
+ *
+ * @param string $period
+ *   A period (eg. '[2004-03-10T00:00:00Z TO 2004-03-10T23:59:59Z]')
+ * @return string $date_start
+ *   A facet start date in ISO8601 format.
+ * @return string $date_end
+ *   A facet end date in ISO8601 format.
+ * @return string $date_gap
+ *   A facet gap date in ISO8601 format.
+ */
+function apachesolr_facet_date_gap_obtain($period, &$date_start = NULL, &$date_end = NULL, &$date_gap = NULL) {
+  if (preg_match('|\[((\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z)\s+TO\s+((\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z)\]|', $period, $matches)) {
+    list(, $date_start, $year_start, $month_start,
+    $day_start, $hour_start, $minute_start, $second_start,
+    $date_end, $year_end, $month_end,
+    $day_end, $hour_end, $minute_end, $second_end) = $matches;
+    
+    $date_gap = '+1YEAR';
+
+    // To avoid many 'if-else' statement we use logical operators.
+    ($year_start   != $year_end)   && ($date_gap = '+1YEAR')   ||
+    ($month_start  != $month_end)  && ($date_gap = '+1MONTH')  ||
+    ($day_start    != $day_end)    && ($date_gap = '+1DAY')    ||
+    ($hour_start   != $hour_end)   && ($date_gap = '+1HOUR')   ||
+    ($minute_start != $minute_end) && ($date_gap = '+1MINUTE') ||
+    ($second_start != $second_end) && ($date_gap = '+1SECOND');
+    return TRUE;
+  }
+  return FALSE;
+}
+
+/**
+ * Format given ISO8601 date according to a gap.
+ */
+function apachesolr_facet_date_gap_format($date, $gap) {
+  $result = '';
+  if ($date = strtotime($date)) {
+    // To avoid many 'if-else' statement we use logical operators.
+    ($gap == '+1YEAR')   && ($result = '')                      ||
+    ($gap == '+1MONTH')  && ($result = date('Y', $date))        ||
+    ($gap == '+1DAY')    && ($result = date('Y-F', $date))      ||
+    ($gap == '+1HOUR')   && ($result = date('Y-F-d', $date))    ||
+    ($gap == '+1MINUTE') && ($result = date('Y-F-d, H', $date)) ||
+    ($gap == '+1SECOND') && ($result = date('Y-F-d, H:i', $date));
+  }
+  return $result;
+}
+/**
  * Implementation of hook_theme().
  */
 function apachesolr_theme() {
Index: apachesolr_search.module
===================================================================
RCS file: /cvs/drupal/contributions/modules/apachesolr/apachesolr_search.module,v
retrieving revision 1.1.2.6.2.25
diff -u -r1.1.2.6.2.25 apachesolr_search.module
--- apachesolr_search.module	4 Nov 2008 14:32:27 -0000	1.1.2.6.2.25
+++ apachesolr_search.module	14 Nov 2008 08:38:52 -0000
@@ -58,21 +58,46 @@
         // of these facets have their blocks enabled, so the list should be
         // filtered by the actual enabled blocks, otherwise we're putting
         // unneeded strain on the Solr server.
+        
+        // hook_apachesolr_facets() should return both field and date facets.
+        // $result['field_facet_name'] = array('type' => 'field');
+        // $result['date_facet_name'] = array('type' => 'date', 'start' => '<date in ISO 8601>', 'end' => '<date in ISO 8601>', 'gap' => '<date in ISO 8601>');
         foreach (module_implements('apachesolr_facets') as $module) {
           $function = $module .'_apachesolr_facets';
           $result = call_user_func_array($function, array());
           if (isset($result) && is_array($result)) {
-            foreach ($result as $facet) {
-              $params['facet.field'][] = $facet; 
+            foreach ($result as $facet => $data) {
+              if ($data['type'] == 'field') {
+                $params['facet.field'][] = $facet;
+              }
+              elseif ($data['type'] == 'date') {
+                $params['facet.date'][] = $facet;
+                $params['f.' . $facet . '.facet.date.start'] = $data['start'];
+                $params['f.' . $facet . '.facet.date.end'] = $data['end'];
+                $params['f.' . $facet . '.facet.date.gap'] = $data['gap'];
+                $params['f.' . $facet . '.facet.date.other'] = 'all';
+              }
             }
           }
         }
-
+        
         // Facet limits
         $facet_query_limits = variable_get('apachesolr_facet_query_limits', array());
         foreach ($facet_query_limits as $fieldname => $limit) {
           $params['f.' . $fieldname . '.facet.limit'] = $limit;
         }
+        
+        // Get from $query object a date field value and set a gap for it.
+        foreach ($params['facet.date'] as $facet) {
+          foreach ($query->get_fields() as $pos => $values) {
+            // Catch a range query
+            if ($values['#name'] == $facet && apachesolr_facet_date_gap_obtain($values['#value'], $date_start, $date_end, $date_gap)) {
+              $params['f.' . $facet . '.facet.date.start'] = $date_start;
+              $params['f.' . $facet . '.facet.date.end'] = $date_end;
+              $params['f.' . $facet . '.facet.date.gap'] = $date_gap;
+            }
+          }
+        }
 
         if (isset($_GET['solrsort'])) {
           $sort = check_plain($_GET['solrsort']);
@@ -165,7 +190,21 @@
 }
 
 function apachesolr_search_apachesolr_facets() {
-  return array_keys(apachesolr_search_block());
+  foreach (array_keys(apachesolr_search_block()) as $facet) {
+    // changed_date is a solr date field.
+    if ($facet == 'changed_date') {
+      $result[$facet] = array(
+        'type' => 'date',
+        'start' => apachesolr_facet_date_range('start', date('Y', db_result(db_query('SELECT MIN(changed) FROM {node} WHERE status = 1')))),
+        'end' => apachesolr_facet_date_range('end', date('Y', db_result(db_query('SELECT MAX(changed) FROM {node} WHERE status = 1')))),
+        'gap' => '+1YEAR'
+      );
+    }
+    else {
+      $result[$facet] = array('type' => 'field');
+    }
+  }
+  return $result;
 }
 
 /**
@@ -175,6 +214,7 @@
   switch ($op) {
     case 'list':
       $blocks['uid'] = array('info' => t('ApacheSolr Search: Filter by author'));
+      $blocks['changed_date'] = array('info' => t('ApacheSolr Search: Filter by date'));
 
       // Get taxonomy vocabulary facets.
       if (module_exists('taxonomy')) {
@@ -259,6 +299,10 @@
             $filter_by = t('Filter by author');
             return apachesolr_facet_block($response, $query, $delta, $filter_by, 'apachesolr_search_get_username');
 
+          case 'changed_date':
+            $filter_by = t('date');
+            return apachesolr_facet_date_block($response, $query, $delta, $filter_by);
+
           default:
            if ($fields = apachesolr_cck_fields()) {
             foreach ($fields as $name => $field) {
@@ -341,6 +385,9 @@
     'apachesolr_breadcrumb_tid' => array(
       'arguments' => array('tid' => NULL),
     ),
+    'apachesolr_breadcrumb_changed_date' => array(
+      'arguments' => array('changed_date' => NULL),
+    ),
   );
 }
 
@@ -358,4 +405,14 @@
 function theme_apachesolr_breadcrumb_tid($tid) {
   $term = taxonomy_get_term($tid);
   return $term->name;
-}
\ No newline at end of file
+}
+
+/**
+ * Return the date period from $changed_date
+ */
+function theme_apachesolr_breadcrumb_changed_date($changed_date) {
+  if (apachesolr_facet_date_gap_obtain($changed_date, $date_start, $date_end, $date_gap)) {
+    return apachesolr_facet_date_gap_format($date_start, $date_gap);
+  }
+  return $changed_date;  
+}
Index: schema.xml
===================================================================
RCS file: /cvs/drupal/contributions/modules/apachesolr/schema.xml,v
retrieving revision 1.1.2.1.2.10
diff -u -r1.1.2.1.2.10 schema.xml
--- schema.xml	10 Nov 2008 22:12:28 -0000	1.1.2.1.2.10
+++ schema.xml	14 Nov 2008 08:05:59 -0000
@@ -255,6 +255,7 @@
    <field name="uid"  type="integer" indexed="true" stored="true"/>
    <field name="name" type="text" indexed="true" stored="true" termVectors="true"/>
    <field name="changed" type="integer" indexed="true" stored="true"/>
+   <field name="changed_date" type="date" indexed="true" stored="true"/>
    <field name="nid"  type="integer" indexed="true" stored="true"/>
    <field name="comment_count" type="integer" indexed="true" stored="true"/>
    <field name="tid"  type="integer" indexed="true" stored="true" multiValued="true"/>

