diff --git a/src/MfGames.Nitride.Calendar/MfGames.Nitride.Calendar.csproj b/src/MfGames.Nitride.Calendar/MfGames.Nitride.Calendar.csproj
index 12b4ffe..f2a7916 100644
--- a/src/MfGames.Nitride.Calendar/MfGames.Nitride.Calendar.csproj
+++ b/src/MfGames.Nitride.Calendar/MfGames.Nitride.Calendar.csproj
@@ -10,16 +10,16 @@
-
-
-
+
+
+
-
-
-
-
+
+
+
+
diff --git a/src/MfGames.Nitride.Feeds/MfGames.Nitride.Feeds.csproj b/src/MfGames.Nitride.Feeds/MfGames.Nitride.Feeds.csproj
index 2e76f41..21b4f95 100644
--- a/src/MfGames.Nitride.Feeds/MfGames.Nitride.Feeds.csproj
+++ b/src/MfGames.Nitride.Feeds/MfGames.Nitride.Feeds.csproj
@@ -10,15 +10,15 @@
-
-
-
+
+
+
-
-
-
+
+
+
diff --git a/src/MfGames.Nitride.Generators/MfGames.Nitride.Generators.csproj b/src/MfGames.Nitride.Generators/MfGames.Nitride.Generators.csproj
index a5f7424..c1dace6 100644
--- a/src/MfGames.Nitride.Generators/MfGames.Nitride.Generators.csproj
+++ b/src/MfGames.Nitride.Generators/MfGames.Nitride.Generators.csproj
@@ -10,7 +10,7 @@
-
+
diff --git a/src/MfGames.Nitride.Handlebars/MfGames.Nitride.Handlebars.csproj b/src/MfGames.Nitride.Handlebars/MfGames.Nitride.Handlebars.csproj
index c6d4e92..47c9801 100644
--- a/src/MfGames.Nitride.Handlebars/MfGames.Nitride.Handlebars.csproj
+++ b/src/MfGames.Nitride.Handlebars/MfGames.Nitride.Handlebars.csproj
@@ -10,15 +10,15 @@
-
-
-
-
-
+
+
+
+
+
-
+
diff --git a/src/MfGames.Nitride.Html/MfGames.Nitride.Html.csproj b/src/MfGames.Nitride.Html/MfGames.Nitride.Html.csproj
index 91923b8..d9ed940 100644
--- a/src/MfGames.Nitride.Html/MfGames.Nitride.Html.csproj
+++ b/src/MfGames.Nitride.Html/MfGames.Nitride.Html.csproj
@@ -9,7 +9,7 @@
-
+
@@ -25,7 +25,7 @@
-
+
diff --git a/src/MfGames.Nitride.IO/MfGames.Nitride.IO.csproj b/src/MfGames.Nitride.IO/MfGames.Nitride.IO.csproj
index 928fe60..41e802c 100644
--- a/src/MfGames.Nitride.IO/MfGames.Nitride.IO.csproj
+++ b/src/MfGames.Nitride.IO/MfGames.Nitride.IO.csproj
@@ -8,17 +8,17 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
+
diff --git a/src/MfGames.Nitride.Json/MfGames.Nitride.Json.csproj b/src/MfGames.Nitride.Json/MfGames.Nitride.Json.csproj
index b32b6c1..a630521 100644
--- a/src/MfGames.Nitride.Json/MfGames.Nitride.Json.csproj
+++ b/src/MfGames.Nitride.Json/MfGames.Nitride.Json.csproj
@@ -14,7 +14,7 @@
-
+
diff --git a/src/MfGames.Nitride.Markdown/MfGames.Nitride.Markdown.csproj b/src/MfGames.Nitride.Markdown/MfGames.Nitride.Markdown.csproj
index cd5884b..d2e64f9 100644
--- a/src/MfGames.Nitride.Markdown/MfGames.Nitride.Markdown.csproj
+++ b/src/MfGames.Nitride.Markdown/MfGames.Nitride.Markdown.csproj
@@ -10,17 +10,17 @@
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
diff --git a/src/MfGames.Nitride.Temporal.Schedules/ApplySchedules.cs b/src/MfGames.Nitride.Temporal.Schedules/ApplySchedules.cs
new file mode 100644
index 0000000..55378d3
--- /dev/null
+++ b/src/MfGames.Nitride.Temporal.Schedules/ApplySchedules.cs
@@ -0,0 +1,6 @@
+namespace MfGames.Nitride.Temporal.Schedules;
+
+public interface ApplySchedules
+{
+
+}
diff --git a/src/MfGames.Nitride.Temporal.Schedules/ISchedule.cs b/src/MfGames.Nitride.Temporal.Schedules/ISchedule.cs
new file mode 100644
index 0000000..3d556ec
--- /dev/null
+++ b/src/MfGames.Nitride.Temporal.Schedules/ISchedule.cs
@@ -0,0 +1,24 @@
+using MfGames.Gallium;
+
+namespace MfGames.Nitride.Temporal.Schedules;
+
+///
+/// A schedule is a specific schedule that can be applied to an entity to make
+/// changes based on components.
+///
+public interface ISchedule
+{
+ ///
+ /// Determines if a schedule applies to a given entity.
+ ///
+ /// The entity to test.
+ /// True if the schedule applies to the given entity, otherwise false.
+ bool CanApply(Entity entity);
+
+ ///
+ /// Applies the schedule changes to the entity and returns the results.
+ ///
+ /// The entity to update.
+ /// The modified entity, if the changes can be applied, otherwise the same entity.
+ Entity Apply(Entity entity);
+}
diff --git a/src/MfGames.Nitride.Temporal.Schedules/MfGames.Nitride.Temporal.Schedules.csproj b/src/MfGames.Nitride.Temporal.Schedules/MfGames.Nitride.Temporal.Schedules.csproj
new file mode 100644
index 0000000..ee081a1
--- /dev/null
+++ b/src/MfGames.Nitride.Temporal.Schedules/MfGames.Nitride.Temporal.Schedules.csproj
@@ -0,0 +1,35 @@
+
+
+
+ net6.0
+ enable
+
+
+
+ An extension to Nitride static site generator to add a scheduling system for posts.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ True
+
+
+
+
+ Analyzer
+ False
+
+
+
+
diff --git a/src/MfGames.Nitride.Temporal.Schedules/NumericalPathSchedule.cs b/src/MfGames.Nitride.Temporal.Schedules/NumericalPathSchedule.cs
new file mode 100644
index 0000000..c964160
--- /dev/null
+++ b/src/MfGames.Nitride.Temporal.Schedules/NumericalPathSchedule.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Text.RegularExpressions;
+
+using MfGames.Gallium;
+using MfGames.Nitride.Generators;
+
+namespace MfGames.Nitride.Temporal.Schedules;
+
+///
+/// A schedule that uses the `UPath` of the entity to determine an offset from
+/// a starting point and calculates the information from there.
+///
+[WithProperties]
+public partial class NumericalPathSchedule
+{
+ public NumericalPathSchedule()
+ {
+ this.PathRegex = new Regex(@"^.*(\d+)");
+ this.GetPath = (entity) => entity.Get().ToString();
+ this.CaptureGroup = 1;
+ this.CaptureOffset = -1;
+ }
+
+ public Regex? PathRegex { get; set; }
+
+ public Func GetPath { get; set; }
+ public DateTime? ScheduleStart { get; set; }
+ public SchedulePeriod SchedulePeriod { get; set; }
+
+ public int CaptureGroup { get; set; }
+
+ public int CaptureOffset { get; set; }
+}
diff --git a/src/MfGames.Nitride.Temporal.Schedules/README.md b/src/MfGames.Nitride.Temporal.Schedules/README.md
new file mode 100644
index 0000000..ee08cbc
--- /dev/null
+++ b/src/MfGames.Nitride.Temporal.Schedules/README.md
@@ -0,0 +1,41 @@
+# Date Processing
+
+One of the common features of static websites are blogs which leads to having
+some form of date-centric processing of pages to build archive pages, calendars,
+and being able to write posts in the future.
+
+With the component system, the date of a given file is simply attached to
+`Nodatime.Instant` component of the `Entity` object for the bulk of the
+processing.
+
+## Supporting Time Zones
+
+The concept of time zones while date processing is one that is frequently
+overlooked. A date is a date, right? However, most blogs and news sites have a
+concept of when a new day starts but it isn't always the same time as the server
+that is building the site. While a blog might be in America/Chicago time, a CI
+server could be set to UTC (such as Azure build servers) and the
+"day" may roll over fix or six hours before or after the blog's time.
+
+This is why Nitride uses `Instant` for when pages are implemented. These are
+points in time that are independent of time zones, but we also provide tools for
+converting a date model or one from the path into a proper instant based on the
+blog's time zone.
+
+## Why NodaTime?
+
+We decided to use [NodaTime](https://nodatime.org/) instead of the built-in date
+time functions for a number of reasons, mainly because it has a more intuitive
+way of handling time zones
+
+## Configuring
+
+There are two callbacks on `NitrideBuilder` that can be used to define the date
+and time processing for the blog.
+
+```csharp
+NitrideBuilder builder;
+
+builder
+ .ConfigureDates((NitrideClock clock) => clock.SetTimeZone())
+```
diff --git a/src/MfGames.Nitride.Temporal.Schedules/Setup/NitrideTemporalSchedulesBuilderExtensions.cs b/src/MfGames.Nitride.Temporal.Schedules/Setup/NitrideTemporalSchedulesBuilderExtensions.cs
new file mode 100644
index 0000000..7234447
--- /dev/null
+++ b/src/MfGames.Nitride.Temporal.Schedules/Setup/NitrideTemporalSchedulesBuilderExtensions.cs
@@ -0,0 +1,79 @@
+using System;
+
+using Autofac;
+
+using MfGames.Nitride.Commands;
+using MfGames.Nitride.Temporal.Cli;
+
+using Serilog;
+
+namespace MfGames.Nitride.Temporal.Schedules.Setup;
+
+public static class NitrideTemporalSchedulesBuilderExtensions
+{
+ ///
+ /// Extends the builder to allow for configuring the temporal
+ /// settings for generation.
+ ///
+ public static NitrideBuilder UseTemporal(
+ this NitrideBuilder builder,
+ Action? configure = null)
+ {
+ // Get the configuration so we can set the various options.
+ var config = new NitrideTemporalConfiguration();
+
+ configure?.Invoke(config);
+
+ // Add in the module registration.
+ builder.ConfigureContainer(
+ x =>
+ {
+ // Register the module.
+ x.RegisterModule();
+
+ // Add in the CLI options.
+ if (config.AddDateOptionToCommandLine)
+ {
+ x.RegisterType()
+ .As();
+ }
+
+ if (config.AddExpireOptionToCommandLine
+ && config.Expiration != null)
+ {
+ x.Register(
+ context =>
+ {
+ ILogger logger = context.Resolve();
+ Timekeeper
+ clock = context.Resolve();
+
+ return new ExpiresPipelineCommandOption(
+ logger,
+ clock,
+ config.Expiration);
+ })
+ .As();
+ }
+ });
+
+ if (config.DateTimeZone != null)
+ {
+ builder.ConfigureSite(
+ (
+ _,
+ scope) =>
+ {
+ ILogger logger = scope.Resolve();
+ Timekeeper timekeeper = scope.Resolve();
+
+ timekeeper.DateTimeZone = config.DateTimeZone;
+ logger.Verbose(
+ "Setting time zone to {Zone:l}",
+ timekeeper.DateTimeZone);
+ });
+ }
+
+ return builder;
+ }
+}
diff --git a/src/MfGames.Nitride.Temporal.Schedules/Setup/NitrideTemporalSchedulesModule.cs b/src/MfGames.Nitride.Temporal.Schedules/Setup/NitrideTemporalSchedulesModule.cs
new file mode 100644
index 0000000..ab79259
--- /dev/null
+++ b/src/MfGames.Nitride.Temporal.Schedules/Setup/NitrideTemporalSchedulesModule.cs
@@ -0,0 +1,20 @@
+using Autofac;
+
+namespace MfGames.Nitride.Temporal.Schedules.Setup;
+
+public class NitrideTemporalSchedulesModule : Module
+{
+ ///
+ protected override void Load(ContainerBuilder builder)
+ {
+ builder.RegisterOperators(this);
+ builder.RegisterValidators(this);
+
+ builder.RegisterType()
+ .AsSelf()
+ .SingleInstance();
+
+ builder.RegisterGeneric(typeof(SetInstantFromComponent<>))
+ .As(typeof(SetInstantFromComponent<>));
+ }
+}
diff --git a/src/MfGames.Nitride.Temporal.Schedules/Validators/ApplySchedulesValidator.cs b/src/MfGames.Nitride.Temporal.Schedules/Validators/ApplySchedulesValidator.cs
new file mode 100644
index 0000000..b2a317c
--- /dev/null
+++ b/src/MfGames.Nitride.Temporal.Schedules/Validators/ApplySchedulesValidator.cs
@@ -0,0 +1,18 @@
+using FluentValidation;
+
+namespace MfGames.Nitride.Temporal.Schedules.Validators;
+
+public class CreateDateIndexesValidator : AbstractValidator
+{
+ public CreateDateIndexesValidator()
+ {
+ this.RuleFor(a => a.Timekeeper)
+ .NotNull();
+
+ this.RuleFor(a => a.CreateIndex)
+ .NotNull();
+
+ this.RuleFor(a => a.Formats)
+ .NotNull();
+ }
+}
diff --git a/src/MfGames.Nitride.Temporal/NitrideTemporalModule.cs b/src/MfGames.Nitride.Temporal/Setup/NitrideTemporalModule.cs
similarity index 100%
rename from src/MfGames.Nitride.Temporal/NitrideTemporalModule.cs
rename to src/MfGames.Nitride.Temporal/Setup/NitrideTemporalModule.cs
diff --git a/src/MfGames.Nitride.Yaml/MfGames.Nitride.Yaml.csproj b/src/MfGames.Nitride.Yaml/MfGames.Nitride.Yaml.csproj
index 1093a94..9cc8072 100644
--- a/src/MfGames.Nitride.Yaml/MfGames.Nitride.Yaml.csproj
+++ b/src/MfGames.Nitride.Yaml/MfGames.Nitride.Yaml.csproj
@@ -14,7 +14,7 @@
-
+
diff --git a/src/MfGames.Nitride/MfGames.Nitride.csproj b/src/MfGames.Nitride/MfGames.Nitride.csproj
index d3d2ff1..9206a33 100644
--- a/src/MfGames.Nitride/MfGames.Nitride.csproj
+++ b/src/MfGames.Nitride/MfGames.Nitride.csproj
@@ -18,7 +18,7 @@
-
+
diff --git a/tests/MfGames.Nitride.IO.Tests/MfGames.Nitride.IO.Tests.csproj b/tests/MfGames.Nitride.IO.Tests/MfGames.Nitride.IO.Tests.csproj
index f1f4f20..00106dc 100644
--- a/tests/MfGames.Nitride.IO.Tests/MfGames.Nitride.IO.Tests.csproj
+++ b/tests/MfGames.Nitride.IO.Tests/MfGames.Nitride.IO.Tests.csproj
@@ -12,7 +12,7 @@
-
+
diff --git a/tests/MfGames.Nitride.Json.Tests/MfGames.Nitride.Json.Tests.csproj b/tests/MfGames.Nitride.Json.Tests/MfGames.Nitride.Json.Tests.csproj
index e250840..e4b5f6c 100644
--- a/tests/MfGames.Nitride.Json.Tests/MfGames.Nitride.Json.Tests.csproj
+++ b/tests/MfGames.Nitride.Json.Tests/MfGames.Nitride.Json.Tests.csproj
@@ -6,7 +6,7 @@
-
+
diff --git a/tests/MfGames.Nitride.Markdown.Tests/MfGames.Nitride.Markdown.Tests.csproj b/tests/MfGames.Nitride.Markdown.Tests/MfGames.Nitride.Markdown.Tests.csproj
index ae576c0..c8214fc 100644
--- a/tests/MfGames.Nitride.Markdown.Tests/MfGames.Nitride.Markdown.Tests.csproj
+++ b/tests/MfGames.Nitride.Markdown.Tests/MfGames.Nitride.Markdown.Tests.csproj
@@ -6,18 +6,18 @@
-
-
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/tests/MfGames.Nitride.Slugs.Tests/MfGames.Nitride.Slugs.Tests.csproj b/tests/MfGames.Nitride.Slugs.Tests/MfGames.Nitride.Slugs.Tests.csproj
index d8a072d..5d0468e 100644
--- a/tests/MfGames.Nitride.Slugs.Tests/MfGames.Nitride.Slugs.Tests.csproj
+++ b/tests/MfGames.Nitride.Slugs.Tests/MfGames.Nitride.Slugs.Tests.csproj
@@ -6,21 +6,21 @@
-
-
-
+
+
+
-
-
-
-
+
+
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
diff --git a/tests/MfGames.Nitride.Temporal.Schedules.Tests/MfGames.Nitride.Temporal.Schedules.Tests.csproj b/tests/MfGames.Nitride.Temporal.Schedules.Tests/MfGames.Nitride.Temporal.Schedules.Tests.csproj
new file mode 100644
index 0000000..da6250d
--- /dev/null
+++ b/tests/MfGames.Nitride.Temporal.Schedules.Tests/MfGames.Nitride.Temporal.Schedules.Tests.csproj
@@ -0,0 +1,30 @@
+
+
+
+ net6.0
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
diff --git a/tests/MfGames.Nitride.Temporal.Schedules.Tests/NumericalPathScheduleTests.cs b/tests/MfGames.Nitride.Temporal.Schedules.Tests/NumericalPathScheduleTests.cs
new file mode 100644
index 0000000..c161cce
--- /dev/null
+++ b/tests/MfGames.Nitride.Temporal.Schedules.Tests/NumericalPathScheduleTests.cs
@@ -0,0 +1,324 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+using MfGames.Gallium;
+using MfGames.Nitride.Temporal.Tests;
+using MfGames.Nitride.Tests;
+
+using Xunit;
+using Xunit.Abstractions;
+
+namespace MfGames.Nitride.Temporal.Schedules.Tests;
+
+public class TestPageModel
+{
+ public string Access { get; set; }
+}
+
+public class TestSchedule : NumericalPathSchedule
+{
+}
+
+public class ApplySchedulesTests : TemporalSchedulesTestBase
+{
+ public ApplySchedulesTests(ITestOutputHelper output)
+ : base(output)
+ {
+ }
+
+ [Fact]
+ public void MonthOnlyIndexes()
+ {
+ using TemporalSchedulesTestContext context = this.CreateContext();
+ Timekeeper timekeeper = context.Resolve();
+
+ CreateDateIndexes op = context.Resolve()
+ .WithFormats("yyyy-MM")
+ .WithCreateIndex(this.CreateIndex);
+
+ List input = new()
+ {
+ new Entity().Add("page1")
+ .Add(timekeeper.CreateInstant(2021, 1, 2)),
+ new Entity().Add("page2")
+ .Add(timekeeper.CreateInstant(2021, 2, 2)),
+ new Entity().Add("page3")
+ .Add(timekeeper.CreateInstant(2022, 1, 2)),
+ };
+
+ List?, List?>> actual =
+ this.GetActual(op, input);
+
+ var expected = new List?, List?>>
+ {
+ new(
+ "index-2021-01",
+ new List { "page1" },
+ new List()),
+ new(
+ "index-2021-02",
+ new List { "page2" },
+ new List()),
+ new(
+ "index-2022-01",
+ new List { "page3" },
+ new List()),
+ new("page1", null, null),
+ new("page2", null, null),
+ new("page3", null, null),
+ };
+
+ TestHelper.CompareObjects(expected, actual);
+ }
+
+ [Fact]
+ public void YearMonthDayIndexes()
+ {
+ using TemporalSchedulesTestContext context = this.CreateContext();
+ Timekeeper timekeeper = context.Resolve();
+
+ CreateDateIndexes op = context.Resolve()
+ .WithFormats("yyyy/MM/dd", "yyyy/MM", "yyyy")
+ .WithCreateIndex(this.CreateIndex);
+
+ List input = new()
+ {
+ new Entity().Add("page1")
+ .Add(timekeeper.CreateInstant(2021, 1, 2)),
+ new Entity().Add("page2")
+ .Add(timekeeper.CreateInstant(2021, 2, 2)),
+ new Entity().Add("page3")
+ .Add(timekeeper.CreateInstant(2022, 1, 2)),
+ };
+
+ List?, List?>> actual =
+ this.GetActual(op, input);
+
+ var expected = new List?, List?>>
+ {
+ new(
+ "index-2021",
+ new List(),
+ new List { "index-2021/01", "index-2021/02" }),
+ new(
+ "index-2021/01",
+ new List(),
+ new List { "index-2021/01/02" }),
+ new(
+ "index-2021/01/02",
+ new List { "page1" },
+ new List()),
+ new(
+ "index-2021/02",
+ new List(),
+ new List { "index-2021/02/02" }),
+ new(
+ "index-2021/02/02",
+ new List { "page2" },
+ new List()),
+ new(
+ "index-2022",
+ new List(),
+ new List { "index-2022/01" }),
+ new(
+ "index-2022/01",
+ new List(),
+ new List { "index-2022/01/02" }),
+ new(
+ "index-2022/01/02",
+ new List { "page3" },
+ new List()),
+ new("page1", null, null),
+ new("page2", null, null),
+ new("page3", null, null),
+ };
+
+ TestHelper.CompareObjects(expected, actual);
+ }
+
+ [Fact]
+ public void YearMonthDayIndexesThreshold1()
+ {
+ using TemporalSchedulesTestContext context = this.CreateContext();
+ Timekeeper timekeeper = context.Resolve();
+
+ CreateDateIndexes op = context.Resolve()
+ .WithFormats("yyyy/MM/dd", "yyyy/MM", "yyyy")
+ .WithCreateIndex(this.CreateIndex)
+ .WithLessThanEqualCollapse(1);
+
+ List input = new()
+ {
+ new Entity().Add("page1")
+ .Add(timekeeper.CreateInstant(2021, 1, 2)),
+ new Entity().Add("page2")
+ .Add(timekeeper.CreateInstant(2021, 2, 2)),
+ new Entity().Add("page3")
+ .Add(timekeeper.CreateInstant(2022, 1, 2)),
+ };
+
+ List?, List?>> actual =
+ this.GetActual(op, input);
+
+ var expected = new List?, List?>>
+ {
+ new(
+ "index-2021",
+ new List(),
+ new List { "index-2021/01", "index-2021/02" }),
+ new(
+ "index-2021/01",
+ new List { "page1" },
+ new List { "index-2021/01/02" }),
+ new(
+ "index-2021/01/02",
+ new List { "page1" },
+ new List()),
+ new(
+ "index-2021/02",
+ new List { "page2" },
+ new List { "index-2021/02/02" }),
+ new(
+ "index-2021/02/02",
+ new List { "page2" },
+ new List()),
+ new(
+ "index-2022",
+ new List { "page3" },
+ new List { "index-2022/01" }),
+ new(
+ "index-2022/01",
+ new List { "page3" },
+ new List { "index-2022/01/02" }),
+ new(
+ "index-2022/01/02",
+ new List { "page3" },
+ new List()),
+ new("page1", null, null),
+ new("page2", null, null),
+ new("page3", null, null),
+ };
+
+ TestHelper.CompareObjects(expected, actual);
+ }
+
+ [Fact]
+ public void YearMonthIndexes()
+ {
+ using TemporalSchedulesTestContext context = this.CreateContext();
+ Timekeeper timekeeper = context.Resolve();
+
+ CreateDateIndexes op = context.Resolve()
+ .WithFormats("yyyy-MM", "yyyy")
+ .WithCreateIndex(this.CreateIndex);
+
+ List input = new()
+ {
+ new Entity().Add("page1")
+ .Add(timekeeper.CreateInstant(2021, 1, 2)),
+ new Entity().Add("page2")
+ .Add(timekeeper.CreateInstant(2021, 2, 2)),
+ new Entity().Add("page3")
+ .Add(timekeeper.CreateInstant(2022, 1, 2)),
+ };
+
+ List?, List?>> actual =
+ this.GetActual(op, input);
+
+ var expected = new List?, List?>>
+ {
+ new(
+ "index-2021",
+ new List(),
+ new List { "index-2021-01", "index-2021-02" }),
+ new(
+ "index-2021-01",
+ new List { "page1" },
+ new List()),
+ new(
+ "index-2021-02",
+ new List { "page2" },
+ new List()),
+ new(
+ "index-2022",
+ new List(),
+ new List { "index-2022-01" }),
+ new(
+ "index-2022-01",
+ new List { "page3" },
+ new List()),
+ new("page1", null, null),
+ new("page2", null, null),
+ new("page3", null, null),
+ };
+
+ TestHelper.CompareObjects(expected, actual);
+ }
+
+ [Fact]
+ public void YearOnlyIndexes()
+ {
+ using TemporalSchedulesTestContext context = this.CreateContext();
+ Timekeeper timekeeper = context.Resolve();
+
+ CreateDateIndexes op = context.Resolve()
+ .WithFormats("yyyy")
+ .WithCreateIndex(this.CreateIndex);
+
+ List input = new()
+ {
+ new Entity().Add("page1")
+ .Add(timekeeper.CreateInstant(2021, 1, 2)),
+ new Entity().Add("page2")
+ .Add(timekeeper.CreateInstant(2021, 2, 2)),
+ new Entity().Add("page3")
+ .Add(timekeeper.CreateInstant(2022, 1, 2)),
+ };
+
+ List?, List?>> actual =
+ this.GetActual(op, input);
+
+ var expected = new List?, List?>>
+ {
+ new(
+ "index-2021",
+ new List { "page1", "page2" },
+ new List()),
+ new("index-2022", new List { "page3" }, new List()),
+ new("page1", null, null),
+ new("page2", null, null),
+ new("page3", null, null),
+ };
+
+ TestHelper.CompareObjects(expected, actual);
+ }
+
+ private Entity CreateIndex(DateIndex a)
+ {
+ return new Entity().Add(a)
+ .Add($"index-{a.Key}");
+ }
+
+ private List?, List?>> GetActual(
+ CreateDateIndexes op,
+ List input)
+ {
+ var actual = op.Run(input)
+ .Select(
+ x => new Tuple?, List?>(
+ x.Get(),
+ x.GetOptional()
+ ?.Entries.Select(a => a.Get())
+ .OrderBy(b => b)
+ .ToList(),
+ x.GetOptional()
+ ?.Indexes.Select(a => a.Get())
+ .OrderBy(b => b)
+ .ToList()))
+ .OrderBy(x => x.Item1)
+ .ToList();
+
+ return actual;
+ }
+}
diff --git a/tests/MfGames.Nitride.Temporal.Schedules.Tests/TemporalSchedulesTestBase.cs b/tests/MfGames.Nitride.Temporal.Schedules.Tests/TemporalSchedulesTestBase.cs
new file mode 100644
index 0000000..a332cb7
--- /dev/null
+++ b/tests/MfGames.Nitride.Temporal.Schedules.Tests/TemporalSchedulesTestBase.cs
@@ -0,0 +1,13 @@
+using MfGames.TestSetup;
+
+using Xunit.Abstractions;
+
+namespace MfGames.Nitride.Temporal.Schedules.Tests;
+
+public abstract class TemporalSchedulesTestBase : TestBase
+{
+ protected TemporalSchedulesTestBase(ITestOutputHelper output)
+ : base(output)
+ {
+ }
+}
diff --git a/tests/MfGames.Nitride.Temporal.Schedules.Tests/TemporalSchedulesTestContext.cs b/tests/MfGames.Nitride.Temporal.Schedules.Tests/TemporalSchedulesTestContext.cs
new file mode 100644
index 0000000..3bc1770
--- /dev/null
+++ b/tests/MfGames.Nitride.Temporal.Schedules.Tests/TemporalSchedulesTestContext.cs
@@ -0,0 +1,16 @@
+using Autofac;
+
+using MfGames.Nitride.Temporal.Setup;
+using MfGames.Nitride.Tests;
+
+namespace MfGames.Nitride.Temporal.Schedules.Tests;
+
+public class TemporalTestContext : NitrideTestContext
+{
+ ///
+ protected override void ConfigureContainer(ContainerBuilder builder)
+ {
+ base.ConfigureContainer(builder);
+ builder.RegisterModule();
+ }
+}
diff --git a/tests/MfGames.Nitride.Tests/MfGames.Nitride.Tests.csproj b/tests/MfGames.Nitride.Tests/MfGames.Nitride.Tests.csproj
index e040383..7e99b4e 100644
--- a/tests/MfGames.Nitride.Tests/MfGames.Nitride.Tests.csproj
+++ b/tests/MfGames.Nitride.Tests/MfGames.Nitride.Tests.csproj
@@ -8,13 +8,13 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
@@ -26,7 +26,7 @@
-
+
diff --git a/tests/MfGames.Nitride.Yaml.Tests/MfGames.Nitride.Yaml.Tests.csproj b/tests/MfGames.Nitride.Yaml.Tests/MfGames.Nitride.Yaml.Tests.csproj
index ec613c8..c6b094c 100644
--- a/tests/MfGames.Nitride.Yaml.Tests/MfGames.Nitride.Yaml.Tests.csproj
+++ b/tests/MfGames.Nitride.Yaml.Tests/MfGames.Nitride.Yaml.Tests.csproj
@@ -6,7 +6,7 @@
-
+