LVGL Calendar Selection: Keep Displayed Dates Separate from Device Clock Changes

Keep the month being browsed, the date being edited and the committed device value in separate application fields. An LVGL calendar displays and reports dates; browsing a month or marking “today” must not itself change the device clock. Validate a complete year-month-day snapshot, then submit it only through an explicit Apply action.

Applicability: LVGL v9.3.0 with the Gregorian calendar widget, serialized UI updates and an application-owned date editor. The hypothetical product permits years 2000 through 2199. This is an illustrative policy range, not a universal RTC limit. A mock commit service is proposed; no system clock, hardware RTC or persistent schedule was changed or tested.

Three independent date-model fields, valid and invalid Gregorian fixtures, and the explicit one-request Apply boundary.
Original hypothetical editor model and validation vectors. No clock write, RTC behavior or executed UI test is claimed.

1. Name the four different dates

Use four model concepts: authoritative date, pending candidate, displayed year/month and today’s reference date. The authoritative date comes from the selected backend. The pending candidate is an optional complete date. The displayed month controls navigation. Today’s reference is presentation supplied by the application.

The calendar reference exposes separate operations for today’s date, the shown month and highlighted dates. In the tagged setters, showing a month stores day one in the shown-date record; it does not represent a user choosing that first day. Setting today updates calendar presentation, not an operating-system clock.

On editor entry, read one backend snapshot and initialize the pending value according to product policy. Display its month, but retain its full day separately. Show a textual pending-date preview so users can still see the candidate while browsing another month.

2. Read a user selection through the date API

For an eligible day-selection LV_EVENT_VALUE_CHANGED, call lv_calendar_get_pressed_date into application-owned storage and check its result. Resolve the event source deliberately: a composite calendar can also contain header controls. Do not interpret every value-change notification anywhere in that subtree as a date selection.

The header specifies an OK/INVALID result. The exact getter returns INVALID when there is no selected button and writes a zero date in that case. With a selected button, it parses its text and combines the day with the shown year/month. It is not a general validation service for arbitrary programmatic button selections.

The normal calendar construction disables weekday-name and adjacent-month cells. Preserve those controls, use the ordinary day-selection path, and independently validate the resulting date. Do not extract an integer from a caption and assume it belongs to the adjacent month printed on the grid.

Copy the full date immediately into the pending model after validation. Never store only “29” and later combine it with whichever month happens to be displayed when Apply is pressed. That turns innocent browsing into an unintended date change.

3. Validate before narrowing or calling setters

The public date structure uses a 16-bit year and signed 8-bit month/day fields, while several setters accept wider unsigned parameters. Validate external integers before narrowing them into that structure. A converted overflow is no longer the original input you intended to check.

Apply this order: permitted product year, month 1..12, then day 1..days_in_month(year,month). April, June, September and November have 30 days; February has 28 or 29; the remaining months have 31. Avoid indexing a month-length array until the month itself has passed its range check.

The U.S. Naval Observatory’s Gregorian rule makes a year leap when divisible by four, except century years must also be divisible by 400. The tagged calendar implementation follows that rule. A test using only 2024 cannot reveal the century exception.

Candidate Expected date validation
2000-02-29 Accept
2024-02-29 Accept
2025-02-29 Reject: February has 28 days
2100-02-29 Reject: century exception
2026-04-31 Reject: April has 30 days
1999-12-31 Reject under this fixture’s product range

Also reject month zero, month thirteen and day zero before touching the widget. Calendar validity and backend representability are separate gates. A mathematically valid date can still be unsupported by a chosen RTC or schedule service.

4. Define browsing and editing differently

In the proposed policy, selecting 2028-02-29 establishes that full pending date. Browsing to March changes only the viewport. The pending preview remains 2028-02-29 and no commit request occurs. Returning to February restores the relevant highlight from the pending model.

If the product also provides explicit year/month edit fields, changing a pending year from 2028 to 2029 makes February 29 invalid. Mark that candidate unresolved and disable Apply until the user chooses a valid day. Do not silently normalize it to March 1 or clamp it to February 28 unless that transformation is an explicit, visible product rule.

The stock arrow header increments or decrements the shown month/year, including year rollover. It does not enforce this article’s 2000..2199 policy. Use bounded application navigation controls or a separately reviewed header adaptation that validates the destination before calling the month setter.

Highlighted-date storage also has a lifetime contract: the setter retains the array pointer. Keep the highlight array in editor-owned storage while referenced. Rebuild it from validated pending dates; a temporary local array must not disappear when an event callback returns.

5. Make Apply a separate transaction

Apply takes one immutable pending-date snapshot, repeats validity and backend-range checks, and submits one request with the editor’s generation or revision. Disable duplicate submission while that request is pending. On success, refresh the authoritative value from the backend’s accepted result; on failure, retain the candidate and show a specific error.

Cancel discards the pending editor state without submitting a change. Test Cancel after a valid selection, after an invalid field edit and after month browsing. Define separately whether cancellation is still possible once the backend has accepted a request; closing a view cannot undo an already-applied change.

A calendar date alone is not a unique timestamp. A clock-setting backend also needs a time-of-day and an explicit interpretation policy. Keep that conversion behind a reviewed interface. This procedure neither maintains timezone data nor guarantees how a platform normalizes local times.

6. Keep a commit-count regression record

With a mock backend, assert zero requests for browsing, today-marker changes, highlights and Cancel. Require exactly one request containing the complete validated date for Apply. Repeat across December/January navigation, leap-day edits, the policy’s first and last months, and a rejected backend response.

Record displayed month, pending date, validation reason, authoritative revision and request count together. Acceptance means the UI can browse freely while only a deliberate, valid commit changes the device’s date-related state.

Similar Posts