Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/Albatross.Expression.Test/Operations/DateOperationTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using NUnit.Framework;
using System;

namespace Albatross.Expression.Test.Operations
{
Expand Down Expand Up @@ -52,6 +53,21 @@ public class DateOperationTests
[TestCase("format(Date(\"2015-2-10\"), \"yyyy-MM-dd\")", ExpectedResult = "2015-02-10")]
[TestCase("GetWorkingDays(Date(\"2023-08-29\"), Date(\"2023-09-03\"),1,1)", ExpectedResult = 4d)]

// Add Business Days
[TestCase("AddBusinessDays(CreateDate(2025, 4, 4), 5)", ExpectedResult = "2025-04-10")]
[TestCase("AddBusinessDays(CreateDate(2025, 4, 5), 2)", ExpectedResult = "2025-04-08")]
[TestCase("AddBusinessDays(CreateDate(2025, 4, 12), 1)", ExpectedResult = "2025-04-14")]
[TestCase("AddBusinessDays(CreateDate(2025, 4, 11), 3)", ExpectedResult = "2025-04-15")]
[TestCase("AddBusinessDays(CreateDate(2025, 4, 10), -1)", ExpectedResult = "2025-04-10")]
[TestCase("AddBusinessDays(CreateDate(2025, 4, 11), -5)", ExpectedResult = "2025-04-07")]
[TestCase("AddBusinessDays(CreateDate(2025, 4, 14), -1)", ExpectedResult = "2025-04-14")]
[TestCase("AddBusinessDays(CreateDate(2025, 4, 12), 10)", ExpectedResult = "2025-04-25")]
[TestCase("AddBusinessDays(CreateDate(2025, 4, 8), 3, 1, 1)", ExpectedResult = "2025-04-10")]
[TestCase("AddBusinessDays(CreateDate(2025, 4, 10), 3, 1, 1)", ExpectedResult = "2025-04-13")]
[TestCase("AddBusinessDays(CreateDate(2025, 4, 12), 1, 1, 1)", ExpectedResult = "2025-04-13")]
[TestCase("AddBusinessDays(CreateDate(2025, 4, 11), 3, 5, 2)", ExpectedResult = "2025-04-13")]
[TestCase("AddBusinessDays(CreateDate(2025, 4, 10), 8, 5, 3)", ExpectedResult = "2025-04-20")]

public object OperationsTesting(string expression)
{
return Factory.Instance.Create().Compile(expression).EvalValue(null);
Expand Down
82 changes: 82 additions & 0 deletions src/Albatross.Expression/Functions/Date/AddBusinessDays.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
using Albatross.Expression.Documentation;
using Albatross.Expression.Documentation.Attributes;
using Albatross.Expression.Tokens;
using System;
using System.Collections.Generic;

namespace Albatross.Expression.Functions.Date
{
[FunctionDoc(Group.Date, "{token}( , )",
@"### Add or Subtract Business Days
#### Inputs:
- `date`: Date (mandatory) - The starting date.
- `count`: Number (mandatory) - Number of business days to add (positive for future, negative for past).
- `startOfWeek`: Start of the week as a value from 1 (Monday) to 7 (Sunday) (Optional, default: 1).
- `weekendLength`: Integer (1 or 2) indicating the length of the weekend (Optional, default: 2).
#### Outputs:
- A calculated `DateTime`, adjusted by the number of business days."
)]
[ParserOperation]
public class AddBusinessDays : PrefixOperationToken
{
public override string Name => "AddBusinessDays";

public override bool Symbolic => false;

public override int MinOperandCount => 2;

public override int MaxOperandCount => 4;

public override object EvalValue(Func<string, object> context)
{
var date = (DateTime)Convert.ChangeType(Operands[0].EvalValue(context), typeof(DateTime));
var count = (int)Convert.ChangeType(Operands[1].EvalValue(context), typeof(int));

var startOfWeek = DayOfWeek.Monday;
if (Operands.Count > 2)
startOfWeek = (DayOfWeek)(((int)Convert.ChangeType(Operands[2].EvalValue(context), typeof(int)) - 1) % 7);

var weekendLength = 2;
if (Operands.Count > 3)
weekendLength = (int)Convert.ChangeType(Operands[3].EvalValue(context), typeof(int));

var adjustedDate = AdjustBusinessDays(date, count, startOfWeek, weekendLength);
return adjustedDate.ToString("yyyy-MM-dd");
}

private DateTime AdjustBusinessDays(DateTime date, int count, DayOfWeek startOfWeek, int weekendLength)
{
var weekendDays = CalculateWeekendDays(startOfWeek, weekendLength);

var step = count > 0 ? 1 : -1;
var daysMoved = 0;

if (!weekendDays.Contains(date.DayOfWeek))
daysMoved++;

while (daysMoved < Math.Abs(count))
{
date = date.AddDays(step);

if (!weekendDays.Contains(date.DayOfWeek))
daysMoved++;
}

return date;
}

private HashSet<DayOfWeek> CalculateWeekendDays(DayOfWeek startOfWeek, int weekendLength)
{
var weekendDays = new HashSet<DayOfWeek>();

var endOfWeek = ((int)startOfWeek + 7 - 1) % 7;
for (var i = 0; i < weekendLength; i++)
{
var weekendDayIndex = (endOfWeek - i + 7) % 7;
weekendDays.Add((DayOfWeek)weekendDayIndex);
}

return weekendDays;
}
}
}