This is an automated email from the ASF dual-hosted git repository.

hansva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git


The following commit(s) were added to refs/heads/main by this push:
     new 7eb4f5c7ea Issue #2584 : Add Calculator conversions between JDE Julian 
and ISO dates (#8257)
7eb4f5c7ea is described below

commit 7eb4f5c7eacfdc7ff3c8cc55a0ce8b8c8f9af000
Author: Matt Casters <[email protected]>
AuthorDate: Fri Sep 4 15:41:13 2026 +0200

    Issue #2584 : Add Calculator conversions between JDE Julian and ISO dates 
(#8257)
---
 .../ROOT/pages/pipeline/transforms/calculator.adoc |   7 +
 .../transforms/0011-calculator-jde-julian.hpl      | 193 ++++++++++++
 .../datasets/golden-calculator-jde-julian.csv      |   8 +
 .../dataset/golden-calculator-jde-julian.json      |  40 +++
 .../unit-test/0011-calculator-jde-julian UNIT.json |  41 +++
 .../transforms/calculator/CalculationType.java     |   4 +
 .../calculations/date/DateToJdeJulian.java         |  54 ++++
 .../calculator/calculations/date/JdeJulian.java    |  83 ++++++
 .../calculations/date/JdeJulianToDate.java         |  54 ++++
 .../calculator/messages/messages_en_US.properties  |   6 +-
 .../samples/transforms/calculator-jde-julian.hpl   | 208 +++++++++++++
 .../transforms/calculator/JdeJulianTest.java       | 323 +++++++++++++++++++++
 12 files changed, 1020 insertions(+), 1 deletion(-)

diff --git 
a/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/calculator.adoc 
b/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/calculator.adoc
index 64a3b2f1aa..7c3151d184 100644
--- 
a/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/calculator.adoc
+++ 
b/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/calculator.adoc
@@ -203,6 +203,13 @@ TIP: Only integer values for B are supported. If you need 
non-integer calculatio
 |Base64 Decode|BASE64_DECODE|Decode a Base64 encoded string. Supports both 
padded or non-padded input.
 |First day of month (A) date|FIRST_DAY_OF_MONTH|Set the day of field (A) to 1.
 |Last day of month (A) date|LAST_DAY_OF_MONTH|Set the day of field (A) to the 
last day of the given month.
+|Date A to JDE Julian (CYYDDD)|DATE_TO_JDE_JULIAN a|Convert date A to a JD 
Edwards Julian date stored as CYYDDD. +
+
+C is the century relative to 1900 (0 = 1900-1999, 1 = 2000-2099), YY is the 
two-digit year, and DDD is the day of the year (001-366). +
+January 1, 1995 becomes `95001`. January 1, 2009 becomes `109001`. Years 
before 1900 are not supported.
+|JDE Julian A (CYYDDD) to Date|JDE_JULIAN_TO_DATE a|Convert a JD Edwards 
Julian date in field A to a Date. +
+
+Field A can be an Integer, Number or String in CYYDDD format. Five-digit 
values such as `95001` are treated as 1 January 1995.
 |Encode a URL|URL_ENCODE|Encode a URL to have browser/webservice save url 
calls.
 |Decode a URL|URL_DECODE|Decode a URL.
 |===
diff --git a/integration-tests/transforms/0011-calculator-jde-julian.hpl 
b/integration-tests/transforms/0011-calculator-jde-julian.hpl
new file mode 100644
index 0000000000..fd082ed819
--- /dev/null
+++ b/integration-tests/transforms/0011-calculator-jde-julian.hpl
@@ -0,0 +1,193 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+-->
+<pipeline>
+  <info>
+    <name>0011-calculator-jde-julian</name>
+    <name_sync_with_filename>Y</name_sync_with_filename>
+    <description/>
+    <extended_description/>
+    <pipeline_version/>
+    <pipeline_type>Normal</pipeline_type>
+    <parameters>
+    </parameters>
+    <capture_transform_performance>N</capture_transform_performance>
+    
<transform_performance_capturing_delay>1000</transform_performance_capturing_delay>
+    
<transform_performance_capturing_size_limit>100</transform_performance_capturing_size_limit>
+    <created_user>-</created_user>
+    <created_date>2026/09/03 00:00:00.000</created_date>
+    <modified_user>-</modified_user>
+    <modified_date>2026/09/03 00:00:00.000</modified_date>
+    <key_for_session_key/>
+    <is_key_private>N</is_key_private>
+  </info>
+  <notepads>
+  </notepads>
+  <order>
+    <hop>
+      <from>test-data</from>
+      <to>Calculator</to>
+      <enabled>Y</enabled>
+    </hop>
+    <hop>
+      <from>Calculator</from>
+      <to>Verify</to>
+      <enabled>Y</enabled>
+    </hop>
+  </order>
+  <transform>
+    <name>test-data</name>
+    <type>DataGrid</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <fields>
+      <field>
+        <name>isoDate</name>
+        <type>Date</type>
+        <format>yyyy-MM-dd</format>
+        <currency/>
+        <decimal/>
+        <group/>
+        <length>-1</length>
+        <precision>-1</precision>
+        <set_empty_string>N</set_empty_string>
+      </field>
+      <field>
+        <name>julianIn</name>
+        <type>Integer</type>
+        <format/>
+        <currency/>
+        <decimal/>
+        <group/>
+        <length>-1</length>
+        <precision>-1</precision>
+        <set_empty_string>N</set_empty_string>
+      </field>
+    </fields>
+    <data>
+      <line>
+        <item>1995-01-01</item>
+        <item>95001</item>
+      </line>
+      <line>
+        <item>1997-11-04</item>
+        <item>97308</item>
+      </line>
+      <line>
+        <item>2009-01-01</item>
+        <item>109001</item>
+      </line>
+      <line>
+        <item>2011-01-13</item>
+        <item>111013</item>
+      </line>
+      <line>
+        <item>2000-08-23</item>
+        <item>100236</item>
+      </line>
+      <line>
+        <item>2000-12-31</item>
+        <item>100366</item>
+      </line>
+      <line>
+        <item>1999-12-31</item>
+        <item>99365</item>
+      </line>
+    </data>
+    <attributes/>
+    <GUI>
+      <xloc>96</xloc>
+      <yloc>80</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>Calculator</name>
+    <type>Calculator</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <failIfNoFile>Y</failIfNoFile>
+    <calculation>
+      <field_name>julianOut</field_name>
+      <calc_type>DATE_TO_JDE_JULIAN</calc_type>
+      <field_a>isoDate</field_a>
+      <field_b/>
+      <field_c/>
+      <value_type>Integer</value_type>
+      <value_length>-1</value_length>
+      <value_precision>-1</value_precision>
+      <remove>N</remove>
+      <conversion_mask/>
+      <decimal_symbol/>
+      <grouping_symbol/>
+      <currency_symbol/>
+    </calculation>
+    <calculation>
+      <field_name>isoOut</field_name>
+      <calc_type>JDE_JULIAN_TO_DATE</calc_type>
+      <field_a>julianIn</field_a>
+      <field_b/>
+      <field_c/>
+      <value_type>Date</value_type>
+      <value_length>-1</value_length>
+      <value_precision>-1</value_precision>
+      <remove>N</remove>
+      <conversion_mask>yyyy-MM-dd</conversion_mask>
+      <decimal_symbol/>
+      <grouping_symbol/>
+      <currency_symbol/>
+    </calculation>
+    <attributes/>
+    <GUI>
+      <xloc>240</xloc>
+      <yloc>80</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>Verify</name>
+    <type>Dummy</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <attributes/>
+    <GUI>
+      <xloc>416</xloc>
+      <yloc>80</yloc>
+    </GUI>
+  </transform>
+  <transform_error_handling>
+  </transform_error_handling>
+  <attributes/>
+</pipeline>
diff --git 
a/integration-tests/transforms/datasets/golden-calculator-jde-julian.csv 
b/integration-tests/transforms/datasets/golden-calculator-jde-julian.csv
new file mode 100644
index 0000000000..8b57c331ce
--- /dev/null
+++ b/integration-tests/transforms/datasets/golden-calculator-jde-julian.csv
@@ -0,0 +1,8 @@
+isoDate,julianIn,julianOut,isoOut
+1995-01-01,95001,95001,1995-01-01
+1997-11-04,97308,97308,1997-11-04
+2009-01-01,109001,109001,2009-01-01
+2011-01-13,111013,111013,2011-01-13
+2000-08-23,100236,100236,2000-08-23
+2000-12-31,100366,100366,2000-12-31
+1999-12-31,99365,99365,1999-12-31
diff --git 
a/integration-tests/transforms/metadata/dataset/golden-calculator-jde-julian.json
 
b/integration-tests/transforms/metadata/dataset/golden-calculator-jde-julian.json
new file mode 100644
index 0000000000..f554ed8f6a
--- /dev/null
+++ 
b/integration-tests/transforms/metadata/dataset/golden-calculator-jde-julian.json
@@ -0,0 +1,40 @@
+{
+  "base_filename": "golden-calculator-jde-julian.csv",
+  "name": "golden-calculator-jde-julian",
+  "description": "",
+  "dataset_fields": [
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 3,
+      "field_precision": -1,
+      "field_format": "yyyy-MM-dd",
+      "field_name": "isoDate"
+    },
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 5,
+      "field_precision": 0,
+      "field_format": "####0;-####0",
+      "field_name": "julianIn"
+    },
+    {
+      "field_comment": "DATE_TO_JDE_JULIAN",
+      "field_length": -1,
+      "field_type": 5,
+      "field_precision": 0,
+      "field_format": "####0;-####0",
+      "field_name": "julianOut"
+    },
+    {
+      "field_comment": "JDE_JULIAN_TO_DATE",
+      "field_length": -1,
+      "field_type": 3,
+      "field_precision": -1,
+      "field_format": "yyyy-MM-dd",
+      "field_name": "isoOut"
+    }
+  ],
+  "folder_name": ""
+}
diff --git 
a/integration-tests/transforms/metadata/unit-test/0011-calculator-jde-julian 
UNIT.json 
b/integration-tests/transforms/metadata/unit-test/0011-calculator-jde-julian 
UNIT.json
new file mode 100644
index 0000000000..2ae7d73f15
--- /dev/null
+++ 
b/integration-tests/transforms/metadata/unit-test/0011-calculator-jde-julian 
UNIT.json      
@@ -0,0 +1,41 @@
+{
+  "variableValues": [],
+  "database_replacements": [],
+  "autoOpening": false,
+  "basePath": "",
+  "golden_data_sets": [
+    {
+      "field_mappings": [
+        {
+          "transform_field": "isoDate",
+          "data_set_field": "isoDate"
+        },
+        {
+          "transform_field": "julianIn",
+          "data_set_field": "julianIn"
+        },
+        {
+          "transform_field": "julianOut",
+          "data_set_field": "julianOut"
+        },
+        {
+          "transform_field": "isoOut",
+          "data_set_field": "isoOut"
+        }
+      ],
+      "field_order": [
+        "isoDate",
+        "julianIn"
+      ],
+      "transform_name": "Verify",
+      "data_set_name": "golden-calculator-jde-julian"
+    }
+  ],
+  "input_data_sets": [],
+  "name": "0011-calculator-jde-julian UNIT",
+  "description": "",
+  "trans_test_tweaks": [],
+  "persist_filename": "",
+  "pipeline_filename": "./0011-calculator-jde-julian.hpl",
+  "test_type": "UNIT_TEST"
+}
diff --git 
a/plugins/transforms/calculator/src/main/java/org/apache/hop/pipeline/transforms/calculator/CalculationType.java
 
