• Support partial birthdate information

    From Rob Swindell@1:103/705 to GitLab note in main/sbbs on Mon Nov 9 12:52:36 2026
    https://gitlab.synchro.net/main/sbbs/-/work_items/326#note_10853

    Design notes from reviewing the current code.

    ## Current behavior

    The parsers accept partial input, but it is not stored as partial. The missing parts are filled in as 01: `1970` is stored as `19700101` and `1970/06` as `19700601` (`exec/tests/user/birthdate.js` expects this). As a result:

    - **Age is overstated by up to a year.** Someone born in Dec 2008 was 17 on 2026-11-09, but a year-only `20080101` makes `getage()` return 18, which passes `AGE 18` in ARS.
    - **Birthdays are invented.** Year-only users would get the logon birthday greeting on Jan 1 and be listed by `birthdays.js` and shown by `@BIRTH@` as if that date were real.
    - **The information is lost.** A placeholder cannot be distinguished from a real Jan 1.

    ## Proposed storage

    Store unknown parts as `00`: `CCYY0000` and `CCYYMM00`. These fit `LEN_BIRTH` (8) and existing records keep their meaning. An unpadded `CCYYMM` will not work: `split_birthdate()` reads `197006` as year 19, month 70.

    ## What has to change

    | Code | Change |
    |---|---|
    | `birthdate_is_valid()` | Accept `00` parts. It rejects them now, so `getage()` would return 0 |
    | `parse_birthdate()` | Stop clamping missing parts to 1, and update the test expectations |
    | `getage()` | Treat unknown parts as Dec 31 or end of month, so age is never overstated |
    | Birthday greeting in `logon.cpp`, `birthdays.js` | Skip unknown parts |
    | `format_birthdate()`, `@BIRTH@` | Show only the known parts |
    | `js_user.cpp` `birthyear`/`birthmonth`/`birthday` setters | They rewrite the other parts through clamped getters, which would turn `00` into `01` |
    | Door drop files | Keep writing 01 placeholders, since doors expect a full date |
    | Terminal prompts, sysop user edit | A format and template per precision; `useredit.cpp` requires exactly 10 characters |
    | Web registration forms | Accept the configured precision |

    `birthdays.js` already counts users with a blank birthdate as January, because `birthmonth` clamps to 1 (same cause as #1041).

    ## Configuration and policy

    - A sysop setting for birthdate precision: Full / Year+Month / Year. This pairs with the Optional state proposed in #1331.
    - Suggested: ask for exactly the configured precision rather than treating it as a minimum the user may exceed. That keeps it to one prompt and the sysop's privacy choice holds.
    - Suggested: age gates use the youngest possible age for a partial date. This can delay access by up to a year but never admits an underage user. A sysop enforcing a minimum age can still require a full date.

    -- *Authored by Claude (Claude Code), on behalf of @rswindell*
    --- SBBSecho 3.38-Linux
    * Origin: Vertrauen - [vert/cvs/bbs].synchro.net (1:103/705)