Creating a DATETIME View handler for Views2
Background
A couple of years ago I created a Drupal module to instantiate and process a content type for my own calendar of events. The "proper" Event module seemed too complicated (at least for a newbie as I then was) and anyway I wanted some practice at coding a module.
However I did take a look at Events and this is what prompted me to use DATETIME date columns in my schema declarations, because Event uses that datatype. At the time I wasn't aware that Event is about the only contributed module that does use DATETIME.
Problem
Jump forward nearly two years and I was trying to build a new view with a "filter >= -1 day" against one of my date fields... and it wasn't working.
Solution
I spent quite some time looking at existing Drupal issues and code snippets (particularly #147563 and #293894) but I couldn't find the exact solution that was compatible with D6 / Views 2, so I had to play around until I hit it.
The first thing required was a query substitution placeholder for a date of DATETIME format. This is declared with a hook_views_query_substitutions() function in the main module.
mymodule.module
/**
* Substitute current time; this works with cached queries.
*/
function mymodule_views_query_substitutions($view) {
global $user;
$tdata = gmdate('Y-m-d H:i', time());
return array('***CURRENT_DATETIME***' => $tdata);
}Then I needed to make sure I declared a custom filter handler in my hook_views_data() function in my .views.inc file. (The code snippet only shows one date field, the approach is the same for the others I had.)
mymodule.views.inc
/**
* Implementation of hook_views_data()
*/
function mymodule_views_data() {
// ----------------------------------------------------------------------
// mymodule table
$data = array(
'mymodule' =>array(
'table' => array(
'group' => t('Event'),
'join' => array(
'node' => array(
'left_field' => 'nid',
'field' => 'nid',
),
),
),
'field_mymodule_date_from' => array(
'title' => t('Date from'),
'help' => t('Event start date'),
'field' => array('handler' => 'views_handler_field_date',),
'filter' => array('handler' => mymodule_handler_filter_datetime,),
'sort' => array('handler' => 'views_handler_sort_date',),
),
),
);
return $data;
}I also needed to declare the handler itself (also in mymodule.views.inc)
/**
* Implementation of hook_views_handlers.
*/
function mymodule_views_handlers() {
return array(
'info' => array(
'path' => drupal_get_path('module', 'mymodule'),
),
'handlers' => array(
'mymodule_handler_filter_datetime' => array(
'parent' => 'views_handler_filter_date',
),
),
);
}The last component is the handler code itself. This is very similar to standard handlers but is tweaked for the requirements of DATETIME. I think the name of the include file is automatically predicted and included by View.
mymodule_handler_filter_datetime.inc
class mymodule_handler_filter_datetime extends views_handler_filter_date {
function op_between($field) {
if ($this->operator == 'between') {
$a = intval(strtotime($this->value['min'], 0));
$b = intval(strtotime($this->value['max'], 0));
}
else {
$a = intval(strtotime($this->value['max'], 0));
$b = intval(strtotime($this->value['min'], 0));
}
if ($this->value['type'] == 'offset') {
$a = (integer)time() + (integer)sprintf('%+d', $a); // keep sign
$b = (integer)time() + (integer)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'", date("Y-m-d H:i:s", $a));
$this->query->add_where($this->options['group'], "$field <= '%s'", date("Y-m-d H:i:s", $b));
}
function op_simple($field) {
$value = intval(strtotime($this->value['value'], 0));
if (!empty($this->value['type']) && $this->value['type'] == 'offset') {
$this->query->add_where($this->options['group'],
"%s %s DATE_ADD('***CURRENT_DATETIME***', INTERVAL %d SECOND)",
$field, $this->operator, $value);
}
else {
$this->query->add_where($this->options['group'], "$field $this->operator %s", $value);
}
}
}Caveat
It's quite possible that to handle the DATETIME data type for all kinds of View activity, I need more custom handlers. However so far the sorting and general field behaviour is OK - it was just the filtering that was obviously not right.
