Skip to content

feat(datetime): Add support for DateTime, DateInterval, DatePeriod, strtotime#343

Draft
Guikingone wants to merge 6 commits into
illegalstudio:mainfrom
Guikingone:feat/datetime
Draft

feat(datetime): Add support for DateTime, DateInterval, DatePeriod, strtotime#343
Guikingone wants to merge 6 commits into
illegalstudio:mainfrom
Guikingone:feat/datetime

Conversation

@Guikingone

@Guikingone Guikingone commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Builtin date/time classes implemented as synthetic PHP over date()/mktime()/ strtotime(): DateTimeInterface, DateTimeZone, DateTime, DateTimeImmutable (construct/format/getTimestamp/getTimezone/set*/add/sub/modify/diff), DateInterval (ISO-8601 parse, calendar y/m/d fill from diff(), format()), and a DatePeriod (start, interval, end) Iterator.

date()/gmdate(): all non-timezone format specifiers + backslash escapes + ISO-8601 week (W) and ISO year (o). gmdate() is the UTC sibling of date().

strtotime(): full relative/absolute grammar — @epoch, American M/D/Y slash dates, textual dates (D Month Y / Month D, Y), "first/last day of month", nth weekday of month, plus the 2-arg base-timestamp form; DateTime::modify() builds on it.

Includes the prerequisite codegen fixes the above depend on: Mixed/Union operand unboxing in mktime()/intdiv()/the remaining int-arg builtins, cast-vs-binary operator precedence in the parser, and array_fill() storing every element for string values.

Docs (docs/php/datetime.md) + a runnable example added.

Also excludes the datetime classes from the dynamic new $var() AOT dispatcher candidate list: they are synthetic builtin classes whose methods are emitted on demand, so referencing their constructors unconditionally would leave undefined symbols at link time (consistent with how other on-demand builtins are handled).

@Guikingone Guikingone force-pushed the feat/datetime branch 2 times, most recently from 89bd2d3 to 7a4028c Compare June 11, 2026 21:45
Complete PHP date/time surface implemented as synthetic classes/methods and
procedural desugaring over the date()/mktime() runtime — no new codegen or
assembly beyond the existing date/strtotime runtime.

- DateTime / DateTimeImmutable / DateTimeInterface / DateTimeZone /
  DateInterval / DatePeriod, with per-object timezones, createFromFormat
  (full specifier + meta set), createFromInterface/Mutable/Immutable/Timestamp,
  add/sub/modify/diff, microseconds, getLastErrors, and the PHP 8.3 Date*
  exception hierarchy
- DateTimeInterface format constants (ATOM, COOKIE, ISO8601, RFC*, RSS, W3C)
- ~45 procedural functions/aliases (mktime, strtotime returning int|false,
  getdate, localtime, gettimeofday, microtime, hrtime, idate, strftime/strptime,
  date_parse*, the date_* OOP aliases, timezone_*), date()/createFromFormat()
  specifier coverage
- Solar functions (date_sun_info / date_sunrise / date_sunset)
- Full ext/calendar: JD conversions (gregorian/julian/french/jewish + inverses),
  cal_*, easter_*, jddayofweek/jdmonthname, unixtojd/jdtounix, CAL_* constants
- Bundled IANA tz introspection (getLocation/getTransitions/listAbbreviations,
  timezone_*_get) via the pay-for-use crates/elephc-tz bridge staticlib

Rebased onto origin/main v0.23.9 (tagged null representation).
…sons

Under the tagged null representation a miss-capable int-array read (or a local
holding one) materializes as a TaggedScalar. Loose equality narrowed the operand
to a plain int via coerce_null_to_zero, but because TaggedScalar was not in the
numeric set it then also ran through coerce_to_int_for_loose_cmp, whose default
arm reloads 0 — overwriting the narrowed value. So `$arr[$i] == 13` compared
`0 == 13` and returned false. This broke jdtojewish()'s `$months[...] == 13`
leap-year test under the default tagged null representation.

Add TaggedScalar to the left/right numeric classification so it is narrowed
exactly once. Regression test in tests/codegen/null_sentinel/tagged.rs.
coerce_to_int (used by the ordering/spaceship comparison coercion and by the
integer-argument array builtins) normalized null and Mixed/Union operands but
left a Str operand untouched. A string lives in the string-result registers; on
x86_64 the string pointer also occupies the integer result register, so an
unconverted string operand is compared as a raw pointer (on AArch64 the string
lands in x1/x2, masking the bug). Parse it through __rt_str_to_int, mirroring the
Mixed/Union arm.

The type checker rejects string operands in ordering/spaceship comparisons, so
no user-level program reaches this path on its own; it is exercised by synthetic
builtin-method bodies that compare string characters numerically (the DateTime
fractional-seconds parser does `$c >= "0" && $c <= "9"`), which returned the
current time on x86_64 before this fix.
Resolve the gaps found while auditing the feat/datetime branch:

- feat: first-class-callable support for checkdate/getdate/localtime/hrtime/
  gmmktime (plus mktime/strtotime) in first_class_callable_builtin_sig, so
  `checkdate(...)` etc. work as closures like the existing `date(...)`.
- docs: drop the stale "this/next/last week not supported" note (it works and
  is already tested), fix the DateInterval `%f` "always 0.0" claim (sub-second
  is implemented), correct strtotime failure (`false`, not `-1`), complete the
  createFromFormat specifier list (D l S F M z v), and fix sidebar.order
  collisions (datetime 16->18, calendar 17->19).
- test: date() `n`/`G` no-pad specifiers; createFromFormat `g`/`G`/lowercase
  `a` and `\`-escape; arity-error diagnostics for checkdate and the DateTime/
  DateTimeImmutable/DateInterval constructors.
- style: move the strftime docblock onto its function (it had merged into the
  extract_micros const docs); align two localtime asm comments to column 81.
…ias calls

Procedural date/time aliases (idate, strftime/gmstrftime, strptime, the
ext/calendar functions, the sun functions, and the date_*/timezone_* aliases)
are desugared by the name resolver only at their supported arities. A call with
the wrong argument count failed to desugar and fell through to the type checker
as "Undefined function: X" — even though function_exists("X") returns true for
these names, an inconsistent diagnostic.

Add date_procedural_alias_arity(), kept in lockstep with the arity guards in
rewrite_date_procedural_alias, and route the checker's unresolved-call path
through it so a wrong-arity alias call reports a precise arity error
("X() takes exactly N arguments" / "N or M" / "N to M"), matching how real
builtins like checkdate() are diagnosed. Genuinely undefined functions keep the
"Undefined function" message.
@nahime0

nahime0 commented Jun 12, 2026

Copy link
Copy Markdown
Member

Hi @Guikingone, I'm currently landing the new IR-based backend path on main. Feel free to mark this PR as ready for review when you're ready. I'll take care of aligning it with the new backend.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants