MAPI utility library – helper library for Microsoft MAPI data structures in the Horde Framework.
| Package | horde/mapi |
| Upstream | github.com/horde/mapi |
| License | LGPL-2.1 |
| Author | Michael J Rubinsky |
| PHP | ^8.1 |
| Dependencies | horde/date, horde/exception, PHP extension bcmath |
MAPI (Messaging Application Programming Interface) is Microsoft's API for messaging, calendars, and contacts—primarily in Exchange and Outlook. In Horde, this library is not a full MAPI client or Exchange connector. It provides binary format helpers used by Microsoft protocols, especially Exchange ActiveSync.
Typical use cases in Horde:
- Encode/decode timezones as MAPI
TIME_ZONE_INFORMATIONblobs (ActiveSync) - Convert Global Object Identifier (GOID) ↔ iCalendar UID (appointments, meeting requests)
- Convert Windows FILETIME to Unix timestamps
The library is intentionally small and consists of three classes:
lib/Horde/
├── Mapi.php → Horde_Mapi (static helpers)
└── Mapi/
├── Timezone.php → Horde_Mapi_Timezone
└── Exception.php → Horde_Mapi_Exception
General MAPI helper functions (all static):
| Method | Description |
|---|---|
isLittleEndian() |
Checks the system's byte order |
chbo($num) |
Reverses the byte order of an integer (for big-endian systems) |
getUidFromGoid($goid) |
Extracts a UID from a Base64-encoded GOID |
createGoid($uid) |
Creates a Base64-encoded GOID from a UID |
filetimeToUnixtime($ft) |
Converts binary Windows FILETIME to a Unix timestamp |
MAPI structures are encoded in little-endian order. On big-endian systems, values are adjusted with chbo() before packing/unpacking.
filetimeToUnixtime() requires the PHP bcmath extension because FILETIME uses 64-bit values that PHP cannot represent natively. Without bcmath, it returns -1.
Converts between Microsoft's timezone blob and PHP/IANA timezones.
Microsoft defines timezones as a binary TIME_ZONE_INFORMATION structure (bias, standard/daylight names, SYSTEMTIME transitions, etc.). ActiveSync transmits this structure Base64-encoded in the WBXML protocol.
| Method | Direction | Description |
|---|---|---|
getOffsetsFromSyncTZ($data) |
MAPI blob → array | Decodes an ActiveSync timezone blob into an offset hash |
getSyncTZFromOffsets($offsets) |
array → MAPI blob | Builds an ActiveSync-compatible timezone blob |
getOffsetsFromDate(Horde_Date $date) |
IANA TZ → array | Derives offsets from a Horde_Date/timezone |
getTimezone($offsets, $expectedTimezone) |
MAPI blob/array → IANA | Guesses the matching Olson timezone |
getListOfTimezones($offsets, $expectedTimezone) |
MAPI blob/array → array | All matching IANA identifiers |
Reverse mapping (MAPI offsets → IANA timezone) is ambiguous: multiple timezones can share the same offsets. You can pass $expectedTimezone to prefer a specific match; otherwise Horde_Date::getTimezoneAlias() picks an alias.
Logic for detecting the nth weekday occurrence in a month (e.g. "second Sunday in March") is partly inspired by the Tine 2.0 project.
Base exception class (extends Horde_Exception_Wrapped), e.g. for missing bcmath extension or no matching timezone.
Horde_Date / IANA timezone
│
▼
getOffsetsFromDate() ← reads DST/STD transitions via DateTimeZone
│
▼
getSyncTZFromOffsets() ← pack() into TIME_ZONE_INFORMATION, base64_encode()
│
▼
ActiveSync WBXML (Appointment.timezone)
Reverse direction when receiving:
Base64 timezone blob from client
│
▼
getOffsetsFromSyncTZ() ← base64_decode(), unpack()
│
▼
getTimezone() / getListOfTimezones() ← match against all IANA zones
│
▼
e.g. "Europe/Berlin"
GOIDs uniquely identify appointments across Exchange/Outlook. The library supports two formats:
- Outlook style: hex representation of the entire GOID, bytes 17–20 set to zero
- vCal style:
vCal-Uidmarker in the blob, UID as plain text after it
iCalendar UID ←→ createGoid() / getUidFromGoid() ←→ Base64 GOID
Windows stores timestamps as 64-bit counters in 100-ns ticks since 1 Jan 1601 UTC. Conversion steps:
- Hex string from binary data
- Flip endianness (
_flipEndian) - Hex → bcmath integer (
_hexToBcint) - Subtract epoch offset and divide by 10,000,000 (
_win64ToUnix)
horde/mapi is used mainly indirectly via ActiveSync:
| Module | File | Usage |
|---|---|---|
| ActiveSync | Horde/ActiveSync/Message/Appointment.php |
setTimezone() / getTimezone() for appointment sync |
| ActiveSync | Horde/ActiveSync/Message/MeetingRequest.php |
createGoid() for meeting UIDs, timezone parsing |
| Kronolith | lib/Event.php |
_handleEas16Exception() – resolve timezone from ActiveSync message |
These functions previously lived in Horde_ActiveSync_Utils and Horde_ActiveSync_Timezone; since Horde 6 they were moved into horde/mapi (the old classes are marked @deprecated).
$date = new Horde_Date(time(), 'Europe/Berlin');
$offsets = Horde_Mapi_Timezone::getOffsetsFromDate($date);
$blob = Horde_Mapi_Timezone::getSyncTZFromOffsets($offsets);
// $blob → Base64 string for ActiveSync$parser = new Horde_Mapi_Timezone();
$tz = $parser->getTimezone($blob, 'Europe/Berlin');
// $tz → e.g. "Europe/Berlin"$goid = Horde_Mapi::createGoid('{81412D3C-2A24-4E9D-B20E-11F7BBE92799}');
$uid = Horde_Mapi::getUidFromGoid($goid);$binaryFiletime = /* from MAPI pTypDate property */;
$unix = Horde_Mapi::filetimeToUnixtime($binaryFiletime);Via Composer (in Horde, typically as a framework dependency):
composer require horde/mapiIn this installation: dev-FRAMEWORK_6_0 (Horde Framework 6).
cd vendor/horde/mapi
vendor/bin/phpunitTests live under test/Unnamespaced/ (MapiTest.php, TimezoneTest.php) with fixtures for GOIDs and FILETIME.