diff --git a/scheduler.module b/scheduler.module
index 30c8034..dcbb7ae 100644
--- a/scheduler.module
+++ b/scheduler.module
@@ -16,6 +16,10 @@ define('SCHEDULER_TIME_ONLY_FORMAT', 'H:i:s');
 // The default time that will be used, until Admin sets a different value.
 define('SCHEDULER_DEFAULT_TIME', '00:00:00');
 
+// The full set of date and time letters allowed in the scheduler date format.
+define('SCHEDULER_DATE_LETTERS', 'djmnyY');
+define('SCHEDULER_TIME_LETTERS', 'hHgGisaA');
+
 /**
  * Implements hook_permission().
  */
@@ -177,7 +181,11 @@ function scheduler_admin() {
     '#maxlength' => 20,
     '#required' => TRUE,
     '#field_suffix' => ' <small>' . $now . '</small>',
-    '#description' => t('The format for entering scheduled dates and times. For the date use the letters djmnyY and for the time use hHgGisaA. See !url for more details.', array('!url' => l(t('the PHP date() function'), 'http://www.php.net/manual/en/function.date.php'))),
+    '#description' => t('The format for entering scheduled dates and times. For the date use the letters !date_letters and for the time use !time_letters. See !url for more details.', array(
+      '!date_letters' => SCHEDULER_DATE_LETTERS,
+      '!time_letters' => SCHEDULER_TIME_LETTERS,
+      '!url' => l(t('the PHP date() function'), 'http://www.php.net/manual/en/function.date.php')
+    )),
   );
 
   $form['scheduler_field_type'] = array(
@@ -264,9 +272,20 @@ function scheduler_admin_validate($form, &$form_state) {
   // single plain space.
   $form_state['values']['scheduler_date_format'] = trim(preg_replace('/\s+/', ' ', $form_state['values']['scheduler_date_format']));
 
+  // Validate the letters used in the scheduler date format. All punctuation is
+  // accepted, so remove everything except word characters then check that there
+  // is nothing else which is not in the list of acceptable date/time letters.
+  $no_punctuation = preg_replace("/[^\w+]/", "", $form_state['values']['scheduler_date_format']);
+  if (preg_match_all("/[^" . SCHEDULER_DATE_LETTERS . SCHEDULER_TIME_LETTERS . "]/", $no_punctuation, $extra)) {
+    form_set_error('scheduler_date_format', t('You may only use the letters $date_letters for the date and $time_letters for the time. Remove the extra characters $extra', array(
+      '$date_letters' => SCHEDULER_DATE_LETTERS,
+      '$time_letters' => SCHEDULER_TIME_LETTERS,
+      '$extra' => implode(' ', $extra[0]),
+    )));
+  };
+
   if ($form_state['values']['scheduler_field_type'] == 'date_popup') {
-    $format = $form_state['values']['scheduler_date_format'];
-    $time_format = date_limit_format($format, array('hour', 'minute', 'second'));
+    $time_format = date_limit_format($form_state['values']['scheduler_date_format'], array('hour', 'minute', 'second'));
     // The Date Popup function date_popup_time_formats() only returns the values
     // 'H:i:s' and 'h:i:sA' but Scheduler can accept more variations than just
     // these. Firstly, we add the lowercase 'a' alternative. Secondly timepicker
@@ -312,15 +331,13 @@ function scheduler_admin_submit($form, &$form_state) {
   // default time functionality. Assume the date and time time parts begin and
   // end with a letter, but any punctuation between these will be retained.
   $format = $form_state['values']['scheduler_date_format'];
-  $time_letters = 'gGhHisaA';
-  $time_start = strcspn($format, $time_letters);
-  $time_length = strlen($format) - strcspn(strrev($format), $time_letters) - $time_start;
+  $time_start = strcspn($format, SCHEDULER_TIME_LETTERS);
+  $time_length = strlen($format) - strcspn(strrev($format), SCHEDULER_TIME_LETTERS) - $time_start;
   $time_only_format = substr($format, $time_start, $time_length);
   variable_set('scheduler_time_only_format', $time_only_format);
 
-  $date_letters = 'djFmMnyY';
-  $date_start = strcspn($format, $date_letters);
-  $date_length = strlen($format) - strcspn(strrev($format), $date_letters) - $date_start;
+  $date_start = strcspn($format, SCHEDULER_DATE_LETTERS);
+  $date_length = strlen($format) - strcspn(strrev($format), SCHEDULER_DATE_LETTERS) - $date_start;
   $date_only_format = substr($format, $date_start, $date_length);
   variable_set('scheduler_date_only_format', $date_only_format);
 
@@ -931,21 +948,43 @@ function _scheduler_strtotime($str) {
 /**
  * Parse a time/date as UTC time.
  *
- * @see date()
+ * The php function strptime() has a limited life, due to it returning varying
+ * results on different operating systems. It is not supported on Windows
+ * platforms at all. The replacement function date_parse_from_format() is not
+ * as flexible as strptime(), for example it forces two-digit minutes and
+ * seconds. _scheduler_strptime() gives us more control over the entities that
+ * are parsed and how the matching is achieved.
  *
  * @param string $date
  *   The string to parse.
  * @param string $format
- *  The date format used in $date. For details on the date format options, see
- *  the PHP date() function. Right now only dHhmiaAsyY are supported.
+ *   The date format used in $date. For details on the date format options, see
+ *   the PHP date() function.
  *
  * @return int
- *  The parsed time as a UTC timestamp.
+ *   The parsed time converted to a UTC timestamp using mktime().
  */
 function _scheduler_strptime($date, $format) {
-  // Build a regex pattern for the date format.
-  $date_entities = array('d', 'H', 'h', 'm', 'i', 'a', 'A', 's', 'y', 'Y', 'n', 'j', 'g', 'G');
-  $date_regex_replacements = array('(\d{2})', '(\d{2})', '(\d{2})', '(\d{2})', '(\d{2})', '([ap]m)', '([AP]M)', '(\d{2})', '(\d{2})', '(\d{4})', '(\d{1,2})', '(\d{1,2})', '(\d{1,2})', '(\d{1,2})');
+  // Build a regex pattern for the date format. Include one entry for each
+  // letter in SCHEDULER_DATE_LETTERS and SCHEDULER_TIME_LETTERS.
+  $date_entities_and_replacements = array(
+    'd' => '(\d{2})',      /* day of the month with leading zero */
+    'H' => '(\d{2})',      /* hours in 24-hour format with leading zero */
+    'h' => '(\d{2})',      /* hours in 12-hour format with leading zero */
+    'm' => '(\d{2})',      /* month number with leading zero */
+    'i' => '(\d{2})',      /* minutes */
+    'a' => '([ap]m)',      /* lower case meridian */
+    'A' => '([AP]M)',      /* upper case meridian */
+    's' => '(\d{2})',      /* seconds */
+    'y' => '(\d{2})',      /* two-digit year */
+    'Y' => '(\d{4})',      /* four-digit year */
+    'n' => '(\d{1,2})',    /* month number without leading zero */
+    'j' => '(\d{1,2})',    /* day of the month without leading zero */
+    'g' => '(\d{1,2})',    /* hours in 12-hour format without leading zero */
+    'G' => '(\d{1,2})',    /* hours in 24-hour format without leading zero */
+  );
+  $date_entities = array_keys($date_entities_and_replacements);
+  $date_regex_replacements = array_values($date_entities_and_replacements);
   $custom_pattern = str_replace($date_entities, $date_regex_replacements, $format);
   if (!preg_match("#$custom_pattern#", $date, $value_matches)) {
     return FALSE;
