Skip to content

Commit 3cf68a5

Browse files
committed
Implemented every expression to return an enumerable for a given interval.
1 parent f7809bc commit 3cf68a5

8 files changed

Lines changed: 190 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
All notable changes to this project will be documented in this file.
44

5+
## [0.3.0] - 2025-11-21
6+
7+
- Implemented `every` expression to return an enumerable for a given interval.
8+
59
## [0.2.1] - 2025-10-07
610

711
- Fixed a bug with `and` throwing an exception.

README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,37 @@ Output:
9797
2020-01-03T19:00:00
9898
```
9999

100+
### Recurring schedules
101+
102+
You can generate a lazy sequence of future dates using `every`. The sequence is infinite, so add your own break condition.
103+
104+
```csharp
105+
using STYME;
106+
107+
var baseTime = new DateTime(2025, 1, 1, 0, 0, 0);
108+
var parser = NaturalDateTime.From(baseTime);
109+
var schedule = parser.Enumerate("every 2 weeks");
110+
111+
foreach (var occurrence in schedule)
112+
{
113+
Console.WriteLine(occurrence);
114+
// Break when you reach the point you care about.
115+
if (occurrence >= new DateTime(2025, 2, 12))
116+
{
117+
break;
118+
}
119+
}
120+
```
121+
122+
Output:
123+
124+
```
125+
2025-01-01T00:00:00
126+
2025-01-15T00:00:00
127+
2025-01-29T00:00:00
128+
2025-02-12T00:00:00
129+
```
130+
100131
### Supported units with `add` and `deduct`
101132

102133
The `add` and `deduct` expressions support the following units (singular and plural forms):
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
namespace STYME.UnitTests.NaturalDateTimeTests;
2+
3+
[TestClass]
4+
public sealed class NaturalDateTimeEveryTests
5+
{
6+
[TestMethod]
7+
public void Given_2025_01_01T00_00_00_When_Every_2_Weeks_Then_Yields_Expected_Sequence()
8+
{
9+
var baseDate = new DateTime(2025, 1, 1, 0, 0, 0);
10+
var sut = NaturalDateTime.From(baseDate);
11+
12+
var results = new List<DateTime>();
13+
foreach (var occurrence in sut.Enumerate("every 2 weeks"))
14+
{
15+
results.Add(occurrence);
16+
17+
if (occurrence >= new DateTime(2025, 2, 12, 0, 0, 0))
18+
{
19+
break;
20+
}
21+
}
22+
23+
var expected = new[]
24+
{
25+
new DateTime(2025, 1, 1, 0, 0, 0),
26+
new DateTime(2025, 1, 15, 0, 0, 0),
27+
new DateTime(2025, 1, 29, 0, 0, 0),
28+
new DateTime(2025, 2, 12, 0, 0, 0),
29+
};
30+
31+
CollectionAssert.AreEqual(expected, results);
32+
}
33+
34+
[TestMethod]
35+
public void Given_2025_01_01T10_00_00_When_Every_3_Days_Then_Yields_Expected_Sequence()
36+
{
37+
var baseDate = new DateTime(2025, 1, 1, 10, 0, 0);
38+
var sut = NaturalDateTime.From(baseDate);
39+
40+
var results = new List<DateTime>();
41+
foreach (var occurrence in sut.Enumerate("every 3 days"))
42+
{
43+
results.Add(occurrence);
44+
45+
if (occurrence >= new DateTime(2025, 1, 7, 10, 0, 0))
46+
{
47+
break;
48+
}
49+
}
50+
51+
var expected = new[]
52+
{
53+
new DateTime(2025, 1, 1, 10, 0, 0),
54+
new DateTime(2025, 1, 4, 10, 0, 0),
55+
new DateTime(2025, 1, 7, 10, 0, 0),
56+
};
57+
58+
CollectionAssert.AreEqual(expected, results);
59+
}
60+
}
61+
62+
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
using System.Collections;
2+
using STYME.Handling;
3+
using STYME.Values.TimeShifts;
4+
5+
namespace STYME.Expressions.Implementations;
6+
7+
internal sealed class EveryExpression : IExpression
8+
{
9+
private readonly IExpression _right;
10+
11+
public EveryExpression(IExpression right)
12+
{
13+
_right = right;
14+
}
15+
16+
public ExpressionResult ExecuteExpression(IMutableTime time)
17+
{
18+
var result = _right.ExecuteExpression(time);
19+
20+
if (result.TryGet<ITimeShift>(out var shift))
21+
{
22+
return new ExpressionResult(new RecurringDateTimeEnumerable(time.ToExternal(), shift));
23+
}
24+
25+
return ExpressionResult.Empty;
26+
}
27+
28+
private sealed class RecurringDateTimeEnumerable : IEnumerable<DateTime>
29+
{
30+
private readonly DateTime _start;
31+
private readonly ITimeShift _shift;
32+
33+
public RecurringDateTimeEnumerable(DateTime start, ITimeShift shift)
34+
{
35+
_start = start;
36+
_shift = shift;
37+
}
38+
39+
public IEnumerator<DateTime> GetEnumerator()
40+
{
41+
return Enumerate().GetEnumerator();
42+
}
43+
44+
IEnumerator IEnumerable.GetEnumerator()
45+
{
46+
return GetEnumerator();
47+
}
48+
49+
private IEnumerable<DateTime> Enumerate()
50+
{
51+
IMutableTime current = new MutableDateTime(_start);
52+
53+
while (true)
54+
{
55+
yield return current.ToExternal();
56+
current = _shift.Shift(current);
57+
}
58+
}
59+
}
60+
}
61+

src/STYME/Locale/English/EnglishParserRules.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public EnglishParserRules()
2525
_constructors = new(StringComparer.OrdinalIgnoreCase);
2626
AddConstructorAliases(new AddExpressionConstructor(), "add");
2727
AddConstructorAliases(new DeductExpressionConstructor(), "deduct", "subtract");
28+
AddConstructorAliases(new EveryExpressionConstructor(), "every");
2829
}
2930

3031
private void AddConstructorAliases(IExpressionConstructor constructor, params string[] names)

src/STYME/NaturalDateTime.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,18 @@ public DateTime Parse(string value)
3737
//TODO: Placeholder.
3838
return DateTime.Now;
3939
}
40+
41+
public IEnumerable<DateTime> Enumerate(string value)
42+
{
43+
var tokens = new Queue<string>(value.Split(' '));
44+
var root = _expressionParser.ParseExpressionTree(tokens) ?? throw new InvalidOperationException("Expression expected.");
45+
46+
var result = root.ExecuteExpression(_mutableTime);
47+
if (result.TryGet<IEnumerable<DateTime>>(out var sequence) && sequence is not null)
48+
{
49+
return sequence;
50+
}
51+
52+
throw new InvalidOperationException("Expression did not produce a recurring sequence.");
53+
}
4054
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using STYME.Expressions;
2+
using STYME.Expressions.Implementations;
3+
4+
namespace STYME.Parser.Expressions.Constructors;
5+
6+
internal sealed class EveryExpressionConstructor : IExpressionConstructor
7+
{
8+
public IExpression Construct(string token, Queue<string> tokens, IExpressionParser parser)
9+
{
10+
var right = parser.ParsePrimary(tokens) ?? throw new InvalidOperationException($"Expected expression after keyword '{token}'.");
11+
return new EveryExpression(right);
12+
}
13+
}
14+

src/STYME/STYME.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<Version>0.2.1</Version>
4+
<Version>0.3.0</Version>
55
<Summary>A zero-dependency library that allows for manipulating dates and times using natural language.</Summary>
66
<Description>A zero-dependency library that allows for manipulating dates and times using natural language, for example "add 1 day" or "deduct 3 hours".</Description>
77
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
@@ -29,9 +29,9 @@
2929
</ItemGroup>
3030

3131
<PropertyGroup>
32-
<Version>0.2.1</Version>
32+
<Version>0.3.0</Version>
3333
<PackageReleaseNotes>
34-
- Fixed a bug with `and` throwing an exception.
34+
- Implemented `every` expression to return an enumerable for a given interval.
3535
</PackageReleaseNotes>
3636
</PropertyGroup>
3737

0 commit comments

Comments
 (0)