diff -ruN date_org/date/date.module date_new_2/date/date.module
--- date_org/date/date.module	2009-06-09 18:20:31.000000000 +0100
+++ date_new_2/date/date.module	2009-08-20 13:36:01.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;
@@ -840,4 +840,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_org/date/date_admin.inc date_new_2/date/date_admin.inc
--- date_org/date/date_admin.inc	2009-06-08 17:49:36.000000000 +0100
+++ date_new_2/date/date_admin.inc	2009-08-20 16:26:46.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_org/date/date_content_generate.inc date_new_2/date/date_content_generate.inc
--- date_org/date/date_content_generate.inc	2008-12-03 15:44:27.000000000 +0000
+++ date_new_2/date/date_content_generate.inc	2009-08-20 19:34:30.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_org/date/date_elements.inc date_new_2/date/date_elements.inc
--- date_org/date/date_elements.inc	2009-07-24 19:48:14.000000000 +0100
+++ date_new_2/date/date_elements.inc	2009-08-20 19:18:53.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']))));
@@ -273,7 +273,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]);
   }
 
@@ -323,11 +323,17 @@
 
   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]['#required'] = preg_match('/^required/',$field['todate']);
     $element[$to_field]['#weight'] += .1;
     if ($field['widget']['type'] == 'date_select') {
       $description .= ' '. t("Empty 'To date' values will use the 'From date' values.");
@@ -435,17 +441,24 @@
                 
     // 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;
+          }
         }
       }
               
@@ -453,7 +466,7 @@
       // 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.');
       }
 
@@ -481,11 +494,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'];
@@ -496,6 +518,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');
       
