---
title: Validate and convert Julian dates
summary: Validate a Julian-format date string or integer and convert it to a `DateTime`, or convert a `DateTime` into Julian format.
status: draft
owner: Framework.NCore maintainers
last_reviewed: 2026-10-02
library: SAIF.NetCore.Common
reference_type: package
operation_id: OP-04
trigger: A consumer calls `Julian.IsValid`, `Julian.GetYear`, `Julian.ToDate`, or the `DateTime.ToJulian()` extension method.
---

# Validate and convert Julian dates

Validate a Julian-format date string or integer and convert it to a `DateTime`, or convert a `DateTime` into Julian format. This operation is triggered when a consumer calls `Julian.IsValid`, `Julian.GetYear`, `Julian.ToDate`, or the `DateTime.ToJulian()` extension method.

| Field | Value |
| --- | --- |
| **Operation ID** | OP-04 |
| **Trigger** | A consumer calls `Julian.IsValid`, `Julian.GetYear`, `Julian.ToDate`, or the `DateTime.ToJulian()` extension method. |
| **Inputs** | `julian` (string or int), optional `millenium` (int), `yearLength` (int, for `ToJulian`) |
| **Outputs** | `IsValid` returns `bool` with an out message; `ToDate` returns `DateTime`; `ToJulian` returns `string`. |
| **Primary components** | `Julian.IsValid`, `Julian.GetYear`, `Julian.ToDate`, `Julian.ToJulian` |
| **Entry point** | `src/SAIF.NetCore.Common/SAIF.NetCore.Common/SAIF.NetCore.Common/Helpers/Julian.cs` |

## Steps

1. Validate that the input contains at least 4 digits. ([BR-017](../business-rules.md#br-017-a-julian-value-requires-at-least-four-digits-and-a-day-of-year-within-the-valid-range))
   - On failure: `IsValid` returns `false` with the message "The julian must contain at least 4 numbers". `ToDate` throws `ApplicationException`.
2. Derive the year: if `millenium < 1000`, derive it from the first digit of the current year times 1000; the year digits are those before the final 3, with `millenium` added when under 1000.
3. Compute `isLeapYear` using `System.Globalization.JulianCalendar.IsLeapYear`.
   - `JulianCalendar` treats every 4th year as a leap year, which does not match Gregorian century-year rules (for example 2100)*.
4. For `ToDate`: reject a day-of-year greater than 365 (or 366 in a leap year). ([BR-017](../business-rules.md#br-017-a-julian-value-requires-at-least-four-digits-and-a-day-of-year-within-the-valid-range))
   - On failure: throws `ApplicationException` with a message that says "must be < {validDaysInYear}" even though the actual check is `>`, so a day equal to 365/366 is accepted*.
5. For `ToDate`: return 1 January of the derived year plus (day − 1) days.
   - A day-of-year value of `000` is not rejected and yields 31 December of the previous year*.
6. For `ToJulian(date, yearLength=2)`: when `yearLength` equals exactly `2`, convert `date.ToString("yy")` to an `int` and concatenate it with the 3-digit day of year as a `string` — the `int` conversion drops any leading zero from the two-digit year (for example January 1, 2005 produces `"5001"`, not `"05001"`). For any other `yearLength` value, the full numeric `date.Year`, without zero padding, is concatenated with the 3-digit day instead (for example January 1, 2005 produces `"2005001"`, while January 1 of year 1 produces `"1001"`, not a zero-padded `"0001001"`). This member is independent of `IsValid`/`ToDate` and does not call either.

## Process flow

The following diagram visualizes this operation's control flow, split into its four independent entry points — `IsValid` ends at its own `bool` return and is never followed by year/day conversion on its own; `ToDate` separately re-derives the year and builds a `DateTime`; `ToJulian` is a wholly independent forward conversion.

```mermaid
flowchart TD
	subgraph IV["Julian.IsValid(julian)"]
		IV_Start([Call IsValid]) --> IV_Check{At least 4 digits?}
		IV_Check -->|No| IV_False(["Return false<br>with message"])
		IV_Check -->|Yes| IV_True(["Return true"])
	end

	subgraph GY["Julian.GetYear(julian)"]
		GY_Start([Call GetYear]) --> GY_Derive["Derive year from<br>digits before final 3"]
		GY_Derive --> GY_Leap["Compute isLeapYear<br>via JulianCalendar"]
		GY_Leap --> GY_Return(["Return year,<br>out isLeapYear"])
	end

	subgraph TD["Julian.ToDate(julian)"]
		TD_Start([Call ToDate]) --> TD_Valid{IsValid?}
		TD_Valid -->|No| TD_Throw1([Throw ApplicationException])
		TD_Valid -->|Yes| TD_Year["Call GetYear"]
		TD_Year --> TD_DayCheck{day-of-year within<br>valid days in year?}
		TD_DayCheck -->|No| TD_Throw2([Throw ApplicationException])
		TD_DayCheck -->|Yes| TD_Build(["Return Jan 1 of year<br>plus day minus 1"])
	end

	subgraph TJ["DateTime.ToJulian(yearLength)"]
		TJ_Start([Call ToJulian]) --> TJ_Check{yearLength == 2?}
		TJ_Check -->|Yes| TJ_Short["year = int of two-digit<br>yy, no leading zero"]
		TJ_Check -->|No| TJ_Full["year = full numeric<br>date.Year, no padding"]
		TJ_Short --> TJ_Build["Concatenate year with<br>3-digit day of year"]
		TJ_Full --> TJ_Build
		TJ_Build --> TJ_Return(["Return string"])
	end
```

## Failure and edge-case behavior

- Fewer than 4 digits: `IsValid` returns `false`; `ToDate` throws `ApplicationException`.
- Day-of-year greater than the valid days in year: throws `ApplicationException`, though the message text does not match the actual `>` comparison used*.
- Day-of-year `000`: accepted, yielding 31 December of the previous year*.
- Leap-year detection via `JulianCalendar` can diverge from Gregorian rules on century years such as 2100*.

## Related documentation
- [Operations and processes](../operations-processes.md)
- [Business rules](../business-rules.md)

> **Note:** Statements marked with `*` were inferred from code structure, naming
> conventions, or contextual evidence where direct confirmation was not available
> in source artifacts. Statements marked with `**` could not be confirmed; treat
> as unknown until verified.

## Sources
- `src/SAIF.NetCore.Common/SAIF.NetCore.Common/SAIF.NetCore.Common/Helpers/Julian.cs`
