Browse Source

alternate schedule and template consistency (#2757)

* refactor classic and block schedules to use same alternate schedule selector

* handle start year and end year

* add migrations for classic and block schedules

* allow editing block template start and end year

* add tests that include years

* add date range editing to classic (alternate) schedules

* fix running tests locally

* restore media files load; needed for local folder scanners

* update changelog

* feedback
pull/2758/head
Jason Dove 7 months ago committed by GitHub
parent
commit
effb96a2c2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 4
      CHANGELOG.md
  2. 9
      ErsatzTV.Application/Playouts/Commands/ReplacePlayoutAlternateSchedule.cs
  3. 62
      ErsatzTV.Application/Playouts/Commands/ReplacePlayoutAlternateScheduleItemsHandler.cs
  4. 9
      ErsatzTV.Application/Playouts/Mapper.cs
  5. 9
      ErsatzTV.Application/Playouts/PlayoutAlternateScheduleViewModel.cs
  6. 25
      ErsatzTV.Application/Playouts/Queries/GetPlayoutAlternateSchedulesHandler.cs
  7. 6
      ErsatzTV.Application/Scheduling/Commands/PreviewBlockPlayoutHandler.cs
  8. 4
      ErsatzTV.Application/Scheduling/Commands/ReplacePlayoutTemplate.cs
  9. 4
      ErsatzTV.Application/Scheduling/Commands/ReplacePlayoutTemplateItemsHandler.cs
  10. 4
      ErsatzTV.Application/Scheduling/Mapper.cs
  11. 4
      ErsatzTV.Application/Scheduling/PlayoutTemplateViewModel.cs
  12. 13
      ErsatzTV.Core.Tests/FFmpeg/DecoSelectorTests.cs
  13. 55
      ErsatzTV.Core.Tests/FFmpeg/GraphicsElementSelectorTests.cs
  14. 55
      ErsatzTV.Core.Tests/FFmpeg/WatermarkSelectorTests.cs
  15. 867
      ErsatzTV.Core.Tests/Scheduling/AlternateScheduleSelectorTests.cs
  16. 24
      ErsatzTV.Core.Tests/Scheduling/BlockScheduling/BlockPlayoutBuilderTests.cs
  17. 21
      ErsatzTV.Core.Tests/Scheduling/BlockScheduling/EffectiveBlockTests.cs
  18. 411
      ErsatzTV.Core.Tests/Scheduling/PlayoutTemplateSelectorTests.cs
  19. 25
      ErsatzTV.Core/Domain/ProgramScheduleAlternate.cs
  20. 16
      ErsatzTV.Core/Domain/Scheduling/IAlternateScheduleItem.cs
  21. 18
      ErsatzTV.Core/Domain/Scheduling/PlayoutTemplate.cs
  22. 2
      ErsatzTV.Core/FFmpeg/DecoSelector.cs
  23. 109
      ErsatzTV.Core/Scheduling/AlternateScheduleSelector.cs
  24. 2
      ErsatzTV.Core/Scheduling/BlockScheduling/BlockPlayoutFillerBuilder.cs
  25. 2
      ErsatzTV.Core/Scheduling/BlockScheduling/EffectiveBlock.cs
  26. 14
      ErsatzTV.Core/Scheduling/PlayoutBuilder.cs
  27. 40
      ErsatzTV.Core/Scheduling/PlayoutScheduleSelector.cs
  28. 86
      ErsatzTV.Core/Scheduling/PlayoutTemplateSelector.cs
  29. 1
      ErsatzTV.FFmpeg.Tests/ErsatzTV.FFmpeg.Tests.csproj
  30. 6950
      ErsatzTV.Infrastructure.MySql/Migrations/20260101170034_Align_ClassicBlockAlternateScheduleFields.Designer.cs
  31. 113
      ErsatzTV.Infrastructure.MySql/Migrations/20260101170034_Align_ClassicBlockAlternateScheduleFields.cs
  32. 27
      ErsatzTV.Infrastructure.MySql/Migrations/TvContextModelSnapshot.cs
  33. 6777
      ErsatzTV.Infrastructure.Sqlite/Migrations/20260101165954_Align_ClassicBlockAlternateScheduleFields.Designer.cs
  34. 113
      ErsatzTV.Infrastructure.Sqlite/Migrations/20260101165954_Align_ClassicBlockAlternateScheduleFields.cs
  35. 27
      ErsatzTV.Infrastructure.Sqlite/Migrations/TvContextModelSnapshot.cs
  36. 1
      ErsatzTV.Infrastructure/Data/Repositories/MetadataRepository.cs
  37. 96
      ErsatzTV/Pages/PlayoutAlternateSchedulesEditor.razor
  38. 36
      ErsatzTV/Pages/PlayoutTemplatesEditor.razor
  39. 29
      ErsatzTV/ViewModels/PlayoutAlternateScheduleEditViewModel.cs
  40. 32
      ErsatzTV/ViewModels/PlayoutTemplateEditViewModel.cs

4
CHANGELOG.md

@ -88,6 +88,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -88,6 +88,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Optimize Jellyfin show library scans by only requesting `People` (actors, directors, writers) when etags don't match
- This should significantly speed up periodic library scans, particularly against Jellyfin 10.11.x
- Lazy load media item images in UI
- Align alternate schedule and template handling (between classic schedules and block schedules)
- Both systems now support limiting to a date range
- This date range can be repeating (when year is not specified for start or end dates)
- This date range can be exact (when year is specified for start and end dates)
## [25.9.0] - 2025-11-29
### Added

9
ErsatzTV.Application/Playouts/Commands/ReplacePlayoutAlternateSchedule.cs

@ -6,4 +6,11 @@ public record ReplacePlayoutAlternateSchedule( @@ -6,4 +6,11 @@ public record ReplacePlayoutAlternateSchedule(
int ProgramScheduleId,
List<DayOfWeek> DaysOfWeek,
List<int> DaysOfMonth,
List<int> MonthsOfYear);
List<int> MonthsOfYear,
bool LimitToDateRange,
int StartMonth,
int StartDay,
int? StartYear,
int EndMonth,
int EndDay,
int? EndYear);

62
ErsatzTV.Application/Playouts/Commands/ReplacePlayoutAlternateScheduleItemsHandler.cs

@ -9,23 +9,13 @@ using Microsoft.Extensions.Logging; @@ -9,23 +9,13 @@ using Microsoft.Extensions.Logging;
namespace ErsatzTV.Application.Playouts;
public class ReplacePlayoutAlternateScheduleItemsHandler :
IRequestHandler<ReplacePlayoutAlternateScheduleItems, Either<BaseError, Unit>>
public class ReplacePlayoutAlternateScheduleItemsHandler(
IDbContextFactory<TvContext> dbContextFactory,
ChannelWriter<IBackgroundServiceRequest> channel,
ILogger<ReplacePlayoutAlternateScheduleItemsHandler> logger)
:
IRequestHandler<ReplacePlayoutAlternateScheduleItems, Either<BaseError, Unit>>
{
private readonly ChannelWriter<IBackgroundServiceRequest> _channel;
private readonly IDbContextFactory<TvContext> _dbContextFactory;
private readonly ILogger<ReplacePlayoutAlternateScheduleItemsHandler> _logger;
public ReplacePlayoutAlternateScheduleItemsHandler(
IDbContextFactory<TvContext> dbContextFactory,
ChannelWriter<IBackgroundServiceRequest> channel,
ILogger<ReplacePlayoutAlternateScheduleItemsHandler> logger)
{
_dbContextFactory = dbContextFactory;
_channel = channel;
_logger = logger;
}
public async Task<Either<BaseError, Unit>> Handle(
ReplacePlayoutAlternateScheduleItems request,
CancellationToken cancellationToken)
@ -34,7 +24,7 @@ public class ReplacePlayoutAlternateScheduleItemsHandler : @@ -34,7 +24,7 @@ public class ReplacePlayoutAlternateScheduleItemsHandler :
try
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
Option<Playout> maybePlayout = await dbContext.Playouts
.Include(p => p.ProgramSchedule)
@ -62,10 +52,9 @@ public class ReplacePlayoutAlternateScheduleItemsHandler : @@ -62,10 +52,9 @@ public class ReplacePlayoutAlternateScheduleItemsHandler :
foreach (DateTimeOffset dayToCheck in daysToCheck)
{
ProgramSchedule schedule = PlayoutScheduleSelector.GetProgramScheduleFor(
playout.ProgramSchedule,
playout.ProgramScheduleAlternates,
dayToCheck);
ProgramSchedule schedule = AlternateScheduleSelector
.GetScheduleForDate(playout.ProgramScheduleAlternates, dayToCheck)
.Match(s => s.ProgramSchedule, playout.ProgramSchedule);
existingScheduleMap.Add(dayToCheck, schedule);
}
@ -77,7 +66,7 @@ public class ReplacePlayoutAlternateScheduleItemsHandler : @@ -77,7 +66,7 @@ public class ReplacePlayoutAlternateScheduleItemsHandler :
ProgramScheduleAlternate[] existing = playout.ProgramScheduleAlternates.ToArray();
var incoming = request.Items.Except(new[] { highest }).ToList();
var incoming = request.Items.Except([highest]).ToList();
var toAdd = incoming.Filter(x => existing.All(e => e.Id != x.Id)).ToList();
var toRemove = existing.Filter(e => incoming.All(m => m.Id != e.Id)).ToList();
@ -95,7 +84,14 @@ public class ReplacePlayoutAlternateScheduleItemsHandler : @@ -95,7 +84,14 @@ public class ReplacePlayoutAlternateScheduleItemsHandler :
ProgramScheduleId = add.ProgramScheduleId,
DaysOfWeek = add.DaysOfWeek,
DaysOfMonth = add.DaysOfMonth,
MonthsOfYear = add.MonthsOfYear
MonthsOfYear = add.MonthsOfYear,
LimitToDateRange = add.LimitToDateRange,
StartMonth = add.StartMonth,
StartDay = add.StartDay,
StartYear = add.StartYear,
EndMonth = add.EndMonth,
EndDay = add.EndDay,
EndYear = add.EndYear
});
}
@ -108,6 +104,13 @@ public class ReplacePlayoutAlternateScheduleItemsHandler : @@ -108,6 +104,13 @@ public class ReplacePlayoutAlternateScheduleItemsHandler :
ex.DaysOfWeek = update.DaysOfWeek;
ex.DaysOfMonth = update.DaysOfMonth;
ex.MonthsOfYear = update.MonthsOfYear;
ex.LimitToDateRange = update.LimitToDateRange;
ex.StartMonth = update.StartMonth;
ex.StartDay = update.StartDay;
ex.StartYear = update.StartYear;
ex.EndMonth = update.EndMonth;
ex.EndDay = update.EndDay;
ex.EndYear = update.EndYear;
}
}
@ -136,21 +139,20 @@ public class ReplacePlayoutAlternateScheduleItemsHandler : @@ -136,21 +139,20 @@ public class ReplacePlayoutAlternateScheduleItemsHandler :
{
foreach (DateTimeOffset dayToCheck in daysToCheck)
{
ProgramSchedule schedule = PlayoutScheduleSelector.GetProgramScheduleFor(
playout.ProgramSchedule,
playout.ProgramScheduleAlternates,
dayToCheck);
ProgramSchedule schedule = AlternateScheduleSelector
.GetScheduleForDate(playout.ProgramScheduleAlternates, dayToCheck)
.Match(s => s.ProgramSchedule, playout.ProgramSchedule);
if (existingScheduleMap.TryGetValue(dayToCheck, out ProgramSchedule existingValue) &&
existingValue.Id != schedule.Id)
{
_logger.LogInformation(
logger.LogInformation(
"Alternate schedule change detected for day {Day}, schedule {One} => {Two}; will refresh playout",
dayToCheck,
existingValue.Name,
schedule.Name);
await _channel.WriteAsync(
await channel.WriteAsync(
new BuildPlayout(request.PlayoutId, PlayoutBuildMode.Refresh),
cancellationToken);
@ -164,7 +166,7 @@ public class ReplacePlayoutAlternateScheduleItemsHandler : @@ -164,7 +166,7 @@ public class ReplacePlayoutAlternateScheduleItemsHandler :
}
catch (Exception ex)
{
_logger.LogError(ex, "Error saving alternate schedule items");
logger.LogError(ex, "Error saving alternate schedule items");
return BaseError.New(ex.Message);
}
}

9
ErsatzTV.Application/Playouts/Mapper.cs

@ -33,7 +33,14 @@ internal static class Mapper @@ -33,7 +33,14 @@ internal static class Mapper
programScheduleAlternate.ProgramScheduleId,
programScheduleAlternate.DaysOfWeek,
programScheduleAlternate.DaysOfMonth,
programScheduleAlternate.MonthsOfYear);
programScheduleAlternate.MonthsOfYear,
programScheduleAlternate.LimitToDateRange,
programScheduleAlternate.StartMonth,
programScheduleAlternate.StartDay,
programScheduleAlternate.StartYear,
programScheduleAlternate.EndMonth,
programScheduleAlternate.EndDay,
programScheduleAlternate.EndYear);
internal static PlayoutHistoryViewModel ProjectToViewModel(PlayoutHistory playoutHistory) =>
new(

9
ErsatzTV.Application/Playouts/PlayoutAlternateScheduleViewModel.cs

@ -6,4 +6,11 @@ public record PlayoutAlternateScheduleViewModel( @@ -6,4 +6,11 @@ public record PlayoutAlternateScheduleViewModel(
int ProgramScheduleId,
ICollection<DayOfWeek> DaysOfWeek,
ICollection<int> DaysOfMonth,
ICollection<int> MonthsOfYear);
ICollection<int> MonthsOfYear,
bool LimitToDateRange,
int StartMonth,
int StartDay,
int? StartYear,
int EndMonth,
int EndDay,
int? EndYear);

25
ErsatzTV.Application/Playouts/Queries/GetPlayoutAlternateSchedulesHandler.cs

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Scheduling;
using ErsatzTV.Infrastructure.Data;
using ErsatzTV.Infrastructure.Extensions;
using Microsoft.EntityFrameworkCore;
@ -6,27 +7,24 @@ using static ErsatzTV.Application.Playouts.Mapper; @@ -6,27 +7,24 @@ using static ErsatzTV.Application.Playouts.Mapper;
namespace ErsatzTV.Application.Playouts;
public class GetPlayoutAlternateSchedulesHandler :
public class GetPlayoutAlternateSchedulesHandler(IDbContextFactory<TvContext> dbContextFactory) :
IRequestHandler<GetPlayoutAlternateSchedules, List<PlayoutAlternateScheduleViewModel>>
{
private readonly IDbContextFactory<TvContext> _dbContextFactory;
public GetPlayoutAlternateSchedulesHandler(IDbContextFactory<TvContext> dbContextFactory) =>
_dbContextFactory = dbContextFactory;
public async Task<List<PlayoutAlternateScheduleViewModel>> Handle(
GetPlayoutAlternateSchedules request,
CancellationToken cancellationToken)
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
List<PlayoutAlternateScheduleViewModel> result = await dbContext.ProgramScheduleAlternates
.AsNoTracking()
.Filter(psa => psa.PlayoutId == request.PlayoutId)
.Include(psa => psa.ProgramSchedule)
.ToListAsync(cancellationToken)
.Map(list => list.Map(ProjectToViewModel).ToList());
Option<ProgramSchedule> maybeDefaultSchedule = await dbContext.Playouts
.AsNoTracking()
.Include(p => p.ProgramSchedule)
.SelectOneAsync(p => p.Id, p => p.Id == request.PlayoutId, cancellationToken)
.MapT(p => p.ProgramSchedule);
@ -40,9 +38,16 @@ public class GetPlayoutAlternateSchedulesHandler : @@ -40,9 +38,16 @@ public class GetPlayoutAlternateSchedulesHandler :
ProgramScheduleId = defaultSchedule.Id,
ProgramSchedule = defaultSchedule,
Index = result.Map(i => i.Index).DefaultIfEmpty().Max() + 1,
DaysOfMonth = ProgramScheduleAlternate.AllDaysOfMonth(),
DaysOfWeek = ProgramScheduleAlternate.AllDaysOfWeek(),
MonthsOfYear = ProgramScheduleAlternate.AllMonthsOfYear()
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
LimitToDateRange = false,
StartMonth = 1,
StartDay = 1,
StartYear = null,
EndMonth = 12,
EndDay = 31,
EndYear = null
};
result.Add(ProjectToViewModel(psa));

6
ErsatzTV.Application/Scheduling/Commands/PreviewBlockPlayoutHandler.cs

@ -49,9 +49,9 @@ public class PreviewBlockPlayoutHandler( @@ -49,9 +49,9 @@ public class PreviewBlockPlayoutHandler(
[
new PlayoutTemplate
{
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(),
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear(),
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
Template = template
}
],

4
ErsatzTV.Application/Scheduling/Commands/ReplacePlayoutTemplate.cs

@ -11,5 +11,7 @@ public record ReplacePlayoutTemplate( @@ -11,5 +11,7 @@ public record ReplacePlayoutTemplate(
bool LimitToDateRange,
int StartMonth,
int StartDay,
int? StartYear,
int EndMonth,
int EndDay);
int EndDay,
int? EndYear);

4
ErsatzTV.Application/Scheduling/Commands/ReplacePlayoutTemplateItemsHandler.cs

@ -59,8 +59,10 @@ public class ReplacePlayoutTemplateItemsHandler( @@ -59,8 +59,10 @@ public class ReplacePlayoutTemplateItemsHandler(
LimitToDateRange = add.LimitToDateRange,
StartMonth = add.StartMonth,
StartDay = add.StartDay,
StartYear = add.StartYear,
EndMonth = add.EndMonth,
EndDay = add.EndDay,
EndYear = add.EndYear,
DateUpdated = now
});
}
@ -78,8 +80,10 @@ public class ReplacePlayoutTemplateItemsHandler( @@ -78,8 +80,10 @@ public class ReplacePlayoutTemplateItemsHandler(
ex.LimitToDateRange = update.LimitToDateRange;
ex.StartMonth = update.StartMonth;
ex.StartDay = update.StartDay;
ex.StartYear = update.StartYear;
ex.EndMonth = update.EndMonth;
ex.EndDay = update.EndDay;
ex.EndYear = update.EndYear;
ex.DateUpdated = now;
}
}

4
ErsatzTV.Application/Scheduling/Mapper.cs

@ -195,8 +195,10 @@ internal static class Mapper @@ -195,8 +195,10 @@ internal static class Mapper
playoutTemplate.LimitToDateRange,
playoutTemplate.StartMonth,
playoutTemplate.StartDay,
playoutTemplate.StartYear,
playoutTemplate.EndMonth,
playoutTemplate.EndDay);
playoutTemplate.EndDay,
playoutTemplate.EndYear);
internal static PlayoutItemPreviewViewModel ProjectToViewModel(PlayoutItem playoutItem) =>
new(

4
ErsatzTV.Application/Scheduling/PlayoutTemplateViewModel.cs

@ -11,5 +11,7 @@ public record PlayoutTemplateViewModel( @@ -11,5 +11,7 @@ public record PlayoutTemplateViewModel(
bool LimitToDateRange,
int StartMonth,
int StartDay,
int? StartYear,
int EndMonth,
int EndDay);
int EndDay,
int? EndYear);

13
ErsatzTV.Core.Tests/FFmpeg/DecoSelectorTests.cs

@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Domain.Scheduling;
using ErsatzTV.Core.FFmpeg;
using ErsatzTV.Core.Scheduling;
using Microsoft.Extensions.Logging;
using NUnit.Framework;
using Serilog;
@ -51,9 +52,9 @@ public class DecoSelectorTests @@ -51,9 +52,9 @@ public class DecoSelectorTests
Id = 1,
Template = new Template { Id = 1, Name = "Test Template" },
DecoTemplate = decoTemplate,
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(),
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear()
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear()
};
var playout = new Playout
@ -105,9 +106,9 @@ public class DecoSelectorTests @@ -105,9 +106,9 @@ public class DecoSelectorTests
Id = 1,
Template = new Template { Id = 1, Name = "Test Template" },
DecoTemplate = decoTemplate,
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(),
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear()
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear()
};
var playout = new Playout

