From 2839ca795ad2abec6f7fdd06e92e07809660ea1c Mon Sep 17 00:00:00 2001 From: Ben Sampica Date: Thu, 16 Mar 2023 21:09:08 -0500 Subject: [PATCH] Change dates to be timezone aware. (#195) --- .../FreeAgents/Detail/BidCountdown.razor.cs | 4 +- src/Client/Features/FreeAgents/List.razor | 2 +- src/Client/Features/Rules/Cost.razor | 2 +- src/Server/Features/Admin/StartSeason.cs | 10 +- .../Features/FreeAgents/Detail/AddBid.cs | 2 +- src/Server/Features/FreeAgents/EndBidding.cs | 2 +- .../OfferMatching/ExpireOfferMatching.cs | 2 +- src/Server/Features/Teams/List.cs | 2 +- ...2013711_ChangeToDateTimeOffset.Designer.cs | 638 ++++++++++++++++++ .../20230302013711_ChangeToDateTimeOffset.cs | 68 ++ .../ApplicationDbContextModelSnapshot.cs | 30 +- .../Infrastructure/MsSqlContainerFactory.cs | 4 - src/Server/Infrastructure/SeedDataCommand.cs | 4 +- src/Server/Models/Bid.cs | 4 +- src/Server/Models/Fine.cs | 2 +- src/Server/Models/Player.cs | 20 +- src/Server/Program.cs | 6 +- .../Features/FreeAgents/Detail/Detail.cs | 2 +- src/Shared/Features/FreeAgents/List.cs | 2 +- src/Tests/Features/Admin/StartSeasonTests.cs | 6 +- .../Features/FreeAgents/Detail/AddBidTests.cs | 14 +- .../FreeAgents/Detail/BidCountdownTests.cs | 6 +- .../Features/FreeAgents/Detail/DetailTests.cs | 2 +- .../Features/FreeAgents/EndBiddingTests.cs | 2 +- src/Tests/Features/FreeAgents/ListTests.cs | 8 +- .../OfferMatching/ExpireOfferMatchingTests.cs | 6 +- .../OfferMatching/MatchPlayerTests.cs | 4 +- src/Tests/Features/Teams/DetailTests.cs | 4 +- src/Tests/Features/Teams/SignPlayerTests.cs | 2 +- src/Tests/Models/PlayerTests.cs | 4 +- 30 files changed, 783 insertions(+), 81 deletions(-) create mode 100644 src/Server/Infrastructure/Migrations/20230302013711_ChangeToDateTimeOffset.Designer.cs create mode 100644 src/Server/Infrastructure/Migrations/20230302013711_ChangeToDateTimeOffset.cs diff --git a/src/Client/Features/FreeAgents/Detail/BidCountdown.razor.cs b/src/Client/Features/FreeAgents/Detail/BidCountdown.razor.cs index f4678c0..d9867ff 100644 --- a/src/Client/Features/FreeAgents/Detail/BidCountdown.razor.cs +++ b/src/Client/Features/FreeAgents/Detail/BidCountdown.razor.cs @@ -7,7 +7,7 @@ namespace DynamoLeagueBlazor.Client.Features.FreeAgents.Detail; public partial class BidCountdown { - [Parameter, EditorRequired] public DateTime DateTime { get; set; } + [Parameter, EditorRequired] public DateTimeOffset DateTime { get; set; } private readonly Timer _timer = new(1000); private string _remainingTime = string.Empty; @@ -21,7 +21,7 @@ protected override void OnInitialized() private void CountDown(object? source, ElapsedEventArgs e) { - var remainingTime = DateTime - DateTime.Now; + var remainingTime = DateTime - DateTimeOffset.UtcNow; if (remainingTime <= TimeSpan.FromDays(1)) { diff --git a/src/Client/Features/FreeAgents/List.razor b/src/Client/Features/FreeAgents/List.razor index 9f7181e..3a17152 100644 --- a/src/Client/Features/FreeAgents/List.razor +++ b/src/Client/Features/FreeAgents/List.razor @@ -41,7 +41,7 @@ @context.Position @context.Team - @context.BiddingEnds.ToShortDateString() + @context.BiddingEnds.LocalDateTime.ToShortDateString() diff --git a/src/Client/Features/Rules/Cost.razor b/src/Client/Features/Rules/Cost.razor index fba8041..b37f5be 100644 --- a/src/Client/Features/Rules/Cost.razor +++ b/src/Client/Features/Rules/Cost.razor @@ -5,7 +5,7 @@ Dues must be paid by January 1st each season. If an owner does not pay by one week before the season, that owner will be removed from the league.

- @foreach(var year in Enumerable.Range(DateTime.Today.Year, 5)) + @foreach(var year in Enumerable.Range(DateTimeOffset.UtcNow.Year, 5)) { var date = new DateOnly(year, 1, 1); diff --git a/src/Server/Features/Admin/StartSeason.cs b/src/Server/Features/Admin/StartSeason.cs index edca0fd..95bca42 100644 --- a/src/Server/Features/Admin/StartSeason.cs +++ b/src/Server/Features/Admin/StartSeason.cs @@ -53,8 +53,8 @@ public record StartSeasonCommand : IRequest { } public class StartSeasonHandler : IRequestHandler { private readonly ApplicationDbContext _dbContext; - private readonly DateTime _boundaryStartDate = new(DateTime.Today.Year, 6, 25); - private readonly DateTime _boundaryEndDate = new(DateTime.Today.Year, 8, 20); + private readonly DateTimeOffset _boundaryStartDate = new(DateTimeOffset.UtcNow.Year, 6, 25, 0, 0, 0, TimeSpan.Zero); + private readonly DateTimeOffset _boundaryEndDate = new(DateTimeOffset.UtcNow.Year, 8, 20, 0, 0, 0, TimeSpan.Zero); public StartSeasonHandler(ApplicationDbContext dbContext) { @@ -66,7 +66,7 @@ public async Task Handle(StartSeasonCommand request, CancellationToken cancellat var random = new Random(); var players = _dbContext.Players - .Where(p => p.YearContractExpires < DateTime.Today.Year) + .Where(p => p.YearContractExpires < DateTimeOffset.UtcNow.Year) .AsTracking(); foreach (var player in players) @@ -76,14 +76,14 @@ public async Task Handle(StartSeasonCommand request, CancellationToken cancellat player.BeginNewSeason(endOfFreeAgency); } - var startOfTheCurrentYear = new DateTime(DateTime.Today.Year, 1, 1); + var startOfTheCurrentYear = new DateTimeOffset(DateTimeOffset.UtcNow.Year, 1, 1, 0, 0, 0, TimeSpan.Zero); var fines = _dbContext.Fines.Where(f => f.CreatedOn < startOfTheCurrentYear); _dbContext.Fines.RemoveRange(fines); await _dbContext.SaveChangesAsync(cancellationToken); } - private static DateTime GetRandomBoundariedDate(Random seed, DateTime boundaryStartDate, DateTime boundaryEndDate) + private static DateTimeOffset GetRandomBoundariedDate(Random seed, DateTimeOffset boundaryStartDate, DateTimeOffset boundaryEndDate) { var maximumDaysToAdd = (boundaryEndDate - boundaryStartDate).Days; var daysToAdd = seed.Next(maximumDaysToAdd); diff --git a/src/Server/Features/FreeAgents/Detail/AddBid.cs b/src/Server/Features/FreeAgents/Detail/AddBid.cs index 3fd19c6..809fd30 100644 --- a/src/Server/Features/FreeAgents/Detail/AddBid.cs +++ b/src/Server/Features/FreeAgents/Detail/AddBid.cs @@ -106,7 +106,7 @@ public async Task HasNotEndedAsync(AddBidRequest request, CancellationToke { var player = await _dbContext.Players.FindAsync(new object[] { request.PlayerId }, cancellationToken); - var hasNotEnded = player!.EndOfFreeAgency > DateTime.Now; + var hasNotEnded = player!.EndOfFreeAgency > DateTimeOffset.UtcNow; return hasNotEnded; } diff --git a/src/Server/Features/FreeAgents/EndBidding.cs b/src/Server/Features/FreeAgents/EndBidding.cs index f7ddee1..fe15e41 100644 --- a/src/Server/Features/FreeAgents/EndBidding.cs +++ b/src/Server/Features/FreeAgents/EndBidding.cs @@ -19,7 +19,7 @@ public async Task Invoke() var players = await _dbContext.Players .AsTracking() .Where(p => p.State == PlayerState.FreeAgent - && p.EndOfFreeAgency <= DateTime.Now) + && p.EndOfFreeAgency <= DateTimeOffset.UtcNow) .ToListAsync(); if (!players.Any()) return; diff --git a/src/Server/Features/OfferMatching/ExpireOfferMatching.cs b/src/Server/Features/OfferMatching/ExpireOfferMatching.cs index 56a33a8..08adb0f 100644 --- a/src/Server/Features/OfferMatching/ExpireOfferMatching.cs +++ b/src/Server/Features/OfferMatching/ExpireOfferMatching.cs @@ -20,7 +20,7 @@ public async Task Invoke() .AsTracking() .Include(p => p.Bids) .Where(p => p.State == PlayerState.OfferMatching - && p.EndOfFreeAgency!.Value.AddDays(3) <= DateTime.Today) + && p.EndOfFreeAgency!.Value.AddDays(3) <= DateTimeOffset.UtcNow) .ToListAsync(); if (!players.Any()) return; diff --git a/src/Server/Features/Teams/List.cs b/src/Server/Features/Teams/List.cs index 1531d20..b9ce5b8 100644 --- a/src/Server/Features/Teams/List.cs +++ b/src/Server/Features/Teams/List.cs @@ -74,7 +74,7 @@ await Parallel.ForEachAsync(teams, async (team, cancellationToken) => var unsignedPlayersContractValue = await unsignedPlayersQuery.SumAsync(rp => rp.ContractValue, cancellationToken); - team.CapSpace = CapSpaceUtilities.GetRemainingCapSpace(DateOnly.FromDateTime(DateTime.Today), rosteredPlayersContractValue, unrosteredPlayersContractValue, unsignedPlayersContractValue).ToString("C0"); + team.CapSpace = CapSpaceUtilities.GetRemainingCapSpace(DateOnly.FromDateTime(DateTimeOffset.UtcNow.DateTime), rosteredPlayersContractValue, unrosteredPlayersContractValue, unsignedPlayersContractValue).ToString("C0"); }); return new TeamListResult diff --git a/src/Server/Infrastructure/Migrations/20230302013711_ChangeToDateTimeOffset.Designer.cs b/src/Server/Infrastructure/Migrations/20230302013711_ChangeToDateTimeOffset.Designer.cs new file mode 100644 index 0000000..895271b --- /dev/null +++ b/src/Server/Infrastructure/Migrations/20230302013711_ChangeToDateTimeOffset.Designer.cs @@ -0,0 +1,638 @@ +// +using System; +using DynamoLeagueBlazor.Server.Infrastructure; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace DynamoLeagueBlazor.Server.Infrastructure.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20230302013711_ChangeToDateTimeOffset")] + partial class ChangeToDateTimeOffset + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Duende.IdentityServer.EntityFramework.Entities.DeviceFlowCodes", b => + { + b.Property("UserCode") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("CreationTime") + .HasColumnType("datetime2"); + + b.Property("Data") + .IsRequired() + .HasMaxLength(50000) + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("DeviceCode") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Expiration") + .IsRequired() + .HasColumnType("datetime2"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("UserCode"); + + b.HasIndex("DeviceCode") + .IsUnique(); + + b.HasIndex("Expiration"); + + b.ToTable("DeviceCodes", (string)null); + }); + + modelBuilder.Entity("Duende.IdentityServer.EntityFramework.Entities.Key", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("Algorithm") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("Data") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("DataProtected") + .HasColumnType("bit"); + + b.Property("IsX509Certificate") + .HasColumnType("bit"); + + b.Property("Use") + .HasColumnType("nvarchar(450)"); + + b.Property("Version") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("Use"); + + b.ToTable("Keys", (string)null); + }); + + modelBuilder.Entity("Duende.IdentityServer.EntityFramework.Entities.PersistedGrant", b => + { + b.Property("Key") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ConsumedTime") + .HasColumnType("datetime2"); + + b.Property("CreationTime") + .HasColumnType("datetime2"); + + b.Property("Data") + .IsRequired() + .HasMaxLength(50000) + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Expiration") + .HasColumnType("datetime2"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("Key"); + + b.HasIndex("ConsumedTime"); + + b.HasIndex("Expiration"); + + b.HasIndex("SubjectId", "ClientId", "Type"); + + b.HasIndex("SubjectId", "SessionId", "Type"); + + b.ToTable("PersistedGrants", (string)null); + }); + + modelBuilder.Entity("DynamoLeagueBlazor.Server.Infrastructure.Identity.ApplicationRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("DynamoLeagueBlazor.Server.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("Approved") + .HasColumnType("bit"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TeamId") + .HasColumnType("int"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.HasIndex("TeamId"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("DynamoLeagueBlazor.Server.Models.Bid", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Amount") + .HasColumnType("int"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset"); + + b.Property("IsOverBid") + .HasColumnType("bit"); + + b.Property("PlayerId") + .HasColumnType("int"); + + b.Property("TeamId") + .HasColumnType("int"); + + b.Property("UpdatedOn") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId"); + + b.HasIndex("TeamId"); + + b.ToTable("Bids"); + }); + + modelBuilder.Entity("DynamoLeagueBlazor.Server.Models.Fine", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Amount") + .HasColumnType("decimal(18,0)"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset"); + + b.Property("PlayerId") + .HasColumnType("int"); + + b.Property("Reason") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("bit"); + + b.Property("TeamId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId"); + + b.HasIndex("TeamId"); + + b.ToTable("Fines"); + }); + + modelBuilder.Entity("DynamoLeagueBlazor.Server.Models.Player", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ContractValue") + .HasColumnType("int"); + + b.Property("EndOfFreeAgency") + .HasColumnType("datetimeoffset"); + + b.Property("HeadShotUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Position") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("State") + .HasColumnType("int"); + + b.Property("TeamId") + .HasColumnType("int"); + + b.Property("YearAcquired") + .HasColumnType("int"); + + b.Property("YearContractExpires") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("TeamId"); + + b.ToTable("Players"); + }); + + modelBuilder.Entity("DynamoLeagueBlazor.Server.Models.Team", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("LogoUrl") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Teams"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("ProviderKey") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("Name") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("DynamoLeagueBlazor.Server.Infrastructure.Identity.ApplicationUser", b => + { + b.HasOne("DynamoLeagueBlazor.Server.Models.Team", "Team") + .WithMany() + .HasForeignKey("TeamId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Team"); + }); + + modelBuilder.Entity("DynamoLeagueBlazor.Server.Models.Bid", b => + { + b.HasOne("DynamoLeagueBlazor.Server.Models.Player", "Player") + .WithMany("Bids") + .HasForeignKey("PlayerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("DynamoLeagueBlazor.Server.Models.Team", "Team") + .WithMany() + .HasForeignKey("TeamId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Player"); + + b.Navigation("Team"); + }); + + modelBuilder.Entity("DynamoLeagueBlazor.Server.Models.Fine", b => + { + b.HasOne("DynamoLeagueBlazor.Server.Models.Player", "Player") + .WithMany("Fines") + .HasForeignKey("PlayerId"); + + b.HasOne("DynamoLeagueBlazor.Server.Models.Team", "Team") + .WithMany("Fines") + .HasForeignKey("TeamId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Player"); + + b.Navigation("Team"); + }); + + modelBuilder.Entity("DynamoLeagueBlazor.Server.Models.Player", b => + { + b.HasOne("DynamoLeagueBlazor.Server.Models.Team", "Team") + .WithMany("Players") + .HasForeignKey("TeamId"); + + b.Navigation("Team"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("DynamoLeagueBlazor.Server.Infrastructure.Identity.ApplicationRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("DynamoLeagueBlazor.Server.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("DynamoLeagueBlazor.Server.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("DynamoLeagueBlazor.Server.Infrastructure.Identity.ApplicationRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("DynamoLeagueBlazor.Server.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("DynamoLeagueBlazor.Server.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("DynamoLeagueBlazor.Server.Models.Player", b => + { + b.Navigation("Bids"); + + b.Navigation("Fines"); + }); + + modelBuilder.Entity("DynamoLeagueBlazor.Server.Models.Team", b => + { + b.Navigation("Fines"); + + b.Navigation("Players"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Server/Infrastructure/Migrations/20230302013711_ChangeToDateTimeOffset.cs b/src/Server/Infrastructure/Migrations/20230302013711_ChangeToDateTimeOffset.cs new file mode 100644 index 0000000..90fc46c --- /dev/null +++ b/src/Server/Infrastructure/Migrations/20230302013711_ChangeToDateTimeOffset.cs @@ -0,0 +1,68 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace DynamoLeagueBlazor.Server.Infrastructure.Migrations +{ + /// + public partial class ChangeToDateTimeOffset : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "EndOfFreeAgency", + table: "Players", + type: "datetimeoffset", + nullable: true, + oldClrType: typeof(DateTime), + oldType: "datetime2", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "CreatedOn", + table: "Fines", + type: "datetimeoffset", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "datetime2"); + + migrationBuilder.AlterColumn( + name: "CreatedOn", + table: "Bids", + type: "datetimeoffset", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "datetime2"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "EndOfFreeAgency", + table: "Players", + type: "datetime2", + nullable: true, + oldClrType: typeof(DateTimeOffset), + oldType: "datetimeoffset", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "CreatedOn", + table: "Fines", + type: "datetime2", + nullable: false, + oldClrType: typeof(DateTimeOffset), + oldType: "datetimeoffset"); + + migrationBuilder.AlterColumn( + name: "CreatedOn", + table: "Bids", + type: "datetime2", + nullable: false, + oldClrType: typeof(DateTimeOffset), + oldType: "datetimeoffset"); + } + } +} diff --git a/src/Server/Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs b/src/Server/Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs index eab536f..a14a260 100644 --- a/src/Server/Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/src/Server/Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs @@ -17,10 +17,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "6.0.8") + .HasAnnotation("ProductVersion", "7.0.3") .HasAnnotation("Relational:MaxIdentifierLength", 128); - SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); modelBuilder.Entity("Duende.IdentityServer.EntityFramework.Entities.DeviceFlowCodes", b => { @@ -105,7 +105,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("Use"); - b.ToTable("Keys"); + b.ToTable("Keys", (string)null); }); modelBuilder.Entity("Duende.IdentityServer.EntityFramework.Entities.PersistedGrant", b => @@ -269,13 +269,13 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("int"); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); b.Property("Amount") .HasColumnType("int"); - b.Property("CreatedOn") - .HasColumnType("datetime2"); + b.Property("CreatedOn") + .HasColumnType("datetimeoffset"); b.Property("IsOverBid") .HasColumnType("bit"); @@ -304,13 +304,13 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("int"); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); b.Property("Amount") .HasColumnType("decimal(18,0)"); - b.Property("CreatedOn") - .HasColumnType("datetime2"); + b.Property("CreatedOn") + .HasColumnType("datetimeoffset"); b.Property("PlayerId") .HasColumnType("int"); @@ -340,13 +340,13 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("int"); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); b.Property("ContractValue") .HasColumnType("int"); - b.Property("EndOfFreeAgency") - .HasColumnType("datetime2"); + b.Property("EndOfFreeAgency") + .HasColumnType("datetimeoffset"); b.Property("HeadShotUrl") .HasColumnType("nvarchar(max)"); @@ -384,7 +384,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("int"); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); b.Property("LogoUrl") .IsRequired() @@ -405,7 +405,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("int"); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); b.Property("ClaimType") .HasColumnType("nvarchar(max)"); @@ -430,7 +430,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("int"); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); b.Property("ClaimType") .HasColumnType("nvarchar(max)"); diff --git a/src/Server/Infrastructure/MsSqlContainerFactory.cs b/src/Server/Infrastructure/MsSqlContainerFactory.cs index 26b53f7..95526e7 100644 --- a/src/Server/Infrastructure/MsSqlContainerFactory.cs +++ b/src/Server/Infrastructure/MsSqlContainerFactory.cs @@ -19,10 +19,6 @@ public static async Task CreateAsync() Password = "yourStrong!Password123" }) .WithImage("mcr.microsoft.com/mssql/server:2019-latest") - .WithCreateContainerParametersModifier(parameters => - { - parameters.Env.Add("TZ=America/Chicago"); // TODO: All dates and comparisons should be UTC. Some tests fail because they use DateTime.Now in production or in the test. - }) .Build(); await _msSqlTestContainer.StartAsync(); diff --git a/src/Server/Infrastructure/SeedDataCommand.cs b/src/Server/Infrastructure/SeedDataCommand.cs index 13c541e..1bd5cd5 100644 --- a/src/Server/Infrastructure/SeedDataCommand.cs +++ b/src/Server/Infrastructure/SeedDataCommand.cs @@ -94,11 +94,11 @@ private async Task SeedApplicationDataAsync(CancellationToken cancellationToken) if (i % 2 == 0) { - player.SignForCurrentTeam(DateTime.Today.AddYears(1).Year, i); + player.SignForCurrentTeam(DateTimeOffset.UtcNow.AddYears(1).Year, i); if (i % 4 == 0) { - player.BeginNewSeason(DateTime.Today.AddYears(1)); + player.BeginNewSeason(DateTimeOffset.UtcNow.AddYears(1)); } if (i % 8 == 0) diff --git a/src/Server/Models/Bid.cs b/src/Server/Models/Bid.cs index 8f8d5f4..661c927 100644 --- a/src/Server/Models/Bid.cs +++ b/src/Server/Models/Bid.cs @@ -8,8 +8,8 @@ public record Bid : BaseEntity public required int PlayerId { get; init; } public required int TeamId { get; init; } public bool IsOverBid { get; set; } - public DateTime CreatedOn { get; private set; } = DateTime.Now; - public DateTimeOffset UpdatedOn { get; set; } = DateTimeOffset.Now; + public DateTimeOffset CreatedOn { get; private set; } = DateTimeOffset.UtcNow; + public DateTimeOffset UpdatedOn { get; set; } = DateTimeOffset.UtcNow; public Team Team { get; private set; } = null!; public Player Player { get; private set; } = null!; } diff --git a/src/Server/Models/Fine.cs b/src/Server/Models/Fine.cs index ba3bba4..97a1418 100644 --- a/src/Server/Models/Fine.cs +++ b/src/Server/Models/Fine.cs @@ -7,7 +7,7 @@ public record Fine : BaseEntity [Column(TypeName = "decimal(18,0)")] public required decimal Amount { get; set; } public bool Status { get; set; } - public DateTime CreatedOn { get; set; } = DateTime.Now; + public DateTimeOffset CreatedOn { get; set; } = DateTimeOffset.UtcNow; public required string Reason { get; set; } public required int TeamId { get; set; } public int? PlayerId { get; set; } diff --git a/src/Server/Models/Player.cs b/src/Server/Models/Player.cs index 7825cd8..a634fb8 100644 --- a/src/Server/Models/Player.cs +++ b/src/Server/Models/Player.cs @@ -6,7 +6,7 @@ public record Player : BaseEntity { private readonly StateMachine _machine; private readonly StateMachine.TriggerWithParameters _rosteredTrigger; - private readonly StateMachine.TriggerWithParameters _freeAgentTrigger; + private readonly StateMachine.TriggerWithParameters _freeAgentTrigger; public Player() { @@ -19,11 +19,11 @@ public Player() _rosteredTrigger = _machine.SetTriggerParameters(PlayerStateTrigger.SignedByTeam); _machine.Configure(PlayerState.Rostered) - .OnEntryFrom(_rosteredTrigger, (yearContractExpires, contractValue) => SetToRostered(yearContractExpires, contractValue)) + .OnEntryFrom(_rosteredTrigger, SetToRostered) .Permit(PlayerStateTrigger.UnrosteredByTeam, PlayerState.Unrostered) .Permit(PlayerStateTrigger.NewSeasonStarted, PlayerState.FreeAgent); - _freeAgentTrigger = _machine.SetTriggerParameters(PlayerStateTrigger.NewSeasonStarted); + _freeAgentTrigger = _machine.SetTriggerParameters(PlayerStateTrigger.NewSeasonStarted); _machine.Configure(PlayerState.FreeAgent) .OnEntryFrom(_freeAgentTrigger, (endOfFreeAgency) => EndOfFreeAgency = endOfFreeAgency) .Permit(PlayerStateTrigger.BiddingEnded, PlayerState.OfferMatching); @@ -40,7 +40,7 @@ public Player() public int ContractValue { get; set; } public int YearAcquired { get; set; } public int? TeamId { get; set; } - public DateTime? EndOfFreeAgency { get; set; } + public DateTimeOffset? EndOfFreeAgency { get; set; } public PlayerState State { get; set; } = PlayerState.Unsigned; public Team Team { get; private set; } = null!; @@ -83,7 +83,7 @@ private void SetToRostered(int yearContractExpires, int contractValue) EndOfFreeAgency = null; } - public void BeginNewSeason(DateTime endOfFreeAgency) => _machine.Fire(_freeAgentTrigger, endOfFreeAgency); + public void BeginNewSeason(DateTimeOffset endOfFreeAgency) => _machine.Fire(_freeAgentTrigger, endOfFreeAgency); // TODO: Refactor this into an event so its easier to understand each individual piece. public void AddBid(int amount, int teamIdOfBidder) @@ -119,7 +119,7 @@ public void AddBid(int amount, int teamIdOfBidder) if (shouldUpdateOverBid) { currentHighestBid!.Amount = amount; - currentHighestBid.UpdatedOn = DateTimeOffset.Now; + currentHighestBid.UpdatedOn = DateTimeOffset.UtcNow; } else if (!isBidByTheSameTeam && currentHighestBid != null) { @@ -142,7 +142,7 @@ void AddCounterBid(Bid currentHighestBid) else if (currentHighestBid!.Amount == amount) { currentHighestBid.IsOverBid = false; - currentHighestBid.UpdatedOn = DateTimeOffset.Now; + currentHighestBid.UpdatedOn = DateTimeOffset.UtcNow; } } @@ -157,11 +157,11 @@ bool IsEligibleForFreeAgencyExtension() { if (isBidByTheSameTeam) return false; - var maxFreeAgencyExtensionDate = new DateTime(DateTime.Now.Year, 8, 28); + var maxFreeAgencyExtensionDate = new DateTimeOffset(DateTimeOffset.UtcNow.Year, 8, 28, 0, 0, 0, TimeSpan.Zero); var isBeforeMaximumExtensionDate = EndOfFreeAgency < maxFreeAgencyExtensionDate; const int maxFreeAgencyExtensionDays = 3; - var isBeforeMaximumExtensionDays = EndOfFreeAgency < DateTime.Now.AddDays(maxFreeAgencyExtensionDays); + var isBeforeMaximumExtensionDays = EndOfFreeAgency < DateTimeOffset.UtcNow.AddDays(maxFreeAgencyExtensionDays); return isBeforeMaximumExtensionDate && isBeforeMaximumExtensionDays; } @@ -189,5 +189,5 @@ public Fine AddFine(decimal amount, string reason) public int GetHighestBidAmount() => Bids.Any(b => b.IsOverBid == false) ? Bids.Where(b => b.IsOverBid == false).FindHighestBid()!.Amount : Bid.MinimumAmount; public string GetOfferingTeam() => Bids.Any() ? Bids.FindHighestBid()!.Team.Name : string.Empty; - public TimeSpan GetRemainingFreeAgencyTime() => EndOfFreeAgency!.Value.AddDays(3) - DateTime.Now; + public TimeSpan GetRemainingFreeAgencyTime() => EndOfFreeAgency!.Value.AddDays(3) - DateTimeOffset.UtcNow; } diff --git a/src/Server/Program.cs b/src/Server/Program.cs index 26aa6db..fe0fc60 100644 --- a/src/Server/Program.cs +++ b/src/Server/Program.cs @@ -141,9 +141,9 @@ app.Services.UseScheduler(scheduler => { - var centralStandardTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time"); + // All bidding closes at 10PM CST. + var centralStandardTimeZone = TimeZoneInfo.FindSystemTimeZoneById("America/Chicago"); - // Hosting provider is in pacific time. scheduler.Schedule() .DailyAtHour(22) .Zoned(centralStandardTimeZone) @@ -155,7 +155,7 @@ .RunOnceAtStart(); }).OnError((ex) => { - Log.Logger.Error(ex, "An exception occured when trying to run a scheduled job."); + Log.Error(ex, "An exception occured when trying to run a scheduled job."); }); } diff --git a/src/Shared/Features/FreeAgents/Detail/Detail.cs b/src/Shared/Features/FreeAgents/Detail/Detail.cs index 026eede..a81f28c 100644 --- a/src/Shared/Features/FreeAgents/Detail/Detail.cs +++ b/src/Shared/Features/FreeAgents/Detail/Detail.cs @@ -6,7 +6,7 @@ public class FreeAgentDetailResult public required string Position { get; set; } public required string HeadShotUrl { get; set; } public required string Team { get; set; } - public DateTime EndOfFreeAgency { get; set; } + public DateTimeOffset EndOfFreeAgency { get; set; } public BidItem? OverBid { get; set; } diff --git a/src/Shared/Features/FreeAgents/List.cs b/src/Shared/Features/FreeAgents/List.cs index d4f1024..9f7ad7e 100644 --- a/src/Shared/Features/FreeAgents/List.cs +++ b/src/Shared/Features/FreeAgents/List.cs @@ -11,7 +11,7 @@ public class FreeAgentItem public string Position { get; set; } = null!; public string Team { get; set; } = null!; public string HeadShotUrl { get; set; } = null!; - public DateTime BiddingEnds { get; set; } + public DateTimeOffset BiddingEnds { get; set; } public int HighestBid { get; set; } public bool CurrentUserIsHighestBidder { get; set; } public string? WinningTeam { get; set; } diff --git a/src/Tests/Features/Admin/StartSeasonTests.cs b/src/Tests/Features/Admin/StartSeasonTests.cs index d72b440..5e4b8ce 100644 --- a/src/Tests/Features/Admin/StartSeasonTests.cs +++ b/src/Tests/Features/Admin/StartSeasonTests.cs @@ -35,7 +35,7 @@ public async Task GivenAuthenticatedAdmin_WhenAPlayerIsAFreeAgent_ThenReturnsTru var application = GetAdminAuthenticatedApplication(); var mockPlayer = CreateFakePlayer(); mockPlayer.State = PlayerState.Rostered; - mockPlayer.BeginNewSeason(DateTime.MaxValue); + mockPlayer.BeginNewSeason(DateTimeOffset.MaxValue); await AddAsync(mockPlayer); var client = application.CreateClient(); @@ -71,7 +71,7 @@ public async Task GivenAuthenticatedAdmin_WhenAFineExistsBeforeJanuary1stOfTheCu await AddAsync(mockPlayer); var mockFine = mockPlayer.AddFine(int.MaxValue, RandomString); - mockFine.CreatedOn = DateTime.MinValue; + mockFine.CreatedOn = DateTimeOffset.MinValue; await UpdateAsync(mockPlayer); var client = application.CreateClient(); @@ -98,7 +98,7 @@ public async Task GivenAuthenticatedAdmin_WhenAFineExistsOnOrAfterJanuary1stOfTh await AddAsync(mockPlayer); var mockFine = mockPlayer.AddFine(int.MaxValue, RandomString); - mockFine.CreatedOn = new DateTime(DateTime.Today.Year, 1, 1); + mockFine.CreatedOn = new DateTimeOffset(DateTimeOffset.UtcNow.Year, 1, 1, 0, 0, 0, TimeSpan.Zero); await UpdateAsync(mockPlayer); var client = application.CreateClient(); diff --git a/src/Tests/Features/FreeAgents/Detail/AddBidTests.cs b/src/Tests/Features/FreeAgents/Detail/AddBidTests.cs index d4025cb..464c498 100644 --- a/src/Tests/Features/FreeAgents/Detail/AddBidTests.cs +++ b/src/Tests/Features/FreeAgents/Detail/AddBidTests.cs @@ -69,7 +69,7 @@ public async Task GivenAnyAuthenticatedUser_WhenHasNotEnded_ThenReturnsTrue() var client = application.CreateClient(); var mockPlayer = CreateFakePlayer(); - mockPlayer.EndOfFreeAgency = DateTime.Now.AddSeconds(1); + mockPlayer.EndOfFreeAgency = DateTimeOffset.UtcNow.AddSeconds(1); await AddAsync(mockPlayer); var request = CreateFakeValidRequest(); request.PlayerId = mockPlayer.Id; @@ -86,7 +86,7 @@ public async Task GivenAnyAuthenticatedUser_WhenHasEnded_ThenReturnsFalse() var client = application.CreateClient(); var mockPlayer = CreateFakePlayer(); - mockPlayer.EndOfFreeAgency = DateTime.Now.AddSeconds(-1); + mockPlayer.EndOfFreeAgency = DateTimeOffset.UtcNow.AddSeconds(-1); await AddAsync(mockPlayer); var request = CreateFakeValidRequest(); request.PlayerId = mockPlayer.Id; @@ -114,7 +114,7 @@ public async Task POST_GivenAnyAuthenticatedUser_WhenIsHighestBid_ThenSavesTheBi await AddAsync(mockTeam); var mockPlayer = CreateFakePlayer(); - mockPlayer.EndOfFreeAgency = DateTime.Now.AddDays(1); + mockPlayer.EndOfFreeAgency = DateTimeOffset.UtcNow.AddDays(1); await AddAsync(mockPlayer); var request = CreateFakeValidRequest(); @@ -133,7 +133,7 @@ public async Task POST_GivenAnyAuthenticatedUser_WhenIsHighestBid_ThenSavesTheBi bid!.Amount.Should().Be(request.Amount); bid.PlayerId.Should().Be(request.PlayerId); bid.TeamId.Should().Be(mockTeam.Id); - bid.CreatedOn.Should().BeCloseTo(DateTime.Now, TimeSpan.FromSeconds(5)); + bid.CreatedOn.Should().BeCloseTo(DateTimeOffset.UtcNow, TimeSpan.FromSeconds(5)); } [Fact] @@ -143,7 +143,7 @@ public async Task POST_GivenAnyAuthenticatedUser_WhenIsHighestPublicBid_ButThere await AddAsync(originalTeam); var mockPlayer = CreateFakePlayer(); - mockPlayer.EndOfFreeAgency = DateTime.Now.AddDays(1); + mockPlayer.EndOfFreeAgency = DateTimeOffset.UtcNow.AddDays(1); await AddAsync(mockPlayer); const int privateBidAmount = 2; @@ -233,7 +233,7 @@ public async Task GivenAPlayerEndOfFreeAgencyOfNow_WhenItIsNow_ThenIsNotValid() var stubTeam = CreateFakeTeam(); await AddAsync(stubTeam); var mockPlayer = CreateFakePlayer(); - mockPlayer.EndOfFreeAgency = DateTime.Now; + mockPlayer.EndOfFreeAgency = DateTimeOffset.UtcNow; await AddAsync(mockPlayer); var request = new AddBidRequest { PlayerId = mockPlayer.Id, Amount = int.MaxValue }; @@ -249,7 +249,7 @@ public async Task GivenAPlayerEndOfFreeAgencyOfNowPlusOneSecond_WhenItIsNow_Then var stubTeam = CreateFakeTeam(); await AddAsync(stubTeam); var mockPlayer = CreateFakePlayer(); - mockPlayer.EndOfFreeAgency = DateTime.Now.AddSeconds(1); + mockPlayer.EndOfFreeAgency = DateTimeOffset.UtcNow.AddSeconds(1); await AddAsync(mockPlayer); var request = new AddBidRequest { PlayerId = mockPlayer.Id, Amount = int.MaxValue }; diff --git a/src/Tests/Features/FreeAgents/Detail/BidCountdownTests.cs b/src/Tests/Features/FreeAgents/Detail/BidCountdownTests.cs index 7ed7277..c89516a 100644 --- a/src/Tests/Features/FreeAgents/Detail/BidCountdownTests.cs +++ b/src/Tests/Features/FreeAgents/Detail/BidCountdownTests.cs @@ -9,7 +9,7 @@ public void CountsDownEverySecondUntilZero() { var cut = RenderComponent(parameters => { - parameters.Add(p => p.DateTime, DateTime.Now.AddSeconds(2)); + parameters.Add(p => p.DateTime, DateTimeOffset.UtcNow.AddSeconds(2)); }); cut.WaitForState(() => cut.Markup.Contains("0 seconds"), TimeSpan.FromSeconds(5)); @@ -20,7 +20,7 @@ public void GivenLessThanADayAway_ThenShowsRedText() { var cut = RenderComponent(parameters => { - parameters.Add(p => p.DateTime, DateTime.Now.AddSeconds(1)); + parameters.Add(p => p.DateTime, DateTimeOffset.UtcNow.AddSeconds(1)); }); cut.Markup.Contains("mud-text-error"); @@ -31,7 +31,7 @@ public void GivenMoreThanADayAway_ThenShowsYellowText() { var cut = RenderComponent(parameters => { - parameters.Add(p => p.DateTime, DateTime.Now.AddDays(1)); + parameters.Add(p => p.DateTime, DateTimeOffset.UtcNow.AddDays(1)); }); cut.Markup.Contains("mud-text-warning"); diff --git a/src/Tests/Features/FreeAgents/Detail/DetailTests.cs b/src/Tests/Features/FreeAgents/Detail/DetailTests.cs index a30634e..4a6a6e8 100644 --- a/src/Tests/Features/FreeAgents/Detail/DetailTests.cs +++ b/src/Tests/Features/FreeAgents/Detail/DetailTests.cs @@ -51,7 +51,7 @@ public async Task GivenAnyAuthenticatedUser_WhenGivenValidPlayerId_ThenReturnsEx var bid = response.Bids.First(); bid.Team.Should().Be(mockTeam.Name); bid.Amount.Should().Be(Bid.MinimumAmount); - DateTime.Parse(bid.CreatedOn).Should().BeExactly(TimeSpan.FromSeconds(0)); + DateTimeOffset.Parse(bid.CreatedOn).Should().BeExactly(TimeSpan.FromSeconds(0)); } } diff --git a/src/Tests/Features/FreeAgents/EndBiddingTests.cs b/src/Tests/Features/FreeAgents/EndBiddingTests.cs index 5bfa13f..c602445 100644 --- a/src/Tests/Features/FreeAgents/EndBiddingTests.cs +++ b/src/Tests/Features/FreeAgents/EndBiddingTests.cs @@ -24,7 +24,7 @@ public async Task GivenAPlayerWhoIsAFreeAgentAndEndFreeAgencyIsNowOrBeforeNow_Th { var mockPlayer = CreateFakePlayer(); mockPlayer.State = PlayerState.FreeAgent; - mockPlayer.EndOfFreeAgency = DateTime.Now.AddDays(-1); + mockPlayer.EndOfFreeAgency = DateTimeOffset.UtcNow.AddDays(-1); await AddAsync(mockPlayer); var sut = GetRequiredService(); diff --git a/src/Tests/Features/FreeAgents/ListTests.cs b/src/Tests/Features/FreeAgents/ListTests.cs index 556a623..a74f25b 100644 --- a/src/Tests/Features/FreeAgents/ListTests.cs +++ b/src/Tests/Features/FreeAgents/ListTests.cs @@ -37,8 +37,8 @@ public async Task GivenAnyAuthenticatedUser_WhenThereIsOnePlayerWhoIsAFreeAgent_ var mockPlayer = CreateFakePlayer(); mockPlayer.TeamId = mockTeam.Id; - mockPlayer.SignForCurrentTeam(DateTime.MinValue.Year, int.MaxValue); - var biddingEnds = DateTime.MaxValue; + mockPlayer.SignForCurrentTeam(DateTimeOffset.MinValue.Year, int.MaxValue); + var biddingEnds = DateTimeOffset.MaxValue; mockPlayer.BeginNewSeason(biddingEnds); await AddAsync(mockPlayer); @@ -74,8 +74,8 @@ public async Task GivenAnyAuthenticatedUser_WhenAFreeAgentHasABidByAnotherTeam_T var mockPlayer = CreateFakePlayer(); mockPlayer.TeamId = mockTeam.Id; - mockPlayer.SignForCurrentTeam(DateTime.MinValue.Year, int.MaxValue); - var biddingEnds = DateTime.MaxValue; + mockPlayer.SignForCurrentTeam(DateTimeOffset.MinValue.Year, int.MaxValue); + var biddingEnds = DateTimeOffset.MaxValue; mockPlayer.BeginNewSeason(biddingEnds); await AddAsync(mockPlayer); diff --git a/src/Tests/Features/OfferMatching/ExpireOfferMatchingTests.cs b/src/Tests/Features/OfferMatching/ExpireOfferMatchingTests.cs index 00313c1..9202dd0 100644 --- a/src/Tests/Features/OfferMatching/ExpireOfferMatchingTests.cs +++ b/src/Tests/Features/OfferMatching/ExpireOfferMatchingTests.cs @@ -23,7 +23,7 @@ public async Task GivenAnNonOfferMatchingPlayer_ThenDoesNothing() public async Task GivenAnOfferMatchingPlayer_WhenTodayIsTwoDaysOrLessAfterEndOfFreeAgency_ThenDoesNothing(int daysAgo) { var mockPlayer = CreateFakePlayer(); - mockPlayer.EndOfFreeAgency = DateTime.Today.AddDays(daysAgo); + mockPlayer.EndOfFreeAgency = DateTimeOffset.UtcNow.AddDays(daysAgo); mockPlayer.State = PlayerState.OfferMatching; await AddAsync(mockPlayer); @@ -44,7 +44,7 @@ public async Task GivenAnOfferMatchingPlayerWithBids_WhenTodayIsThreeDaysOrMoreA var mockPlayer = CreateFakePlayer(); mockPlayer.State = PlayerState.OfferMatching; mockPlayer.AddBid(Bid.MinimumAmount, biddingTeam.Id); - mockPlayer.EndOfFreeAgency = DateTime.Today.AddDays(-3); + mockPlayer.EndOfFreeAgency = DateTimeOffset.UtcNow.AddDays(-3); await AddAsync(mockPlayer); var sut = GetRequiredService(); @@ -61,7 +61,7 @@ public async Task GivenAnOfferMatchingPlayerWithBids_WhenTodayIsThreeDaysOrMoreA public async Task GivenAnOfferMatchingPlayerWithoutBids_WhenTodayIsThreeDaysOrMoreAfterEndOfFreeAgency_ThenIsRemovedFromTheLeague() { var mockPlayer = CreateFakePlayer(); - mockPlayer.EndOfFreeAgency = DateTime.Today.AddDays(-3); + mockPlayer.EndOfFreeAgency = DateTimeOffset.UtcNow.AddDays(-3); mockPlayer.State = PlayerState.OfferMatching; await AddAsync(mockPlayer); diff --git a/src/Tests/Features/OfferMatching/MatchPlayerTests.cs b/src/Tests/Features/OfferMatching/MatchPlayerTests.cs index 00fc245..0f7f10a 100644 --- a/src/Tests/Features/OfferMatching/MatchPlayerTests.cs +++ b/src/Tests/Features/OfferMatching/MatchPlayerTests.cs @@ -17,7 +17,7 @@ public async Task GivenAnyAuthenticatedUser_WhenPlayerIsMatchedAndHasBids_ThenPl mockPlayer.TeamId = matchingTeam.Id; mockPlayer.State = PlayerState.OfferMatching; mockPlayer.AddBid(Bid.MinimumAmount, biddingTeam.Id); - mockPlayer.EndOfFreeAgency = DateTime.Today.AddDays(-3); + mockPlayer.EndOfFreeAgency = DateTimeOffset.UtcNow.AddDays(-3); await AddAsync(mockPlayer); var request = new MatchPlayerRequest(mockPlayer.Id); @@ -31,7 +31,7 @@ public async Task GivenAnyAuthenticatedUser_WhenPlayerIsMatchedAndHasBids_ThenPl var unsignedPlayer = await FirstOrDefaultAsync(); unsignedPlayer!.YearContractExpires.Should().Be(null); unsignedPlayer.EndOfFreeAgency.Should().Be(null); - unsignedPlayer.YearAcquired.Should().Be(DateTime.Today.Year); + unsignedPlayer.YearAcquired.Should().Be(DateTimeOffset.UtcNow.Year); unsignedPlayer.ContractValue.Should().Be(Bid.MinimumAmount); unsignedPlayer.State.Should().Be(PlayerState.Unsigned); unsignedPlayer.TeamId.Should().Be(matchingTeam.Id); diff --git a/src/Tests/Features/Teams/DetailTests.cs b/src/Tests/Features/Teams/DetailTests.cs index 7d716df..c6426f9 100644 --- a/src/Tests/Features/Teams/DetailTests.cs +++ b/src/Tests/Features/Teams/DetailTests.cs @@ -30,7 +30,7 @@ public async Task GivenAnyAuthenticatedUser_WhenGivenValidTeamId_ThenReturnsExpe var mockRosteredPlayer = CreateFakePlayer(); mockRosteredPlayer.TeamId = stubTeam.Id; - mockRosteredPlayer.SignForCurrentTeam(DateTime.MaxValue.Year, 1); + mockRosteredPlayer.SignForCurrentTeam(DateTimeOffset.MaxValue.Year, 1); mockRosteredPlayer.State = PlayerState.Rostered; await AddAsync(mockRosteredPlayer); @@ -52,7 +52,7 @@ public async Task GivenAnyAuthenticatedUser_WhenGivenValidTeamId_ThenReturnsExpe response.Should().NotBeNull(); response!.Name.Should().Be(stubTeam.Name); var expectedCapSpace = CapSpaceUtilities.GetRemainingCapSpace( - DateOnly.FromDateTime(DateTime.Today), + DateOnly.FromDateTime(DateTimeOffset.UtcNow.DateTime), mockRosteredPlayer.ContractValue, mockUnrosteredPlayer.ContractValue, mockUnsignedPlayer.ContractValue).ToString("C0"); diff --git a/src/Tests/Features/Teams/SignPlayerTests.cs b/src/Tests/Features/Teams/SignPlayerTests.cs index b5556e9..7fbd36f 100644 --- a/src/Tests/Features/Teams/SignPlayerTests.cs +++ b/src/Tests/Features/Teams/SignPlayerTests.cs @@ -32,7 +32,7 @@ public async Task GivenAuthenticatedUser_ThenSignsPlayer() var application = GetUserAuthenticatedApplication(); var player = CreateFakePlayer(); player.Position = Position.QuarterBack.Name; - player.YearContractExpires = DateTime.Now.Year; + player.YearContractExpires = DateTimeOffset.UtcNow.Year; await AddAsync(player); var request = CreateFakeValidRequest(); request.YearContractExpires = (int)player.YearContractExpires; diff --git a/src/Tests/Models/PlayerTests.cs b/src/Tests/Models/PlayerTests.cs index 0dbf7a1..b60fe80 100644 --- a/src/Tests/Models/PlayerTests.cs +++ b/src/Tests/Models/PlayerTests.cs @@ -194,7 +194,7 @@ public void GivenAnRosteredPlayer_WhenANewSeasonStarts_ThenMovesToFreeAgent() var rosteredPlayer = CreateFakePlayer(); rosteredPlayer.State = PlayerState.Rostered; - rosteredPlayer.BeginNewSeason(DateTime.MaxValue); + rosteredPlayer.BeginNewSeason(DateTimeOffset.MaxValue); rosteredPlayer.State.Should().Be(PlayerState.FreeAgent); } @@ -208,7 +208,7 @@ public void GivenABrandNewPlayer_ThenCanGoThroughTheCompleteLifecycle() FluentActions.Invoking(() => { player.SignForCurrentTeam(int.MaxValue, int.MaxValue); - player.BeginNewSeason(DateTime.MaxValue); + player.BeginNewSeason(DateTimeOffset.MaxValue); player.EndBidding(); player.MatchOffer(); }).Should().NotThrow();