Base R date-time manipulation with POSIXlt
POSIXlt: date components as mutable fields
POSIXlt is a list where the date components ($year, $mon, $mday, etc.) are plain integers you can assign to. Normalizing back through as.POSIXct applies standard mktime overflow rules — month/day overflow cascades.
No string substitution, no regex. Adding a year is lt$year <- lt$year + 1.
Cascade semantics vs. clamping
Month/day overflow rolls forward (POSIX mktime behavior). Adding 13 months to February advances the year. If you need lubridate-style clamp (Feb 29 -> Feb 28 instead of March 1), handle that explicitly — base R won’t do it for you.
Timezone: CET / CEST from IANA zones
You cannot parse CEST as an input timezone abbreviation (that’s the unknown timezone 'CEST' error). But you don’t need to. Use a location-based IANA zone and R derives the correct DST abbreviation and offset from the database.
The distinction: CET/CEST are output labels, not input zones. Europe/Madrid (or Europe/Paris, etc.) is the zone. Whether a given instant prints as CET or CEST is resolved automatically from the date. You don’t check %m to pick the abbreviation — R does that for you.
Parsing flexible date strings
When input may or may not include a time component, anytime/anydate works, but base R handles it too:
The gap vs. lubridate
The one real gap is ergonomics — no %m+% operator, no clamping, and you carry the cascade semantics yourself. But the functionality is all in POSIXlt field mutation plus IANA zone names. Zero external dependencies.