Index: date/date.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/date/date/date.module,v
retrieving revision 1.61.2.4.2.74
diff -u -p -r1.61.2.4.2.74 date.module
--- date/date.module	29 Nov 2010 18:22:00 -0000	1.61.2.4.2.74
+++ date/date.module	7 Dec 2010 00:48:23 -0000
@@ -106,7 +106,7 @@ function date_content_is_empty($item, $f
   if (empty($item['value'])) {
     return TRUE;
   }
-  elseif($field['todate'] == 'required' && empty($item['value2'])) {
+  elseif(strpos($field['todate'], 'required') !== FALSE && empty($item['value2'])) {
     return TRUE;
   }
   return FALSE;
@@ -885,4 +885,4 @@ function date_field_all_day($field, $dat
   $date2 = date_format($date2, DATE_FORMAT_DATETIME);
   return date_is_all_day($date1, $date2, $granularity, $increment);
 
-}
\ No newline at end of file
+}
Index: date/date_admin.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/date/date/date_admin.inc,v
retrieving revision 1.40.2.3.2.41
diff -u -p -r1.40.2.3.2.41 date_admin.inc
--- date/date_admin.inc	6 Nov 2010 02:06:20 -0000	1.40.2.3.2.41
+++ date/date_admin.inc	7 Dec 2010 00:48:23 -0000
@@ -241,6 +241,9 @@ function _date_field_settings($op, $fiel
       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 (strpos($field['todate'], 'timeonly') !== FALSE && !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':
@@ -346,11 +349,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'] : '',
     );
@@ -715,4 +720,4 @@ function _date_formatter_settings($form_
       );  
   }
   return $form;
-}
\ No newline at end of file
+}
Index: date/date_content_generate.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/date/date/Attic/date_content_generate.inc,v
retrieving revision 1.1.2.2.2.12
diff -u -p -r1.1.2.2.2.12 date_content_generate.inc
--- date/date_content_generate.inc	3 Dec 2008 15:44:27 -0000	1.1.2.2.2.12
+++ date/date_content_generate.inc	7 Dec 2010 00:48:23 -0000
@@ -29,9 +29,11 @@ function _date_content_generate($node, $
   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
+	                                : (strpos($field['todate'], 'timeonly') !== FALSE ? 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
+}
Index: date/date_elements.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/date/date/date_elements.inc,v
retrieving revision 1.46.2.2.2.69
diff -u -p -r1.46.2.2.2.69 date_elements.inc
--- date/date_elements.inc	27 Nov 2010 12:48:51 -0000	1.46.2.2.2.69
+++ date/date_elements.inc	7 Dec 2010 00:48:23 -0000
@@ -28,7 +28,7 @@ function _date_field_validate($op, &$nod
           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']
+          && strpos($field['todate'], 'required') !== FALSE && ($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, $e
   $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 (strpos($field['todate'], 'required') === FALSE && !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, $e
 
   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 (strpos($field['todate'], 'timeonly') !== FALSE) {
+      $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, &
                 
     // Check todate input for blank values and substitute in fromdate
     // values where needed, then re-compute the todate with those values.
+    $timeonly = strpos($field['todate'], 'timeonly') !== FALSE;
     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 && strpos($field['todate'], 'required') !== FALSE) {
         $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, &
       $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, &
       $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');
       
Index: date_popup/date_popup.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/date/date_popup/date_popup.module,v
retrieving revision 1.42.2.1.2.65
diff -u -p -r1.42.2.1.2.65 date_popup.module
--- date_popup/date_popup.module	20 Nov 2010 11:33:30 -0000	1.42.2.1.2.65
+++ date_popup/date_popup.module	7 Dec 2010 00:48:23 -0000
@@ -259,7 +259,6 @@ function date_popup_process_date(&$eleme
   $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
@@ -296,7 +295,7 @@ function date_popup_process_date(&$eleme
   // 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,
