From mpdonadio's tip above, I actually need to set the min attribute value on the HTML 5 date value. I came across this article, which includes code to remove the seconds from the time element.
Combining those two tips, the following code works to set the min attribute on the date field:
/**
* Implements hook_element_info_alter().
*/
function mymodule_element_info_alter(array &$types) {
// Alter dateteime fields
$types['datetime']['#process'][] = 'mymodule_datetime_set_startdate';
}
/**
* Custom function to alter HTML 5 date fields.
*/
function mymodule_datetime_set_startdate($element) {
if ($element['#parents'][0] == 'field_my_date_field') {
// get the start date config value
$config = \Drupal::config('mymodule.config');
$start_date = strtotime($config->get('mymodule_start_date'));
// For browsers using the HTML 5 date pop-up, set the min attribute value
$element['date']['#attributes']['min'] = format_date($start_date, 'html_date');
}
return $element;
}
This method works better in my case than a standard form alter because there is another date field in nested paragraphs that are tricky to form alter.
The min value seems to be picked up by the fallback jQuery UI datepicker too - it works in all the browsers I've checked (including IE 11).