Apply comprehensive fixes from code reviews including: - Extract shared utilities (SqlFormatHelper, CellValueConverter, DbDestinationBase) - Add interface abstractions (IAuthenticationService, IDatabaseMigrator, IMisQueryBuilder) - Implement SecureStore for encrypted secrets storage - Fix error handling with proper HTTP status codes and logging - Optimize double enumeration in DevEtlRegistry - Add DataSync.Dev README for developer onboarding - Extract filter panel base classes to reduce duplication - Update code review docs to mark all issues as fixed
4.0 KiB
JDE Date Conversion Pattern
Overview
This document explains the JDE (JD Edwards) date conversion pattern used throughout the data model classes. This pattern appears in 15+ model files and is intentionally repeated rather than abstracted—it is required boilerplate for Dapper ORM column mapping.
JDE Date Format
JD Edwards stores dates in a proprietary Julian date format called CYYDDD:
| Component | Description | Example |
|---|---|---|
| C | Century indicator (0=1900s, 1=2000s) | 1 |
| YY | Year within century | 25 |
| DDD | Day of year (001-366) | 019 |
Example: January 19, 2025 = 125019
- Century: 1 (2000s)
- Year: 25 (2025)
- Day: 019 (19th day of year)
JDE also stores time separately as HHMMSS:
- 2:30:45 PM =
143045
The Pattern
Why Private Backing Fields?
Dapper requires a settable property to map database columns. Since we want:
- The raw JDE integer values mapped from the database
- A clean
DateTime?exposed to consumers - No public setters on the converted dates
We use this pattern:
using JdeScoping.Core.Helpers;
public class WorkOrder
{
// ... other properties ...
/// <summary>
/// JDE date of last update to record (private backing field for Dapper mapping)
/// </summary>
private int LastUpdateDate { get; set; }
/// <summary>
/// JDE time of day of last update to record (private backing field for Dapper mapping)
/// </summary>
private int LastUpdateTime { get; set; }
/// <summary>
/// Timestamp of last update to record (computed from JDE date/time)
/// </summary>
public DateTime? LastUpdateDt => JdeDateConverter.ToDateTime(LastUpdateDate, LastUpdateTime);
}
Why This Cannot Be Abstracted Further
-
Dapper requires properties on the model class itself - It cannot map to properties on a composed helper object.
-
Each model has different date fields - WorkOrder has
LastUpdateDate/Time, WorkOrderStep hasEndDate/Time, etc. The field names vary per entity. -
Some models have date-only, some have date+time - The pattern adapts to each entity's needs.
-
Private backing fields must match SQL column aliases - Dapper maps by property name matching column name.
Centralized Helper
The actual conversion logic is centralized in:
NEW/src/JdeScoping.Core/Helpers/JdeDateConverter.cs
This static helper provides:
ToDateTime(int jdeDate)- Date only conversionToDateTime(int jdeDate, int jdeTime)- Date + time conversionToJdeDate(this DateTime)- Convert DateTime to JDE dateToJdeTime(this DateTime)- Convert DateTime to JDE time
Files Using This Pattern
The pattern appears in these model files:
Models/Inventory/Item.csModels/Inventory/Lot.csModels/Inventory/LotUsage.csModels/Lookup/StatusCode.csModels/Organization/Branch.csModels/Organization/JdeUser.csModels/Organization/OrgHierarchy.csModels/Organization/ProfitCenter.csModels/Organization/RouteMaster.csModels/Organization/WorkCenter.csModels/WorkOrders/WorkOrder.csModels/WorkOrders/WorkOrderComponent.csModels/WorkOrders/WorkOrderRouting.csModels/WorkOrders/WorkOrderStep.csModels/WorkOrders/WorkOrderTime.cs
Why Not Records or Init-Only Properties?
Using records with init-only properties would prevent Dapper from setting the values since Dapper uses property setters for mapping. The private setter approach is the cleanest pattern that:
- Hides raw JDE integers from consumers
- Exposes clean nullable DateTime
- Works with Dapper's property-based mapping
Summary
This is not code duplication - it is the minimum required boilerplate for:
- Database column mapping (private field + Dapper)
- Type conversion (JdeDateConverter helper)
- Clean public API (computed DateTime property)
Each repetition is 3 lines of trivial code that cannot be abstracted without breaking Dapper mapping or over-engineering the solution.