diff --git date/date.module date/date.module
index d960b63..3b3d82a 100644
--- date/date.module
+++ date/date.module
@@ -105,7 +105,7 @@ function date_content_is_empty($item, $field) {
   if (empty($item['value'])) {
     return TRUE;
   }
-  elseif($field['todate'] == 'required' && empty($item['value2'])) {
+  elseif(preg_match('/^required/',$field['todate']) && empty($item['value2'])) {
     return TRUE;
   }
   return FALSE;
@@ -857,4 +857,4 @@ function date_formatter_get_settings($field_name, $type_name, $context) {
   $options['multiple']['multiple_to'] = variable_get($value .'_multiple_to', '');
   $options['fromto']['fromto'] = variable_get($value .'_fromto', 'both');  
   return $options;
-}
\ No newline at end of file
+}
diff --git date/date_admin.inc date/date_admin.inc
index 39de6e0..30cdc6a 100644
--- date/date_admin.inc
+++ date/date_admin.inc
@@ -241,6 +241,9 @@ function _date_field_settings($op, $field) {
       if ($field['tz_handling'] != 'none' && !in_array('hour', array_filter($field['granularity']))) {
         form_set_error('tz_handling', t('Dates without hours granularity must not use any timezone handling.'));
       }
+      if (preg_match('/timeonly$/',$field['todate']) && !in_array('hour', array_filter($field['granularity']))) {
+        form_set_error('granularity', t('Dates without hours granularity must not use a "Time only" To Date setting.'));
+      }
       break;
 
     case 'save':
@@ -344,11 +347,13 @@ function date_field_settings_form($field) {
     $form['repeat'] = array('#type' => 'hidden', '#value' => 0);
   }
 
-  $description = t("Display a matching second date field as a 'To date'. If marked 'Optional' field will be presented but not required. If marked 'Required' the 'To date' will be required if the 'From date' is required or filled in.");
+  $description = t("Display a matching second date field as a 'To date'. If marked 'Optional' field will be presented but not required. If marked 'Required' the 'To date' will be required if the 'From date' is required or filled in.<br/>If a 'Time only' option is chosen, then you will not be able to select a year, month or date for the 'To date', and these values will instead be set to the same as the 'From date' (or the day after if the time is after midnight). You must include Hours in the granularity if you select a 'Time only' option.");
   $description .= date_data_loss_warning('To date');
   $form['input']['todate'] = array(
     '#type' => 'radios', '#title' => t('To Date'),
-    '#options' => array('' => t('Never'), 'optional' => t('Optional'), 'required' => t('Required')),
+    '#options' => array('' => t('Never'),
+                        'optional' => t('Optional'), 'optional_timeonly' => t('Optional (time only)'),
+                        'required' => t('Required'), 'required_timeonly' => t('Required (time only)')),
     '#description' => $description,
     '#default_value' => isset($field['todate']) ? $field['todate'] : '',
     );
@@ -713,4 +718,4 @@ function _date_formatter_settings($form_state = NULL, $field, $options = array()
       );  
   }
   return $form;
-}
\ No newline at end of file
+}
diff --git date/date_content_generate.inc date/date_content_generate.inc
index 011d8b9..01bb25c 100644
--- date/date_content_generate.inc
+++ date/date_content_generate.inc
@@ -29,9 +29,11 @@ function _date_content_generate($node, $field) {
   date_increment_round($start, $increment);
     
 	// Modify To date by 1 hour to 3 days, shorter for repeating dates
-	// longer for others.
+	// longer for others. Limit to 20 hours (just under one day) if
+	// we have a 'Time only' To Date.
 	$start2 = drupal_clone($start);
-	$max = !empty($field['repeat']) ? 720 : 4320;
+	$max = !empty($field['repeat']) ? 720
+	                                : (preg_match('/timeonly$/',$field['todate']) ? 1200 : 4320);
 	$max = 240;
   date_modify($start2, '+'. mt_rand(60, $max) .' minutes');  
   date_increment_round($start2, $increment);
@@ -182,4 +184,4 @@ function date_content_repeat_dow_options() {
     }
   }
   return $options;
