Index: views.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/views/views.module,v
retrieving revision 1.246
diff -u -r1.246 views.module
--- views.module	3 Apr 2008 17:38:36 -0000	1.246
+++ views.module	9 Apr 2008 19:02:55 -0000
@@ -956,166 +956,6 @@
 }
 
 /**
- * Helper function to create cross-database SQL dates.
- *
- * @param $field
- *   The real table and field name, like 'tablename.fieldname'.
- * @param $field_type
- *   The type of date field, 'int' or 'datetime'.
- * @param $set_offset
- *   The name of a field that holds the timezone offset or a fixed timezone
- *   offset value. If not provided, the normal Drupal timezone handling
- *   will be used, i.e. $set_offset = 0 will make no timezone adjustment.
- * @return
- *   An appropriate SQL string for the db type and field type.
- */
-function views_date_sql_field($field, $field_type = 'int', $set_offset = NULL) {
-  $db_type = $GLOBALS['db_type'];
-  $offset = $set_offset !== NULL ? $set_offset : views_get_timezone();
-  switch ($db_type) {
-    case 'mysql':
-    case 'mysqli':
-      switch ($field_type) {
-        case 'int':
-          $field = "FROM_UNIXTIME($field)";
-          break;
-        case 'datetime':
-          break;
-      }
-      if (!empty($offset)) {
-        $field = "($field + INTERVAL $offset SECOND)";
-      }
-      return $field;
-    case 'pgsql':
-      switch ($field_type) {
-        case 'int':
-          $field = "$field::ABSTIME";
-          break;
-        case 'datetime':
-          break;
-      }
-      if (!empty($offset)) {
-        $field = "($field + 'INTERVAL $offset SECONDS')";
-      }
-      return $field;
-  }
-}
-
-/**
- * Helper function to create cross-database SQL date formatting.
- *
- * @param $format
- *   A format string for the result, like 'Y-m-d H:i:s'.
- * @param $field
- *   The real table and field name, like 'tablename.fieldname'.
- * @param $field_type
- *   The type of date field, 'int' or 'datetime'.
- * @param $set_offset
- *   The name of a field that holds the timezone offset or a fixed timezone
- *   offset value. If not provided, the normal Drupal timezone handling
- *   will be used, i.e. $set_offset = 0 will make no timezone adjustment.
- * @return
- *   An appropriate SQL string for the db type and field type.
- */
-function views_date_sql_format($format, $field, $field_type = 'int', $set_offset = NULL) {
-  $db_type = $GLOBALS['db_type'];
-  $field = views_date_sql_field($field, $field_type, $set_offset);
-  switch ($db_type) {
-    case 'mysql':
-    case 'mysqli':
-      $replace = array(
-        'Y' => '%Y',
-        'm' => '%m',
-        'd' => '%%d',
-        'H' => '%H',
-        'i' => '%i',
-        's' => '%s',
-        );
-      $format = strtr($format, $replace);
-      return "DATE_FORMAT($field, '$format')";
-    case 'pgsql':
-      $replace = array(
-        'Y' => 'YY',
-        'm' => 'MM',
-        'd' => 'DD',
-        'H' => 'HH24',
-        'i' => 'MI',
-        's' => 'SS',
-        );
-      $format = strtr($format, $replace);
-      return "TO_CHAR($field, '$format')";
-  }
-}
-
-/**
- * Helper function to create cross-database SQL date extraction.
- *
- * @param $extract_type
- *   The type of value to extract from the date, like 'MONTH'.
- * @param $field
- *   The real table and field name, like 'tablename.fieldname'.
- * @param $field_type
- *   The type of date field, 'int' or 'datetime'.
- * @param $set_offset
- *   The name of a field that holds the timezone offset or a fixed timezone
- *   offset value. If not provided, the normal Drupal timezone handling
- *   will be used, i.e. $set_offset = 0 will make no timezone adjustment.
- * @return
- *   An appropriate SQL string for the db type and field type.
- */
-function views_date_sql_extract($extract_type, $field, $field_type = 'int', $set_offset = NULL) {
-  $db_type = $GLOBALS['db_type'];
-  $field = views_date_sql_field($field, $field_type, $set_offset);
-
-  // Note there is no space after FROM to avoid db_rewrite problems
-  // see http://drupal.org/node/79904.
-  switch ($extract_type) {
-  case('DATE'):
-    return $field;
-  case('YEAR'):
-    return "EXTRACT(YEAR FROM($field))";
-  case('MONTH'):
-    return "EXTRACT(MONTH FROM($field))";
-  case('DAY'):
-    return "EXTRACT(DAY FROM($field))";
-  case('HOUR'):
-    return "EXTRACT(HOUR FROM($field))";
-  case('MINUTE'):
-    return "EXTRACT(MINUTE FROM($field))";
-  case('SECOND'):
-    return "EXTRACT(SECOND FROM($field))";
-  case('WEEK'):  // ISO week number for date
-    switch ($db_type) {
-      case('mysql'):
-      case('mysqli'):
-        // WEEK using arg 3 in mysql should return the same value as postgres EXTRACT
-        return "WEEK($field, 3)";
-      case('pgsql'):
-        return "EXTRACT(WEEK FROM($field))";
-    }
-  case('DOW'):
-    switch ($db_type) {
-      case('mysql'):
-      case('mysqli'):
-        // mysql returns 1 for Sunday through 7 for Saturday
-        // php date functions and postgres use 0 for Sunday and 6 for Saturday
-        return "INTEGER(DAYOFWEEK($field) - 1)";
-      case('pgsql'):
-        return "EXTRACT(DOW FROM($field))";
-    }
-  case('DOY'):
-    switch ($db_type) {
-      case('mysql'):
-      case('mysqli'):
-        return "DAYOFYEAR($field)";
-      case('pgsql'):
-        return "EXTRACT(DOY FROM($field))";
-    }
-  }
-
-}
-
-/**
  * Form builder for the exposed widgets form.
  *
  * Be sure that $view and $display are references.
@@ -1241,3 +1081,360 @@
   return array('***CURRENT_TIME***' => time(), '***CURRENT_LANGUAGE***' => $language->language);
 }
 
+/**
+ * A class to manipulate dates.
+ */
+class views_date_handler {
+  var $granularity = array();
+  var $db_type = 'mysql';
+  var $date_type = 'int';
+
+  /**
+   * Make sure granularity has a sane default value.
+   */
+  function construct($date_type = 'int') {
+    $this->granularity = $this->granularity_keys();
+    $this->db_type = $GLOBALS['db_type'];
+    $this->date_type = $date_type;
+  }
+
+  /**
+   * An array of all date parts,
+   * optionally limited to an array of allowed parts.
+   */
+  function date_parts($limit = array()) {
+    $parts =  array(
+      'year' => t('Year'), 'month' => t('Month'), 'day' => t('Day'),
+      'hour' => t('Hour'), 'minute' => t('Minute'), 'second' => t('Second'),
+      'adjustment' => t('Adjustment'),
+      );
+    if (!empty($limit)) {
+      foreach ($parts as $key => $part) {
+        if (!in_array($key, $limit)) {
+          unset($parts[$key]);
+        }
+      }
+    }
+    return $parts;
+  }
+
+  /**
+   * The minimum valid value for a date part, or an array of all minimums.
+   */
+  function part_min($part = NULL) {
+    $min = array(
+      'year' => 100, 'month' => 1, 'day' => 1,
+      'hour' => 0, 'minute' => 0, 'second' => 0);
+    if (!empty($part) && array_key_exists($part, $min)) {
+      return $min[$part];
+    }
+    return $min;
+  }
+
+  /**
+   * The maximum valid value for a date part, or an array of all maximums.
+   */
+  function part_max($part = NULL) {
+    $max = array(
+      'year' => 4000, 'month' => 12, 'day' => 31,
+      'hour' => 23, 'minute' => 59, 'second' => 59);
+    if (!empty($part) && array_key_exists($part, $max)) {
+      return $max[$part];
+    }
+    return $max;
+  }
+
+  /**
+   * The SQL format for a date part, or an array of all formats.
+   */
+  function part_format($part = NULL) {
+    $formats = array(
+      'year' => 'Y', 'month' => 'm', 'day' => 'd',
+      'hour' => 'H', 'minute' => 'i', 'second' => 's');
+    if (!empty($part) && array_key_exists($part, $formats)) {
+      return $formats[$part];
+    }
+    return $formats;
+  }
+
+  /**
+   * Convert a format string into help text,
+   * i.e. 'Y-m-d' becomes 'YYYY-MM-DD'.
+   *
+   * @param unknown_type $format
+   * @return unknown
+   */
+  function format_help($format) {
+    $replace = array(
+      'Y' => 'YYYY', 'm' => 'MM', 'd' => 'DD',
+      'H' => 'HH', 'i' => 'MM', 's' => 'SS', '\T' => 'T');
+    return strtr($format, $replace);
+  }
+
+  /**
+   * Rewrite a format string so it only inludes elements from a
+   * specified granularity array.
+   *
+   * Example:
+   *   date_limit_format('F j, Y - H:i', array('year', 'month', 'day'));
+   *   returns 'F j, Y'
+   *
+   * @param $format
+   *   a format string
+   * @param $granularity
+   *   an array of allowed date parts, all others will be removed
+   *   array('year', 'month', 'day', 'hour', 'minute', 'second');
+   * @return
+   *   a format string with all other elements removed
+   */
+  function limit_format($format, $granularity) {
+    // Strip out timezone formatting.
+    $regex = array('([OZPe])');
+    // Get rid of dash separating date and time if either is missing.
+    if (!date_has_time($granularity)
+      || sizeof(array_intersect($granularity, array('year', 'month', 'day')) == 0)) {
+      $regex[] = '( -)';
+    }
+    if (!date_has_time($granularity)) {
+        $regex[] = '(a|A)';
+        $regex[] = '(\\\T)';
+    }
+    // Create regular expressions to remove selected values from string.
+    $nongranularity = array_diff(array('year', 'month', 'day', 'hour', 'minute', 'second'), $granularity);
+    foreach ($nongranularity as $element) {
+      switch ($element) {
+        case 'year':
+          $regex[] = '([\-/\.]?[Yy][\-/\.,]?)';
+          break;
+        case 'day':
+          $regex[] = '([\-/\.]?[lDdj][\-/\.,]?)';
+          break;
+        case 'month':
+          $regex[] = '([\-/\.]?[FMmn][\-/\.,]?)';
+          break;
+        case 'hour':
+          $regex[] = '([HhGg][:]?)';
+          break;
+        case 'minute':
+          $regex[] = '([:]?[i])';
+          break;
+        case 'second':
+          $regex[] = '([:]?[s])';
+          break;
+      }
+    }
+    // Remove selected values from string.
+    // Don't leave any trailing punctuation behind.
+    $format = trim(preg_replace($regex, array(), $format));
+    return preg_replace('([\-/\.,]$)', '', $format);
+  }
+
+  /**
+   * Normalized, validated granularity key array.
+   *
+   * Produce a simple key array from a variety of
+   * possible granularity inputs:
+   *   date_parts() array format:
+   *       $key => $label,
+   *       $key2 => $label2,
+   *       $key3 => $label3,
+   *   FAPI checkboxes $form_values format:
+   *       $key => 0,
+   *       $key2 => $key2,
+   *       $key3 => $key3,
+   *   Normal format:
+   *       0 => $key,
+   *       1 => $key2,
+   *       2 => $key3,
+   *
+   * @param array $granularity
+   *   The granularity array to analyze, all possible keys will
+   *   be returned if left empty.
+   */
+  function granularity_keys($granularity = array()) {
+    $keys = array_keys($this->date_parts());
+    $labels = array_values($this->date_parts());
+    $values = array();
+    foreach ((array) $granularity as $key => $value) {
+      if (is_numeric($key) && in_array($value, $keys)) {
+        $values[] = $value;
+      }
+      elseif (in_array($key, $keys) && $value !== 0) {
+        $values[] = $key;
+      }
+    }
+    return !empty($values) ? $values : $keys;
+  }
+
+  /**
+   * A form element to select the granularity.
+   */
+  function granularity_form($granularity) {
+    $form = array(
+      '#title' => t('Granularity'),
+      '#type' => 'checkboxes',
+      '#default_value' => $this->granularity_keys($granularity),
+      '#options' => $this->date_parts(),
+     );
+     return $form;
+  }
+
+  /**
+   * Helper function to create cross-database SQL dates.
+   *
+   * @param $field
+   *   The real table and field name, like 'tablename.fieldname'.
+   * @param $set_offset
+   *   The name of a field that holds the timezone offset or a fixed timezone
+   *   offset value. If not provided, the normal Drupal timezone handling
+   *   will be used, i.e. $set_offset = 0 will make no timezone adjustment.
+   * @return
+   *   An appropriate SQL string for the db type and field type.
+   */
+  function sql_field($field, $set_offset = NULL) {
+    if ($field == 'NOW()') {
+      return $field;
+    }
+    $offset = $set_offset !== NULL ? $set_offset : views_get_timezone();
+    switch ($this->db_type) {
+      case 'mysql':
+      case 'mysqli':
+        switch ($this->date_type) {
+          case 'int':
+            $field = "FROM_UNIXTIME($field)";
+            break;
+          case 'iso':
+            $field = "STR_TO_DATE($field, '%Y-%m-%%dT%T')";
+            break;
+          case 'datetime':
+            break;
+        }
+        if (!empty($offset)) {
+          $field = "($field + INTERVAL $offset SECOND)";
+        }
+        return $field;
+      case 'pgsql':
+        switch ($this->date_type) {
+          case 'int':
+            $field = "$field::ABSTIME";
+            break;
+          case 'iso':
+            $field = "TO_DATE($field, 'FMYYYY-FMMM-FMDDTFMHH:FMMI:FMSS')";
+            break;
+          case 'datetime':
+            break;
+        }
+        if (!empty($offset)) {
+          $field = "($field + 'INTERVAL $offset SECONDS')";
+        }
+        return $field;
+    }
+  }
+
+  /**
+   * Helper function to create cross-database SQL date formatting.
+   *
+   * @param $format
+   *   A format string for the result, like 'Y-m-d H:i:s'.
+   * @param $field
+   *   The real table and field name, like 'tablename.fieldname'.
+   * @param $set_offset
+   *   The name of a field that holds the timezone offset or a fixed timezone
+   *   offset value. If not provided, the normal Drupal timezone handling
+   *   will be used, i.e. $set_offset = 0 will make no timezone adjustment.
+   * @return
+   *   An appropriate SQL string for the db type and field type.
+   */
+  function sql_format($format, $field, $set_offset = NULL) {
+    $field = $this->sql_field($field, $set_offset);
+    switch ($this->db_type) {
+      case 'mysql':
+      case 'mysqli':
+        $replace = array(
+          'Y' => '%Y',
+          'm' => '%m',
+          'd' => '%%d',
+          'H' => '%H',
+          'i' => '%i',
+          's' => '%s',
+          );
+        $format = strtr($format, $replace);
+        return "DATE_FORMAT($field, '$format')";
+      case 'pgsql':
+        $replace = array(
+          'Y' => 'YY',
+          'm' => 'MM',
+          'd' => 'DD',
+          'H' => 'HH24',
+          'i' => 'MI',
+          's' => 'SS',
+          );
+        $format = strtr($format, $replace);
+        return "TO_CHAR($field, '$format')";
+    }
+  }
+
+  /**
+   * Helper function to create cross-database SQL date extraction.
+   *
+   * @param $extract_type
+   *   The type of value to extract from the date, like 'MONTH'.
+   * @param $field
+   *   The real table and field name, like 'tablename.fieldname'.
+   * @param $set_offset
+   *   The name of a field that holds the timezone offset or a fixed timezone
+   *   offset value. If not provided, the normal Drupal timezone handling
+   *   will be used, i.e. $set_offset = 0 will make no timezone adjustment.
+   * @return
+   *   An appropriate SQL string for the db type and field type.
+   */
+  function sql_extract($extract_type, $field, $set_offset = NULL) {
+    $field = $this->sql_field($field, $set_offset);
+
+    // Note there is no space after FROM to avoid db_rewrite problems
+    // see http://drupal.org/node/79904.
+    switch ($extract_type) {
+    case('DATE'):
+      return $field;
+    case('YEAR'):
+      return "EXTRACT(YEAR FROM($field))";
+    case('MONTH'):
+      return "EXTRACT(MONTH FROM($field))";
+    case('DAY'):
+      return "EXTRACT(DAY FROM($field))";
+    case('HOUR'):
+      return "EXTRACT(HOUR FROM($field))";
+    case('MINUTE'):
+      return "EXTRACT(MINUTE FROM($field))";
+    case('SECOND'):
+      return "EXTRACT(SECOND FROM($field))";
+    case('WEEK'):  // ISO week number for date
+      switch ($this->db_type) {
+        case('mysql'):
+        case('mysqli'):
+          // WEEK using arg 3 in mysql should return the same value as postgres EXTRACT
+          return "WEEK($field, 3)";
+        case('pgsql'):
+          return "EXTRACT(WEEK FROM($field))";
+      }
+    case('DOW'):
+      switch ($this->db_type) {
+        case('mysql'):
+        case('mysqli'):
+          // mysql returns 1 for Sunday through 7 for Saturday
+          // php date functions and postgres use 0 for Sunday and 6 for Saturday
+          return "INTEGER(DAYOFWEEK($field) - 1)";
+        case('pgsql'):
+          return "EXTRACT(DOW FROM($field))";
+      }
+    case('DOY'):
+      switch ($this->db_type) {
+        case('mysql'):
+        case('mysqli'):
+          return "DAYOFYEAR($field)";
+        case('pgsql'):
+          return "EXTRACT(DOY FROM($field))";
+      }
+    }
+  }
+}
\ No newline at end of file
Index: css/views.css
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/views/css/views.css,v
retrieving revision 1.3
diff -u -r1.3 views.css
--- css/views.css	29 Mar 2008 00:57:37 -0000	1.3
+++ css/views.css	9 Apr 2008 19:14:19 -0000
@@ -23,3 +23,9 @@
   background:url(../images/status-active.gif) no-repeat right center;
   padding-right:18px;
 }