55
ErsatzTV.Core.Tests/FFmpeg/GraphicsElementSelectorTests.cs

@ -2,6 +2,7 @@ using ErsatzTV.Core.Domain; @@ -2,6 +2,7 @@ using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Domain.Filler;
using ErsatzTV.Core.Domain.Scheduling;
using ErsatzTV.Core.FFmpeg;
using ErsatzTV.Core.Scheduling;
using Microsoft.Extensions.Logging;
using NUnit.Framework;
using Serilog;
@ -138,9 +139,9 @@ public class GraphicsElementSelectorTests @@ -138,9 +139,9 @@ public class GraphicsElementSelectorTests
[
new PlayoutTemplate
{
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(),
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear(),
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
DecoTemplate = decoWithInherit
}
]
@ -152,9 +153,9 @@ public class GraphicsElementSelectorTests @@ -152,9 +153,9 @@ public class GraphicsElementSelectorTests
[
new PlayoutTemplate
{
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(),
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear(),
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
DecoTemplate = decoWithDisable
}
]
@ -166,9 +167,9 @@ public class GraphicsElementSelectorTests @@ -166,9 +167,9 @@ public class GraphicsElementSelectorTests
[
new PlayoutTemplate
{
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(),
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear(),
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
DecoTemplate = decoWithOverride
}
]
@ -180,9 +181,9 @@ public class GraphicsElementSelectorTests @@ -180,9 +181,9 @@ public class GraphicsElementSelectorTests
[
new PlayoutTemplate
{
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(),
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear(),
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
DecoTemplate = decoWithMerge
}
]
@ -194,9 +195,9 @@ public class GraphicsElementSelectorTests @@ -194,9 +195,9 @@ public class GraphicsElementSelectorTests
[
new PlayoutTemplate
{
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(),
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear(),
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
DecoTemplate = decoWithMergeFillerDisabled
}
]
@ -247,9 +248,9 @@ public class GraphicsElementSelectorTests @@ -247,9 +248,9 @@ public class GraphicsElementSelectorTests
[
new PlayoutTemplate
{
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(),
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear(),
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
DecoTemplate = decoWithInherit
}
],
@ -265,9 +266,9 @@ public class GraphicsElementSelectorTests @@ -265,9 +266,9 @@ public class GraphicsElementSelectorTests
[
new PlayoutTemplate
{
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(),
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear(),
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
DecoTemplate = decoWithMerge
}
],
@ -283,9 +284,9 @@ public class GraphicsElementSelectorTests @@ -283,9 +284,9 @@ public class GraphicsElementSelectorTests
[
new PlayoutTemplate
{
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(),
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear(),
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
DecoTemplate = decoWithMerge
}
],
@ -301,9 +302,9 @@ public class GraphicsElementSelectorTests @@ -301,9 +302,9 @@ public class GraphicsElementSelectorTests
[
new PlayoutTemplate
{
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(),
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear(),
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
DecoTemplate = decoWithOverride
}
],

