diff -ruN date_orig/date/date.module date/date/date.module
--- date_orig/date/date.module	2010-08-12 21:19:50.000000000 +0100
+++ date/date/date.module	2010-08-16 10:54:16.000000000 +0100
@@ -105,7 +105,7 @@
   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 @@
   $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 -ruN date_orig/date/date_admin.inc date/date/date_admin.inc
--- date_orig/date/date_admin.inc	2010-04-13 14:50:40.000000000 +0100
+++ date/date/date_admin.inc	2010-08-16 10:54:16.000000000 +0100
@@ -241,6 +241,9 @@
       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 @@
     $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 @@
       );  
   }
   return $form;
-}
\ No newline at end of file
+}
diff -ruN date_orig/date/date_content_generate.inc date/date/date_content_generate.inc
--- date_orig/date/date_content_generate.inc	2008-12-03 15:44:27.000000000 +0000
+++ date/date/date_content_generate.inc	2010-08-16 10:54:16.000000000 +0100
@@ -29,9 +29,11 @@
   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 @@
     }
   }
   return $options;
-}
\ No newline at end of file
+}
diff -ruN date_orig/date/date_elements.inc date/date/date_elements.inc
--- date_orig/date/date_elements.inc	2010-08-12 22:31:07.000000000 +0100
+++ date/date/date_elements.inc	2010-08-16 10:54:17.000000000 +0100
@@ -28,7 +28,7 @@
           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 @@
   $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 @@
 
   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 @@
                 
     // 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 @@
       $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 @@
       $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 -ruN date_orig/date_popup/date_popup.module date/date_popup/date_popup.module
--- date_orig/date_popup/date_popup.module	2010-08-13 19:48:34.000000000 +0100
+++ date/date_popup/date_popup.module	2010-08-16 10:56:37.000000000 +0100
@@ -228,7 +228,6 @@
   $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 @@
   // 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 @@
   $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
+}