-}
\ No newline at end of file
+}
diff --git date/date_elements.inc date/date_elements.inc
index 232abbf..ecb6eae 100644
--- date/date_elements.inc
+++ date/date_elements.inc
@@ -28,7 +28,7 @@ function _date_field_validate($op, &$node, $field, &$items, $teaser, $page) {
           form_set_error($error_field, t("A 'From date' date is required for field %field %delta.", array('%delta' => $field['multiple'] ? intval($delta + 1) : '', '%field' => t($field['widget']['label']))));
         }
         if ($processed == 'value2'
-          && $field['todate'] == 'required' && ($field['required']
+          && preg_match('/^required/',$field['todate']) && ($field['required']
           && date_is_valid($item['value'], $field['type'], $field['granularity'])
           && !date_is_valid($item['value2'], $field['type'], $field['granularity']))) {
           form_set_error($error_field, t("A 'To date' is required for field %field %delta.", array('%delta' => $field['multiple'] ? intval($delta + 1) : '', '%field' => t($field['widget']['label']))));
@@ -269,7 +269,7 @@ function date_combo_process($element, $edit, &$form_state, $form) {
   $offset_field = 'offset';
   $offset_field2 = 'offset2';
 
-  if ($field['todate'] != 'required' && !empty($element['#default_value'][$to_field]) && $element['#default_value'][$to_field] == $element['#default_value'][$from_field]) {
+  if (!preg_match('/^required/',$field['todate']) && !empty($element['#default_value'][$to_field]) && $element['#default_value'][$to_field] == $element['#default_value'][$from_field]) {
     unset($element['#default_value'][$to_field]);
   }
 
@@ -318,9 +318,15 @@ function date_combo_process($element, $edit, &$form_state, $form) {
 
   if (!empty($field['todate'])) {
     $element['#date_float'] = TRUE;
-    $element[$from_field]['#title']  = t('From date');
     $element[$to_field] = $element[$from_field];
-    $element[$to_field]['#title'] = t('To date');
+    if (preg_match('/timeonly$/',$field['todate'])) {
+      $element[$from_field]['#title'] = t('Date and start time');
+      $element[$to_field]['#title'] = t('End time');
+      $element[$to_field]['#date_format'] = date_limit_format($element[$to_field]['#date_format'], array('hour','minute','second'));
+    } else {
+      $element[$from_field]['#title'] = t('From date');
+      $element[$to_field]['#title'] = t('To date');
+    }
     $element[$to_field]['#default_value'] = isset($element['#value'][$to_field]) ? $element['#value'][$to_field] : '';
     $element[$to_field]['#required'] = false;
     $element[$to_field]['#weight'] += .1;
@@ -430,29 +436,39 @@ function date_combo_validate($element, &$form_state) {
                 
     // Check todate input for blank values and substitute in fromdate
     // values where needed, then re-compute the todate with those values.
+    $timeonly = preg_match('/timeonly$/', $field['todate']);
     if ($field['todate']) {
       $merged_date = array();
       $to_date_empty = TRUE;
       foreach ($posted[$to_field] as $part => $value) {
-        $to_date_empty = $to_date_empty && empty($value);
-        $merged_date[$part] = empty($value) ? $posted[$from_field][$part] : $value;
-        if ($part == 'ampm' && $merged_date['ampm'] == 'pm' && $merged_date['hour'] < 12) {
-          $merged_date['hour'] += 12;
-        }
-        elseif ($part == 'ampm' && $merged_date['ampm'] == 'am' && $merged_date['hour'] == 12) {
-          $merged_date['hour'] -= 12;
+        if ($timeonly && ($part=='date' || $part=='day' || $part=='month' || $part=='year')) {
+          // force end date (not time) to match start date
+          // Note: $to_date_empty is deliberately only checked on the remaining time fields
+          $merged_date[$part] = $posted[$from_field][$part];
+        } else {
+          $to_date_empty = $to_date_empty && empty($value);
+          $merged_date[$part] = empty($value) ? $posted[$from_field][$part] : $value;
+          if ($part == 'ampm' && $merged_date['ampm'] == 'pm' && $merged_date['hour'] < 12) {
+            $merged_date['hour'] += 12;
+          }
+          elseif ($part == 'ampm' && $merged_date['ampm'] == 'am' && $merged_date['hour'] == 12) {
+            $merged_date['hour'] -= 12;
+          }
         }
       }
-              
+
       // If all date values were empty and a date is required, throw 
       // an error on the first element. We don't want to create 
       // duplicate messages on every date part, so the error will 
       // only go on the first.  
-      if ($to_date_empty && $field['todate'] == 'required') {
+      if ($to_date_empty && preg_match('/^required/',$field['todate'])) {
         $errors[] = t('Some value must be entered in the To date.');
       }
 
       $element[$to_field]['#value'] = $merged_date;
+      if ($timeonly) {
+        $element[$to_field]['#date_format'] = $element[$from_field]['#date_format'];
+      }
       
       // Call the right function to turn this altered user input into
       // a new value for the todate.
@@ -476,11 +492,20 @@ function date_combo_validate($element, &$form_state) {
       $item = date_element_empty($element, $form_state);
       $errors[] = t('The dates are invalid.');
     }
-    elseif (!empty($field['todate']) && $from_date > $to_date) {
+    elseif (!empty($field['todate']) && $from_date > $to_date && !$timeonly) {
       form_set_value($element[$to_field], $to_date, $form_state);
       $errors[] = t('The To date must be greater than the From date.');
     }
     else {
+      if ($timeonly) {
+        // In a time only To date mode, we have already forced the date part of
+        // to_date to be the same as from_date. Therefore, if from_date >
+        // to_date, this means that we have a post-midnight time, and want to
+        // increment to_date by one. This is done by setting a flag here, and
+        // doing the calculation later.
+        $inc_to_by_one_day = ($from_date > $to_date);
+      }
+
       // Convert input dates back to their UTC values and re-format to ISO
       // or UNIX instead of the DATETIME format used in element processing.
       $timezone = !empty($item[$tz_field]) ? $item[$tz_field] : $element['#date_timezone'];
@@ -491,6 +516,9 @@ function date_combo_validate($element, &$form_state) {
       $item[$offset_field] = date_offset_get($from_date);
       
       $to_date = date_make_date($to_date, $timezone);
+      if ($timeonly && $inc_to_by_one_day) {
+        $to_date->modify('+1 day');
+      }
       $test_from = date_format($from_date, 'r');
       $test_to = date_format($to_date, 'r');
       
diff --git date_popup/date_popup.module date_popup/date_popup.module
index 6591e81..a151c45 100644
--- date_popup/date_popup.module
+++ date_popup/date_popup.module
@@ -228,7 +228,6 @@ function date_popup_process_date(&$element, $edit = NULL, $date = NULL) {
   $date_granularity = array_intersect($granularity, array('month', 'day', 'year'));
   $time_granularity = array_intersect($granularity, array('hour', 'minute', 'second'));
   $date_format = (date_limit_format($element['#date_format'], $date_granularity));
-  if (empty($date_granularity)) return array();
   
   // The datepicker can't handle zero or negative values like 0:+1
   // even though the Date API can handle them, so rework the value
@@ -268,7 +267,7 @@ function date_popup_process_date(&$element, $edit = NULL, $date = NULL) {
   // Create a unique id for each set of custom settings.
   $id = date_popup_js_settings_id($element['#id'], 'datepicker', $settings);
   $sub_element = array(
-    '#type' => 'textfield',
+    '#type' => (empty($date_granularity) ? 'hidden' : 'textfield'),
     '#default_value' => (!empty($element['#value']['date']) || !empty($edit['date'])) && is_object($date) ? date_format_date($date, 'custom', $date_format) : '',
     '#id' => $id,    
     '#size' => !empty($element['#size']) ? $element['#size'] : 20,
@@ -620,4 +619,4 @@ EOM;
   $form['#suffix'] = t('<p>The Date Popup calendar includes some css for IE6 that breaks css validation. Since IE 6 is now superceded by IE 7 and IE 8, the special css for IE 6 has been removed from the regular css used by the Date Popup. If you find you need that css after all, you can add it back in your theme. Look at the way the Garland theme adds special IE-only css in in its page.tpl.php file. The css you need is:</p>') .'<blockquote><PRE>'. $css .'</PRE></blockquote>';
 
   return system_settings_form($form);
-}
\ No newline at end of file
+}