55
ErsatzTV.Core.Tests/FFmpeg/WatermarkSelectorTests.cs

@ -2,6 +2,7 @@ using ErsatzTV.Core.Domain; @@ -2,6 +2,7 @@ using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Domain.Scheduling;
using ErsatzTV.Core.FFmpeg;
using ErsatzTV.Core.Interfaces.Images;
using ErsatzTV.Core.Scheduling;
using Microsoft.Extensions.Logging;
using NSubstitute;
using NUnit.Framework;
@ -134,9 +135,9 @@ public class WatermarkSelectorTests @@ -134,9 +135,9 @@ public class WatermarkSelectorTests
[
new PlayoutTemplate
{
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(),
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear(),
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
DecoTemplate = decoWithInherit
}
]
@ -148,9 +149,9 @@ public class WatermarkSelectorTests @@ -148,9 +149,9 @@ public class WatermarkSelectorTests
[
new PlayoutTemplate
{
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(),
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear(),
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
DecoTemplate = decoWithDisable
}
]
@ -162,9 +163,9 @@ public class WatermarkSelectorTests @@ -162,9 +163,9 @@ public class WatermarkSelectorTests
[
new PlayoutTemplate
{
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(),
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear(),
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
DecoTemplate = decoWithOverride
}
]
@ -238,9 +239,9 @@ public class WatermarkSelectorTests @@ -238,9 +239,9 @@ public class WatermarkSelectorTests
[
new PlayoutTemplate
{
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(),
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear(),
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
DecoTemplate = decoWithInherit
}
],
@ -264,9 +265,9 @@ public class WatermarkSelectorTests @@ -264,9 +265,9 @@ public class WatermarkSelectorTests
[
new PlayoutTemplate
{
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(),
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear(),
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
DecoTemplate = decoWithInherit
}
],
@ -290,9 +291,9 @@ public class WatermarkSelectorTests @@ -290,9 +291,9 @@ public class WatermarkSelectorTests
[
new PlayoutTemplate
{
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(),
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear(),
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
DecoTemplate = decoWithMerge
}
],
@ -316,9 +317,9 @@ public class WatermarkSelectorTests @@ -316,9 +317,9 @@ public class WatermarkSelectorTests
[
new PlayoutTemplate
{
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(),
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear(),
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
DecoTemplate = decoWithMerge
}
],
@ -338,9 +339,9 @@ public class WatermarkSelectorTests @@ -338,9 +339,9 @@ public class WatermarkSelectorTests
[
new PlayoutTemplate
{
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(),
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear(),
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
DecoTemplate = decoWithMerge
}
],
@ -364,9 +365,9 @@ public class WatermarkSelectorTests @@ -364,9 +365,9 @@ public class WatermarkSelectorTests
[
new PlayoutTemplate
{
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(),
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear(),
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
DecoTemplate = decoWithMerge
}
],

867
ErsatzTV.Core.Tests/Scheduling/AlternateScheduleSelectorTests.cs

