Merge a date range into an existing date log with conflict resolution
MergeDateLogHandle merges a source date range into a destination date log, with conflict resolution selected by flags. It is a high-level wrapper that manages synchronization, file I/O and recovery for date log merge operations.
The function lets you consolidate multiple date logs or date ranges into a single authoritative date log. Two merge strategies are supported, selected by exactly one flag:
DATE_RANGE_MERGE_FAVOR_DEST): keep the destination’s GUIDs when dates overlap.DATE_RANGE_MERGE_FAVOR_SOURCE): replace the destination’s GUIDs with the source’s values when dates overlap.DATE_RANGE_MERGE_RESTORE_ON_FAILURE).
HRESULT _Must_inspect_result_ _Success_(SUCCEEDED(return)) MergeDateLogHandle(
_Inout_ _Pre_valid_ DATE_LOG_HANDLE hDestDateLog,
_In_ __readonly _Pre_valid_ DATE_RANGE_HANDLE hSourceDateLog,
_In_ DWORD dwFlags
);
This declaration matches datelog.h.
Standard C calling convention with SAL annotations for parameter validation and return value inspection.
[In, Out] DATE_LOG_HANDLE
Handle to the destination date log that is modified with the merged data.
InitializeDateLogHandle (HandleIsForDatelog) with a valid context and an associated holidays handle.ERR_INVALID_DATELOG_HANDLE.CloseDateLogHandle. If the merge fails with ERR_DATE_LOG_RESTORE_FAILED the destination context may be invalid.[In] DATE_RANGE_HANDLE
Read-only handle to the source date range that is merged into the destination.
HandleIsForRange) whose context holds populated date items. It can represent a subset or a complete date range.ERR_INVALID_DATELOG_HANDLE.[In] DWORD
Flags that control merge behavior. Exactly one conflict-resolution flag is required.
| Flag | Value and meaning |
|---|---|
DATE_RANGE_MERGE_FAVOR_DEST | 0x04 — Keep destination GUIDs in the overlapping date range. Useful when the destination is the authoritative source. |
DATE_RANGE_MERGE_FAVOR_SOURCE | 0x08 — Replace destination GUIDs with the source’s values in the overlapping range. Useful when the source has more recent or preferred data. |
DATE_RANGE_MERGE_RESTORE_ON_FAILURE | 0x10 — (Optional) Enable automatic recovery if the merge fails. Requires destination file info to be present; creates a backup of the original array and file regions before modifying and restores the original state on failure. |
DATE_RANGE_MERGE_FAVOR_DEST or DATE_RANGE_MERGE_FAVOR_SOURCE must be set.ERROR_INVALID_FLAGS.DATE_RANGE_MERGE_RESTORE_ON_FAILURE is optional and can be combined (OR) with either conflict-resolution flag.| Value | Meaning |
|---|---|
S_OK | The merge completed successfully; the destination date log has been updated. |
ERR_INVALID_DATELOG_HANDLE | The destination or source handle is invalid, is not a date log/range, or its context is NULL. |
ERROR_INVALID_FLAGS | Neither DATE_RANGE_MERGE_FAVOR_DEST nor DATE_RANGE_MERGE_FAVOR_SOURCE was specified. |
ERR_DATE_LOG_PENDING_IO | The required mutex locks could not be acquired within the 30-second timeout; another operation holds a lock or the system is under contention. Wait briefly and retry. |
HRESULT_FROM_WIN32(GetLastError()) | Generic synchronization error from a mutex or lock acquisition failure. |
ERROR_OUTOFMEMORY | Memory for the merge context or backup structures could not be allocated. |
ERROR_INVALID_HANDLE | The merge lock context allocation failed or an internal handle validation failed. |
ERROR_FILE_CORRUPT, ERR_DATELOG_ZERO_GAP, ERR_DATEITEM_NOT_FOUND, ERR_DATE_EARLIER_BASE, ERR_DATE_IS_FUTURE_DATE, ERR_MAX_DATELOG_REACHED | Returned from the internal MergeDateLogCores() step. |
ERR_DATE_LOG_RESTORE_FAILED | The array or collection could not be restored after a merge failure; the destination context may be invalid and the handle should be closed. |
ERR_DATE_FILE_RESTORE_FAILED | The file region could not be restored from backup after a failed merge; verify the file’s integrity. |
Test with SUCCEEDED/FAILED; the result must be inspected. The ERR_* codes are defined in date_error.h, which also documents ERR_DATELOG_MERGE_GAP (ranges neither overlap nor are contiguous), ERR_NOMERGE_BYFLAG, ERR_MERGE_NO_COMPLETE and INFO_MERGE_IS_COMPLETE for merge-related conditions. ERROR_INVALID_FLAGS, ERROR_OUTOFMEMORY, ERROR_INVALID_HANDLE and ERROR_FILE_CORRUPT are Win32 error codes, not library codes.
Locks are acquired in a fixed order and released in reverse order, which prevents circular waits between threads:
MERGE_LOCK_CONTEXT that tracks merge state.MergeDateLogCores() to perform the in-memory merge.bHasChanged = TRUE; see DateLogHandleHasChanged).MergeDateLogFileInfo().If the destination has associated file info, the file handle is duplicated for safe concurrent I/O, the affected regions are locked, the merged items are written at the appropriate offsets, the number of written bytes is verified, and the regions are always unlocked before the locks are released.
If DATE_RANGE_MERGE_RESTORE_ON_FAILURE is set and the merge or file update fails, the function checks whether the array backup was created, restores the in-memory array, restores the file regions from backup if the file was modified, and marks the context invalid if restoration itself fails.
In the __finally block all acquired locks are released and temporary merge resources (backups, duplicated file handles, merge lock context) are freed.
The merge is thread-safe: both handles’ mutexes are acquired with a 30-second timeout using a consistent order, so two threads merging the same pair of handles in opposite directions cannot deadlock. If a lock cannot be acquired in time, ERR_DATE_LOG_PENDING_IO is returned and the operation can be retried. Other operations on the same context (including CloseDateLogHandle, which refuses to close a handle that is in use) are serialized against the merge.
CloseDateLogHandle; the source remains valid and unchanged after a successful merge.DuplicateDateLogHandle) observe the merged data, because the date items live in the shared context.ERR_DATE_LOG_RESTORE_FAILED should be closed and re-created from its data source.LoadDateLogHandle; the source must contain date items and the destination must have an associated holidays handle.ERR_DATELOG_MERGE_GAP applies (date_error.h).DateLogHandleHasChanged reports TRUE for the destination and callers use it to decide whether the file must be rewritten.ConvertDateLogHandle promotes a verified range to a date log.DATE_LOG_MAX (0x591C8 = 370,120) items; otherwise ERR_MAX_DATELOG_REACHED is returned.LoadDateLogHandleConvertDateLogHandleDateLogHandleHasChangedDuplicateDateLogHandleInitializeDateLogHandleGetDateCollectionLengthIsValidDateLogHandleCloseDateLogHandleThe sample builds a destination date log for 2025-01-02 through 2025-02-28 and a source date range for 2025-02-03 through 2025-03-31, merges the range into the log favoring the source’s GUIDs, retries a few times if the log is busy (ERR_DATE_LOG_PENDING_IO), and reports the item counts before and after. Both collections and the holidays handle are closed exactly once on every path.
#include <Windows.h>
#include <stdio.h>
#include "datelog.h"
static BOOL MakeFileTime(WORD wYear, WORD wMonth, WORD wDay, PFILETIME pFt)
{
SYSTEMTIME st = { 0 };
st.wYear = wYear;
st.wMonth = wMonth;
st.wDay = wDay;
return SystemTimeToFileTime(&st, pFt);
}
/* Creates a holidays handle and loads it with the range [first, last]. */
static HRESULT BuildHolidays(PFILETIME pFirst, PFILETIME pLast, HOLIDAYS_HANDLE *phHolidays)
{
HRESULT hr = S_OK;
HOLIDAYS_HANDLE hHolidays = NULL;
DATE_ITEM_HANDLE hFirst = NULL;
DATE_ITEM_HANDLE hLast = NULL;
*phHolidays = NULL;
hr = InitializeDateLogHandle(NULL, DATE_LOG_MAX, NULL, &hHolidays, HandleIsForHolidays);
if (FAILED(hr)) goto cleanup;
hr = InitializeDateItem(hHolidays, pFirst, NULL, &hFirst);
if (FAILED(hr)) goto cleanup;
hr = InitializeDateItem(hHolidays, pLast, NULL, &hLast);
if (FAILED(hr)) goto cleanup;
hr = LoadDateLogHandle(NULL, hFirst, hLast, hHolidays, NULL, 0);
if (FAILED(hr)) goto cleanup;
*phHolidays = hHolidays;
hHolidays = NULL;
cleanup:
FreeDateItem(hLast);
FreeDateItem(hFirst);
if (hHolidays != NULL) {
(void)CloseDateLogHandle(hHolidays);
}
return hr;
}
/* Creates a date log or range handle of the given type and generates [first, last] into it. */
static HRESULT BuildCollection(HOLIDAYS_HANDLE hHolidays, DateHandleType type,
PFILETIME pFirst, PFILETIME pLast, DATE_LOG_HANDLE *phCollection)
{
HRESULT hr = S_OK;
DATE_LOG_HANDLE hCollection = NULL;
DATE_ITEM_HANDLE hFirst = NULL;
DATE_ITEM_HANDLE hLast = NULL;
*phCollection = NULL;
hr = InitializeDateLogHandle(NULL, DATE_LOG_MAX, hHolidays, &hCollection, type);
if (FAILED(hr)) goto cleanup;
hr = InitializeDateItem(hHolidays, pFirst, NULL, &hFirst);
if (FAILED(hr)) goto cleanup;
hr = InitializeDateItem(hHolidays, pLast, NULL, &hLast);
if (FAILED(hr)) goto cleanup;
hr = LoadDateLogHandle(NULL, hFirst, hLast, hCollection, NULL, 0);
if (FAILED(hr)) goto cleanup;
*phCollection = hCollection;
hCollection = NULL;
cleanup:
FreeDateItem(hLast);
FreeDateItem(hFirst);
if (hCollection != NULL) {
(void)CloseDateLogHandle(hCollection);
}
return hr;
}
int main(void)
{
int exitCode = 1;
HRESULT hr = S_OK;
FILETIME ftFirst = { 0 };
FILETIME ftDestLast = { 0 };
FILETIME ftSourceFirst = { 0 };
FILETIME ftLast = { 0 };
HOLIDAYS_HANDLE hHolidays = NULL;
DATE_LOG_HANDLE hDest = NULL;
DATE_RANGE_HANDLE hSource = NULL;
DWORD dwBefore = 0;
DWORD dwAfter = 0;
int attempt = 0;
if (!MakeFileTime(2025, 1, 2, &ftFirst) ||
!MakeFileTime(2025, 2, 28, &ftDestLast) ||
!MakeFileTime(2025, 2, 3, &ftSourceFirst) ||
!MakeFileTime(2025, 3, 31, &ftLast)) {
printf("Invalid calendar date.\n");
return 1;
}
/* The holidays handle covers the whole span used by both collections. */
hr = BuildHolidays(&ftFirst, &ftLast, &hHolidays);
if (FAILED(hr)) {
printf("BuildHolidays failed: 0x%08lX\n", (unsigned long)hr);
goto cleanup;
}
/* Destination: 2025-01-02 .. 2025-02-28. Source range: 2025-02-03 .. 2025-03-31 (overlaps). */
hr = BuildCollection(hHolidays, HandleIsForDatelog, &ftFirst, &ftDestLast, &hDest);
if (FAILED(hr)) {
printf("Building the destination failed: 0x%08lX\n", (unsigned long)hr);
goto cleanup;
}
hr = BuildCollection(hHolidays, HandleIsForRange, &ftSourceFirst, &ftLast, &hSource);
if (FAILED(hr)) {
printf("Building the source range failed: 0x%08lX\n", (unsigned long)hr);
goto cleanup;
}
dwBefore = GetDateCollectionLength(hDest);
if (dwBefore > DATE_LOG_MAX) {
printf("GetDateCollectionLength reported a failure.\n");
goto cleanup;
}
/* Exactly one conflict-resolution flag is required. DATE_RANGE_MERGE_RESTORE_ON_FAILURE is
optional and needs file information on the destination, so it is not used here. */
for (attempt = 0; attempt < 3; attempt++) {
hr = MergeDateLogHandle(hDest, hSource, DATE_RANGE_MERGE_FAVOR_SOURCE);
if (hr != ERR_DATE_LOG_PENDING_IO) {
break;
}
printf("The date log is busy; retrying...\n");
Sleep(100);
}
if (FAILED(hr)) {
if (hr == ERR_DATELOG_MERGE_GAP) {
printf("The ranges neither overlap nor touch.\n");
} else if (hr == ERR_DATE_LOG_RESTORE_FAILED) {
printf("The merge failed and the destination could not be restored; recreate it.\n");
} else {
printf("MergeDateLogHandle failed: 0x%08lX\n", (unsigned long)hr);
}
goto cleanup;
}
dwAfter = GetDateCollectionLength(hDest);
printf("Merged: the destination grew from %lu to %lu date items.\n",
(unsigned long)dwBefore, (unsigned long)dwAfter);
if (DateLogHandleHasChanged(hDest)) {
printf("The destination has unsaved changes; write the file if one is attached.\n");
}
exitCode = 0;
cleanup:
if (hSource != NULL) {
(void)CloseDateLogHandle(hSource);
}
if (hDest != NULL) {
(void)CloseDateLogHandle(hDest);
}
if (hHolidays != NULL) {
(void)CloseDateLogHandle(hHolidays);
}
return exitCode;
}