Date display format — SAP’s rules, and where OpenADS deliberately deviates
Every rule here was probed against SAP ace64.dll (scratch ADT + DBF
tables, tools-style dynamic loading), not inferred from documentation.
Re-probe before changing behaviour; the help files do not spell most of
this out.
The split that matters: GetField formats, GetString does not
| Read | Date column | Blank date | Timestamp column |
|---|---|---|---|
AdsGetField |
formatted per the date format — 01/15/2024 (len 10) |
` / / ` (format with digits blanked) | 01/15/2024 01:45:59 PM (len 22, 12-hour, 2-digit hour) |
AdsGetString |
raw 20240115 (len 8) — format-independent |
` ` (8 spaces, len 8) | SAP: error 5066 · OpenADS: raw YYYYMMDDhhmmss (deviation, see below) |
AdsGetDate |
formatted (delegates through GetField) | formatted blank | — |
AdsGetJulian |
numeric JDN | 0 | — |
This split is SAP’s own behaviour, not an OpenADS invention. Do not “unify” the two entry points — clients depend on each side:
- The S4 parity harness (
dd_meta_dump) reads viaAdsGetField, so SQL result dates compare formatted. php_ads(DA-Web, OpenERP) reads only viaAdsGetStringand parses the rawYYYYMMDD/YYYYMMDDhhmmssshapes itself. FormattingAdsGetStringwould break every PHP date read.- Engine internals (index keys, WHERE compares, MIN/MAX text accumulation,
materialised temp cells) use the raw decode —
YYYYMMDDorders lexicographically as it does chronologically, which those paths rely on.
AdsGetString on remote/backend handles delegates through AdsGetField;
a thread-local (g_field_read_raw) keeps that delegated read raw.
The format string
- Process-wide; default
MM/DD/CCYY(SAP’s default, probed). AdsSetDateFormatnormalises: uppercased, andYYYYis stored asCCYY(DD.MM.YYYYreads backDD.MM.CCYY).- Recognised tokens:
CCYY/YYYY,YY,MM,DD; everything else is a literal separator. (format_ace_date/blank_ace_date/parse_date_by_formatinsrc/abi/ace_exports.cpp.)
Writes
| Write | SAP | OpenADS |
|---|---|---|
AdsSetDate("03/07/2025") (current format) |
parses per format | same (parse_date_by_format) |
AdsSetDate("20250307") raw |
rejects (5080) | accepts (superset — kept for existing callers) |
AdsSetDate ISO 2025-03-07 |
— | accepts (legacy OpenADS shape) |
AdsSetString into a Date/Timestamp |
rejects (5066) | accepts raw text (the remote twin routes every SetField through AdsSetString; php writes raw) |
AdsSetTimeStamp("01/15/2024 13:45:59") |
parses (24-hour input) | same; also accepts ISO and compact 14-digit |
AdsSetEmpty on an ADT Date |
blank (JDN 0) | same — fixed 2026-08-04: the empty string used to fall into the space-pad branch of encode_field_string, storing 0x20202020 = JDN 538976288 = the year 1470954 |
2-digit-year formats (MM/DD/YY) |
epoch-resolved | same (openads::engine::epoch()) |
Deliberate deviations (do not “fix” toward SAP without checking php)
AdsGetStringon a Timestamp returns the rawYYYYMMDDhhmmss. SAP raises 5066 there.php_adsreads timestamps exactly this way — adopting SAP’s error would break every PHP timestamp read.- String writes into date fields keep working (raw
YYYYMMDD, ISO, or current-format text). SAP 5066s / 5080s these. The remote session’s SetField path and php both write through strings. ModTime(ADT type 22) is not display-formatted — php reads it raw via GetString; SAP behaviour for GetField on ModTime is unprobed.
Regression tests
tests/unit/abi_date_format_test.cpp— the probe table above as assertions, verified to fail with the formatting reverted.
If a gate case or client suddenly sees 20240115 where it expected
01/15/2024 (or vice versa), check which entry point it reads through
before touching the engine — the difference is the spec, not a bug.