@ -0,0 +1,867 @@ @@ -0,0 +1,867 @@
using ErsatzTV.Core.Domain.Scheduling;
using ErsatzTV.Core.Scheduling;
using NUnit.Framework;
using Shouldly;
namespace ErsatzTV.Core.Tests.Scheduling;
public static class AlternateScheduleSelectorTests
{
[TestFixture]
public class GetScheduleForDate
{
private static readonly TimeSpan Offset = TimeSpan.FromHours(-5);
[Test]
public void LimitToDateRange_Before_Start_Should_Not_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 4,
StartDay = 1,
EndMonth = 6,
EndDay = 15
};
Option<PlayoutTemplate> result = AlternateScheduleSelector.GetScheduleForDate(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2024, 3, 31, 0, 0, 0, Offset));
result.IsNone.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_Before_Start_With_Year_Should_Not_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 4,
StartDay = 1,
StartYear = 2024,
EndMonth = 6,
EndDay = 15,
EndYear = 2024
};
Option<PlayoutTemplate> result = AlternateScheduleSelector.GetScheduleForDate(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2024, 3, 31, 0, 0, 0, Offset));
result.IsNone.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_On_Start_Should_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 4,
StartDay = 1,
EndMonth = 6,
EndDay = 15
};
Option<PlayoutTemplate> result = AlternateScheduleSelector.GetScheduleForDate(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2024, 4, 1, 0, 0, 0, Offset));
result.IsSome.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_On_Start_With_Year_Should_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 4,
StartDay = 1,
StartYear = 2024,
EndMonth = 6,
EndDay = 15,
EndYear = 2024
};
Option<PlayoutTemplate> result = AlternateScheduleSelector.GetScheduleForDate(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2024, 4, 1, 0, 0, 0, Offset));
result.IsSome.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_In_Range_Should_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 4,
StartDay = 1,
EndMonth = 6,
EndDay = 15
};
Option<PlayoutTemplate> result = AlternateScheduleSelector.GetScheduleForDate(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2024, 4, 20, 0, 0, 0, Offset));
result.IsSome.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_In_Range_With_Year_Should_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 4,
StartDay = 1,
StartYear = 2024,
EndMonth = 6,
EndDay = 15,
EndYear = 2024
};
Option<PlayoutTemplate> result = AlternateScheduleSelector.GetScheduleForDate(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2024, 4, 20, 0, 0, 0, Offset));
result.IsSome.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_On_End_Should_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 4,
StartDay = 1,
EndMonth = 6,
EndDay = 15
};
Option<PlayoutTemplate> result = AlternateScheduleSelector.GetScheduleForDate(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2024, 6, 15, 0, 0, 0, Offset));
result.IsSome.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_On_End_With_Year_Should_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 4,
StartDay = 1,
StartYear = 2023,
EndMonth = 6,
EndDay = 15,
EndYear = 2024
};
Option<PlayoutTemplate> result = AlternateScheduleSelector.GetScheduleForDate(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2024, 6, 15, 0, 0, 0, Offset));
result.IsSome.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_After_End_Should_Not_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 4,
StartDay = 1,
EndMonth = 6,
EndDay = 15
};
Option<PlayoutTemplate> result = AlternateScheduleSelector.GetScheduleForDate(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2024, 6, 16, 0, 0, 0, Offset));
result.IsNone.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_After_End_With_Year_Should_Not_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 4,
StartDay = 1,
StartYear = 2024,
EndMonth = 6,
EndDay = 15,
EndYear = 2024
};
Option<PlayoutTemplate> result = AlternateScheduleSelector.GetScheduleForDate(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2024, 6, 16, 0, 0, 0, Offset));
result.IsNone.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_Before_Invalid_Start_Date_Should_Not_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 2,
StartDay = 29,
EndMonth = 6,
EndDay = 15
};
Option<PlayoutTemplate> result = AlternateScheduleSelector.GetScheduleForDate(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2023, 2, 28, 0, 0, 0, Offset));
result.IsNone.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_Before_Invalid_Start_Date_With_Year_Should_Not_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 2,
StartDay = 29,
StartYear = 2023,
EndMonth = 6,
EndDay = 15,
EndYear = 2023
};
Option<PlayoutTemplate> result = AlternateScheduleSelector.GetScheduleForDate(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2023, 2, 28, 0, 0, 0, Offset));
result.IsNone.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_After_Invalid_Start_Date_Should_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 2,
StartDay = 29,
EndMonth = 6,
EndDay = 15
};
Option<PlayoutTemplate> result = AlternateScheduleSelector.GetScheduleForDate(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2023, 3, 1, 0, 0, 0, Offset));
result.IsSome.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_After_Invalid_Start_Date_With_Year_Should_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 2,
StartDay = 29,
StartYear = 2023,
EndMonth = 6,
EndDay = 15,
EndYear = 2023
};
Option<PlayoutTemplate> result = AlternateScheduleSelector.GetScheduleForDate(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2023, 3, 1, 0, 0, 0, Offset));
result.IsSome.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_Before_Invalid_End_Date_Should_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 1,
StartDay = 1,
EndMonth = 2,
EndDay = 29
};
Option<PlayoutTemplate> result = AlternateScheduleSelector.GetScheduleForDate(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2023, 2, 28, 0, 0, 0, Offset));
result.IsSome.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_Before_Invalid_End_Date_With_Year_Should_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 1,
StartDay = 1,
StartYear = 2023,
EndMonth = 2,
EndDay = 29,
EndYear = 2023
};
Option<PlayoutTemplate> result = AlternateScheduleSelector.GetScheduleForDate(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2023, 2, 28, 0, 0, 0, Offset));
result.IsSome.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_After_Invalid_End_Date_Should_Not_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 1,
StartDay = 1,
EndMonth = 2,
EndDay = 29
};
Option<PlayoutTemplate> result = AlternateScheduleSelector.GetScheduleForDate(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2023, 3, 1, 0, 0, 0, Offset));
result.IsNone.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_After_Invalid_End_Date_With_Year_Should_Not_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 1,
StartDay = 1,
StartYear = 2023,
EndMonth = 2,
EndDay = 29,
EndYear = 2023
};
Option<PlayoutTemplate> result = AlternateScheduleSelector.GetScheduleForDate(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2023, 3, 1, 0, 0, 0, Offset));
result.IsNone.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_Wrap_Around_Before_Start_Should_Not_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 6,
StartDay = 15,
EndMonth = 4,
EndDay = 1
};
Option<PlayoutTemplate> result = AlternateScheduleSelector.GetScheduleForDate(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2024, 6, 14, 0, 0, 0, Offset));
result.IsNone.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_Wrap_Around_Before_Start_With_Year_Should_Not_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 6,
StartDay = 15,
StartYear = 2023,
EndMonth = 4,
EndDay = 1,
EndYear = 2024
};
Option<PlayoutTemplate> result = AlternateScheduleSelector.GetScheduleForDate(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2024, 6, 14, 0, 0, 0, Offset));
result.IsNone.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_Wrap_Around_On_Start_Should_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 6,
StartDay = 15,
EndMonth = 4,
EndDay = 1
};
Option<PlayoutTemplate> result = AlternateScheduleSelector.GetScheduleForDate(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2024, 6, 15, 0, 0, 0, Offset));
result.IsSome.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_Wrap_Around_On_Start_With_Year_Should_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 6,
StartDay = 15,
StartYear = 2024,
EndMonth = 4,
EndDay = 1,
EndYear = 2025
};
Option<PlayoutTemplate> result = AlternateScheduleSelector.GetScheduleForDate(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2024, 6, 15, 0, 0, 0, Offset));
result.IsSome.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_Wrap_Around_In_Range_Should_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 6,
StartDay = 15,
EndMonth = 4,
EndDay = 1
};
Option<PlayoutTemplate> result = AlternateScheduleSelector.GetScheduleForDate(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2024, 7, 20, 0, 0, 0, Offset));
result.IsSome.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_Wrap_Around_In_Range_With_Year_Should_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 6,
StartDay = 15,
StartYear = 2024,
EndMonth = 4,
EndDay = 1,
EndYear = 2025
};
Option<PlayoutTemplate> result = AlternateScheduleSelector.GetScheduleForDate(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2024, 7, 20, 0, 0, 0, Offset));
result.IsSome.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_Wrap_Around_On_End_Should_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 6,
StartDay = 15,
EndMonth = 4,
EndDay = 1
};
Option<PlayoutTemplate> result = AlternateScheduleSelector.GetScheduleForDate(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2024, 4, 1, 0, 0, 0, Offset));
result.IsSome.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_Wrap_Around_On_End_With_Year_Should_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 6,
StartDay = 15,
StartYear = 2023,
EndMonth = 4,
EndDay = 1,
EndYear = 2024
};
Option<PlayoutTemplate> result = AlternateScheduleSelector.GetScheduleForDate(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2024, 4, 1, 0, 0, 0, Offset));
result.IsSome.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_Wrap_Around_After_End_Should_Not_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 6,
StartDay = 15,
EndMonth = 4,
EndDay = 1
};
Option<PlayoutTemplate> result = AlternateScheduleSelector.GetScheduleForDate(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2024, 4, 2, 0, 0, 0, Offset));
result.IsNone.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_Wrap_Around_After_End_With_Year_Should_Not_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 6,
StartDay = 15,
StartYear = 2023,
EndMonth = 4,
EndDay = 1,
EndYear = 2024
};
Option<PlayoutTemplate> result = AlternateScheduleSelector.GetScheduleForDate(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2024, 4, 2, 0, 0, 0, Offset));
result.IsNone.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_Wrap_Around_Before_Invalid_Start_Date_Should_Not_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 2,
StartDay = 29,
EndMonth = 1,
EndDay = 1
};
Option<PlayoutTemplate> result = AlternateScheduleSelector.GetScheduleForDate(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2023, 2, 28, 0, 0, 0, Offset));
result.IsNone.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_Wrap_Around_Before_Invalid_Start_Date_With_Year_Should_Not_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 2,
StartDay = 29,
StartYear = 2023,
EndMonth = 1,
EndDay = 1,
EndYear = 2024
};
Option<PlayoutTemplate> result = AlternateScheduleSelector.GetScheduleForDate(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2023, 2, 28, 0, 0, 0, Offset));
result.IsNone.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_Wrap_Around_After_Invalid_Start_Date_Should_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 2,
StartDay = 29,
EndMonth = 1,
EndDay = 1
};
Option<PlayoutTemplate> result = AlternateScheduleSelector.GetScheduleForDate(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2023, 3, 1, 0, 0, 0, Offset));
result.IsSome.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_Wrap_Around_After_Invalid_Start_Date_With_Year_Should_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 2,
StartDay = 29,
StartYear = 2023,
EndMonth = 1,
EndDay = 1,
EndYear = 2024
};
Option<PlayoutTemplate> result = AlternateScheduleSelector.GetScheduleForDate(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2023, 3, 1, 0, 0, 0, Offset));
result.IsSome.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_Wrap_Around_Before_Invalid_End_Date_Should_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 5,
StartDay = 1,
EndMonth = 2,
EndDay = 29
};
Option<PlayoutTemplate> result = AlternateScheduleSelector.GetScheduleForDate(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2023, 2, 28, 0, 0, 0, Offset));
result.IsSome.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_Wrap_Around_Before_Invalid_End_Date_With_Year_Should_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 5,
StartDay = 1,
StartYear = 2022,
EndMonth = 2,
EndDay = 29,
EndYear = 2023
};
Option<PlayoutTemplate> result = AlternateScheduleSelector.GetScheduleForDate(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2023, 2, 28, 0, 0, 0, Offset));
result.IsSome.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_Wrap_Around_After_Invalid_End_Date_Should_Not_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 5,
StartDay = 1,
EndMonth = 2,
EndDay = 29
};
Option<PlayoutTemplate> result = AlternateScheduleSelector.GetScheduleForDate(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2023, 3, 1, 0, 0, 0, Offset));
result.IsNone.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_Wrap_Around_After_Invalid_End_Date_With_Year_Should_Not_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 5,
StartDay = 1,
StartYear = 2022,
EndMonth = 2,
EndDay = 29,
EndYear = 2023
};
Option<PlayoutTemplate> result = AlternateScheduleSelector.GetScheduleForDate(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2023, 3, 1, 0, 0, 0, Offset));
result.IsNone.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_December_32nd_Should_Not_Crash()
{
var template = new PlayoutTemplate
{
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 12,
StartDay = 32,
StartYear = 2022,
EndMonth = 12,
EndDay = 32,
EndYear = 2023
};
Option<PlayoutTemplate> result = AlternateScheduleSelector.GetScheduleForDate(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2023, 3, 1, 0, 0, 0, Offset));
result.IsNone.ShouldBeFalse();
}
}
}

24
ErsatzTV.Core.Tests/Scheduling/BlockScheduling/BlockPlayoutBuilderTests.cs

@ -87,9 +87,9 @@ public class BlockPlayoutBuilderTests @@ -87,9 +87,9 @@ public class BlockPlayoutBuilderTests
Index = 1,
Template = template,
TemplateId = template.Id,
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(),
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear()
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear()
};
var playout = new Playout
@ -221,9 +221,9 @@ public class BlockPlayoutBuilderTests @@ -221,9 +221,9 @@ public class BlockPlayoutBuilderTests
Index = 1,
Template = template,
TemplateId = template.Id,
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(),
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear()
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear()
};
var playout = new Playout
@ -383,9 +383,9 @@ public class BlockPlayoutBuilderTests @@ -383,9 +383,9 @@ public class BlockPlayoutBuilderTests
Index = 1,
Template = template,
TemplateId = template.Id,
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(),
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear()
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear()
};
var playout = new Playout
@ -553,9 +553,9 @@ public class BlockPlayoutBuilderTests @@ -553,9 +553,9 @@ public class BlockPlayoutBuilderTests
Index = 1,
Template = template,
TemplateId = template.Id,
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(),
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear()
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear()
};
var playout = new Playout

21
ErsatzTV.Core.Tests/Scheduling/BlockScheduling/EffectiveBlockTests.cs

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
using ErsatzTV.Core.Domain.Scheduling;
using ErsatzTV.Core.Scheduling;
using ErsatzTV.Core.Scheduling.BlockScheduling;
using NUnit.Framework;
using Shouldly;
@ -54,8 +55,8 @@ public class EffectiveBlockTests @@ -54,8 +55,8 @@ public class EffectiveBlockTests
{
Index = 1,
DaysOfWeek = [DayOfWeek.Sunday],
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
Template = SingleBlockTemplate(now),
DateUpdated = now.UtcDateTime
}
@ -80,8 +81,8 @@ public class EffectiveBlockTests @@ -80,8 +81,8 @@ public class EffectiveBlockTests
{
Index = 1,
DaysOfWeek = [DayOfWeek.Monday, DayOfWeek.Wednesday, DayOfWeek.Friday],
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
Template = SingleBlockTemplate(now),
DateUpdated = now.UtcDateTime
}
@ -114,9 +115,9 @@ public class EffectiveBlockTests @@ -114,9 +115,9 @@ public class EffectiveBlockTests
new()
{
Index = 1,
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(),
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear(),
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
Template = SingleBlockTemplate(now), // 9am block
DateUpdated = now.UtcDateTime
}
@ -153,9 +154,9 @@ public class EffectiveBlockTests @@ -153,9 +154,9 @@ public class EffectiveBlockTests
new()
{
Index = 1,
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(),
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear(),
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
Template = SingleBlockTemplate(now), // 9am block
DateUpdated = now.UtcDateTime
}