b/plugins/transforms/calculator/src/main/java/org/apache/hop/pipeline/transforms/calculator/CalculationType.java
index 27a338de3e..e6c1f6ac7e 100644
--- 
a/plugins/transforms/calculator/src/main/java/org/apache/hop/pipeline/transforms/calculator/CalculationType.java
+++ 
b/plugins/transforms/calculator/src/main/java/org/apache/hop/pipeline/transforms/calculator/CalculationType.java
@@ -46,12 +46,14 @@ import 
org.apache.hop.pipeline.transforms.calculator.calculations.date.AddMonths
 import 
org.apache.hop.pipeline.transforms.calculator.calculations.date.AddSeconds;
 import 
org.apache.hop.pipeline.transforms.calculator.calculations.date.AddTimeToDate;
 import 
org.apache.hop.pipeline.transforms.calculator.calculations.date.DateDiff;
+import 
org.apache.hop.pipeline.transforms.calculator.calculations.date.DateToJdeJulian;
 import 
org.apache.hop.pipeline.transforms.calculator.calculations.date.DateWorkingDiff;
 import 
org.apache.hop.pipeline.transforms.calculator.calculations.date.DayOfMonth;
 import 
org.apache.hop.pipeline.transforms.calculator.calculations.date.DayOfWeek;
 import 
