Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Preserve the vendored php-src timelib snapshot byte-for-byte.
crates/elephc-tz/vendor/timelib/** -whitespace
crates/elephc-tz/vendor/timelib/README.rst conflict-marker-size=32
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

154 changes: 154 additions & 0 deletions DATETIME_PHP_SRC_COMPLIANCE_SPEC.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
---
title: "DateTime php-src Compliance Spec v3.0"
description: "Conformité ext/date vérifiée contre php-src 8.5 HEAD, timelib et tzdb 2026.3."
---

# DateTime php-src Compliance Spec v3.0

## Référence normative

- Branche Elephc: `feat/datetime-php-src-compliance`.
- php-src: commit `47b563cbb856ec19155aacc3246931dfacbebd21` (`PHP 8.5.10-dev`).
- Sources: `ext/date/php_date.stub.php`, `ext/date/php_date.c`, `ext/date/lib/` et les PHPT
de `ext/date/tests/`.
- Base timezone: timelib et IANA tzdb `2026.3`, vendus dans `crates/elephc-tz`.
- Cibles Elephc: macOS AArch64, Linux AArch64 et Linux x86_64.
- `ext/calendar` est validé séparément et n'entre pas dans ce périmètre.

La référence est le comportement observable de php-src: surface, ordre et casse des symboles,
signatures Reflection, constantes et attributs, retours, exceptions, messages, warnings,
sérialisation, debug, parsing, arithmétique et effets de bord.

## Surface verrouillée

L'inventaire comprend les 48 fonctions exposées par `get_extension_funcs("date")`, les fonctions
standard associées (`strptime`, `gettimeofday`, `microtime`, `hrtime`), et les types suivants:

- `DateTimeInterface`;
- `DateTime` et `DateTimeImmutable`;
- `DateTimeZone`;
- `DateInterval`;
- `DatePeriod`;
- `DateError`, `DateObjectError`, `DateRangeError`, `DateException`,
`DateInvalidTimeZoneException`, `DateInvalidOperationException`,
`DateMalformedStringException`, `DateMalformedIntervalStringException` et
`DateMalformedPeriodStringException`.

Les constantes globales `DATE_*`/`SUNFUNCS_*`, les constantes de format de
`DateTimeInterface`, les sélecteurs de `DateTimeZone` et les options de `DatePeriod` conservent
leurs valeurs, types, classes déclarantes et attributs `Deprecated`.

## Implémentation

### Parsing et arithmétique

Elephc compile la copie vendue du timelib de php-src pour:

- `strtotime()`, `date_parse()` et `date_parse_from_format()`;
- `DateTime*::createFromFormat()` et `getLastErrors()`;
- la grammaire ISO et relative de `DateInterval`;
- la grammaire ISO de `DatePeriod`;
- `DateTime*::add()`/`sub()` et les distinctions wall/civil de php-src.

Les structures C sont couvertes par des assertions de taille/offset pour les trois cibles
64 bits. Les objets timelib et leurs `tz_info` suivent le contrat d'ownership du code vendu.

### Fuseaux horaires

- Les tables location, transitions et abréviations sont générées depuis le même php-src.
- `timezone_version_get()` retourne `2026.3`.
- Les identifiants IANA avec ou sans `/`, les alias BC, les abréviations, les lettres militaires
et les offsets avec secondes conservent le `timezone_type`, le nom public et l'offset php-src.
- `format("e")` conserve le nom public; `format("T")` conserve les zones de type 2 et résout
l'abréviation active des zones de type 3.
- Les offsets fixes sont adaptés à la convention POSIX inversée uniquement à la frontière libc;
cette adaptation n'altère jamais le nom PHP stocké.

### Diagnostics et suppression

Le canal de diagnostics runtime émet les `E_WARNING`/`E_DEPRECATED` date/time concernés et
l'opérateur `@` les supprime avec restauration du niveau de suppression après exceptions.
Cela couvre notamment:

- `idate()` invalide;
- `strftime()`/`gmstrftime()` et `strptime()`;
- le constructeur string de `DatePeriod`;
- `SUNFUNCS_RET_*` et `DateTimeInterface::RFC7231`;
- les hooks `__wakeup()` et les résultats ignorés de `DateTimeImmutable`;
- les pseudo-propriétés debug de `DateInterval`.

Les exceptions et messages des constructeurs, parseurs, sérialisations et arguments invalides
suivent php-src, y compris le type concret rejeté dans les `TypeError` de `add()`/`sub()` et de
`date_add()`/`date_sub()`.

### Reflection, sérialisation et debug

- Les inventaires de fonctions et de méthodes sont verrouillés en ordre/casse php-src.
- Les signatures procédurales vérifient noms, types nullables/unions, optionnalité, références,
variadiques et retours.
- Les helpers internes `__elephc_*` restent absents de Reflection et des probes PHP.
- Les hooks `__serialize`, `__unserialize`, `__set_state` et `__wakeup` reproduisent les shapes et
erreurs de `DateTime`, `DateTimeImmutable`, `DateTimeZone`, `DateInterval` et `DatePeriod`.
- `var_dump()` utilise les handlers virtuels php-src pour les cinq familles d'objets.
- Les sept propriétés de `DatePeriod` sont virtuelles, non modifiables par l'utilisateur et
adossées à un itérateur indépendant retourné par `getIterator()`.

## Régressions exhaustives

Les gates permanents comprennent:

- inventaire des fonctions ext/date, avec lookup insensible à la casse;
- inventaire et ordre exact des méthodes des six types principaux;
- inventaire complet des signatures procédurales;
- constantes globales/de classe, attributs `Deprecated`, `NoDiscard` et retours tentatifs;
- hiérarchie des exceptions;
- shapes Reflection, sérialisation et `var_dump`;
- diagnostics byte-positionnés de timelib et `getLastErrors()`;
- grammaires libres/ISO/relatives, DateInterval et DatePeriod;
- offsets IANA, alias, POSIX, abréviations et lettres militaires;
- nullable timestamps de `date`, `gmdate`, `strtotime`, `localtime`, `getdate`,
`mktime` et `gmmktime`;
- arithmétique, microsecondes, DST, ownership, COW et parité AArch64/x86_64.

Commande locale principale:

```bash
ELEPHC_PHP_CHECK=1 cargo test --test codegen_tests datetime -- --nocapture
```

Le contrôle PHP intégré peut utiliser un PHP système différent. Les verdicts de l'audit sont donc
épinglés sur le binaire construit depuis le commit php-src normatif ci-dessus; les différences
d'environnement Xdebug ou de version tzdb d'un PHP système ne constituent pas l'oracle.

## Validation indépendante

Les trois relectures Ollama ont reçu le stub php-src, les sources/diffs Elephc et les tests
pertinents en ligne:

- Kimi K2.7: surface, signatures, constantes et Reflection;
- GLM-5.2: ABI timelib, parsing, DateInterval, DatePeriod et ownership;
- MiniMax M3: diagnostics, sérialisation, propriétés virtuelles, Reflection et parité de cibles.

Chaque alerte a été rejouée contre le php-src normatif. Les faux positifs documentés pendant la
revue incluaient l'appartenance de `strptime` à `standard`, le type 2 de `GMT`, le rejet de la
soustraction d'un weekday relatif, le contrat de `timelib_time_dtor`, les clés optionnelles de
`date_parse()["relative"]` et les appels `timelib_update_ts(..., NULL)` de DatePeriod.

Les écarts confirmés par la revue — hooks Reflection manquants, identifiants IANA sans slash,
zones militaires attachées, `format("T"/"e")`, signatures nullable/unions et messages TypeError —
ont été corrigés et verrouillés par régression.

## Critère de clôture

La branche est publiable uniquement si:

- le gate datetime complet est vert;
- `cargo test -p elephc-tz` est vert;
- `cargo check --tests` et `cargo build` sont sans warning;
- `git diff --check` est propre;
- les trois revues ne conservent aucun écart confirmé;
- la CI valide les trois cibles supportées.

Aucune limitation résiduelle connue de la surface `ext/date` auditée n'est acceptée sur cette
branche. Toute divergence future contre le commit normatif doit rouvrir ce document et ajouter une
régression avant correction.
21 changes: 17 additions & 4 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -720,19 +720,32 @@ binaries never link the bridge.

### Date/time and calendar parity

Full OOP + procedural date/time surface (`DateTime`, `DateTimeImmutable`, `DateTimeInterface`, `DateTimeZone`, `DateInterval`, `DatePeriod`, and ~45 procedural functions/aliases), plus the complete `ext/calendar` extension, implemented as synthetic classes/methods over the `date()`/`mktime()`/`gmmktime()` runtime — no new codegen or assembly.
Broad OOP + procedural date/time surface (`DateTime`, `DateTimeImmutable`, `DateTimeInterface`, `DateTimeZone`, `DateInterval`, `DatePeriod`, and the complete ext/date function inventory), plus `ext/calendar`, implemented as synthetic classes/methods over the date runtime. The normative php-src snapshot and permanent parity gates are documented in `DATETIME_PHP_SRC_COMPLIANCE_SPEC.md`.

- [x] `DateTime` / `DateTimeImmutable` — construct (string + `?DateTimeZone`), `format`, `getTimestamp` / `setTimestamp`, `getTimezone` / `setTimezone`, `getOffset`, `getMicrosecond` / `setMicrosecond`, `setTime`, `setDate`, `setISODate`, `add` / `sub`, `modify`, `diff`, `createFromFormat`, `createFromInterface`, `createFromMutable` / `createFromImmutable`, `createFromTimestamp`, `getLastErrors`; per-object timezone honored in `format()`
- [x] PHP 8.3+ constructor/`modify()` validation — `DateTime` / `DateTimeImmutable::__construct` and `modify()` throw `DateMalformedStringException` on an unparseable string; `DateTimeZone::__construct` throws `DateInvalidTimeZoneException` on an unrecognized identifier and canonicalizes timelib numeric offsets, including `GMT`-prefixed and second-precision forms
- [x] `DateTime::createFromTimestamp(int|float)` preserves the fractional part as microseconds (PHP 8.4+ parity)
- [x] `DateTime::setTimestamp()` resets microseconds to zero, while calendar setters and mutable/immutable conversion factories preserve them
- [x] `idate()` returns `false` for an empty or unrecognized literal or runtime-computed format and emits php-src's suppressible `E_WARNING`
- [x] `date_create()` / `date_create_immutable()` / `date_modify()` return `false` on invalid input (PHP `DateTime|false` parity via synthetic wrappers catching `DateMalformedStringException`; EIR mixed-receiver method dispatch fix ensures the wrappers compose with downstream method calls)
- [x] `DateInterval` PHP 8.2+ `from_string` / `date_string` debug-serialization state (not exposed as ordinary readable properties)
- [x] `DatePeriod` PHP 8.2+ virtual properties with PHP recurrence-count semantics and handler-level write rejection; `implements IteratorAggregate`; independent `getIterator()` instances; `getStartDate(): DateTimeInterface` / `getEndDate(): ?DateTimeInterface` return types
- [x] `DatePeriod` string ctor overload (`new DatePeriod($runtimeSpecification[, $options])`) via name-resolver rewrite to `createFromISO8601String`
- [x] Serialization surface: `__serialize`/`__unserialize`/`__set_state`/`__wakeup` on `DateTime`, `DateTimeImmutable`, `DateTimeZone`, `DateInterval`, `DatePeriod` — real implementations matching PHP's array shape
- [x] `strtotime()` accepts 2-digit ISO year `YY-MM-DD` with PHP's shorthand (70-100→1970-2000, 0-69→2000-2069)
- [x] `getLastErrors()` shares state across mutable/immutable classes, returns `false` when clean, and exposes timelib's exact byte-positioned warning/error maps and duplicate-position counts
- [x] `timezone_name_from_abbr()` follows php-src's complete ordered timelib abbreviation lookup, including exact UTC-offset disambiguation and the `(offset, isDST)` fallback map
- [x] `DateTimeInterface` format constants (`ATOM`, `COOKIE`, `ISO8601`, `ISO8601_EXPANDED`, the `RFC*` family, `RSS`, `W3C`) on the interface and both classes
- [x] `DateInterval` — ISO 8601 duration parsing, `format()` (`%y %m %d %h %i %s %a %R %r %f %F` …), `createFromDateString`
- [x] `DatePeriod` — `Iterator` over a date range, end-date and recurrence-count forms, `EXCLUDE_START_DATE` / `INCLUDE_END_DATE`, `createFromISO8601String`
- [x] `DatePeriod` — date-range iteration, end-date and recurrence-count forms, `EXCLUDE_START_DATE` / `INCLUDE_END_DATE`, runtime string overload, `createFromISO8601String`
- [x] PHP 8.3 date exception hierarchy — `DateError` / `DateObjectError` / `DateRangeError` (extend `Error`); `DateException` / `DateInvalidTimeZoneException` / `DateInvalidOperationException` / `DateMalformed{String,IntervalString,PeriodString}Exception` (extend `Exception`)
- [x] `date()` / `gmdate()` specifiers including `O P Z T e I c r u v p B X x`; `createFromFormat()` specifiers including `D l S F M z v O P Z T e X x` and the `! | # ? * +` metas with strict trailing-data rejection
- [x] `date()` / `gmdate()` specifiers including `O P Z T e I c r u v p B X x`; `createFromFormat()` specifiers including `D l S F M z v O P T e X x` and the `! | # ? * +` metas with strict trailing-data rejection (`Z` is literal there); embedded `O`/`P`/`T`/`e` zones override the optional third timezone argument
- [x] `strtotime()` returns `int|false` (PHP-compatible failure value; `-1` stays a valid pre-epoch timestamp); broad relative/ISO/keyword/timezone-suffix grammar
- [x] Procedural date/time functions — `mktime` / `gmmktime`, `checkdate`, `getdate`, `localtime`, `gettimeofday`, `microtime`, `hrtime`, `idate`, `strftime` / `gmstrftime`, `strptime`, `date_parse` / `date_parse_from_format`, `date_default_timezone_get/set`, the `date_*` OOP aliases, and `timezone_open` / `timezone_identifiers_list` / `timezone_name_get` / `timezone_offset_get` / `timezone_name_from_abbr` / `timezone_version_get`
- [x] Solar functions — `date_sun_info()`, and the deprecated `date_sunrise()` / `date_sunset()` (with `SUNFUNCS_RET_*`), a faithful bit-exact port of PHP's timelib astronomical algorithm
- [x] `ext/calendar` — `gregoriantojd` / `juliantojd` / `frenchtojd` / `jewishtojd` and inverses, `cal_to_jd` / `cal_from_jd` / `cal_days_in_month` / `cal_info`, `jddayofweek`, `jdmonthname`, `easter_days` / `easter_date`, `unixtojd` / `jdtounix`, and the `CAL_*` constants (Gregorian, Julian, French Republican, and Jewish calendars; bit-exact vs PHP across the full Serial-Day-Number range)
- [x] Bundled IANA timezone-database introspection — `DateTimeZone::getLocation()` / `getTransitions()` / `listAbbreviations()` and `timezone_location_get` / `timezone_transitions_get` / `timezone_abbreviations_list`. The three tables are baked from PHP's own timelib data (byte-for-byte identical to PHP) into the pure-Rust `crates/elephc-tz` bridge staticlib, linked **only** into programs that use one of these methods (pay-for-use, like the TLS/PDO/crypto bridges); the transition `time` string is recomputed at runtime by a proleptic-Gregorian formatter exact to `PHP_INT_MIN`
- [x] Bundled php-src timelib + IANA tzdb 2026.3 bridge — exact parsing for `strtotime`/`date_parse`/`createFromFormat`, DateInterval/DatePeriod grammar and arithmetic, plus `DateTimeZone::getLocation()` / `getTransitions()` / `listAbbreviations()` and their procedural aliases. Tables and vendored C timelib are linked on demand; FFI layout is asserted on all supported 64-bit targets, and transition formatting remains exact through `PHP_INT_MIN`
- [x] php-src metadata gates — complete ext/date function inventory and signatures, exact six-class method order/casing, constants/attributes/tentative returns, exception hierarchy, serialization/debug shapes, nullable timestamp arguments, military/IANA-no-slash timezone representations, and exact `add`/`sub` TypeError messages

## v0.24.x — EIR introduction and register allocation

Expand Down
11 changes: 4 additions & 7 deletions crates/elephc-tz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "elephc-tz"
version = "0.1.0"
edition = "2021"
license = "MIT"
description = "Pure-Rust IANA timezone-introspection bridge staticlib (transitions/location/abbreviations) for the elephc PHP-to-native compiler's DateTimeZone introspection family"
description = "php-src timelib and IANA timezone bridge for elephc's DateTime family"
publish = false

# Built as a C-callable staticlib (linked into compiled PHP programs that use
Expand All @@ -13,10 +13,7 @@ publish = false
[lib]
crate-type = ["staticlib", "rlib"]

# No dependencies: the IANA tables are baked from the PHP interpreter at
# generation time (see data/generate.php) and embedded with include_str!, so the
# bridge is byte-for-byte identical to PHP and needs nothing at runtime but std.
# (A computed approach via tzdb/tz-rs cannot match PHP: those crates ship slim
# TZif without the fat recurring-transition expansion, and Ramadan zones are not
# POSIX-expressible.)
[dependencies]

[build-dependencies]
cc = "1"
42 changes: 42 additions & 0 deletions crates/elephc-tz/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//! Purpose:
//! Compiles the exact timelib sources vendored from the audited php-src PHP-8.5 commit.
//!
//! Called from:
//! - Cargo when building the `elephc-tz` bridge.
//!
//! Key details:
//! - The source list mirrors `ext/date/config0.m4`.
//! - Using php-src's generated parsers and timezone database keeps grammar and
//! tzdb provenance identical on every supported target.

/// Compiles php-src's standalone timelib C sources into the bridge.
fn main() {
let sources = [
"vendor/timelib/astro.c",
"vendor/timelib/dow.c",
"vendor/timelib/parse_date.c",
"vendor/timelib/parse_tz.c",
"vendor/timelib/parse_posix.c",
"vendor/timelib/timelib.c",
"vendor/timelib/tm2unixtime.c",
"vendor/timelib/unixtime2tm.c",
"vendor/timelib/parse_iso_intervals.c",
"vendor/timelib/interval.c",
];
let mut build = cc::Build::new();
build
.files(sources)
.include("vendor/timelib")
.define("HAVE_STDINT_H", None)
.define("HAVE_GETTIMEOFDAY", None)
.flag_if_supported("-fwrapv")
.flag_if_supported("-Wno-implicit-fallthrough")
.flag_if_supported("-Wno-unused-parameter");
if std::env::var_os("CARGO_CFG_WINDOWS").is_some() {
build.define("HAVE_IO_H", None);
} else {
println!("cargo:rustc-link-lib=m");
build.define("HAVE_UNISTD_H", None);
}
build.compile("elephc_php_src_timelib");
}
4 changes: 2 additions & 2 deletions crates/elephc-tz/data/location.data
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ America/Dawson_Creek CA 55.76666 -120.23334 MST - BC (Dawson Cr, Ft St John)
America/Denver US 39.73916 -104.98417 Mountain (most areas)
America/Detroit US 42.331379999999996 -83.04584 Eastern - MI (most areas)
America/Dominica DM 15.299999999999997 -61.400000000000006
America/Edmonton CA 53.55000000000001 -113.46667 Mountain - AB, BC(E), NT(E), SK(W)
America/Edmonton CA 53.55000000000001 -113.46667 CST - AB, BC(E), NT(E), SK(W)
America/Eirunepe BR -6.666669999999996 -69.86667 Amazonas (west)
America/El_Salvador SV 13.700000000000003 -89.2
America/Ensenada ?? -90 -180
Expand Down Expand Up @@ -215,7 +215,7 @@ America/Thunder_Bay ?? -90 -180
America/Tijuana MX 32.53333000000001 -117.01667 Baja California
America/Toronto CA 43.650000000000006 -79.38334 Eastern - ON & QC (most areas)
America/Tortola VG 18.450000000000003 -64.61667
America/Vancouver CA 49.26666 -123.11667 Pacific - BC (most areas)
America/Vancouver CA 49.26666 -123.11667 MST - BC (most areas)
America/Virgin ?? -90 -180
America/Whitehorse CA 60.71665999999999 -135.05001 MST - Yukon (east)
America/Winnipeg CA 49.88333 -97.15001 Central - ON (west), Manitoba
Expand Down
Loading