411
ErsatzTV.Core.Tests/Scheduling/PlayoutTemplateSelectorTests.cs

@ -1,411 +0,0 @@ @@ -1,411 +0,0 @@
using ErsatzTV.Core.Domain.Scheduling;
using ErsatzTV.Core.Scheduling;
using NUnit.Framework;
using Shouldly;
namespace ErsatzTV.Core.Tests.Scheduling;
public static class PlayoutTemplateSelectorTests
{
[TestFixture]
public class GetPlayoutTemplateFor
{
private static readonly TimeSpan Offset = TimeSpan.FromHours(-5);
[Test]
public void LimitToDateRange_Before_Start_Should_Not_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(),
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 4,
StartDay = 1,
EndMonth = 6,
EndDay = 15
};
Option<PlayoutTemplate> result = PlayoutTemplateSelector.GetPlayoutTemplateFor(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2024, 3, 31, 0, 0, 0, Offset));
result.IsNone.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_On_Start_Should_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(),
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 4,
StartDay = 1,
EndMonth = 6,
EndDay = 15
};
Option<PlayoutTemplate> result = PlayoutTemplateSelector.GetPlayoutTemplateFor(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2024, 4, 1, 0, 0, 0, Offset));
result.IsSome.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_In_Range_Should_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(),
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 4,
StartDay = 1,
EndMonth = 6,
EndDay = 15
};
Option<PlayoutTemplate> result = PlayoutTemplateSelector.GetPlayoutTemplateFor(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2024, 4, 20, 0, 0, 0, Offset));
result.IsSome.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_On_End_Should_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(),
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 4,
StartDay = 1,
EndMonth = 6,
EndDay = 15
};
Option<PlayoutTemplate> result = PlayoutTemplateSelector.GetPlayoutTemplateFor(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2024, 6, 15, 0, 0, 0, Offset));
result.IsSome.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_After_End_Should_Not_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(),
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 4,
StartDay = 1,
EndMonth = 6,
EndDay = 15
};
Option<PlayoutTemplate> result = PlayoutTemplateSelector.GetPlayoutTemplateFor(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2024, 6, 16, 0, 0, 0, Offset));
result.IsNone.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_Before_Invalid_Start_Date_Should_Not_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(),
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 2,
StartDay = 29,
EndMonth = 6,
EndDay = 15
};
Option<PlayoutTemplate> result = PlayoutTemplateSelector.GetPlayoutTemplateFor(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2023, 2, 28, 0, 0, 0, Offset));
result.IsNone.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_After_Invalid_Start_Date_Should_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(),
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 2,
StartDay = 29,
EndMonth = 6,
EndDay = 15
};
Option<PlayoutTemplate> result = PlayoutTemplateSelector.GetPlayoutTemplateFor(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2023, 3, 1, 0, 0, 0, Offset));
result.IsSome.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_Before_Invalid_End_Date_Should_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(),
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 1,
StartDay = 1,
EndMonth = 2,
EndDay = 29
};
Option<PlayoutTemplate> result = PlayoutTemplateSelector.GetPlayoutTemplateFor(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2023, 2, 28, 0, 0, 0, Offset));
result.IsSome.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_After_Invalid_End_Date_Should_Not_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(),
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 1,
StartDay = 1,
EndMonth = 2,
EndDay = 29
};
Option<PlayoutTemplate> result = PlayoutTemplateSelector.GetPlayoutTemplateFor(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2023, 3, 1, 0, 0, 0, Offset));
result.IsNone.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_Wrap_Around_Before_Start_Should_Not_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(),
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 6,
StartDay = 15,
EndMonth = 4,
EndDay = 1
};
Option<PlayoutTemplate> result = PlayoutTemplateSelector.GetPlayoutTemplateFor(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2024, 6, 14, 0, 0, 0, Offset));
result.IsNone.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_Wrap_Around_On_Start_Should_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(),
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 6,
StartDay = 15,
EndMonth = 4,
EndDay = 1
};
Option<PlayoutTemplate> result = PlayoutTemplateSelector.GetPlayoutTemplateFor(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2024, 6, 15, 0, 0, 0, Offset));
result.IsSome.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_Wrap_Around_In_Range_Should_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(),
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 6,
StartDay = 15,
EndMonth = 4,
EndDay = 1
};
Option<PlayoutTemplate> result = PlayoutTemplateSelector.GetPlayoutTemplateFor(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2024, 7, 20, 0, 0, 0, Offset));
result.IsSome.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_Wrap_Around_On_End_Should_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(),
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 6,
StartDay = 15,
EndMonth = 4,
EndDay = 1
};
Option<PlayoutTemplate> result = PlayoutTemplateSelector.GetPlayoutTemplateFor(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2024, 4, 1, 0, 0, 0, Offset));
result.IsSome.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_Wrap_Around_After_End_Should_Not_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(),
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 6,
StartDay = 15,
EndMonth = 4,
EndDay = 1
};
Option<PlayoutTemplate> result = PlayoutTemplateSelector.GetPlayoutTemplateFor(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2024, 4, 2, 0, 0, 0, Offset));
result.IsNone.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_Wrap_Around_Before_Invalid_Start_Date_Should_Not_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(),
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 2,
StartDay = 29,
EndMonth = 1,
EndDay = 1
};
Option<PlayoutTemplate> result = PlayoutTemplateSelector.GetPlayoutTemplateFor(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2023, 2, 28, 0, 0, 0, Offset));
result.IsNone.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_Wrap_Around_After_Invalid_Start_Date_Should_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(),
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 2,
StartDay = 29,
EndMonth = 1,
EndDay = 1
};
Option<PlayoutTemplate> result = PlayoutTemplateSelector.GetPlayoutTemplateFor(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2023, 3, 1, 0, 0, 0, Offset));
result.IsSome.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_Wrap_Around_Before_Invalid_End_Date_Should_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(),
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 5,
StartDay = 1,
EndMonth = 2,
EndDay = 29
};
Option<PlayoutTemplate> result = PlayoutTemplateSelector.GetPlayoutTemplateFor(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2023, 2, 28, 0, 0, 0, Offset));
result.IsSome.ShouldBeTrue();
}
[Test]
public void LimitToDateRange_Wrap_Around_After_Invalid_End_Date_Should_Not_Return_Template()
{
var template = new PlayoutTemplate
{
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(),
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear(),
LimitToDateRange = true,
StartMonth = 5,
StartDay = 1,
EndMonth = 2,
EndDay = 29
};
Option<PlayoutTemplate> result = PlayoutTemplateSelector.GetPlayoutTemplateFor(
new List<PlayoutTemplate> { template },
new DateTimeOffset(2023, 3, 1, 0, 0, 0, Offset));
result.IsNone.ShouldBeTrue();
}
}
}

25
ErsatzTV.Core/Domain/ProgramScheduleAlternate.cs

@ -1,6 +1,8 @@ @@ -1,6 +1,8 @@
using ErsatzTV.Core.Domain.Scheduling;
namespace ErsatzTV.Core.Domain;
public class ProgramScheduleAlternate
public class ProgramScheduleAlternate : IAlternateScheduleItem
{
public int Id { get; set; }
public int PlayoutId { get; set; }
@ -11,18 +13,11 @@ public class ProgramScheduleAlternate @@ -11,18 +13,11 @@ public class ProgramScheduleAlternate
public ICollection<DayOfWeek> DaysOfWeek { get; set; }
public ICollection<int> DaysOfMonth { get; set; }
public ICollection<int> MonthsOfYear { get; set; }
public static List<DayOfWeek> AllDaysOfWeek() => new()
{
DayOfWeek.Monday,
DayOfWeek.Tuesday,
DayOfWeek.Wednesday,
DayOfWeek.Thursday,
DayOfWeek.Friday,
DayOfWeek.Saturday,
DayOfWeek.Sunday
};
public static List<int> AllDaysOfMonth() => Enumerable.Range(1, 31).ToList();
public static List<int> AllMonthsOfYear() => Enumerable.Range(1, 12).ToList();
public bool LimitToDateRange { get; set; }
public int StartMonth { get; set; }
public int StartDay { get; set; }
public int? StartYear { get; set; }
public int EndMonth { get; set; }
public int EndDay { get; set; }
public int? EndYear { get; set; }
}

16
ErsatzTV.Core/Domain/Scheduling/IAlternateScheduleItem.cs

@ -0,0 +1,16 @@ @@ -0,0 +1,16 @@
namespace ErsatzTV.Core.Domain.Scheduling;
public interface IAlternateScheduleItem
{
int Index { get; }
ICollection<DayOfWeek> DaysOfWeek { get; }
ICollection<int> DaysOfMonth { get; }
ICollection<int> MonthsOfYear { get; }
bool LimitToDateRange { get; }
int StartMonth { get; }
int StartDay { get; }
int? StartYear { get; }
int EndMonth { get; }
int EndDay { get; }
int? EndYear { get; }
}

18
ErsatzTV.Core/Domain/Scheduling/PlayoutTemplate.cs

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
namespace ErsatzTV.Core.Domain.Scheduling;
public class PlayoutTemplate
public class PlayoutTemplate : IAlternateScheduleItem
{
public int Id { get; set; }
public int PlayoutId { get; set; }
@ -16,23 +16,11 @@ public class PlayoutTemplate @@ -16,23 +16,11 @@ public class PlayoutTemplate
public bool LimitToDateRange { get; set; }
public int StartMonth { get; set; }
public int StartDay { get; set; }
public int? StartYear { get; set; }
public int EndMonth { get; set; }
public int EndDay { get; set; }
public int? EndYear { get; set; }
public DateTime DateUpdated { get; set; }
//public ICollection<DateTimeOffset> AdditionalDays { get; set; }
public static List<DayOfWeek> AllDaysOfWeek() =>
[
DayOfWeek.Monday,
DayOfWeek.Tuesday,
DayOfWeek.Wednesday,
DayOfWeek.Thursday,
DayOfWeek.Friday,
DayOfWeek.Saturday,
DayOfWeek.Sunday
];
public static List<int> AllDaysOfMonth() => Enumerable.Range(1, 31).ToList();
public static List<int> AllMonthsOfYear() => Enumerable.Range(1, 12).ToList();
}