+
+.views-exposed-date-filter {
+  float:left !important;
+  margin-right:2px !important;
+  padding:0 !important;
+}
\ No newline at end of file
Index: includes/argument.handlers.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/views/includes/argument.handlers.inc,v
retrieving revision 1.1
diff -u -r1.1 argument.handlers.inc
--- includes/argument.handlers.inc	3 Apr 2008 00:22:26 -0000	1.1
+++ includes/argument.handlers.inc	9 Apr 2008 18:59:01 -0000
@@ -25,6 +25,7 @@
     }
   }
 
+
   /**
    * Give an argument the opportunity to modify the breadcrumb, if it wants.
    * This only gets called on displays where a breadcrumb is actually used.
@@ -561,5 +562,149 @@
 }
 
 /**
+ * A flexible, configurable date argument.
+ *
+ * This argument allows you to set one or more date parts to filter on,
+ * such as year, month, and day; month only; a complete date, etc.
+ *
+ * @ingroup views_argument_handlers
+ */
+class views_handler_argument_date extends views_handler_argument_formula {
+  var $date_handler = NULL;
+
+  /**
+   * Add date handler to the argument.
+   */
+  function construct() {
+    parent::construct();
+    $this->date_handler = new views_date_handler();
+    $this->date_handler->construct();
+    if (isset($this->definition['date_type'])) {
+      $this->date_handler->date_type = $this->definition['date_type'];
+    }
+  }
+
+  /**
+   * Get granularity and use it to create the formula and a format
+   * for the results.
+   */
+  function init(&$view, $options) {
+    parent::init(&$view, $options);
+    $handler = $this->date_handler;
+    $handler->granularity = $handler->granularity_keys($options['granularity']);
+    $sql_format = $handler->limit_format('Y-m-d\TH:i:s', $handler->granularity);
+    $this->formula = $handler->sql_format($sql_format, "***table***.$this->real_field");
+    $this->format = $handler->limit_format(variable_get('date_format_long', 'l, F j, Y - H:i'), $handler->granularity);
+  }
+
+  /**
+   * Default value for the granularity option.
+   */
+  function options(&$options) {
+    parent::options($options);
+    $parts = $this->date_handler->date_parts();
+    unset($parts['adjustment']);
+    $options['granularity'] = $this->date_handler->granularity_keys($parts);
+  }
+
+  /**
+   * Add a form element to select granularity.
+   */
+  function options_form(&$form, &$form_state) {
+    parent::options_form($form, $form_state);
+    // Select the granularity of the date parts to use in the argument.
+    $handler = $this->date_handler;
+    $form['granularity'] = $handler->granularity_form($this->options['granularity']);
+    $form['granularity']['#description'] = t('Limit the argument to use only the selected date parts.');
+    unset($form['granularity']['#options']['adjustment']);
+  }
+
+  /**
+   * Re-work the form values array into a proper granularity array.
+   */
+  function options_validate($form, &$form_state) {
+    $values = $form_state['values'];
+    $handler = $this->date_handler;
+    form_set_value($form['granularity'], $handler->granularity_keys($values['options']['granularity']), $form_state);
+  }
+
+  // Update the summary values to show selected granularity.
+  function admin_summary() {
+    $handler = $this->date_handler;
+    $format = $handler->format_help($handler->limit_format('Y-m-d\TH:i:s', $handler->granularity));
+    return t('<br />Argument format: @format', array('@format' => $format));
+  }
+
+  /**
+   * Provide a link to the next level of the view
+   */
+  function summary_name($data) {
+    $created = $data->{$this->name_alias};
+    return format_date(strtotime($created), 'custom', $this->format, 0);
+  }
+
+  /**
+   * Provide a link to the next level of the view
+   */
+  function title() {
+    return format_date(strtotime($this->argument), 'custom', $this->format, 0);
+  }
+
+  /**
+   * Create a summary query that matches the granularity.
+   *
+   * Needed or Views will do a groupby on the complete date instead
+   * of only the part of the date actually used in the argument.
+   */
+  function summary_query() {
+    $this->ensure_my_table();
+    $handler = $this->date_handler;
+
+    // We need to alter the base alias so the groupby clause will group by
+    // the relevant part of the date instead of a complete date.
+    $alias = $this->name_alias = $this->table_alias ."_". $this->real_field;
+    $this->query->add_field(NULL, $this->get_formula(), $alias);
+    $this->base_alias = $this->get_formula();
+
+    return $this->summary_basics();
+  }
+
+  /**
+   * Need to override the basic link since base_alias is now a formula.
+   */
+  function summary_link($data, $url) {
+    $value = $data->{$this->name_alias};
+    return url("$url/$value");
+  }
+
+  /**
+   * Create a query that matches the argument.
+   *
+   * Move through the arg and pick out date values to add to the query.
+   */
+  function query() {
+    $this->ensure_my_table();
+    $handler = $this->date_handler;
+    $granularity = $handler->granularity;
+    $parts = $handler->date_parts();
+    unset($parts['adjustment']);
+    $i = 0;
+    foreach ($parts as $key => $label) {
+      if (!in_array($key, $granularity)) {
+        // Skip values not in the requested granularity.
+      }
+      else {
+        // Find the next date part in the arg to evaluate
+        $arg = intval(substr($this->argument, $i, $key == 'year' ? 4 : 2));
+        $extract = $handler->sql_extract(strtoupper($key), "$this->table_alias.$this->real_field");
+        $this->query->add_where(0, $extract. " = %s", $arg);
+      }
+      $i += $key == 'year' ? 5 : 3;
+    }
+  }
+
+}
+
+/**
  * @}
  */
