Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Scan for outdated or missing drivers - takes under a minute3Clear out junk files and repair common Windows errorsUse a weekday bitmask to select the day a block starts, treat an overnight interval as continuing into the next calendar day, and have a collected Kotlin Flow wait for the next boundary before emitting updated state. That Flow is suitable while the app process is active; it is not a system alarm and cannot guarantee work after the process stops.
Define the schedule rules before writing code
A bitmask is only meaningful when its weekday mapping and interval policy are stable. The example below assigns Monday to bit 0 through Sunday to bit 6. A set bit means a block starts on that weekday. Persist that mapping as part of the schedule format; changing it later can reinterpret saved masks.
As an Amazon Associate I earn from qualifying purchases.
This implementation uses a half-open interval: the start is included and the end is excluded. Equal start and end times mean an empty interval, not a full-day block. If a product needs different behavior, change the policy deliberately and test it rather than relying on accidental time comparisons.
Windows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallOutdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchRepresent weekdays with a bitmask
import java.time.DayOfWeek
import java.time.LocalTime
import java.time.ZonedDateTime
private fun DayOfWeek.bit(): Int = 1 shl (value - 1)
data class DailyBlock(
val daysMask: Int,
val start: LocalTime,
val end: LocalTime,
) {
fun startsOn(day: DayOfWeek): Boolean =
(daysMask and day.bit()) != 0
}
DayOfWeek.value numbers Monday as 1 and Sunday as 7, so this mapping produces Monday = 1, Tuesday = 2, and Sunday = 64. For example, a Monday-and-Friday mask is 1 or 16, or 17. The platform does not prescribe a weekday bit assignment or persistence format; this is an application choice that should be documented and covered by serialization tests.
#1 Best Overall
Evaluate regular and overnight windows
For a same-day window, the current weekday’s bit controls whether the interval is active. For an overnight window, its early-morning portion belongs to the previous calendar day’s selected start. For example, a Monday 22:00–02:00 block is active Monday at 23:00 and Tuesday at 01:00, but not Tuesday at 22:00 unless Tuesday is also selected.
fun DailyBlock.isActiveAt(now: ZonedDateTime): Boolean {
if (start == end) return false
val time = now.toLocalTime()
return if (start < end) {
startsOn(now.dayOfWeek) && time >= start && time < end
} else {
val selectedStartDay =
(time >= start && startsOn(now.dayOfWeek)) ||
(time < end && startsOn(now.minusDays(1).dayOfWeek))
selectedStartDay
}
}
The comparisons implement the stated inclusive-start, exclusive-end rule: a block is active exactly at its start and inactive exactly at its end. The code uses the supplied ZonedDateTime‘s local date and time. It does not define product policy for timezone changes or daylight-saving gaps and overlaps; decide whether a schedule follows the device’s current zone or a fixed zone, then test those transitions explicitly.
Rank #2
Find the next schedule boundary
A boundary-driven Flow needs the next start or end instant, not a loop that polls every second. Construct candidate boundaries from selected start days, retain only future instants, and take the earliest one. Passing a zone-aware time avoids treating a local clock time as a universal instant.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
import java.time.Instant
import java.time.temporal.ChronoUnit
fun DailyBlock.nextBoundaryAfter(now: ZonedDateTime): Instant? {
if (start == end || daysMask == 0) return null
return (0L..8L).asSequence()
.map { offset -> now.toLocalDate().plusDays(offset) }
.filter { date -> startsOn(date.dayOfWeek) }
.flatMap { date ->
sequenceOf(
date.atTime(start).atZone(now.zone).toInstant(),
date.atTime(end).let { endDateTime ->
if (start > end) endDateTime.plusDays(1) else endDateTime
}.atZone(now.zone).toInstant(),
)
}
.filter { it > now.toInstant() }
.minOrNull()
}
The search spans today through eight days ahead so that a selected weekday can be found even when the current day is just past its boundary. Each enabled start day contributes a start and an end instant; for an overnight window, the end is placed on the following date. If no day is enabled, or the interval is empty under this policy, there is no boundary.
Rank #3
Local-time conversion across daylight-saving transitions requires a product decision: a local time can be skipped or occur twice. Java’s zone rules resolve such conversions, but the resolution may not match the intended blocking policy. Add explicit tests for the zones and transition behavior the app supports.
Emit state and wait until the next boundary
Kotlin Flow is an asynchronous stream. A cold Flow begins its upstream work when collected, and each collector runs a separate upstream instance. This pattern emits the current state, computes the next boundary, and suspends with a cancellable delay before reevaluating.
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import java.time.Duration
import java.time.ZonedDateTime
fun DailyBlock.activeFlow(
clock: () -> ZonedDateTime,
): Flow<Boolean> = flow {
while (true) {
val now = clock()
emit(isActiveAt(now))
val next = nextBoundaryAfter(now) ?: return@flow
val millis = Duration.between(now.toInstant(), next).toMillis()
delay(millis.coerceAtLeast(1L))
}
}
Provide a clock that reads the current zoned wall time on each iteration, rather than capturing one timestamp when constructing the Flow. The delay is an in-process wait: Kotlin documents that delay waits at least the requested duration without blocking a thread and is cancellable. It may resume later than the target, so recomputing the state after wake-up is essential. Collect in a lifecycle-appropriate scope; cancellation of that collection stops the timer.
Free tools Windows power users keep installed
One-click scans. No signup required.
This simple loop does not itself react immediately to a manual clock or timezone change while suspended. If those changes must take effect promptly, observe the relevant system change in the owner and cancel/restart the collection, or use a scheduling mechanism designed for the required lifetime and behavior.
Best Value
Choose an Android timing mechanism by required lifetime
A Flow with delay is appropriate when the app needs to refresh an in-memory state while its process is running. It should not be used as a promise to execute while the app is stopped or the device is asleep. Android describes AlarmManager alarms as time-based operations outside the lifetime of an application.
| Approach | Process must remain alive? | Device wake behavior | Precision and ownership | Restart and time semantics |
|---|---|---|---|---|
Collected Flow with delay |
Yes; collection and process must remain active. | Does not independently wake a sleeping device. | Convenient for active-app state; cancellable with its coroutine scope and may run late. | Recreate collection after restart. Base the calculation on wall-clock time for time-of-day schedules. |
| Handler timing APIs | Yes; intended for work during app lifetime. | Not a substitute for an alarm that must wake the device. | Useful for in-process scheduling, with lifecycle ownership handled by the app. | App must restore its own in-process work; choose timing semantics appropriate to the task. |
| AlarmManager | No; alarms operate outside app lifetime. | Alarm guidance covers operation while the device sleeps; select the alarm behavior that fits the requirement. | Prefer inexact alarms for most cases. Exact alarms are for genuinely precise, user-facing behavior and have access requirements and battery trade-offs. | Alarms are cleared on reboot and must be restored. Use RTC for a particular wall-clock time or elapsed-time alarms for a duration. |
See Android’s alarm scheduling guidance and AlarmManager reference for current constraints, exact-alarm access, and alarm types. Do not request exact-alarm access merely to make a foreground Flow appear more punctual.
Handle reboot and clock meaning deliberately
Alarms do not survive device reboot, so an app that depends on a persistent schedule must restore or reschedule them after boot according to its permissions and product requirements. The relevant Android guidance is in the AlarmManager reference.
Use wall-clock/RTC semantics when the event means “at 9:00 AM,” because the target is a civil time. Use elapsed time when the requirement means “after 30 minutes.” Kotlin’s time-measurement guidance notes that Android’s System.nanoTime() does not count deep-sleep time, so a monotonic duration measurement and a wall-clock appointment are not interchangeable.
Quick Recap
Test the boundaries and policy
- Verify each weekday maps to exactly one stable bit, including Monday and Sunday, and round-trip persisted masks.
- Test a same-day block before its start, exactly at start, just before end, exactly at end, and after end.
- Test an overnight block on its selected start day and the next morning, including the exclusive end instant.
- Test unselected weekdays, an empty mask, and equal start/end times according to the chosen policy.
- Test next-boundary selection when the current time is just before and just after a boundary, and when the next enabled day is a week away.
- Test Flow cancellation and verify a collected flow emits a fresh state after waking late.
- For supported zones, test daylight-saving gaps, repeated local times, timezone changes, device clock changes, and restoration after reboot.
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

