Here is code from 4.0.3_r1 in frameworks/base/services/java/android/server/AlarmManagerService.java.
First, we create a PendingIntent mDateChangeSender;
private final PendingIntent mDateChangeSender;
Then, in the constructor of AlarmManagerService.java, we setup the PendingIntent:
Intent intent = new Intent(Intent.ACTION_DATE_CHANGED);
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
mDateChangeSender = PendingIntent.getBroadcast(context, 0, intent, 0);
Then later in the constructor:
mClockReceiver.scheduleDateChangedEvent();
So what is mClockReceiver? Just a BroadcastReceiver listening for Intent.ACTION_TIME_TICK and Intent.ACTION_DATE_CHANGED. In it's onReceive():
...
else if (intent.getAction().equals(Intent.ACTION_DATE_CHANGED)) {
...
scheduleDateChangedEvent();
}
Then, later we find the method scheduleDateChangedEvent():
public void scheduleDateChangedEvent() {
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(System.currentTimeMillis());
calendar.set(Calendar.HOUR, 0);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
calendar.add(Calendar.DAY_OF_MONTH, 1);
set(AlarmManager.RTC, calendar.getTimeInMillis(), mDateChangeSender);
}
So it sets a one-shot alarm, starting with the current time, then setting hour/min/sec/milli to zero, then adding a day, so if it was 1:30pm today, the next time it will get fired would be in 10 hours and 30 minutes.
This isn't to say there aren't bugs or anything here, but it LOOKS like ACTION_DATE_CHANGED should fire at midnight every day.
NOW - if I were to change the date on the phone lets say 10 years into the future. The code to handle the change in time will fire the first ACTION_DATE_CHANGED event then schedule a new ACTION_DATE_CHANGED to get fired, at 10 years + some fraction of a day. Then if we change the date back 10 years, to the correct date, the alarm is still scheduled to be fired in 10 years, thuse ACTION_DATE_CHANGED will no longer get fired (unless you set the date further than 10 years from now - try it!).
tl;dr: This is a bug in Android.