Index: includes/filter.handlers.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/views/includes/filter.handlers.inc,v
retrieving revision 1.1
diff -u -r1.1 filter.handlers.inc
--- includes/filter.handlers.inc	3 Apr 2008 00:22:26 -0000	1.1
+++ includes/filter.handlers.inc	9 Apr 2008 19:13:31 -0000
@@ -1028,7 +1028,6 @@
         $options[] = $id;
       }
     }
-
     return $options;
   }
   /**
@@ -1146,78 +1145,6 @@
   }
 }
 
-class views_handler_filter_date extends views_handler_filter_numeric {
-  function options(&$options) {
-    parent::options($options);
-    $options['value']['type'] = 'date';
-  }
-
-  /**
-   * Add a type selector to the value form
-   */
-  function value_form(&$form, &$form_state) {
-    $form['value']['type'] = array(
-      '#type' => 'radios',
-      '#title' => t('Value type'),
-      '#options' => array(
-        'date' => t('A date in any machine readable format. CCYY-MM-DD HH:MM:SS is preferred.'),
-        'offset' => t('An offset from the current time such as "+1 day" or "-2 hours and 30 minutes"'),
-      ),
-      '#default_value' => $this->value['type'],
-    );
-    parent::value_form($form, $form_state);
-  }
-
-  function options_validate(&$form, &$form_state) {
-    parent::options_validate($form, $form_state);
-    $operators = $this->operators();
-    if ($operators[$form_state['values']['options']['operator']]['values'] == 1) {
-      $convert = strtotime($form_state['values']['options']['value']['value']);
-      if ($convert == -1 || $convert === FALSE) {
-        form_error($form['value']['value'], t('Invalid date format.'));
-      }
-    }
-    else {
-      $min = strtotime($form_state['values']['options']['value']['min']);
-      if ($min == -1 || $min === FALSE) {
-        form_error($form['value']['min'], t('Invalid date format.'));
-      }
-      $max = strtotime($form_state['values']['options']['value']['max']);
-      if ($max == -1 || $max === FALSE) {
-        form_error($form['value']['max'], t('Invalid date format.'));
-      }
-    }
-  }
-
-  function op_between($field) {
-    if ($this->operator == 'between') {
-      $a = strtotime($this->value['min'], 0);
-      $b = strtotime($this->value['max'], 0);
-    }
-    else {
-      $a = strtotime($this->value['max'], 0);
-      $b = strtotime($this->value['min'], 0);
-    }
-
-    if ($this->value['type'] == 'offset') {
-      $a = '***CURRENT_TIME***' . sprintf('%+d', $a); // keep sign
-      $b = '***CURRENT_TIME***' . sprintf('%+d', $b); // keep sign
-    }
-    // %s is safe here because strtotime scrubbed the input and we might
-    // have a string if using offset.
-    $this->query->add_where($this->options['group'], "$field >= %s", $a);
-    $this->query->add_where($this->options['group'], "$field <= %s", $b);
-  }
-
-  function op_simple($field) {
-    $value = strtotime($this->value['value'], 0);
-    if ($this->value['type'] == 'offset') {
-      $value = '***CURRENT_TIME***' . sprintf('%+d', $value); // keep sign
-    }
-    $this->query->add_where($this->options['group'], "$field $this->operator %s", $value);
-  }
-}
-
 /**
  * Complex filter to handle filtering for many to one relationships,
  * such as terms (many terms per node) or roles (many roles per user).
@@ -1338,7 +1265,302 @@
 }
 
 /**
- * @}
+ * A flexible, configurable date filter.
+ *
+ * This filter allows you to select one or more date parts to filter on,
+ * such as year, month, and day; month only; a complete date, etc.
+ *
+ * Each part can be set to blank to show all values; 'now' to filter for
+ * the current value of that part, or a specific value.
+ *
+ * An adjustment field is provided that will adjust the selected filter
+ * value by something like '+90 days' or '-1 month';
  */
