Skip to content

Commit

Permalink
feat: add functions to get or change some parts of a date (#214)
Browse files Browse the repository at this point in the history
* feat: add datepart functions returning textual values
* feat: add datepart functions returning numeric values
* feat: add functions to change year or month
* Update the automatically generated documentation related to the list of functions and predicates

---------

Co-authored-by: AppVeyor bot <[email protected]>
  • Loading branch information
Seddryck and AppVeyor bot authored Dec 22, 2023
1 parent ef20deb commit 6a029fb
Show file tree
Hide file tree
Showing 12 changed files with 1,061 additions and 289 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Expressif.Functions.Temporal;
using Expressif.Values;

namespace Expressif.Testing.Functions.Temporal;
public class DatePartChangeFunctionsTest
{
[Test]
[TestCase(1900, 1903)]
[TestCase(2000, 2000)]
[TestCase(-45, 16)]
[TestCase(800, 803)]
[TestCase(12300, 2000)]
public void ChangeOfYear_Integer_Valid(int year, int expected)
=> Assert.That(new ChangeOfYear(() => expected).Evaluate(year), Is.EqualTo(expected));

[Test]
[TestCase("1900-01-01", 1903, "1903-01-01")]
[TestCase("2000-01-01", 2000, "2000-01-01")]
[TestCase("800-01-01", 2000, "2000-01-01")]
[TestCase("2004-02-29", 2008, "2008-02-29")]
[TestCase("2004-02-29", 2005, "2005-02-28")]
public void ChangeOfYear_DateTime_Valid(DateTime dt, int newYear, DateTime expected)
=> Assert.That(new ChangeOfYear(() => newYear).Evaluate(dt), Is.EqualTo(expected));

[Test]
[TestCase("1900-01", 1903, "1903-01")]
[TestCase("2000-01", 2000, "2000-01")]
[TestCase("0800-01", 2000, "2000-01")]
public void ChangeOfYear_YearMonth_Valid(YearMonth yearMonth, int newYear, YearMonth expected)
=> Assert.That(new ChangeOfYear(() => newYear).Evaluate(yearMonth), Is.EqualTo(expected));

[Test]
[TestCase("1900-01-01", 3, "1900-03-01")]
[TestCase("2000-01-31", 3, "2000-03-31")]
[TestCase("2000-01-31", 4, "2000-04-30")]
[TestCase("2000-01-31", 2, "2000-02-29")]
[TestCase("2001-01-31", 2, "2001-02-28")]
public void ChangeOfMonth_DateTime_Valid(DateTime dt, int newMonth, DateTime expected)
=> Assert.That(new ChangeOfMonth(() => newMonth).Evaluate(dt), Is.EqualTo(expected));

[Test]
[TestCase("1900-01-01", -1)]
[TestCase("1900-01-01", 0)]
[TestCase("1900-01-01", 13)]
public void ChangeOfMonth_DateTime_Invalid(DateTime dt, int newMonth)
=> Assert.That(new ChangeOfMonth(() => newMonth).Evaluate(dt)!.Equals(null), Is.True);

[Test]
[TestCase("1900-01", 3, "1900-03")]
[TestCase("1900-01", 3, "1900-03")]
[TestCase("2000-01", 1, "2000-01")]
[TestCase("0800-01", 3, "0800-03")]
public void ChangeOfMonth_YearMonth_Valid(YearMonth yearMonth, int newMonth, YearMonth expected)
=> Assert.That(new ChangeOfMonth(() => newMonth).Evaluate(yearMonth), Is.EqualTo(expected));

[Test]
[TestCase("1900-01", -1)]
[TestCase("1900-01", 0)]
[TestCase("1900-01", 13)]
public void ChangeOfMonth_YearMonth_Invalid(YearMonth yearMonth, int newMonth)
=> Assert.That(new ChangeOfMonth(() => newMonth).Evaluate(yearMonth)!.Equals(null), Is.True);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Expressif.Functions.Temporal;
using Expressif.Values;

namespace Expressif.Testing.Functions.Temporal;
public class DatePartNumericFunctionsTest
{
[Test]
[TestCase(1900, 1900)]
[TestCase(2000, 2000)]
[TestCase(-45, -0045)]
[TestCase(800, 800)]
[TestCase(12300, 12300)]
public void YearOfEra_Integer_Valid(int year, int expected)
=> Assert.That(new YearOfEra().Evaluate(year), Is.EqualTo(expected));

[Test]
[TestCase("1900-01-01", 1900)]
[TestCase("2000-01-01", 2000)]
[TestCase("800-01-01", 800)]
public void YearOfEra_DateTime_Valid(DateTime dt, int expected)
=> Assert.That(new YearOfEra().Evaluate(dt), Is.EqualTo(expected));

[Test]
[TestCase("1900-01", 1900)]
[TestCase("2000-01", 2000)]
[TestCase("0800-01", 800)]
public void YearOfEra_YearMonth_Valid(YearMonth yearMonth, int expected)
=> Assert.That(new YearOfEra().Evaluate(yearMonth), Is.EqualTo(expected));

[Test]
[TestCase(1)]
[TestCase(10)]
public void MonthOfYear_Integer_Valid(int month)
=> Assert.That(new MonthOfYear().Evaluate(month), Is.Null);

[Test]
[TestCase("1900-01-01", 1)]
[TestCase("2000-10-01", 10)]
[TestCase("800-03-01", 3)]
public void MonthOfYear_DateTime_Valid(DateTime dt, int expected)
=> Assert.That(new MonthOfYear().Evaluate(dt), Is.EqualTo(expected));

[Test]
[TestCase("1900-01", 1)]
[TestCase("2000-10", 10)]
[TestCase("0800-03", 3)]
public void MonthOfYear_YearMonth_Valid(YearMonth yearMonth, int expected)
=> Assert.That(new MonthOfYear().Evaluate(yearMonth), Is.EqualTo(expected));

[Test]
[TestCase("2000-01-01", 6)]
[TestCase("2000-01-02", 7)]
[TestCase("2000-01-03", 1)]
public void DayOfWeek_DateTime_Valid(DateTime dt, int expected)
=> Assert.That(new Expressif.Functions.Temporal.DayOfWeek().Evaluate(dt), Is.EqualTo(expected));

[Test]
[TestCase("1900-01-01", 1)]
[TestCase("2000-10-28", 28)]
[TestCase("800-03-17", 17)]
public void DayOfMonth_DateTime_Valid(DateTime dt, int expected)
=> Assert.That(new DayOfMonth().Evaluate(dt), Is.EqualTo(expected));

[Test]
[TestCase("1900-01-01", 1)]
[TestCase("2000-02-01", 32)]
[TestCase("800-03-17", 77)]
public void DayOfYear_DateTime_Valid(DateTime dt, int expected)
=> Assert.That(new DayOfYear().Evaluate(dt), Is.EqualTo(expected));

[Test]
[TestCase("1999-12-31", 362)]
[TestCase("2000-01-01", 363)]
[TestCase("2000-01-02", 364)]
[TestCase("2000-01-03", 001)]
public void IsoDayOfYear_DateTime_Valid(DateTime dt, int expected)
=> Assert.That(new IsoDayOfYear().Evaluate(dt), Is.EqualTo(expected));

[Test]
[TestCase("1999-12-31", 52)]
[TestCase("2000-01-01", 52)]
[TestCase("2000-01-02", 52)]
[TestCase("2000-01-03", 01)]
public void IsoWeekOfYear_DateTime_Valid(DateTime dt, int expected)
=> Assert.That(new IsoWeekOfYear().Evaluate(dt), Is.EqualTo(expected));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Expressif.Functions.Temporal;
using Expressif.Values;

namespace Expressif.Testing.Functions.Temporal;
public class DatePartTextualFunctionsTest
{
[Test]
[TestCase(1900, "1900")]
[TestCase(2000, "2000")]
[TestCase(-45, "-0045")]
[TestCase(800, "0800")]
[TestCase(12300, "12300")]
public void Year_Integer_Valid(int year, string expected)
=> Assert.That(new Year().Evaluate(year), Is.EqualTo(expected));

[Test]
[TestCase("1900-01-01", "1900")]
[TestCase("2000-01-01", "2000")]
[TestCase("800-01-01", "0800")]
public void Year_DateTime_Valid(DateTime dt, string expected)
=> Assert.That(new Year().Evaluate(dt), Is.EqualTo(expected));

[Test]
[TestCase("1900-01", "1900")]
[TestCase("2000-01", "2000")]
[TestCase("0800-01", "0800")]
public void Year_YearMonth_Valid(YearMonth yearMonth, string expected)
=> Assert.That(new Year().Evaluate(yearMonth), Is.EqualTo(expected));

[Test]
[TestCase(1)]
[TestCase(10)]
public void Month_Integer_Valid(int month)
=> Assert.That(new Month().Evaluate(month), Is.Null);

[Test]
[TestCase("1900-01-01", "01")]
[TestCase("2000-10-01", "10")]
[TestCase("800-03-01", "03")]
public void Month_DateTime_Valid(DateTime dt, string expected)
=> Assert.That(new Month().Evaluate(dt), Is.EqualTo(expected));

[Test]
[TestCase("1900-01", "01")]
[TestCase("2000-10", "10")]
[TestCase("0800-03", "03")]
public void Month_YearMonth_Valid(YearMonth yearMonth, string expected)
=> Assert.That(new Month().Evaluate(yearMonth), Is.EqualTo(expected));

[Test]
[TestCase("1900-01-01", "01-01")]
[TestCase("2000-10-01", "10-01")]
[TestCase("800-03-17", "03-17")]
public void MonthDay_DateTime_Valid(DateTime dt, string expected)
=> Assert.That(new MonthDay().Evaluate(dt), Is.EqualTo(expected));

[Test]
[TestCase("2000-01-01", "1999-W52")]
[TestCase("2000-01-08", "2000-W01")]
public void IsoYearWeek_DateTime_Valid(DateTime dt, string expected)
=> Assert.That(new IsoYearWeek().Evaluate(dt), Is.EqualTo(expected));

[Test]
[TestCase("1999-12-31", "1999-W52-5")]
[TestCase("2000-01-01", "1999-W52-6")]
[TestCase("2000-01-02", "1999-W52-7")]
[TestCase("2000-01-03", "2000-W01-1")]
public void IsoYearWeekDay_DateTime_Valid(DateTime dt, string expected)
=> Assert.That(new IsoYearWeekDay().Evaluate(dt), Is.EqualTo(expected));

[Test]
[TestCase("1999-12-31", "1999-362")]
[TestCase("2000-01-01", "1999-363")]
[TestCase("2000-01-02", "1999-364")]
[TestCase("2000-01-03", "2000-001")]
public void IsoYearDay_DateTime_Valid(DateTime dt, string expected)
=> Assert.That(new IsoYearDay().Evaluate(dt), Is.EqualTo(expected));
}
84 changes: 84 additions & 0 deletions Expressif/Functions/Temporal/DatePartChangeFunctions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Expressif.Functions.Special;
using Expressif.Values;
using Expressif.Values.Casters;
using Expressif.Values.Special;

namespace Expressif.Functions.Temporal;
public abstract class BaseDatePartChangeFunction : BaseTemporalFunction
{
protected override object? EvaluateUncasted(object value)
{
if (new Null().Equals(value))
return EvaluateNull();

if (new IntegerCaster().TryCast(value, out var integer))
return EvaluateInteger(integer);

if (new YearMonthCaster().TryCast(value, out var yearMonth))
return EvaluateYearMonth(yearMonth);

if (new DateTimeCaster().TryCast(value, out var dateTime))
return EvaluateDateTime(dateTime);

return null;
}

protected virtual object? EvaluateInteger(int numeric) => null;
protected virtual object? EvaluateYearMonth(YearMonth yearMonth) => null;
}

/// <summary>
/// returns a temporal value corresponding to the same day and month of the argument value but of the year passed as the parameter.
/// If the original date was the 29th of February and the year passed as a parameter is not a leap year then it returns the 28th of February.
/// </summary>
public class ChangeOfYear : BaseDatePartNumericFunction
{
public Func<int> Year { get; }
public ChangeOfYear(Func<int> year)
=> Year = year;

protected override object EvaluateDateTime(DateTime value)
{
var newYear = Year.Invoke();
if (newYear < 1 || newYear > 9999)
return new Null();
var newDay = value.Month == 2 && value.Day == 29 && !DateTime.IsLeapYear(newYear) ? 28 : value.Day;
return new DateTime(newYear, value.Month, newDay, value.Hour, value.Minute, value.Second, value.Millisecond);
}
protected override object? EvaluateInteger(int numeric) => Year.Invoke();
protected override object? EvaluateYearMonth(YearMonth yearMonth) => new YearMonth(Year.Invoke(), yearMonth.Month);
}

/// <summary>
/// returns a temporal value corresponding to the same day and year of the argument value but of the month passed as the parameter.
/// If the original day is 29, 30, or 31 and the new month passed as a parameter has fewer days then it returns the last day of the corresponding month.
/// </summary>
public class ChangeOfMonth : BaseDatePartChangeFunction
{
public Func<int> Month { get; }
public ChangeOfMonth(Func<int> month)
=> Month = month;

protected override object EvaluateDateTime(DateTime value)
{
var newMonth = Month.Invoke();
if (newMonth < 1 || newMonth > 12)
return new Null();
var lastDayOfMonth = new DateTime(value.Year, newMonth, 1).AddMonths(1).AddDays(-1).Day;
var newDay = value.Day > lastDayOfMonth ? lastDayOfMonth : value.Day;
return new DateTime(value.Year, newMonth, newDay, value.Hour, value.Minute, value.Second, value.Millisecond);
}
protected override object? EvaluateYearMonth(YearMonth yearMonth)
{
var newMonth = Month.Invoke();
if (newMonth < 1 || newMonth > 12)
return new Null();
return new YearMonth(yearMonth.Year, newMonth);
}
}
Loading

0 comments on commit 6a029fb

Please sign in to comment.