Files
jdescopingtool/DOCUMENTATION/Patterns/JdeDateConversionPattern.md
T
Joseph Doherty 604bfe919c refactor: address code review findings across all projects
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
2026-01-19 11:05:36 -05:00

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:

  1. The raw JDE integer values mapped from the database
  2. A clean DateTime? exposed to consumers
  3. 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

  1. Dapper requires properties on the model class itself - It cannot map to properties on a composed helper object.

  2. Each model has different date fields - WorkOrder has LastUpdateDate/Time, WorkOrderStep has EndDate/Time, etc. The field names vary per entity.

  3. Some models have date-only, some have date+time - The pattern adapts to each entity's needs.

  4. 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 conversion
  • ToDateTime(int jdeDate, int jdeTime) - Date + time conversion
  • ToJdeDate(this DateTime) - Convert DateTime to JDE date
  • ToJdeTime(this DateTime) - Convert DateTime to JDE time

Files Using This Pattern

The pattern appears in these model files:

  • Models/Inventory/Item.cs
  • Models/Inventory/Lot.cs
  • Models/Inventory/LotUsage.cs
  • Models/Lookup/StatusCode.cs
  • Models/Organization/Branch.cs
  • Models/Organization/JdeUser.cs
  • Models/Organization/OrgHierarchy.cs
  • Models/Organization/ProfitCenter.cs
  • Models/Organization/RouteMaster.cs
  • Models/Organization/WorkCenter.cs
  • Models/WorkOrders/WorkOrder.cs
  • Models/WorkOrders/WorkOrderComponent.cs
  • Models/WorkOrders/WorkOrderRouting.cs
  • Models/WorkOrders/WorkOrderStep.cs
  • Models/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:

  1. Hides raw JDE integers from consumers
  2. Exposes clean nullable DateTime
  3. 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.