+class views_handler_filter_date extends views_handler_filter_numeric {
+  var $date_handler = NULL;
 
+  // Add a date handler to the filter.
+  function construct() {
+    parent::construct();
+    $this->date_handler = new views_date_handler();
+    $this->date_handler->construct();
+    if (isset($this->definition['date_type'])) {
+      $this->date_handler->date_type = $this->definition['date_type'];
+    }
+  }
+
+  function init(&$view, $options) {
+    parent::init(&$view, $options);
+    $handler = $this->date_handler;
+    $handler->granularity = $handler->granularity_keys($options['granularity']);
+  }
+
+  // Set default values for the date filter.
+  function options(&$options) {
+    parent::options($options);
+    $options['granularity'] = $this->date_handler->granularity_keys();
 
+    // We use different values than the parent form, so we must
+    // construct our own value options.
+    $options['value'] = array();
+    foreach (array('value', 'min', 'max') as $prefix) {
+      foreach ($this->date_handler->date_parts() as $key => $part) {
+        $options['value'][$prefix . $key] = '';
+      }
+    }
+  }
+
+  /**
+   * Set the granularity of the date parts to use in the filter.
+    */
+  function has_extra_options() { return TRUE; }
+
+  function extra_options_form(&$form, &$form_state) {
+    $form['granularity'] = $this->date_handler->granularity_form($this->options['granularity']);
+    $form['granularity']['#description'] = t('Limit the filter to use only the selected date parts.');
+  }
+
+  function extra_options_validate($form, &$form_state) {
+    $values = $form_state['values'];
+    $handler = $this->date_handler;
+    form_set_value($form['granularity'], $handler->granularity_keys($values['granularity']), $form_state);
+  }
+
+  /**
+   * Add the selectors to the value form using the date handler.
+   */
+  function value_form(&$form, &$form_state) {
+    // We use different values than the parent form, so we must
+    // construct our own form element.
+    $form['value'] = array();
+    $form['value']['#tree'] = TRUE;
+    $which = 'all';
+    if (!empty($form['operator'])) {
+      $source = ($form['operator']['#type'] == 'radios') ? 'radio:options[operator]' : 'edit-options-operator';
+    }
+
+    if (!empty($form_state['exposed'])) {
+      if (empty($this->options['expose']['operator'])) {
+        // exposed and locked.
+        $which = in_array($this->operator, $this->operator_values(2)) ? 'minmax' : 'value';
+      }
+      else {
+        $source = 'edit-' . form_clean_id($this->options['expose']['operator']);
+      }
+    }
+
+    $handler = $this->date_handler;
+    if ($which == 'all' || $which == 'value') {
+      $form['value'] += $this->date_parts_form('value', $source, $which, $this->operator_values(1));
+    }
+
+    if ($which == 'all' || $which == 'minmax') {
+      $form['value'] += $this->date_parts_form('min', $source, $which, $this->operator_values(2));
+      $form['value'] += $this->date_parts_form('max', $source, $which, $this->operator_values(2));
+    }
+    $form['value']['description'] = array(
+      '#prefix' => '<div class=""><div class="form-item"><div class="description">',
+      '#suffix' => '</div></div></div>',
+      '#value' => t('Blank values do no filtering, \'now\' filters for the current value.'),
+      );
+    if (in_array('adjustment', $handler->granularity)) {
+      $form['value']['description']['#value'] .= t(' \'Adjustment\' adds a value like \'+1 day\' to the other values.');
+    }
+
+  }
+
+  /**
+   * The first date part used in this instance, to know when to start
+   * a new sub-grouping.
+   */
+  function first_part() {
+    $parts = (array) $this->date_handler->granularity;
+    return array_shift($parts);
+  }
+
+  /**
+   * The last date part used in this instance, to know when to end
+   * a sub-grouping.
+   */
+  function last_part() {
+    $parts = (array) $this->date_handler->granularity;
+    return array_pop($parts);
+  }
+
+  /**
+   * A form element to select date part values.
+   *
+   * @param string $prefix
+   *   A prefix for the date values, 'value', 'min', or 'max'.
+   * @param string $source
+   *   The operator for this element.
+   * @param string $which
+   *   Which element to provide, 'all', 'value', or 'minmax'.
+   * @param array $operator_values
+   *   An array of the allowed operators for this element.
+   * @param array $limit
+   *   An array of date parts to limit this element to.
+   *
+   * @return
+   *   The form date part element for this instance.
+   */
+  function date_parts_form($prefix, $source, $which, $operator_values) {
+    $prefixname = $prefix == 'value' ? '' : ($prefix == 'min' ? t('From') : t('To'));
+    $handler = $this->date_handler;
+    $min = $handler->part_min();
+    $max = $handler->part_max();
+    $limit = $handler->granularity;
+    foreach ($handler->date_parts($limit) as $key => $name) {
+      $options = array('' => '', 'now' => 'now');
+      $type = 'select';
+      if ($key == 'year' || $key == 'adjustment') {
+        $type = 'textfield';
+      }
+      $form[$prefix . $key] = array(
+        '#title' => t('@value', array('@type' => $prefixname, '@value' => $name)),
+        '#type' => $type,
+        '#size' => $key == 'adjustment' ? 20 : ($key == 'year' ? 6 : 1),
+        //'#options' => $options,
+        '#default_value' => !empty($this->value[$prefix . $key]) ? $this->value[$prefix . $key] : '',
+        '#prefix' => '<div class="views-exposed-date-filter">',
+        '#suffix' => '</div>',
+      );
+      switch ($key) {
+        case 'year':
+        case 'adjustment':
+          break;
+        case 'month':
+          $form[$prefix . $key]['#options'] = $options + drupal_map_assoc(range(1, 12), 'map_month');
+          break;
+        default:
+          $form[$prefix . $key]['#options'] = $options + drupal_map_assoc(range($min[$key], $max[$key]));
+          break;
+      }
+      if ($type == 'textfield') {
+        unset($form[$prefix . $key]['#options']);
+      }
+      if ($which == 'all') {
+        $dependency = array(
+          '#process' => array('views_process_dependency'),
+          '#dependency' => array($source => $operator_values),
+        );
+        $form[$prefix . $key] += $dependency;
+      }
+      // Add wrappers to force each date grouping to a separate line.
+      if ($key == $this->first_part()) {
+        $form[$prefix . $key]['#prefix'] = '<div class="clear-block"><div class="views-left-75">' .
+          $form[$prefix . $key]['#prefix'];
+      }
+      if ($key == $this->last_part()) {
+        $form[$prefix . $key]['#suffix'] .= '</div></div>';
+      }
+    }
+    return $form;
+  }
+
+  // User the date handler to validate the form.
+  function options_validate(&$form, &$form_state) {
+    if (!isset($form_state['values']['options']['value'])) {
+      return;
+    }
+    $handler = $this->date_handler;
+    $min = $handler->part_min();
+    $max = $handler->part_max();
+    $values = $form_state['values']['options']['value'];
+
+    // Validate date values.
+    unset($values['offset']);
+    foreach ($values as $name => $value) {
+      $pos = 3;
+      if (substr($name, 0, 5) == 'value') {
+        $pos = 5;
+      }
+      $part = substr($name, $pos, strlen($name));
+      if (!empty($part) && $value != '' && $value != 'now' &&
+        ($value < $min[$part] || $value > $max[$part])) {
+        form_error($form['value'][$name], t('@value is invalid.', array('@value' => $parts[$part])));
+      }
+    }
+  }
+
+  // Update the summary values to provide
+  // meaningful information for each option.
+  function admin_summary() {
+    $handler = $this->date_handler;
+    $output = check_plain($this->operator) . ' ';
+
+    // If the filter is exposed, display the granularity.
+    if ($this->options['exposed']) {
+      return t('<strong>Exposed</strong> @format', array('@format' => implode(', ', ($handler->date_parts($handler->granularity)))));
+    }
+    // If the filter is not exposed, display the selected values.
+    // Check both empty and is_numeric to show all non-blank values,
+    // including zero values.
+    $min = array();
+    $max = array();
+    $handler = $this->date_handler;
+    if (in_array($this->operator, $this->operator_values(2))) {
+      foreach ($handler->date_parts($this->value['granularity']) as $key => $part) {
+        if (!empty($this->value['min'. $key]) || !empty($this->value['max'. $key])
+        || is_numeric($this->value['min'. $key]) || is_numeric($this->value['max'. $key])) {
+          $min[] = $part .'='. check_plain($this->value['min'. $key]);
+          $max[] = $part .'='. check_plain($this->value['max'. $key]);
+        }
+      }
+      $output .= t('@min and @max', array('@min' => implode(': ', $min), '@max' => implode(': ', $max)));
+    }
+    else {
+      foreach ($handler->date_parts($handler->granularity) as $key => $part) {
+        if (!empty($this->value['value'. $key]) || is_numeric($this->value['value'. $key])) {
+          $min[]= $part .'='. check_plain($this->value['value'. $key]);
+        }
+      }
+      $output .= implode(': ', $min);
+    }
+    return $output;
+  }
+
+  function op_between($field) {
+    $value = $this->date_filter('min', $field, '>=');
+    $value = $this->date_filter('max', $field, '<=');
+    return;
+  }
+
+  function op_simple($field) {
+    $value = $this->date_filter('value', $field, $this->operator);
+    return;
+  }
+
+  function date_filter($prefix, $field, $operator) {
+    $handler = $this->date_handler;
+    $granularity = $handler->granularity;
+    $parts = $handler->date_parts();
+    unset($parts['adjustment']);
+    if (!empty($this->value[$prefix .'adjustment'])) {
+      $adjustment = views_get_timezone() + strtotime($this->value[$prefix .'adjustment']) - time();
+    }
+    foreach ($parts as $key => $label) {
+      if (!empty($adjustment)) {
+          $extract = $handler->sql_extract(strtoupper($key), $field, $adjustment);
+        }
+        else {
+          $extract = $handler->sql_extract(strtoupper($key), $field);
+      }
+      if (!in_array($key, $granularity) || empty($this->value[$prefix . $key])) {
+        // Skip this value
+      }
+      elseif ($this->value[$prefix . $key] == 'now') {
+        $value = $handler->sql_extract(strtoupper($key), "NOW()", 0);
+        $this->query->add_where($this->options['group'], $extract ." $operator %s", $value);
+      }
+      else {
+        $value = $this->value[$prefix . $key];
+        $this->query->add_where($this->options['group'], $extract. " $operator %s", $value);
+      }
+    }
+  }
+}
+
+/**
+ * @}
+ */
\ No newline at end of file
Index: includes/sort.handlers.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/views/includes/sort.handlers.inc,v
retrieving revision 1.2
diff -u -r1.2 sort.handlers.inc
--- includes/sort.handlers.inc	3 Apr 2008 16:29:05 -0000	1.2
+++ includes/sort.handlers.inc	9 Apr 2008 14:23:48 -0000
@@ -94,6 +94,12 @@
 }
 
 class views_handler_sort_date extends views_handler_sort {
+  function construct() {
+    parent::construct();
+    $this->date_handler = new views_date_handler();
+    $this->date_handler->construct();
+  }
+
   function options(&$options) {
     parent::options($options);
     $options['granularity'] = 'second';
@@ -101,21 +107,11 @@
 
   function options_form(&$form, &$form_state) {
     parent::options_form($form, $form_state);
-
-    $form['granularity'] = array(
-      '#type' => 'radios',
-      '#title' => t('Granularity'),
-      '#options' => array(
-        'second' => t('Second'),
-        'minute' => t('Minute'),
-        'hour'   => t('Hour'),
-        'day'    => t('Day'),
-        'month'  => t('Month'),
-        'year'   => t('Year'),
-      ),
-      '#description' => t('The granularity is the smallest unit to use when determining whether two dates are the same; for example, if the granularity is "Year" then all dates in 1999, regardless of when they fall in 1999, will be considered the same date.'),
-      '#default_value' => $this->options['granularity'],
-    );
+    $handler = $this->date_handler;
+    $form['granularity'] = $handler->granularity_form($this->options['granularity']);
+    unset($form['granularity']['#options']['adjustment']);
+    $form['granularity']['#type'] = 'radios';
+    $form['granularity']['#description'] = t('The granularity is the smallest unit to use when determining whether two dates are the same; for example, if the granularity is "Year" then all dates in 1999, regardless of when they fall in 1999, will be considered the same date.');
   }
 
   /**
@@ -123,25 +119,26 @@
    */
   function query() {
     $this->ensure_my_table();
+    $handler = $this->date_handler;
     switch($this->options['granularity']) {
       case 'second':
       default:
         $this->query->add_orderby($this->table_alias, $this->real_field, $this->options['order']);
         return;
       case 'minute':
-        $formula = views_date_sql_format('YmdHi', "$this->table_alias.$this->real_field");
+        $formula = $handler->sql_format('YmdHi', "$this->table_alias.$this->real_field");
         break;
       case 'hour':
-        $formula = views_date_sql_format('YmdH', "$this->table_alias.$this->real_field");
+        $formula = $handler->sql_format('YmdH', "$this->table_alias.$this->real_field");
         break;
       case 'day':
-        $formula = views_date_sql_format('Ymd', "$this->table_alias.$this->real_field");
+        $formula = $handler->sql_format('Ymd', "$this->table_alias.$this->real_field");
         break;
       case 'month':
-        $formula = views_date_sql_format('Ym', "$this->table_alias.$this->real_field");
+        $formula = $handler->sql_format('Ym', "$this->table_alias.$this->real_field");
         break;
       case 'year':
-        $formula = views_date_sql_format('Y', "$this->table_alias.$this->real_field");
+        $formula = $handler->sql_format('Y', "$this->table_alias.$this->real_field");
         break;
     }
 
Index: modules/node.views.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/views/modules/node.views.inc,v
retrieving revision 1.45
diff -u -r1.45 node.views.inc
--- modules/node.views.inc	3 Apr 2008 17:19:19 -0000	1.45
+++ modules/node.views.inc	9 Apr 2008 18:25:05 -0000
@@ -118,6 +118,9 @@
     'filter' => array(
       'handler' => 'views_handler_filter_date',
     ),
+    'argument' => array(
+      'handler' => 'views_handler_argument_date',
+    ),
   );
 
   // changed field
@@ -134,6 +137,9 @@
     'filter' => array(
       'handler' => 'views_handler_filter_date',
     ),
+    'argument' => array(
+      'handler' => 'views_handler_argument_date',
+    ),
   );
 
   // Node type
@@ -253,53 +259,6 @@
     );
   }
 
