From 62641017da814425f0cde193e61580a22096af0b Mon Sep 17 00:00:00 2001 From: Cesar Parra Date: Tue, 16 Dec 2025 10:46:40 -0400 Subject: [PATCH 1/2] Feat: add ADDDAYS function to manipulate dates --- .../std-lib/DateAndTimeFunctions.cls | 97 +++++++++++++------ .../std-lib/DateAndTimeFunctions.cls-meta.xml | 2 +- .../DateAndTimeFunctionsTest.cls | 5 + .../DateAndTimeFunctionsTest.cls-meta.xml | 2 +- 4 files changed, 72 insertions(+), 34 deletions(-) diff --git a/expression-src/main/src/interpreter/std-lib/DateAndTimeFunctions.cls b/expression-src/main/src/interpreter/std-lib/DateAndTimeFunctions.cls index 40f13ce5..0e9166b9 100644 --- a/expression-src/main/src/interpreter/std-lib/DateAndTimeFunctions.cls +++ b/expression-src/main/src/interpreter/std-lib/DateAndTimeFunctions.cls @@ -5,7 +5,8 @@ * @display-icon utility:date_input */ public with sharing class DateAndTimeFunctions { - private static final Map FUNCTIONS = new Map{ + private static final Map FUNCTIONS = new Map { + 'ADDDAYS' => new DateAndTimeFunctions.AddDaysFn(), 'ADDMONTHS' => new DateAndTimeFunctions.AddMonthsFn(), 'DATE' => new DateAndTimeFunctions.DateFn(), 'DATETIME' => new DateAndTimeFunctions.DatetimeFn(), @@ -44,6 +45,38 @@ public with sharing class DateAndTimeFunctions { MILLISECOND, MINUTE, SECOND, HOUR_FN } + /** + * @description Returns a date that is a specified number of days before or after a given date. + * + * Accepts 2 arguments: the date and the number of days to add. + * @function ADDDAYS + * @example + * ADDDAYS(DATE(2020, 1, 1), 1) // 2020-01-02 + */ + private class AddDaysFn extends StandardFunction { + public override Object call(List arguments) { + Object startDate = evaluate(arguments.get(0)); + if (!(startDate instanceof Date)) { + throw new FunctionExecutionException( + 'Error executing "ADDDAYS" function: the argument must evaluate to a date value.' + ); + } + + Object daysToAdd = evaluate(arguments.get(1)); + if (!(daysToAdd instanceof Decimal)) { + throw new FunctionExecutionException( + 'Error executing "ADDDAYS" function: the argument must evaluate to a number value.' + ); + } + + return ((Date)startDate).addDays(((Decimal)daysToAdd).intValue()); + } + + public override Arity getArity() { + return Arity.exactly(2); + } + } + /** * @description Returns a date that is a specified number of months before or after a given date. * @@ -68,7 +101,7 @@ public with sharing class DateAndTimeFunctions { ); } - return ((Date) startMonth).addMonths(((Decimal) monthsToAdd).intValue()); + return ((Date)startMonth).addMonths(((Decimal)monthsToAdd).intValue()); } public override Arity getArity() { @@ -192,7 +225,7 @@ public with sharing class DateAndTimeFunctions { ); } - return ((Date) dateValue).day(); + return ((Date)dateValue).day(); } public override Arity getArity() { @@ -217,7 +250,7 @@ public with sharing class DateAndTimeFunctions { ); } - return ((Date) dateValue).dayOfYear(); + return ((Date)dateValue).dayOfYear(); } public override Arity getArity() { @@ -262,9 +295,9 @@ public with sharing class DateAndTimeFunctions { } if (expression instanceof String) { - return Date.valueOf((String) expression); + return Date.valueOf((String)expression); } else { - return ((Datetime) expression).date(); + return ((Datetime)expression).date(); } } @@ -290,7 +323,7 @@ public with sharing class DateAndTimeFunctions { ); } - return Datetime.valueOf((String) expression); + return Datetime.valueOf((String)expression); } public override Arity getArity() { @@ -334,13 +367,13 @@ public with sharing class DateAndTimeFunctions { } if (type == TimeType.MILLISECOND) { - return ((Time) timeValue).millisecond(); + return ((Time)timeValue).millisecond(); } else if (type == TimeType.MINUTE) { - return ((Time) timeValue).minute(); + return ((Time)timeValue).minute(); } else if (type == TimeType.SECOND) { - return ((Time) timeValue).second(); + return ((Time)timeValue).second(); } else { - return ((Time) timeValue).hour(); + return ((Time)timeValue).hour(); } } @@ -424,7 +457,7 @@ public with sharing class DateAndTimeFunctions { if (dateTimeOrText instanceof String) { // A received string should be in the format of HH:mm:ss.SSS - String[] parts = ((String) dateTimeOrText).split(':'); + String[] parts = ((String)dateTimeOrText).split(':'); if (parts.size() != 3) { throw new FunctionExecutionException( 'Error executing "TIMEVALUE" function: the argument must evaluate to a string in the format of HH:mm:ss.SSS.' @@ -446,7 +479,7 @@ public with sharing class DateAndTimeFunctions { return Time.newInstance(hours, minutes, seconds, 0); } } else { - return ((Datetime) dateTimeOrText).time(); + return ((Datetime)dateTimeOrText).time(); } } @@ -490,7 +523,7 @@ public with sharing class DateAndTimeFunctions { ); } - return Integer.valueOf(Datetime.newInstanceGmt((Date) dateValue, Time.newInstance(0, 0, 0, 0)).format('w')); + return Integer.valueOf(Datetime.newInstanceGmt((Date)dateValue, Time.newInstance(0, 0, 0, 0)).format('w')); } public override Arity getArity() { @@ -515,7 +548,7 @@ public with sharing class DateAndTimeFunctions { ); } - return Integer.valueOf(Datetime.newInstanceGmt((Date) dateValue, Time.newInstance(0, 0, 0, 0)).format('Y')); + return Integer.valueOf(Datetime.newInstanceGmt((Date)dateValue, Time.newInstance(0, 0, 0, 0)).format('Y')); } public override Arity getArity() { @@ -540,7 +573,7 @@ public with sharing class DateAndTimeFunctions { ); } - return ((Date) dateValue).year(); + return ((Date)dateValue).year(); } public override Arity getArity() { @@ -565,7 +598,7 @@ public with sharing class DateAndTimeFunctions { ); } - Long timeStamp = ((Decimal) unixTime).longValue() * 1000; + Long timeStamp = ((Decimal)unixTime).longValue() * 1000; Datetime gmtDatetime = Datetime.newInstance(timeStamp); return gmtDatetime; } @@ -597,14 +630,14 @@ public with sharing class DateAndTimeFunctions { if (dateOrDateTimeOrTime instanceof Date) { // Convert to datetime first - Datetime gmtDatetime = Datetime.newInstanceGmt((Date) dateOrDateTimeOrTime, Time.newInstance(0, 0, 0, 0)); + Datetime gmtDatetime = Datetime.newInstanceGmt((Date)dateOrDateTimeOrTime, Time.newInstance(0, 0, 0, 0)); return gmtDatetime.getTime() / 1000; } else if (dateOrDateTimeOrTime instanceof Datetime) { - Datetime gmtDateTime = Datetime.newInstanceGmt(((Datetime) dateOrDateTimeOrTime).date(), ((Datetime) dateOrDateTimeOrTime).time()); + Datetime gmtDateTime = Datetime.newInstanceGmt(((Datetime)dateOrDateTimeOrTime).date(), ((Datetime)dateOrDateTimeOrTime).time()); return gmtDateTime.getTime() / 1000; } else { // Calculate the number of seconds in the day for a given time - return ((Time) dateOrDateTimeOrTime).hour() * 3600 + ((Time) dateOrDateTimeOrTime).minute() * 60 + ((Time) dateOrDateTimeOrTime).second(); + return ((Time)dateOrDateTimeOrTime).hour() * 3600 + ((Time)dateOrDateTimeOrTime).minute() * 60 + ((Time)dateOrDateTimeOrTime).second(); } } @@ -631,7 +664,7 @@ public with sharing class DateAndTimeFunctions { ); } - Date dateObj = (Date) dateValue; + Date dateObj = (Date)dateValue; return Integer.valueOf(Datetime.newInstanceGmt( dateObj.year(), @@ -668,7 +701,7 @@ public with sharing class DateAndTimeFunctions { // Expects either 2 Times or 2 DateTimes Object startVal = evaluate(arguments.get(0)); if (startVal instanceof Time) { - Time startTime = (Time) startVal; + Time startTime = (Time)startVal; // Check that the second value is also a Time Object endVal = evaluate(arguments.get(1)); if (!(endVal instanceof Time)) { @@ -677,12 +710,12 @@ public with sharing class DateAndTimeFunctions { ); } - Time endTime = (Time) endVal; + Time endTime = (Time)endVal; // Convert both times to date times startDateTime = Datetime.newInstanceGmt(Date.newInstance(0, 1, 1), startTime); endDateTime = Datetime.newInstanceGmt(Date.newInstance(0, 1, 1), endTime); } else if (startVal instanceof Datetime) { - startDateTime = (Datetime) startVal; + startDateTime = (Datetime)startVal; // Check that the second value is also a Datetime Object endVal = evaluate(arguments.get(1)); if (!(endVal instanceof Datetime)) { @@ -691,7 +724,7 @@ public with sharing class DateAndTimeFunctions { ); } - endDateTime = (Datetime) endVal; + endDateTime = (Datetime)endVal; } else { throw new FunctionExecutionException( 'Error executing "FORMATDURATION" function: the first argument must evaluate to a time or datetime value.' @@ -700,9 +733,9 @@ public with sharing class DateAndTimeFunctions { // Calculate the difference between the two Long differenceInMs = Math.abs(endDateTime.getTime() - startDateTime.getTime()); - Integer hours = (Integer) Math.floor(differenceInMs / 1000 / 60 / 60); - Integer minutes = (Integer) Math.floor((differenceInMs - hours * 1000 * 60 * 60) / 1000 / 60); - Integer seconds = (Integer) Math.floor((differenceInMs - hours * 1000 * 60 * 60 - minutes * 1000 * 60) / 1000); + Integer hours = (Integer)Math.floor(differenceInMs / 1000 / 60 / 60); + Integer minutes = (Integer)Math.floor((differenceInMs - hours * 1000 * 60 * 60) / 1000 / 60); + Integer seconds = (Integer)Math.floor((differenceInMs - hours * 1000 * 60 * 60 - minutes * 1000 * 60) / 1000); // Format in HH:MM:SS // Use StringUtils.leftPad if necessary @@ -736,7 +769,7 @@ public with sharing class DateAndTimeFunctions { ); } - return ((Date) dateValue).month(); + return ((Date)dateValue).month(); } public override Arity getArity() { @@ -770,7 +803,7 @@ public with sharing class DateAndTimeFunctions { ); } - return ((Datetime) dateTimeValue).format((String) formatValue); + return ((Datetime)dateTimeValue).format((String)formatValue); } public override Arity getArity() { @@ -796,7 +829,7 @@ public with sharing class DateAndTimeFunctions { ); } - return Datetime.newInstance((Date) dateValue, Time.newInstance(0, 0, 0, 0)); + return Datetime.newInstance((Date)dateValue, Time.newInstance(0, 0, 0, 0)); } public override Arity getArity() { @@ -830,7 +863,7 @@ public with sharing class DateAndTimeFunctions { ); } - return ((Date) dateValue).daysBetween((Date) dateValue2); + return ((Date)dateValue).daysBetween((Date)dateValue2); } public override Arity getArity() { diff --git a/expression-src/main/src/interpreter/std-lib/DateAndTimeFunctions.cls-meta.xml b/expression-src/main/src/interpreter/std-lib/DateAndTimeFunctions.cls-meta.xml index f5e18fd1..82775b98 100644 --- a/expression-src/main/src/interpreter/std-lib/DateAndTimeFunctions.cls-meta.xml +++ b/expression-src/main/src/interpreter/std-lib/DateAndTimeFunctions.cls-meta.xml @@ -1,5 +1,5 @@ - 60.0 + 65.0 Active diff --git a/expression-src/spec/language/std-functions/DateAndTimeFunctionsTest.cls b/expression-src/spec/language/std-functions/DateAndTimeFunctionsTest.cls index e1dadc49..66d4b53c 100644 --- a/expression-src/spec/language/std-functions/DateAndTimeFunctionsTest.cls +++ b/expression-src/spec/language/std-functions/DateAndTimeFunctionsTest.cls @@ -11,6 +11,11 @@ private class DateAndTimeFunctionsTest { Evaluator.run('DATETIME(2015, 1, 1, 0, 0, 0)')); } + @IsTest + private static void addDaysFunctionAddsDays() { + Assert.areEqual(Date.newInstance(2015, 1, 2), Evaluator.run('ADDDAYS(DATE(2015, 1, 1), 1)')); + } + @IsTest private static void addMonthsFunctionAddsMonths() { Assert.areEqual(Date.newInstance(2015, 2, 1), Evaluator.run('ADDMONTHS(DATE(2015, 1, 1), 1)')); diff --git a/expression-src/spec/language/std-functions/DateAndTimeFunctionsTest.cls-meta.xml b/expression-src/spec/language/std-functions/DateAndTimeFunctionsTest.cls-meta.xml index f5e18fd1..82775b98 100644 --- a/expression-src/spec/language/std-functions/DateAndTimeFunctionsTest.cls-meta.xml +++ b/expression-src/spec/language/std-functions/DateAndTimeFunctionsTest.cls-meta.xml @@ -1,5 +1,5 @@ - 60.0 + 65.0 Active From e9906f3ba9860bc08d8fc2de618ba3f086885c57 Mon Sep 17 00:00:00 2001 From: Cesar Parra Date: Tue, 16 Dec 2025 10:52:42 -0400 Subject: [PATCH 2/2] Releasing new package version --- docs/public/packages.json | 2 +- sfdx-project_packaging.json | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/public/packages.json b/docs/public/packages.json index a5cd09b5..a34f1c99 100644 --- a/docs/public/packages.json +++ b/docs/public/packages.json @@ -1,4 +1,4 @@ { - "packageId": "04tRb0000042CNlIAM", + "packageId": "04tRb000004400jIAA", "componentPackageId": "04tRb0000012Mv8IAE" } diff --git a/sfdx-project_packaging.json b/sfdx-project_packaging.json index bac1a7ab..1ac725c2 100644 --- a/sfdx-project_packaging.json +++ b/sfdx-project_packaging.json @@ -3,7 +3,7 @@ { "package": "Expression", "versionName": "Version 1.36", - "versionNumber": "1.45.0.NEXT", + "versionNumber": "1.46.0.NEXT", "path": "expression-src", "default": false, "versionDescription": "Expression core language", @@ -74,6 +74,7 @@ "Expression@1.42.0-1": "04tRb000003xe4XIAQ", "Expression@1.43.0-1": "04tRb000003xzXCIAY", "Expression@1.44.0-1": "04tRb000003z0hJIAQ", - "Expression@1.45.0-1": "04tRb0000042CNlIAM" + "Expression@1.45.0-1": "04tRb0000042CNlIAM", + "Expression@1.46.0-1": "04tRb000004400jIAA" } } \ No newline at end of file