Date and time change listener in Android?

In my application, there's a alarm service, and I find that if user change it's date or time to a passed time. My alarm will not be triggered at the time I expect.

So, I may have to reset all the alarms again. Is there an date and time change listener in android?


Create an intent filter :

static {
    s_intentFilter = new IntentFilter();
    s_intentFilter.addAction(Intent.ACTION_TIME_TICK);
    s_intentFilter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
    s_intentFilter.addAction(Intent.ACTION_TIME_CHANGED);
}

and a broadcast receiver:

private final BroadcastReceiver m_timeChangedReceiver = new BroadcastReceiver() {                                                                                             
    @Override
    public void onReceive(Context context, Intent intent) {
        final String action = intent.getAction();

        if (action.equals(Intent.ACTION_TIME_CHANGED) ||
                    action.equals(Intent.ACTION_TIMEZONE_CHANGED)) {
            doWorkSon();
        }
    }
};

register the receiver:

public void onCreate() {
    super.onCreate();
    registerReceiver(m_timeChangedReceiver, s_intentFilter);     
}

EDIT:

and unregister it:

public void onDestroy() {
    super.onDestroy();
    unregisterReceiver(m_timeChangedReceiver);     
}

In addition to the accepted answer

If you want to listen to time changes while your app is not running I would register in the manifest:

<receiver android:name="com.your.pacakge.TimeChangeBroadcastReceiver">
    <intent-filter>
        <action android:name="android.intent.action.TIME_SET"/>
        <action android:name="android.intent.action.TIMEZONE_CHANGED"/>
    </intent-filter>
</receiver>

If you do this, do not explicitly register the receiver in the code with registerReceiver and unregisterReceiver.

Again, this is just an addition to the accepted answer.


In order to detect a change in date, you need to register to these actions:

  • Intent.ACTION_TIME_CHANGED - "The time was set."
  • Intent.ACTION_DATE_CHANGED - "The date has changed."
  • Intent.ACTION_TIMEZONE_CHANGED - "The timezone has changed"

Here's one solution that I wrote, so all you have to do is to extend the class, and register&unregister from it on the Activity/Fragment :

abstract class DateChangedBroadcastReceiver : BroadcastReceiver() {
    private var curDate = LocalDate.now()

    /**called when the receiver detected the date has changed. You should still check it yourself, because you might already be synced with the new date*/
    abstract fun onDateChanged(previousDate: LocalDate, newDate: LocalDate)

    @Suppress("MemberVisibilityCanBePrivate")
    fun register(context: Context, date: LocalDate) {
        curDate = date
        val filter = IntentFilter()
        filter.addAction(Intent.ACTION_TIME_CHANGED)
        filter.addAction(Intent.ACTION_DATE_CHANGED)
        filter.addAction(Intent.ACTION_TIMEZONE_CHANGED)
        context.registerReceiver(this, filter)
        val newDate = LocalDate.now()
        if (newDate != curDate) {
            curDate = newDate
            onDateChanged(date, newDate)
        }
    }

    /**a convenient way to auto-unregister when activity/fragment has stopped. This should be called on the onResume method of the fragment/activity*/
    fun registerOnResume(activity: AppCompatActivity, date: LocalDate, fragment: androidx.fragment.app.Fragment? = null) {
        register(activity, date)
        val lifecycle = fragment?.lifecycle ?: activity.lifecycle
        lifecycle.addObserver(object : LifecycleObserver {
            @Suppress("unused")
            @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
            fun onPause() {
//                Log.d("AppLog", "onPause, so unregistering")
                lifecycle.removeObserver(this)
                activity.unregisterReceiver(this@DateChangedBroadcastReceiver)
            }
        })
    }

    override fun onReceive(context: Context, intent: Intent) {
        val newDate = LocalDate.now()
//        Log.d("AppLog", "got intent:" + intent.action + " curDate:" + curDate + " newDate:" + newDate)
        if (newDate != curDate) {
//            Log.d("AppLog", "cur date is different, so posting event")
            val previousDate = curDate
            curDate = newDate
            onDateChanged(previousDate, newDate)
        }
    }

}