-  // Bogus fields for aliasing purposes.
-
-  $data['node']['created_fulldate'] = array(
-    'title' => t('Created date'),
-    'help' => t('In the form of CCYYMMDD.'),
-    'argument' => array(
-      'field' => 'created',
-      'handler' => 'views_handler_argument_node_created_fulldate',
-    ),
-  );
-
-  $data['node']['created_year_month'] = array(
-    'title' => t('Created year + month'),
-    'help' => t('In the form of YYYYMM.'),
-    'argument' => array(
-      'field' => 'created',
-      'handler' => 'views_handler_argument_node_created_year_month',
-    ),
-  );
-
-  $data['node']['created_year'] = array(
-    'title' => t('Created year'),
-    'help' => t('In the form of YYYY.'),
-    'argument' => array(
-      'field' => 'created',
-      'handler' => 'views_handler_argument_node_created_year',
-    ),
-  );
-
-  $data['node']['created_month'] = array(
-    'title' => t('Created month'),
-    'help' => t('In the form of MM (01 - 12).'),
-    'argument' => array(
-      'field' => 'created',
-      'handler' => 'views_handler_argument_node_created_month',
-    ),
-  );
-
-  $data['node']['created_week'] = array(
-    'title' => t('Created week'),
-    'help' => t('In the form of WW (01 - 53).'),
-    'argument' => array(
-      'field' => 'created',
-      'handler' => 'views_handler_argument_node_created_week',
-    ),
-  );
-
   // ----------------------------------------------------------------------
   // Node revisions table
 