2
ErsatzTV.Core/FFmpeg/DecoSelector.cs

@ -21,7 +21,7 @@ public class DecoSelector(ILogger<DecoSelector> logger) : IDecoSelector @@ -21,7 +21,7 @@ public class DecoSelector(ILogger<DecoSelector> logger) : IDecoSelector
Option<Deco> maybeTemplateDeco = Option<Deco>.None;
Option<PlayoutTemplate> maybeActiveTemplate =
PlayoutTemplateSelector.GetPlayoutTemplateFor(playout.Templates, now);
AlternateScheduleSelector.GetScheduleForDate(playout.Templates, now);
foreach (PlayoutTemplate activeTemplate in maybeActiveTemplate)
{

109
ErsatzTV.Core/Scheduling/AlternateScheduleSelector.cs

@ -0,0 +1,109 @@ @@ -0,0 +1,109 @@
using ErsatzTV.Core.Domain.Scheduling;
namespace ErsatzTV.Core.Scheduling;
public static class AlternateScheduleSelector
{
public static List<DayOfWeek> AllDaysOfWeek() =>
[
DayOfWeek.Monday,
DayOfWeek.Tuesday,
DayOfWeek.Wednesday,
DayOfWeek.Thursday,
DayOfWeek.Friday,
DayOfWeek.Saturday,
DayOfWeek.Sunday
];
public static List<int> AllDaysOfMonth() => Enumerable.Range(1, 31).ToList();
public static List<int> AllMonthsOfYear() => Enumerable.Range(1, 12).ToList();
public static Option<T> GetScheduleForDate<T>(IEnumerable<T> items, DateTimeOffset date)
where T : IAlternateScheduleItem
{
foreach (T item in items.OrderBy(x => x.Index))
{
if (item.LimitToDateRange)
{
bool reverse = item.StartMonth * 100 + item.StartDay >
item.EndMonth * 100 + item.EndDay;
int startYear = date.LocalDateTime.Year;
int endYear = date.LocalDateTime.Year;
if (item.StartYear.HasValue && item.EndYear.HasValue)
{
startYear = item.StartYear.Value;
endYear = item.EndYear.Value;
reverse = false;
}
DateTime start;
DateTime end;
try
{
start = new DateTime(startYear, item.StartMonth, item.StartDay, 0, 0, 0, DateTimeKind.Local);
}
catch (ArgumentOutOfRangeException)
{
// this should only happen with days that are greater than the actual days in the month,
// so roll over to the 1st of the next month
start = new DateTime(startYear, item.StartMonth, 1, 0, 0, 0, DateTimeKind.Local).AddMonths(1);
}
try
{
end = new DateTime(endYear, item.EndMonth, item.EndDay, 0, 0, 0, DateTimeKind.Local);
}
catch (ArgumentOutOfRangeException)
{
// this should only happen with days that are greater than the actual days in the month,
// so reduce to the max days in the month
end = new DateTime(
endYear,
item.EndMonth,
DateTime.DaysInMonth(endYear, item.EndMonth),
0,
0,
0,
DateTimeKind.Local);
}
if (reverse)
{
(start, end) = (end, start);
if (date.Date > start.Date && date.Date < end.Date)
{
continue;
}
}
else if (date.Date < start.Date || date.Date > end.Date)
{
continue;
}
}
bool daysOfWeek = item.DaysOfWeek.Contains(date.DayOfWeek);
if (!daysOfWeek)
{
continue;
}
bool daysOfMonth = item.DaysOfMonth.Contains(date.Day);
if (!daysOfMonth)
{
continue;
}
bool monthOfYear = item.MonthsOfYear.Contains(date.Month);
if (monthOfYear)
{
return item;
}
}
return Option<T>.None;
}
}

2
ErsatzTV.Core/Scheduling/BlockScheduling/BlockPlayoutFillerBuilder.cs

@ -483,7 +483,7 @@ public class BlockPlayoutFillerBuilder( @@ -483,7 +483,7 @@ public class BlockPlayoutFillerBuilder(
private static Option<Deco> GetDecoFor(PlayoutReferenceData referenceData, DateTimeOffset start)
{
Option<PlayoutTemplate> maybeTemplate =
PlayoutTemplateSelector.GetPlayoutTemplateFor(referenceData.PlayoutTemplates, start);
AlternateScheduleSelector.GetScheduleForDate(referenceData.PlayoutTemplates, start);
foreach (PlayoutTemplate template in maybeTemplate)
{
if (template.DecoTemplate is not null)

2
ErsatzTV.Core/Scheduling/BlockScheduling/EffectiveBlock.cs

@ -16,7 +16,7 @@ internal record EffectiveBlock(Block Block, BlockKey BlockKey, DateTimeOffset St @@ -16,7 +16,7 @@ internal record EffectiveBlock(Block Block, BlockKey BlockKey, DateTimeOffset St
DateTimeOffset current = new DateTimeOffset(start.Year, start.Month, start.Day, 0, 0, 0, start.Offset);
while (current < finish)
{
Option<PlayoutTemplate> maybeTemplate = PlayoutTemplateSelector.GetPlayoutTemplateFor(templates, current);
Option<PlayoutTemplate> maybeTemplate = AlternateScheduleSelector.GetScheduleForDate(templates, current);
foreach (PlayoutTemplate playoutTemplate in maybeTemplate)
{
// logger.LogDebug(

14
ErsatzTV.Core/Scheduling/PlayoutBuilder.cs

@ -570,10 +570,9 @@ public class PlayoutBuilder : IPlayoutBuilder @@ -570,10 +570,9 @@ public class PlayoutBuilder : IPlayoutBuilder
{
var random = new Random(playout.Seed);
ProgramSchedule activeSchedule = PlayoutScheduleSelector.GetProgramScheduleFor(
referenceData.ProgramSchedule,
referenceData.ProgramScheduleAlternates,
playoutStart);
ProgramSchedule activeSchedule = AlternateScheduleSelector
.GetScheduleForDate(referenceData.ProgramScheduleAlternates, playoutStart)
.Match(s => s.ProgramSchedule, referenceData.ProgramSchedule);
if (activeSchedule.Items.Count == 0)
{
@ -959,10 +958,9 @@ public class PlayoutBuilder : IPlayoutBuilder @@ -959,10 +958,9 @@ public class PlayoutBuilder : IPlayoutBuilder
}
}
ProgramSchedule activeScheduleAtAnchor = PlayoutScheduleSelector.GetProgramScheduleFor(
referenceData.ProgramSchedule,
referenceData.ProgramScheduleAlternates,
playoutBuilderState.CurrentTime);
ProgramSchedule activeScheduleAtAnchor = AlternateScheduleSelector
.GetScheduleForDate(referenceData.ProgramScheduleAlternates, playoutBuilderState.CurrentTime)
.Match(s => s.ProgramSchedule, referenceData.ProgramSchedule);
// if we ended in a different alternate schedule, fix the anchor data
if (playoutBuilderState.CurrentTime >= playoutFinish && activeScheduleAtAnchor.Id != activeSchedule.Id &&

40
ErsatzTV.Core/Scheduling/PlayoutScheduleSelector.cs

@ -1,40 +0,0 @@ @@ -1,40 +0,0 @@
using ErsatzTV.Core.Domain;
namespace ErsatzTV.Core.Scheduling;
public static class PlayoutScheduleSelector
{
public static ProgramSchedule GetProgramScheduleFor(
ProgramSchedule defaultSchedule,
IEnumerable<ProgramScheduleAlternate> alternates,
DateTimeOffset date)
{
foreach (ProgramScheduleAlternate alternate in alternates.OrderBy(x => x.Index))
{
bool daysOfWeek = alternate.DaysOfWeek.Count is 0 or 7 ||
alternate.DaysOfWeek.Contains(date.DayOfWeek);
if (!daysOfWeek)
{
continue;
}
bool daysOfMonth = alternate.DaysOfMonth.Count is 0 or 31 ||
alternate.DaysOfMonth.Contains(date.Day);
if (!daysOfMonth)
{
continue;
}
bool monthOfYear = alternate.MonthsOfYear.Count is 0 or 12 ||
alternate.MonthsOfYear.Contains(date.Month);
if (monthOfYear)
{
return alternate.ProgramSchedule;
}
}
return defaultSchedule;
}
}

86
ErsatzTV.Core/Scheduling/PlayoutTemplateSelector.cs

@ -1,86 +0,0 @@ @@ -1,86 +0,0 @@
using ErsatzTV.Core.Domain.Scheduling;
namespace ErsatzTV.Core.Scheduling;
public static class PlayoutTemplateSelector
{
public static Option<PlayoutTemplate> GetPlayoutTemplateFor(
IEnumerable<PlayoutTemplate> templates,
DateTimeOffset date)
{
foreach (PlayoutTemplate template in templates.OrderBy(x => x.Index))
{
if (template.LimitToDateRange)
{
bool reverse = template.StartMonth * 100 + template.StartDay >
template.EndMonth * 100 + template.EndDay;
int year = date.LocalDateTime.Year;
DateTime start;
DateTime end;
try
{
start = new DateTime(year, template.StartMonth, template.StartDay, 0, 0, 0, DateTimeKind.Local);
}
catch (ArgumentOutOfRangeException)
{
// this should only happen with days that are greater than the actual days in the month,
// so roll over to the 1st of the next month
start = new DateTime(year, template.StartMonth + 1, 1, 0, 0, 0, DateTimeKind.Local);
}
try
{
end = new DateTime(year, template.EndMonth, template.EndDay, 0, 0, 0, DateTimeKind.Local);
}
catch (ArgumentOutOfRangeException)
{
// this should only happen with days that are greater than the actual days in the month,
// so reduce to the max days in the month
end = new DateTime(
year,
template.EndMonth,
DateTime.DaysInMonth(year, template.EndMonth),
0,
0,
0,
DateTimeKind.Local);
}
if (reverse)
{
(start, end) = (end, start);
if (date.Date > start.Date && date.Date < end.Date)
{
continue;
}
}
else if (date.Date < start.Date || date.Date > end.Date)
{
continue;
}
}
bool daysOfWeek = template.DaysOfWeek.Contains(date.DayOfWeek);
if (!daysOfWeek)
{
continue;
}
bool daysOfMonth = template.DaysOfMonth.Contains(date.Day);
if (!daysOfMonth)
{
continue;
}
bool monthOfYear = template.MonthsOfYear.Contains(date.Month);
if (monthOfYear)
{
return template;
}
}
return Option<PlayoutTemplate>.None;
}
}

1
ErsatzTV.FFmpeg.Tests/ErsatzTV.FFmpeg.Tests.csproj

@ -8,6 +8,7 @@ @@ -8,6 +8,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="8.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
<PackageReference Include="NSubstitute" Version="5.3.0" />

6950
ErsatzTV.Infrastructure.MySql/Migrations/20260101170034_Align_ClassicBlockAlternateScheduleFields.Designer.cs generated

File diff suppressed because it is too large Load Diff

113
ErsatzTV.Infrastructure.MySql/Migrations/20260101170034_Align_ClassicBlockAlternateScheduleFields.cs

@ -0,0 +1,113 @@ @@ -0,0 +1,113 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.MySql.Migrations
{
/// <inheritdoc />
public partial class Align_ClassicBlockAlternateScheduleFields : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "EndDay",
table: "ProgramScheduleAlternate",
type: "int",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<int>(
name: "EndMonth",
table: "ProgramScheduleAlternate",
type: "int",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<int>(
name: "EndYear",
table: "ProgramScheduleAlternate",
type: "int",
nullable: true);
migrationBuilder.AddColumn<bool>(
name: "LimitToDateRange",
table: "ProgramScheduleAlternate",
type: "tinyint(1)",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<int>(
name: "StartDay",
table: "ProgramScheduleAlternate",
type: "int",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<int>(
name: "StartMonth",
table: "ProgramScheduleAlternate",
type: "int",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<int>(
name: "StartYear",
table: "ProgramScheduleAlternate",
type: "int",
nullable: true);
migrationBuilder.AddColumn<int>(
name: "EndYear",
table: "PlayoutTemplate",
type: "int",
nullable: true);
migrationBuilder.AddColumn<int>(
name: "StartYear",
table: "PlayoutTemplate",
type: "int",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "EndDay",
table: "ProgramScheduleAlternate");
migrationBuilder.DropColumn(
name: "EndMonth",
table: "ProgramScheduleAlternate");
migrationBuilder.DropColumn(
name: "EndYear",
table: "ProgramScheduleAlternate");
migrationBuilder.DropColumn(
name: "LimitToDateRange",
table: "ProgramScheduleAlternate");
migrationBuilder.DropColumn(
name: "StartDay",
table: "ProgramScheduleAlternate");
migrationBuilder.DropColumn(
name: "StartMonth",
table: "ProgramScheduleAlternate");
migrationBuilder.DropColumn(
name: "StartYear",
table: "ProgramScheduleAlternate");
migrationBuilder.DropColumn(
name: "EndYear",
table: "PlayoutTemplate");
migrationBuilder.DropColumn(
name: "StartYear",
table: "PlayoutTemplate");
}
}
}

27
ErsatzTV.Infrastructure.MySql/Migrations/TvContextModelSnapshot.cs

@ -2321,9 +2321,21 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations @@ -2321,9 +2321,21 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
b.Property<string>("DaysOfWeek")
.HasColumnType("longtext");
b.Property<int>("EndDay")
.HasColumnType("int");
b.Property<int>("EndMonth")
.HasColumnType("int");
b.Property<int?>("EndYear")
.HasColumnType("int");
b.Property<int>("Index")
.HasColumnType("int");
b.Property<bool>("LimitToDateRange")
.HasColumnType("tinyint(1)");
b.Property<string>("MonthsOfYear")
.HasColumnType("longtext");
@ -2333,6 +2345,15 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations @@ -2333,6 +2345,15 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
b.Property<int>("ProgramScheduleId")
.HasColumnType("int");
b.Property<int>("StartDay")
.HasColumnType("int");
b.Property<int>("StartMonth")
.HasColumnType("int");
b.Property<int?>("StartYear")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("PlayoutId");
@ -3056,6 +3077,9 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations @@ -3056,6 +3077,9 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
b.Property<int>("EndMonth")
.HasColumnType("int");
b.Property<int?>("EndYear")
.HasColumnType("int");
b.Property<int>("Index")
.HasColumnType("int");
@ -3074,6 +3098,9 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations @@ -3074,6 +3098,9 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
b.Property<int>("StartMonth")
.HasColumnType("int");
b.Property<int?>("StartYear")
.HasColumnType("int");
b.Property<int>("TemplateId")
.HasColumnType("int");

6777
ErsatzTV.Infrastructure.Sqlite/Migrations/20260101165954_Align_ClassicBlockAlternateScheduleFields.Designer.cs generated

File diff suppressed because it is too large Load Diff

113
ErsatzTV.Infrastructure.Sqlite/Migrations/20260101165954_Align_ClassicBlockAlternateScheduleFields.cs

@ -0,0 +1,113 @@ @@ -0,0 +1,113 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.Sqlite.Migrations
{
/// <inheritdoc />
public partial class Align_ClassicBlockAlternateScheduleFields : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "EndDay",
table: "ProgramScheduleAlternate",
type: "INTEGER",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<int>(
name: "EndMonth",
table: "ProgramScheduleAlternate",
type: "INTEGER",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<int>(
name: "EndYear",
table: "ProgramScheduleAlternate",
type: "INTEGER",
nullable: true);
migrationBuilder.AddColumn<bool>(
name: "LimitToDateRange",
table: "ProgramScheduleAlternate",
type: "INTEGER",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<int>(
name: "StartDay",
table: "ProgramScheduleAlternate",
type: "INTEGER",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<int>(
name: "StartMonth",
table: "ProgramScheduleAlternate",
type: "INTEGER",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<int>(
name: "StartYear",
table: "ProgramScheduleAlternate",
type: "INTEGER",
nullable: true);
migrationBuilder.AddColumn<int>(
name: "EndYear",
table: "PlayoutTemplate",
type: "INTEGER",
nullable: true);
migrationBuilder.AddColumn<int>(
name: "StartYear",
table: "PlayoutTemplate",
type: "INTEGER",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "EndDay",
table: "ProgramScheduleAlternate");
migrationBuilder.DropColumn(
name: "EndMonth",
table: "ProgramScheduleAlternate");
migrationBuilder.DropColumn(
name: "EndYear",
table: "ProgramScheduleAlternate");
migrationBuilder.DropColumn(
name: "LimitToDateRange",
table: "ProgramScheduleAlternate");
migrationBuilder.DropColumn(
name: "StartDay",
table: "ProgramScheduleAlternate");
migrationBuilder.DropColumn(
name: "StartMonth",
table: "ProgramScheduleAlternate");
migrationBuilder.DropColumn(
name: "StartYear",
table: "ProgramScheduleAlternate");
migrationBuilder.DropColumn(
name: "EndYear",
table: "PlayoutTemplate");
migrationBuilder.DropColumn(
name: "StartYear",
table: "PlayoutTemplate");
}
}
}

27
ErsatzTV.Infrastructure.Sqlite/Migrations/TvContextModelSnapshot.cs

@ -2214,9 +2214,21 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations @@ -2214,9 +2214,21 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
b.Property<string>("DaysOfWeek")
.HasColumnType("TEXT");
b.Property<int>("EndDay")
.HasColumnType("INTEGER");
b.Property<int>("EndMonth")
.HasColumnType("INTEGER");
b.Property<int?>("EndYear")
.HasColumnType("INTEGER");
b.Property<int>("Index")
.HasColumnType("INTEGER");
b.Property<bool>("LimitToDateRange")
.HasColumnType("INTEGER");
b.Property<string>("MonthsOfYear")
.HasColumnType("TEXT");
@ -2226,6 +2238,15 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations @@ -2226,6 +2238,15 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
b.Property<int>("ProgramScheduleId")
.HasColumnType("INTEGER");
b.Property<int>("StartDay")
.HasColumnType("INTEGER");
b.Property<int>("StartMonth")
.HasColumnType("INTEGER");
b.Property<int?>("StartYear")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("PlayoutId");
@ -2919,6 +2940,9 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations @@ -2919,6 +2940,9 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
b.Property<int>("EndMonth")
.HasColumnType("INTEGER");
b.Property<int?>("EndYear")
.HasColumnType("INTEGER");
b.Property<int>("Index")
.HasColumnType("INTEGER");
@ -2937,6 +2961,9 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations @@ -2937,6 +2961,9 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
b.Property<int>("StartMonth")
.HasColumnType("INTEGER");
b.Property<int?>("StartYear")
.HasColumnType("INTEGER");
b.Property<int>("TemplateId")
.HasColumnType("INTEGER");

1
ErsatzTV.Infrastructure/Data/Repositories/MetadataRepository.cs

@ -121,6 +121,7 @@ public class MetadataRepository(IDbContextFactory<TvContext> dbContextFactory) : @@ -121,6 +121,7 @@ public class MetadataRepository(IDbContextFactory<TvContext> dbContextFactory) :
.TagWithCallSite()
.Include(v => v.Streams)
.Include(v => v.Chapters)
.Include(v => v.MediaFiles)
.OrderBy(v => v.Id)
.SingleOrDefaultAsync(v => v.Id == mediaVersionId);

96
ErsatzTV/Pages/PlayoutAlternateSchedulesEditor.razor

@ -4,6 +4,7 @@ @@ -4,6 +4,7 @@
@using ErsatzTV.Application.Channels
@using ErsatzTV.Application.Playouts
@using ErsatzTV.Application.ProgramSchedules
@using ErsatzTV.Core.Scheduling
@implements IDisposable
@inject NavigationManager NavigationManager
@inject ILogger<ScheduleItemsEditor> Logger
@ -106,6 +107,70 @@ @@ -106,6 +107,70 @@
}
</MudSelect>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Limit to Date Range</MudText>
</div>
<MudCheckBox T="bool" @bind-Value="_selectedItem.LimitToDateRange" Dense="true"/>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Start Day</MudText>
</div>
<MudSelect T="int" @bind-Value="_selectedItem.StartMonth" Disabled="@(!_selectedItem.LimitToDateRange)">
@foreach (int month in Enumerable.Range(1, 12))
{
<MudSelectItem Value="@month">@_dtf.GetMonthName(month)</MudSelectItem>
}
</MudSelect>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex"></div>
<MudSelect T="int" @bind-Value="_selectedItem.StartDay" Disabled="@(!_selectedItem.LimitToDateRange)">
@foreach (int day in Enumerable.Range(1, 31))
{
<MudSelectItem Value="@day">@day.ToString()</MudSelectItem>
}
</MudSelect>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex"></div>
<MudSelect T="int?" @bind-Value="_selectedItem.StartYear" Disabled="@(!_selectedItem.LimitToDateRange)" Clearable="true">
@foreach (int year in Enumerable.Range(DateTime.Today.Year, 50))
{
<MudSelectItem T="int?" Value="@year">@year.ToString()</MudSelectItem>
}
</MudSelect>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>End Day</MudText>
</div>
<MudSelect T="int" @bind-Value="_selectedItem.EndMonth" Disabled="@(!_selectedItem.LimitToDateRange)">
@foreach (int month in Enumerable.Range(1, 12))
{
<MudSelectItem Value="@month">@_dtf.GetMonthName(month)</MudSelectItem>
}
</MudSelect>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex"></div>
<MudSelect T="int" @bind-Value="_selectedItem.EndDay" Disabled="@(!_selectedItem.LimitToDateRange)">
@foreach (int day in Enumerable.Range(1, 31))
{
<MudSelectItem Value="@day">@day.ToString()</MudSelectItem>
}
</MudSelect>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex"></div>
<MudSelect T="int?" @bind-Value="_selectedItem.EndYear" Disabled="@(!_selectedItem.LimitToDateRange)" Clearable="true">
@foreach (int year in Enumerable.Range(DateTime.Today.Year, 50))
{
<MudSelectItem T="int?" Value="@year">@year.ToString()</MudSelectItem>
}
</MudSelect>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Days of the Week</MudText>
@ -211,7 +276,14 @@ @@ -211,7 +276,14 @@
ProgramSchedule = _schedules.Find(vm => vm.Id == item.ProgramScheduleId),
DaysOfWeek = item.DaysOfWeek.OrderBy(x => ((int)x + 6) % 7).ToList(),
DaysOfMonth = item.DaysOfMonth.ToList(),
MonthsOfYear = item.MonthsOfYear.ToList()
MonthsOfYear = item.MonthsOfYear.ToList(),
LimitToDateRange = item.LimitToDateRange,
StartMonth = item.StartMonth,
StartDay = item.StartDay,
StartYear = item.StartYear,
EndMonth = item.EndMonth,
EndDay = item.EndDay,
EndYear = item.EndYear
};
private void SelectWeekdays()
@ -243,9 +315,16 @@ @@ -243,9 +315,16 @@
{
Index = _items.Map(i => i.Index).DefaultIfEmpty().Max() + 1,
ProgramSchedule = _schedules.Head(),
DaysOfWeek = ProgramScheduleAlternate.AllDaysOfWeek(),
DaysOfMonth = ProgramScheduleAlternate.AllDaysOfMonth(),
MonthsOfYear = ProgramScheduleAlternate.AllMonthsOfYear()
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear(),
LimitToDateRange = false,
StartMonth = 1,
StartDay = 1,
StartYear = null,
EndMonth = 1,
EndDay = 1,
EndYear = null
};
_items.Add(item);
@ -280,7 +359,14 @@ @@ -280,7 +359,14 @@
item.ProgramSchedule.Id,
item.DaysOfWeek,
item.DaysOfMonth,
item.MonthsOfYear)).ToList();
item.MonthsOfYear,
item.LimitToDateRange,
item.StartMonth,
item.StartDay,
item.StartYear,
item.EndMonth,
item.EndDay,
item.EndYear)).ToList();
Seq<BaseError> errorMessages = await Mediator.Send(new ReplacePlayoutAlternateScheduleItems(Id, items), _cts.Token)
.Map(e => e.LeftToSeq());

36
ErsatzTV/Pages/PlayoutTemplatesEditor.razor

@ -3,7 +3,7 @@ @@ -3,7 +3,7 @@
@using System.Text
@using ErsatzTV.Application.Channels
@using ErsatzTV.Application.Scheduling
@using ErsatzTV.Core.Domain.Scheduling
@using ErsatzTV.Core.Scheduling
@implements IDisposable
@inject NavigationManager NavigationManager
@inject ILogger<PlayoutTemplatesEditor> Logger
@ -38,7 +38,7 @@ @@ -38,7 +38,7 @@
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
<MudText Typo="Typo.h5" Class="mb-2">@_channelName - Templates</MudText>
<MudDivider Class="mb-6"/>
<MudText Typo="Typo.body2" Class="mb-6">In priority order from top to bottom. The bottom entry will *always* match all days and all months, as a catch-all.</MudText>
<MudText Typo="Typo.body2" Class="mb-6">In priority order from top to bottom.</MudText>
<MudTable T="PlayoutTemplateEditViewModel" Class="mt-4" Hover="true" Items="_items.OrderBy(i => i.Index)" Dense="true" SelectedItem="@_selectedItem" SelectedItemChanged="@(vm => SelectedItemChanged(vm))" RowClassFunc="@SelectedRowClassFunc">
<ColGroup>
<MudHidden Breakpoint="Breakpoint.Xs">
@ -172,6 +172,15 @@ @@ -172,6 +172,15 @@
}
</MudSelect>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex"></div>
<MudSelect T="int?" @bind-Value="_selectedItem.StartYear" Disabled="@(!_selectedItem.LimitToDateRange)" Clearable="true">
@foreach (int year in Enumerable.Range(DateTime.Today.Year, 50))
{
<MudSelectItem T="int?" Value="@year">@year.ToString()</MudSelectItem>
}
</MudSelect>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>End Day</MudText>
@ -192,6 +201,15 @@ @@ -192,6 +201,15 @@
}
</MudSelect>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex"></div>
<MudSelect T="int?" @bind-Value="_selectedItem.EndYear" Disabled="@(!_selectedItem.LimitToDateRange)" Clearable="true">
@foreach (int year in Enumerable.Range(DateTime.Today.Year, 50))
{
<MudSelectItem T="int?" Value="@year">@year.ToString()</MudSelectItem>
}
</MudSelect>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Days of the Week</MudText>
@ -322,8 +340,10 @@ @@ -322,8 +340,10 @@
LimitToDateRange = item.LimitToDateRange,
StartMonth = item.StartMonth,
StartDay = item.StartDay,
StartYear = item.StartYear,
EndMonth = item.EndMonth,
EndDay = item.EndDay
EndDay = item.EndDay,
EndYear = item.EndYear
};
private async Task UpdateTemplateGroupItems(TemplateGroupViewModel templateGroup)
@ -385,9 +405,9 @@ @@ -385,9 +405,9 @@
var item = new PlayoutTemplateEditViewModel
{
Index = _items.Map(i => i.Index).DefaultIfEmpty().Max() + 1,
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(),
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear()
DaysOfWeek = AlternateScheduleSelector.AllDaysOfWeek(),
DaysOfMonth = AlternateScheduleSelector.AllDaysOfMonth(),
MonthsOfYear = AlternateScheduleSelector.AllMonthsOfYear()
};
_items.Add(item);
@ -433,8 +453,10 @@ @@ -433,8 +453,10 @@
item.LimitToDateRange,
item.StartMonth,
item.StartDay,
item.StartYear,
item.EndMonth,
item.EndDay)).ToList();
item.EndDay,
item.EndYear)).ToList();
Option<BaseError> maybeError = await Mediator.Send(new ReplacePlayoutTemplateItems(Id, items), _cts.Token);

29
ErsatzTV/ViewModels/PlayoutAlternateScheduleEditViewModel.cs

@ -10,4 +10,33 @@ public class PlayoutAlternateScheduleEditViewModel @@ -10,4 +10,33 @@ public class PlayoutAlternateScheduleEditViewModel
public List<DayOfWeek> DaysOfWeek { get; set; }
public List<int> DaysOfMonth { get; set; }
public List<int> MonthsOfYear { get; set; }
public bool LimitToDateRange { get; set; }
public int StartMonth
{
get => field == 0 ? 1 : field;
set;
}
public int StartDay
{
get => field == 0 ? 1 : field;
set;
}
public int? StartYear { get; set; }
public int EndMonth
{
get => field == 0 ? 12 : field;
set;
}
public int EndDay
{
get => field == 0 ? 31 : field;
set;
}
public int? EndYear { get; set; }
}