If you can't use LocalDate (as it uses relatively new API : 26 , which is currently used on about 21% of devices), you can use this instead:

abstract class DateChangedBroadcastReceiver : BroadcastReceiver() {
    private var curDate = Calendar.getInstance()

    /**called when the receiver detected the date has changed. You should still check it yourself, because you might already be synced with the new date*/
    abstract fun onDateChanged(previousDate: Calendar, newDate: Calendar)

    companion object {
        fun toString(cal: Calendar): String {
            return "${cal.get(Calendar.YEAR)}-${cal.get(Calendar.MONTH)}-${cal.get(Calendar.DAY_OF_MONTH)}"
        }

        fun resetDate(date: Calendar) {
            date.set(Calendar.HOUR_OF_DAY, 0)
            date.set(Calendar.MINUTE, 0)
            date.set(Calendar.SECOND, 0)
            date.set(Calendar.MILLISECOND, 0)
        }

        fun areOfSameDate(date: Calendar, otherDate: Calendar) =
            date.get(Calendar.DAY_OF_YEAR) == otherDate.get(Calendar.DAY_OF_YEAR) &&
                    date.get(Calendar.YEAR) == otherDate.get(Calendar.YEAR)
    }

    @Suppress("MemberVisibilityCanBePrivate")
    fun register(context: Context, date: Calendar) {
        curDate = date.clone() as Calendar
        resetDate(curDate)
        val filter = IntentFilter()
        filter.addAction(Intent.ACTION_TIME_CHANGED)
        filter.addAction(Intent.ACTION_DATE_CHANGED)
        filter.addAction(Intent.ACTION_TIMEZONE_CHANGED)
        context.registerReceiver(this, filter)
        val newDate = Calendar.getInstance()
        resetDate(newDate)
        if (!areOfSameDate(newDate, curDate)) {
            val previousDate = curDate.clone() as Calendar
            curDate = newDate
            onDateChanged(previousDate, curDate)
        }
    }

    /**a convenient way to auto-unregister when activity/fragment has stopped. This should be called on the onResume method of the fragment/activity*/
    fun registerOnResume(activity: AppCompatActivity, date: Calendar, fragment: Fragment? = null) {
        register(activity as Context, date)
        val lifecycle = fragment?.lifecycle ?: activity.lifecycle
        lifecycle.addObserver(object : LifecycleObserver {
            @Suppress("unused")
            @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
            fun onPause() {
//                Log.d("AppLog", "onPause, so unregistering")
                lifecycle.removeObserver(this)
                activity.unregisterReceiver(this@DateChangedBroadcastReceiver)
            }
        })
    }

    override fun onReceive(context: Context, intent: Intent) {
        val newDate = Calendar.getInstance()
        resetDate(newDate)
//        Log.d("AppLog", "got intent:${intent.action} curDate:${toString(curDate)} newDate:${toString(newDate)}")
        if (!areOfSameDate(newDate, curDate)) {
//            Log.d("AppLog", "cur date is different, so posting event")
            val previousDate = curDate.clone() as Calendar
            curDate = newDate
            onDateChanged(previousDate, newDate)
        }
    }

}

Example usage:

class MainActivity : AppCompatActivity() {
    var curDate = Calendar.getInstance()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

    override fun onResume() {
        super.onResume()
        object : DateChangedBroadcastReceiver() {
            override fun onDateChanged(previousDate: Calendar, newDate: Calendar) {
                Log.d("AppLog", "MainActivity: ${DateChangedBroadcastReceiver.toString(previousDate)} -> ${DateChangedBroadcastReceiver.toString(newDate)}")
                curDate = newDate.clone() as Calendar
                //TODO handle date change
            }
        }.registerOnResume(this, curDate)
    }
}