@@ -879,132 +838,6 @@
 }
 
 /**
- * Argument handler for a full date (CCYYMMDD)
- *
- * @ingroup views_argument_handlers
- */
-class views_handler_argument_node_created_fulldate extends views_handler_argument_formula {
-  /**
-   * Constructor implementation
-   */
-  function construct() {
-    $this->formula = views_date_sql_format('Ymd', "***table***.$this->real_field");
-    $this->format = 'F j, Y';
-  }
-
-  /**
-   * Provide a link to the next level of the view
-   */
-  function summary_name($data) {
-    $created = $data->{$this->name_alias};
-    return format_date(strtotime($created), 'custom', $this->format, 0);
-  }
-
-  /**
-   * Provide a link to the next level of the view
-   */
-  function title() {
-    return format_date(strtotime($this->argument), 'custom', $this->format, 0);
-  }
-}
-
-/**
- * Argument handler for a year (CCYY)
- *
- * @ingroup views_argument_handlers
- */
-class views_handler_argument_node_created_year extends views_handler_argument_formula {
-  /**
-   * Constructor implementation
-   */
-  function construct() {
-    $this->formula = views_date_sql_extract('YEAR', "***table***.$this->real_field");
-  }
-}
-
-/**
- * Argument handler for a year plus month (CCYYMM)
- *
- * @ingroup views_argument_handlers
- */
-class views_handler_argument_node_created_year_month extends views_handler_argument_formula {
-  /**
-   * Constructor implementation
-   */
-  function construct() {
-    $this->formula = views_date_sql_format('Ym', "***table***.$this->real_field");
-    $this->format = 'F, Y';
-  }
-
-  /**
-   * Provide a link to the next level of the view
-   */
-  function summary_name($data) {
-    $created = $data->{$this->name_alias};
-    return format_date(strtotime($created . "15"), 'custom', $this->format, 0);
-  }
-
-  /**
-   * Provide a link to the next level of the view
-   */
-  function title() {
-    return format_date(strtotime($this->argument . "15"), 'custom', $this->format, 0);
-  }
-}
-
-/**
- * Argument handler for a month (MM)
- *
- * @ingroup views_argument_handlers
- */
-class views_handler_argument_node_created_month extends views_handler_argument_formula {
-  /**
-   * Constructor implementation
-   */
-  function construct() {
-    $this->formula = views_date_sql_extract('MONTH', "***table***.$this->real_field");
-    $this->format = 'F';
-  }
-
-  /**
-   * Provide a link to the next level of the view
-   */
-  function summary_name($data) {
-    $month = str_pad($data->{$this->name_alias}, 2, '0', STR_PAD_LEFT);
-    return format_date(strtotime("2005" . $month . "15"), 'custom', $this->format);
-  }
-
-  /**
-   * Provide a link to the next level of the view
-   */
-  function title() {
-    return format_date(strtotime("2005" . $this->argument . "15"), 'custom', $this->format, 0);
-  }
-}
-
-/**
- * Argument handler for a full date (CCYYMMDD)
- *
- * @ingroup views_argument_handlers
- */
-class views_handler_argument_node_created_week extends views_handler_argument_formula {
-  /**
-   * Constructor implementation
-   */
-  function construct() {
-    $this->formula = views_date_sql_extract('WEEK', "***table***.$this->real_field");
-  }
-
-  /**
-   * Provide a link to the next level of the view
-   */
-  function summary_name($data) {
-    $created = $data->{$this->name_alias};
-    return t('Week @week', array('@week' => $created));
-  }
-}
-
-/**
  * Filter by node type
  *
  * @ingroup views_filter_handlers
