How can I override date field validation?
You can achieve this by prepending your own custom #element_validate function before Datelist::validateDatelist().

As in your question, you have to add an after_build function to access the individual parts (year, month, day) of the form element:

function MYMODULE_form_MYFORM_alter(&amp;$form, FormStateInterface $form_state, $form_id) {
  $form['#after_build'][] = 'MYMODULE_date_remove_day_after_build';
}


You can make the form element disappear by setting '#access' to FALSE.
Then prepend your custom #element_validate function to the array #element_validate on the element.

function MYMODULE_date_remove_day_after_build($form, &amp;$form_state) {
  $form['field_MYFIELD']['widget'][0]['value']['day']['#access'] = FALSE;
  array_unshift($form['field_MYFIELD']['widget'][0]['value']['#element_validate'], 'mymodule_date_set_day');
  return $form;
}


Then in your custom element validate function, unset everything if Year or Month are unset, otherwise, set the day.  Notice you have to create the object before passing it to Datelist::validateDatelist().

use Drupal\Core\Form\FormStateInterface;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Datetime\DrupalDateTime;

function mymodule_date_set_day(&amp;$element, FormStateInterface $form_state, &amp;$complete_form) {

  $input = NestedArray::getValue($form_state-&gt;getValues(), $element['#parents']);

  if (empty($input['year']) || empty($input['month'])) {
    $form_state-&gt;setValueForElement($element, []);
  }
  else {
    $input['day'] = "15";
    $timezone = !empty($element['#date_timezone']) ? $element['#date_timezone'] : NULL;
    $input['object'] = DrupalDateTime::createFromArray($input, $timezone);
    $form_state-&gt;setValueForElement($element, $input);
  }
}