org.apache.hop.pipeline.transforms.calculator.calculations.date.DayOfYear;
 import 
org.apache.hop.pipeline.transforms.calculator.calculations.date.FirstDayOfMonth;
 import 
org.apache.hop.pipeline.transforms.calculator.calculations.date.HourOfDay;
+import 
org.apache.hop.pipeline.transforms.calculator.calculations.date.JdeJulianToDate;
 import 
org.apache.hop.pipeline.transforms.calculator.calculations.date.LastDayOfMonth;
 import 
org.apache.hop.pipeline.transforms.calculator.calculations.date.MinuteOfHour;
 import 
org.apache.hop.pipeline.transforms.calculator.calculations.date.MonthOfDate;
@@ -210,6 +212,8 @@ public enum CalculationType implements 
IEnumHasCodeAndDescription {
   BASE64_DECODE(new Base64Decode()),
   FIRST_DAY_OF_MONTH(new FirstDayOfMonth()),
   LAST_DAY_OF_MONTH(new LastDayOfMonth()),
+  DATE_TO_JDE_JULIAN(new DateToJdeJulian()),
+  JDE_JULIAN_TO_DATE(new JdeJulianToDate()),
   URL_ENCODE(new UrlEncode()),
   URL_DECODE(new UrlDecode());
 
diff --git 
a/plugins/transforms/calculator/src/main/java/org/apache/hop/pipeline/transforms/calculator/calculations/date/DateToJdeJulian.java
 
b/plugins/transforms/calculator/src/main/java/org/apache/hop/pipeline/transforms/calculator/calculations/date/DateToJdeJulian.java
new file mode 100644
index 0000000000..ceb979efad
--- /dev/null
+++ 
b/plugins/transforms/calculator/src/main/java/org/apache/hop/pipeline/transforms/calculator/calculations/date/DateToJdeJulian.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.pipeline.transforms.calculator.calculations.date;
+
+import org.apache.hop.core.exception.HopValueException;
+import org.apache.hop.core.row.IValueMeta;
+import org.apache.hop.i18n.BaseMessages;
+import org.apache.hop.pipeline.transforms.calculator.CalculationInput;
+import org.apache.hop.pipeline.transforms.calculator.CalculationOutput;
+import org.apache.hop.pipeline.transforms.calculator.ICalculation;
+
+public class DateToJdeJulian implements ICalculation {
+  private static final String description =
+      BaseMessages.getString(PKG, 
"CalculatorMetaFunction.CalcFunctions.DateToJdeJulian");
+
+  @Override
+  public String getCode() {
+    return "DATE_TO_JDE_JULIAN";
+  }
+
+  @Override
+  public String getDescription() {
+    return description;
+  }
+
+  @Override
+  public int getDefaultResultType() {
+    return IValueMeta.TYPE_INTEGER;
+  }
+
+  @Override
+  public CalculationOutput calculate(CalculationInput in) throws 
HopValueException {
+    Object value = null;
+    if (in.metaA != null && !in.metaA.isNull(in.dataA)) {
+      value = JdeJulian.fromDate(in.metaA.getDate(in.dataA));
+    }
+    return new CalculationOutput(getDefaultResultType(), value);
+  }
+}
diff --git 
a/plugins/transforms/calculator/src/main/java/org/apache/hop/pipeline/transforms/calculator/calculations/date/JdeJulian.java
 
b/plugins/transforms/calculator/src/main/java/org/apache/hop/pipeline/transforms/calculator/calculations/date/JdeJulian.java
new file mode 100644
index 0000000000..0b85c4777b
--- /dev/null
+++ 
b/plugins/transforms/calculator/src/main/java/org/apache/hop/pipeline/transforms/calculator/calculations/date/JdeJulian.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.pipeline.transforms.calculator.calculations.date;
+
+import java.text.SimpleDateFormat;
+import java.time.DateTimeException;
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.util.Calendar;
+import java.util.Date;
+import org.apache.hop.core.exception.HopValueException;
+import org.apache.hop.i18n.BaseMessages;
+import org.apache.hop.pipeline.transforms.calculator.ICalculation;
+
+/**
+ * Converts between a {@link Date} and a JD Edwards Julian date stored as 
CYYDDD.
+ *
+ * <p>C is the century relative to 1900 (0 = 1900-1999, 1 = 2000-2099), YY is 
the two-digit year,
+ * and DDD is the day of the year (001-366). Examples: 95001 is 1995-01-01, 
109001 is 2009-01-01.
+ */
+public final class JdeJulian {
+  private JdeJulian() {}
+
+  public static Long fromDate(Date date) throws HopValueException {
+    if (date == null) {
+      return null;
+    }
+
+    Calendar calendar = Calendar.getInstance();
+    calendar.setTime(date);
+    int year = calendar.get(Calendar.YEAR);
+    if (year < 1900) {
+      throw new HopValueException(
+          BaseMessages.getString(
+              ICalculation.PKG, "Calculator.Error.DateBeforeJdeEpoch", 
formatDate(date)));
+    }
+
+    return (year - 1900) * 1000L + calendar.get(Calendar.DAY_OF_YEAR);
+  }
+
+  public static Date toDate(Long julian) throws HopValueException {
+    if (julian == null) {
+      return null;
+    }
+
+    long yearLong = 1900L + julian / 1000L;
+    int dayOfYear = (int) (julian % 1000L);
+    if (julian < 1L || yearLong < 1900L || yearLong > LocalDate.MAX.getYear()) 
{
+      throw invalidJulian(julian);
+    }
+
+    try {
+      LocalDate localDate = LocalDate.ofYearDay((int) yearLong, dayOfYear);
+      return 
Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
+    } catch (DateTimeException e) {
+      throw invalidJulian(julian);
+    }
+  }
+
+  private static HopValueException invalidJulian(long julian) {
+    return new HopValueException(
+        BaseMessages.getString(ICalculation.PKG, 
"Calculator.Error.InvalidJdeJulian", julian));
+  }
+
+  private static String formatDate(Date date) {
+    return new SimpleDateFormat("yyyy-MM-dd").format(date);
+  }
+}
diff --git 
a/plugins/transforms/calculator/src/main/java/org/apache/hop/pipeline/transforms/calculator/calculations/date/JdeJulianToDate.java
 
b/plugins/transforms/calculator/src/main/java/org/apache/hop/pipeline/transforms/calculator/calculations/date/JdeJulianToDate.java
new file mode 100644
index 0000000000..e5e798a791
--- /dev/null
+++ 
b/plugins/transforms/calculator/src/main/java/org/apache/hop/pipeline/transforms/calculator/calculations/date/JdeJulianToDate.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.pipeline.transforms.calculator.calculations.date;
+
+import org.apache.hop.core.exception.HopValueException;
+import org.apache.hop.core.row.IValueMeta;
+import org.apache.hop.i18n.BaseMessages;
+import org.apache.hop.pipeline.transforms.calculator.CalculationInput;
+import org.apache.hop.pipeline.transforms.calculator.CalculationOutput;
+import org.apache.hop.pipeline.transforms.calculator.ICalculation;
+
+public class JdeJulianToDate implements ICalculation {
+  private static final String description =
+      BaseMessages.getString(PKG, 
"CalculatorMetaFunction.CalcFunctions.JdeJulianToDate");
+
+  @Override
+  public String getCode() {
+    return "JDE_JULIAN_TO_DATE";
+  }
+
+  @Override
+  public String getDescription() {
+    return description;
+  }
+
+  @Override
+  public int getDefaultResultType() {
+    return IValueMeta.TYPE_DATE;
+  }
+
+  @Override
+  public CalculationOutput calculate(CalculationInput in) throws 
HopValueException {
+    Object value = null;
+    if (in.metaA != null && !in.metaA.isNull(in.dataA)) {
+      value = JdeJulian.toDate(in.metaA.getInteger(in.dataA));
+    }
+    return new CalculationOutput(getDefaultResultType(), value);
+  }
+}
diff --git 
a/plugins/transforms/calculator/src/main/resources/org/apache/hop/pipeline/transforms/calculator/messages/messages_en_US.properties
 
b/plugins/transforms/calculator/src/main/resources/org/apache/hop/pipeline/transforms/calculator/messages/messages_en_US.properties
index 774fbbf584..05a4e6eaf6 100644
--- 
a/plugins/transforms/calculator/src/main/resources/org/apache/hop/pipeline/transforms/calculator/messages/messages_en_US.properties
+++ 
b/plugins/transforms/calculator/src/main/resources/org/apache/hop/pipeline/transforms/calculator/messages/messages_en_US.properties
@@ -17,6 +17,8 @@
 
 BaseTransform.TypeLongDesc.Calculator=Calculator
 BaseTransform.TypeTooltipDesc.Calculator=Create new fields by performing 
simple calculations
+Calculator.Error.DateBeforeJdeEpoch=Date {0} is before the year 1900 and 
cannot be converted to a JD Edwards Julian date (CYYDDD)
+Calculator.Error.InvalidJdeJulian=Invalid JD Edwards Julian date {0}. Expected 
CYYDDD (century, two-digit year, day of year)
 Calculator.Error.NoNameField=There is no name specified for calculated field 
\# {0}
 Calculator.Error.UnableFindField=Unable to find the specified fieldname '{0}' 
for calculation \#{1}
 Calculator.ErrorInTransformRunning=Error while running the transform 
@@ -63,7 +65,7 @@ CalculatorMeta.Injection.Calculation.ValuePrecision=Precision
 CalculatorMeta.Injection.Calculation.ValueType=Output value type
 CalculatorMeta.Injection.Calculations=List of calculations
 CalculatorMeta.Injection.FailIfNoFile=Fail if no file
-CalculatorMeta.keyword=calculate,math,formula,arithmetic,compute
+CalculatorMeta.keyword=calculate,math,formula,arithmetic,compute,julian,jde,date
 CalculatorMetaFunction.CalcFunctions.Abs=Absolute value ABS( A )
 CalculatorMetaFunction.CalcFunctions.AddSeconds=Date A + B Seconds
 CalculatorMetaFunction.CalcFunctions.AddTimeToDate=Add time B to date A
@@ -89,6 +91,7 @@ CalculatorMetaFunction.CalcFunctions.DatePlusDays=Date A + B 
Days
 CalculatorMetaFunction.CalcFunctions.DatePlusHours=Date A + B Hours
 CalculatorMetaFunction.CalcFunctions.DatePlusMinutes=Date A + B Minutes
 CalculatorMetaFunction.CalcFunctions.DatePlusMonths=Date A + B Months
+CalculatorMetaFunction.CalcFunctions.DateToJdeJulian=Date A to JDE Julian 
(CYYDDD)
 CalculatorMetaFunction.CalcFunctions.DayOfMonth=Day of month of date A
 CalculatorMetaFunction.CalcFunctions.DayOfWeek=Day of week of date A
 CalculatorMetaFunction.CalcFunctions.DayOfYear=Day of year of date A
@@ -96,6 +99,7 @@ CalculatorMetaFunction.CalcFunctions.DoubleMetaphone=Double 
metaphone of A(phone
 CalculatorMetaFunction.CalcFunctions.EscapeHTML=Escape HTML content
 CalculatorMetaFunction.CalcFunctions.EscapeSQL=Escape SQL content
 CalculatorMetaFunction.CalcFunctions.FirstDayOfMonthDate=First day of month 
(A) date
+CalculatorMetaFunction.CalcFunctions.JdeJulianToDate=JDE Julian A (CYYDDD) to 
Date
 CalculatorMetaFunction.CalcFunctions.LastDayOfMonthDate=Last day of month (A) 
date
 CalculatorMetaFunction.CalcFunctions.Floor=FLOOR( A )
 CalculatorMetaFunction.CalcFunctions.GetFileEncoding=Get encoding of file A
diff --git 
a/plugins/transforms/calculator/src/main/samples/transforms/calculator-jde-julian.hpl
 
b/plugins/transforms/calculator/src/main/samples/transforms/calculator-jde-julian.hpl
new file mode 100644
index 0000000000..cc25a6dcb4
--- /dev/null
+++ 
b/plugins/transforms/calculator/src/main/samples/transforms/calculator-jde-julian.hpl
@@ -0,0 +1,208 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+-->
+<pipeline>
+  <info>
+    <name>calculator-jde-julian</name>
+    <name_sync_with_filename>Y</name_sync_with_filename>
+    <description>Cross convert between JD Edwards Julian dates (CYYDDD) and 
ISO dates</description>
+    <extended_description/>
+    <pipeline_version/>
+    <pipeline_type>Normal</pipeline_type>
+    <parameters>
+    </parameters>
+    <capture_transform_performance>N</capture_transform_performance>
+    
<transform_performance_capturing_delay>1000</transform_performance_capturing_delay>
+    
<transform_performance_capturing_size_limit>100</transform_performance_capturing_size_limit>
+    <created_user>-</created_user>
+    <created_date>2026/09/03 00:00:00.000</created_date>
+    <modified_user>-</modified_user>
+    <modified_date>2026/09/03 00:00:00.000</modified_date>
+    <key_for_session_key/>
+    <is_key_private>N</is_key_private>
+  </info>
+  <notepads>
+    <notepad>
+      <note>JD Edwards stores dates as CYYDDD.
+
+C = century relative to 1900 (0 = 1900-1999, 1 = 2000-2099)
+YY = two-digit year
+DDD = day of the year (001-366)
+
+95001 = 1 January 1995
+97318 = 14 November 1997
+109001 = 1 January 2009
+111013 = 13 January 2011</note>
+      <xloc>64</xloc>
+      <yloc>256</yloc>
+      <width>520</width>
+      <heigth>170</heigth>
+      <fontname>Segoe UI</fontname>
+      <fontsize>11</fontsize>
+      <fontbold>N</fontbold>
+      <fontitalic>N</fontitalic>
+      <fontcolorred>14</fontcolorred>
+      <fontcolorgreen>58</fontcolorgreen>
+      <fontcolorblue>90</fontcolorblue>
+      <backgroundcolorred>201</backgroundcolorred>
+      <backgroundcolorgreen>232</backgroundcolorgreen>
+      <backgroundcolorblue>251</backgroundcolorblue>
+      <bordercolorred>14</bordercolorred>
+      <bordercolorgreen>58</bordercolorgreen>
+      <bordercolorblue>90</bordercolorblue>
+    </notepad>
+  </notepads>
+  <order>
+    <hop>
+      <from>Test date cases</from>
+      <to>Calculator</to>
+      <enabled>Y</enabled>
+    </hop>
+  </order>
+  <transform>
+    <name>Calculator</name>
+    <type>Calculator</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <failIfNoFile>Y</failIfNoFile>
+    <calculation>
+      <field_name>julianOut</field_name>
+      <calc_type>DATE_TO_JDE_JULIAN</calc_type>
+      <field_a>isoDate</field_a>
+      <field_b/>
+      <field_c/>
+      <value_type>Integer</value_type>
+      <value_length>-1</value_length>
+      <value_precision>-1</value_precision>
+      <remove>N</remove>
+      <conversion_mask/>
+      <decimal_symbol/>
+      <grouping_symbol/>
+      <currency_symbol/>
+    </calculation>
+    <calculation>
+      <field_name>isoOut</field_name>
+      <calc_type>JDE_JULIAN_TO_DATE</calc_type>
+      <field_a>julianIn</field_a>
+      <field_b/>
+      <field_c/>
+      <value_type>Date</value_type>
+      <value_length>-1</value_length>
+      <value_precision>-1</value_precision>
+      <remove>N</remove>
+      <conversion_mask>yyyy-MM-dd</conversion_mask>
+      <decimal_symbol/>
+      <grouping_symbol/>
+      <currency_symbol/>
+    </calculation>
+    <attributes/>
+    <GUI>
+      <xloc>336</xloc>
+      <yloc>96</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>Test date cases</name>
+    <type>DataGrid</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <fields>
+      <field>
+        <name>isoDate</name>
+        <type>Date</type>
+        <format>yyyy-MM-dd</format>
+        <currency/>
+        <decimal/>
+        <group/>
+        <length>-1</length>
+        <precision>-1</precision>
+        <set_empty_string>N</set_empty_string>
+      </field>
+      <field>
+        <name>julianIn</name>
+        <type>Integer</type>
+        <format/>
+        <currency/>
+        <decimal/>
+        <group/>
+        <length>-1</length>
+        <precision>-1</precision>
+        <set_empty_string>N</set_empty_string>
+      </field>
+      <field>
+        <name>note</name>
+        <type>String</type>
+        <format/>
+        <currency/>
+        <decimal/>
+        <group/>
+        <length>-1</length>
+        <precision>-1</precision>
+        <set_empty_string>N</set_empty_string>
+      </field>
+    </fields>
+    <data>
+      <line>
+        <item>1995-01-01</item>
+        <item>95001</item>
+        <item>The 1st day of 1995</item>
+      </line>
+      <line>
+        <item>1997-11-04</item>
+        <item>97308</item>
+        <item>The 308th day of 1997</item>
+      </line>
+      <line>
+        <item>2009-01-01</item>
+        <item>109001</item>
+        <item>The 1st day of 2009</item>
+      </line>
+      <line>
+        <item>2011-01-13</item>
+        <item>111013</item>
+        <item>The 13th day of 2011</item>
+      </line>
+      <line>
+        <item>2000-08-23</item>
+        <item>100236</item>
+        <item>The 236th day of leap year 2000</item>
+      </line>
+    </data>
+    <attributes/>
+    <GUI>
+      <xloc>96</xloc>
+      <yloc>96</yloc>
+    </GUI>
+  </transform>
+  <transform_error_handling>
+  </transform_error_handling>
+  <attributes/>
+</pipeline>
diff --git 
a/plugins/transforms/calculator/src/test/java/org/apache/hop/pipeline/transforms/calculator/JdeJulianTest.java
 
b/plugins/transforms/calculator/src/test/java/org/apache/hop/pipeline/transforms/calculator/JdeJulianTest.java
new file mode 100644
index 0000000000..6391f0096a
--- /dev/null
+++ 
b/plugins/transforms/calculator/src/test/java/org/apache/hop/pipeline/transforms/calculator/JdeJulianTest.java
@@ -0,0 +1,323 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.pipeline.transforms.calculator;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.stream.Stream;
+import org.apache.hop.core.HopEnvironment;
+import org.apache.hop.core.IRowSet;
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.exception.HopValueException;
+import org.apache.hop.core.logging.ILoggingObject;
+import org.apache.hop.core.row.IRowMeta;
+import org.apache.hop.core.row.IValueMeta;
+import org.apache.hop.core.row.RowMeta;
+import org.apache.hop.core.row.value.ValueMetaDate;
+import org.apache.hop.core.row.value.ValueMetaInteger;
+import org.apache.hop.core.row.value.ValueMetaNumber;
+import org.apache.hop.core.row.value.ValueMetaString;
+import org.apache.hop.junit.rules.RestoreHopEngineEnvironmentExtension;
+import org.apache.hop.pipeline.transform.RowAdapter;
+import 
org.apache.hop.pipeline.transforms.calculator.calculations.date.DateToJdeJulian;
+import 
org.apache.hop.pipeline.transforms.calculator.calculations.date.JdeJulian;
+import 
org.apache.hop.pipeline.transforms.calculator.calculations.date.JdeJulianToDate;
+import org.apache.hop.pipeline.transforms.mock.TransformMockHelper;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+class JdeJulianTest {
+  private static final SimpleDateFormat ISO_DATE = new 
SimpleDateFormat("yyyy-MM-dd");
+
+  private TransformMockHelper<CalculatorMeta, CalculatorData> smh;
+
+  @RegisterExtension
+  static RestoreHopEngineEnvironmentExtension env = new 
RestoreHopEngineEnvironmentExtension();
+
+  @BeforeAll
+  static void init() throws HopException {
+    HopEnvironment.init();
+  }
+
+  @BeforeEach
+  void setUp() {
+    smh = new TransformMockHelper<>("Calculator", CalculatorMeta.class, 
CalculatorData.class);
+    when(smh.logChannelFactory.create(any(), any(ILoggingObject.class)))
+        .thenReturn(smh.iLogChannel);
+    when(smh.pipeline.isRunning()).thenReturn(true);
+  }
+
+  @AfterEach
+  void cleanUp() {
+    smh.cleanUp();
+  }
+
+  static Stream<Arguments> sampleConversions() {
+    return Stream.of(
+        Arguments.of("1995-01-01", 95001L),
+        Arguments.of("1997-11-04", 97308L),
+        Arguments.of("1997-11-14", 97318L),
+        Arguments.of("2000-08-23", 100236L),
+        Arguments.of("2000-12-31", 100366L),
+        Arguments.of("1999-12-31", 99365L),
+        Arguments.of("2009-01-01", 109001L),
+        Arguments.of("2011-01-13", 111013L),
+        Arguments.of("1900-01-01", 1L),
+        Arguments.of("2000-02-29", 100060L));
+  }
+
+  @ParameterizedTest
+  @MethodSource("sampleConversions")
+  void convertsDateToJulianAndBack(String isoDate, long julian) throws 
Exception {
+    Date date = ISO_DATE.parse(isoDate);
+    assertEquals(julian, JdeJulian.fromDate(date));
+    assertEquals(midnight(date), JdeJulian.toDate(julian));
+  }
+
+  @Test
+  void fromDateUsesDayOfYearAndIgnoresTime() throws Exception {
+    Date afternoon = new SimpleDateFormat("yyyy-MM-dd 
HH:mm:ss").parse("2009-01-01 15:30:45");
+    assertEquals(109001L, JdeJulian.fromDate(afternoon));
+    assertEquals(ISO_DATE.parse("2009-01-01"), JdeJulian.toDate(109001L));
+  }
+
+  @Test
+  void nullInputsStayNull() throws Exception {
+    assertNull(JdeJulian.fromDate(null));
+    assertNull(JdeJulian.toDate(null));
+
+    DateToJdeJulian dateToJulian = new DateToJdeJulian();
+    JdeJulianToDate julianToDate = new JdeJulianToDate();
+    ValueMetaDate dateMeta = new ValueMetaDate("isoDate");
+    ValueMetaInteger julianMeta = new ValueMetaInteger("julian");
+
+    assertNull(
+        dateToJulian.calculate(
+                new CalculationInput(
+                    dateMeta, null, null, null, null, null, null, null, null, 
false))
+            .value);
+    assertNull(
+        julianToDate.calculate(
+                new CalculationInput(
+                    julianMeta, null, null, null, null, null, null, null, 
null, false))
+            .value);
+  }
+
+  @Test
+  void rejectsDatesBefore1900() throws Exception {
+    Date date = ISO_DATE.parse("1899-12-31");
+    HopValueException exception =
+        assertThrows(HopValueException.class, () -> JdeJulian.fromDate(date));
+    assertTrue(exception.getMessage().contains("1899-12-31"));
+  }
+
+  @Test
+  void rejectsInvalidJulianValues() {
+    assertThrows(HopValueException.class, () -> JdeJulian.toDate(0L));
+    assertThrows(HopValueException.class, () -> JdeJulian.toDate(-1L));
+    assertThrows(HopValueException.class, () -> JdeJulian.toDate(100000L));
+    assertThrows(HopValueException.class, () -> JdeJulian.toDate(99366L));
+    assertThrows(HopValueException.class, () -> JdeJulian.toDate(100367L));
+  }
+
+  @Test
+  void julianToDateAcceptsIntegerNumberAndString() throws Exception {
+    JdeJulianToDate calc = new JdeJulianToDate();
+    Date expected = ISO_DATE.parse("1997-11-04");
+
+    assertEquals(
+        expected,
+        calc.calculate(
+                new CalculationInput(
+                    new ValueMetaInteger("julian"),
+                    null,
+                    null,
+                    97308L,
+                    null,
+                    null,
+                    null,
+                    null,
+                    null,
+                    false))
+            .value);
+    assertEquals(
+        expected,
+        calc.calculate(
+                new CalculationInput(
+                    new ValueMetaNumber("julian"),
+                    null,
+                    null,
+                    97308.0,
+                    null,
+                    null,
+                    null,
+                    null,
+                    null,
+                    false))
+            .value);
+    assertEquals(
+        expected,
+        calc.calculate(
+                new CalculationInput(
+                    new ValueMetaString("julian"),
+                    null,
+                    null,
+                    "097308",
+                    null,
+                    null,
+                    null,
+                    null,
+                    null,
+                    false))
+            .value);
+    assertEquals(
+        ISO_DATE.parse("2009-01-01"),
+        calc.calculate(
+                new CalculationInput(
+                    new ValueMetaString("julian"),
+                    null,
+                    null,
+                    "109001",
+                    null,
+                    null,
+                    null,
+                    null,
+                    null,
+                    false))
+            .value);
+  }
+
+  @Test
+  void calculatorTransformRoundTrip() throws Exception {
+    RowMeta inputRowMeta = new RowMeta();
+    inputRowMeta.addValueMeta(new ValueMetaDate("isoDate"));
+    inputRowMeta.addValueMeta(new ValueMetaInteger("julianIn"));
+
+    Date isoDate = ISO_DATE.parse("2011-01-13");
+    IRowSet inputRowSet = smh.getMockInputRowSet(new Object[][] {{isoDate, 
111013L}});
+    inputRowSet.setRowMeta(inputRowMeta);
+
+    CalculatorMeta meta = new CalculatorMeta();
+    meta.getFunctions()
+        .add(
+            new CalculatorMetaFunction(
+                "julianOut",
+                CalculationType.DATE_TO_JDE_JULIAN,
+                "isoDate",
+                null,
+                null,
+                "Integer",
+                0,
+                0,
+                "",
+                "",
+                "",
+                "",
+                false));
+    meta.getFunctions()
+        .add(
+            new CalculatorMetaFunction(
+                "isoOut",
+                CalculationType.JDE_JULIAN_TO_DATE,
+                "julianIn",
+                null,
+                null,
+                "Date",
+                0,
+                0,
+                "yyyy-MM-dd",
+                "",
+                "",
+                "",
+                false));
+
+    Calculator calculator =
+        new Calculator(
+            smh.transformMeta, meta, new CalculatorData(), 0, 
smh.pipelineMeta, smh.pipeline);
+    calculator.addRowSetToInputRowSets(inputRowSet);
+    calculator.setInputRowMeta(inputRowMeta);
+    calculator.init();
+    calculator.addRowListener(
+        new RowAdapter() {
+          @Override
+          public void rowWrittenEvent(IRowMeta rowMeta, Object[] row) {
+            assertEquals(111013L, row[2]);
+            assertEquals(midnight(isoDate), row[3]);
+          }
+        });
+
+    assertTrue(calculator.processRow());
+  }
+
+  @Test
+  void calculationTypeCodesMatchMetadata() {
+    assertEquals("DATE_TO_JDE_JULIAN", 
CalculationType.DATE_TO_JDE_JULIAN.getCode());
+    assertEquals("JDE_JULIAN_TO_DATE", 
CalculationType.JDE_JULIAN_TO_DATE.getCode());
+    assertEquals(
+        CalculationType.DATE_TO_JDE_JULIAN,
+        
CalculationType.findByDescription(CalculationType.DATE_TO_JDE_JULIAN.getDescription()));
+    assertEquals(
+        CalculationType.JDE_JULIAN_TO_DATE,
+        
CalculationType.findByDescription(CalculationType.JDE_JULIAN_TO_DATE.getDescription()));
+  }
+
+  @Test
+  void dateToJulianResultTypeIsInteger() throws Exception {
+    DateToJdeJulian calc = new DateToJdeJulian();
+    Date date = ISO_DATE.parse("2000-08-23");
+    CalculationOutput output =
+        calc.calculate(
+            new CalculationInput(
+                new ValueMetaDate("isoDate"),
+                null,
+                null,
+                date,
+                null,
+                null,
+                null,
+                null,
+                null,
+                false));
+    assertEquals(100236L, output.value);
+    assertEquals(IValueMeta.TYPE_INTEGER, output.resultType);
+  }
+
+  private static Date midnight(Date date) {
+    Calendar calendar = Calendar.getInstance();
+    calendar.setTime(date);
+    calendar.set(Calendar.HOUR_OF_DAY, 0);
+    calendar.set(Calendar.MINUTE, 0);
+    calendar.set(Calendar.SECOND, 0);
+    calendar.set(Calendar.MILLISECOND, 0);
+    return calendar.getTime();
+  }
+}

Reply via email to