This feels very topical to me as just in the last few days I’d started adding notes for a few people in my personal kb TW, and added ‘birthday’ fields where appropriate. I’d gotten as far as populating a dozen or so folks with that, with the data in TW core date format, and then constructing a table which calculated age since date of birth, added age in days because-I-could, and if there was a deathday field, gave that information too (and a similar memorial table for all people with deathday fields too)

Sorting, however, was simply oldest to youngest, which I wasn’t too happy with, and your post got me rethinking from first principles - especially that birthdays are a special form of date where the time/timezone is 100% irrelevant for future calculations! ie, even if you were born at 1am in the +1400 timezone (and so 99.9+% of the rest of the planet’s surface and population were on the previous day) it doesn’t matter where you later travel, the date celebrated for your birthday is the one recorded at that time of birth. relative offset is irrelevant. This had been a small headache to my calculations since I initially filled the birthday field with just YYYYMMDD - which works out for me in my timezone, but it felt awkward knowing the calculations could be made inaccurate via an external timezone setting (and adding a “12” to the field to make it think midday UTC, just felt clumsy in a different way and only minimises the problem, not properly solve it), plus my setup wouldn’t suit your filter need at all.
So in the last day I went down the rabbithole of adding ParseDate from tiddlytools, so now I can fill the field with a human readable date (as you’ve done), but still retain all my calculations due to being able to treat it as a parseable date (this version also fixed an off-by-one error on the age-in-days from the previous!)

Anyway, this is the revised code for the birthday table, where the ‘birthdate’ field is human readable (I’ve been filling it in as DDth MMM YYYY
and then that gets included literally to the table for the places I want that format.
<$list filter="[has:field[birthdate]] :sort:string:reverse[get[birthdate]split[ ]last[]]">
<$let
now-ts=<<now TIMESTAMP>>
bday-ts={{{ [{!!birthdate}unixtime[]] }}}
age-days={{{ [<now-ts>subtract<bday-ts>divide[86400000]floor[]] }}}
age-yrs={{{ [<now-ts>subtract<bday-ts>divide[315569280]floor[]divide[100]] }}}
dday-ts={{{ [{!!deathday}format:date[TIMESTAMP]] }}}
>
<tr>
<td><$link>{{!!title}}</$link></td>
<td><strong><<age-yrs>> </strong>years since <strong>{{!!birthdate}}</strong></td>
<td>(<<age-days>> days)</td>
<td><$list filter="[<currentTiddler>has[deathdate]]">
(Passed {{!!deathdate}})
</$list></td>
</tr>
</$let>
</$list>
</table>
I’ve not yet implemented a month filter, but I now feel like sorting the entire list into birthday order is within reach for me - iterate through a list of months, for each one filter as you’ve done, and within those filtered results, sort by day. (Sorting by “next upcoming birthday” feels the ultimate end-goal to me, though that’s definitely beyond my current skills)