
A participant opens a daily challenge in Shanghai. Another opens the same challenge in New York. Their devices can show different dates while referring to the same instant. Which day receives the check-in?
Define the challenge's day before formatting the calendar. The rule needs an authoritative clock, a day boundary and an eligibility window. The interface can display that rule in the participant's timezone, but changing the device timezone should not create another eligible day.
ChainPunch offers a useful concrete case. Its current contract divides time into UTC days. It does not store an IANA timezone or implement an arbitrary local-midnight schedule.
Read the contract's definition of a day
The contract normalizes a requested start date by rounding its Unix timestamp down to a multiple of 86,400 seconds. It then checks that the normalized start is still in the future. A date that looked acceptable before normalization can therefore be rejected after it is rounded down.
During check-in, the contract uses block.timestamp to decide eligibility. It calculates the time within the UTC day and the day index relative to the normalized start.
seconds_into_day = block_timestamp % 86400
challenge_day = (block_timestamp - normalized_start) // 86400
These expressions describe the current contract. They do not convert to the participant's timezone. A client should render the corresponding opening and closing instants rather than reinterpret the stored numbers as local hours.
The activity interval includes its start and excludes its overall end. Within an active day, the check-in window includes both configured endpoints. The contract also requires the daily start to be earlier than the daily end, so an overnight window cannot be entered directly as 22:00 to 02:00.
Put exact endpoints in the test sheet
For an illustrative daily window from 08:00 to 20:00 UTC, test the second before each boundary as well as the boundary itself. The following results assume an active challenge, an eligible participant and no check-in already recorded for that day.
| Authoritative time | Expected daily-window result |
|---|---|
| 07:59:59 UTC | Reject |
| 08:00:00 UTC | Accept |
| 20:00:00 UTC | Accept |
| 20:00:01 UTC | Reject |
| The exact overall challenge end | Reject even if a daily window would otherwise allow it |
The inclusive daily closing second is an implementation detail worth preserving in the UI and tests. A display that promises “before 20:00” would describe a different rule.
A submitted transaction also needs to be included in a block. The time a person clicks the button does not establish that the contract will accept it. Show the authoritative window and confirmation state, and allow users enough time to submit before closing.

This illustration uses the offset in effect in New York on 27 October 2026. That offset changes with daylight saving time.
Decide whether UTC days fit the activity
A global challenge can intentionally use a shared UTC schedule. Participants then see one common opening instant in their own local time. That is a straightforward fit for the current ChainPunch model.
A challenge that promises “check in once on each New York calendar day” has a different requirement. A local day can span 23 or 25 elapsed hours around daylight-saving transitions. Advancing the previous opening instant by 86,400 seconds does not always reach the next local midnight.
For that design, keep the named timezone and the intended local date, and resolve the next local boundary using timezone rules. Define how to handle a window that touches an ambiguous or nonexistent local time. Store the resolved instants and the rule version needed to explain historical eligibility. Python's zoneinfo documentation describes IANA timezone support and ambiguous-time handling.
Adding such a schedule to a contract-based product needs a separate design. It raises questions about who supplies the boundaries, how updates are accepted and how participants verify them. Changing a frontend date label cannot add that behavior to the current contract.
Treat make-up check-ins as a separate action
ChainPunch's make-up function accepts a previous challenge-day index. It rejects today and future days, a day already recorded and a request beyond the task's make-up allowance. The task must still be active. Its make-up path does not repeat the normal daily-window check.
The interface should therefore present an explicit missed day and the applicable rule. Sending a make-up request as though it were an ordinary late check-in hides a distinction the contract already makes. This article focuses on time and eligibility; settlement and reward calculations belong in a separate review.
Reuse the boundary vectors
Download the time-window exercise and test sheet. The small Python exercise mirrors the contract's time arithmetic and checks the opening second, closing second, challenge end, day rollover and duplicate-day rejection. It also includes separate timezone examples showing why a local calendar day can differ from 24 elapsed hours.
The arithmetic tests are teaching examples, not a rerun of the Solidity integration suite. The article was checked against the current contract source. ChainPunch's existing product demonstration uses a local test chain and test CIT, with chain time advanced during testing.
Explore ChainPunch if you are planning a community challenge or learning program. Bring the intended timezone, daily window, late-entry policy and make-up rules. Those decisions make a useful acceptance test before the calendar interface is built.