32
ErsatzTV/ViewModels/PlayoutTemplateEditViewModel.cs

@ -7,10 +7,6 @@ namespace ErsatzTV.ViewModels; @@ -7,10 +7,6 @@ namespace ErsatzTV.ViewModels;
public class PlayoutTemplateEditViewModel
{
private int _endDay;
private int _endMonth;
private int _startDay;
private int _startMonth;
public int Id { get; set; }
public int Index { get; set; }
public TemplateViewModel Template { get; set; }
@ -25,28 +21,32 @@ public class PlayoutTemplateEditViewModel @@ -25,28 +21,32 @@ public class PlayoutTemplateEditViewModel
public int StartMonth
{
get => _startMonth == 0 ? 1 : _startMonth;
set => _startMonth = value;
get => field == 0 ? 1 : field;
set;
}
public int StartDay
{
get => _startDay == 0 ? 1 : _startDay;
set => _startDay = value;
get => field == 0 ? 1 : field;
set;
}
public int? StartYear { get; set; }
public int EndMonth
{
get => _endMonth == 0 ? 12 : _endMonth;
set => _endMonth = value;
get => field == 0 ? 12 : field;
set;
}
public int EndDay
{
get => _endDay == 0 ? 31 : _endDay;
set => _endDay = value;
get => field == 0 ? 31 : field;
set;
}
public int? EndYear { get; set; }
public bool AppliesToDate(DateTime date)
{
// share the PlayoutTemplateSelector logic
@ -59,16 +59,18 @@ public class PlayoutTemplateEditViewModel @@ -59,16 +59,18 @@ public class PlayoutTemplateEditViewModel
LimitToDateRange = LimitToDateRange,
StartMonth = StartMonth,
StartDay = StartDay,
StartYear = StartYear,
EndMonth = EndMonth,
EndDay = EndDay
EndDay = EndDay,
EndYear = EndYear
};
TimeSpan offset = TimeZoneInfo.Local.GetUtcOffset(
new DateTime(date.Year, date.Month, date.Day, 0, 0, 0, DateTimeKind.Local));
Option<PlayoutTemplate> result =
PlayoutTemplateSelector.GetPlayoutTemplateFor(
new[] { template },
AlternateScheduleSelector.GetScheduleForDate(
[template],
new DateTimeOffset(
date.Year,
date.Month,

Loading…
Cancel
Save