diff --git a/src/GmRelay.DiscordBot/Features/Sessions/DiscordNewSessionHandler.cs b/src/GmRelay.DiscordBot/Features/Sessions/DiscordNewSessionHandler.cs index 7712396..55d2c7e 100644 --- a/src/GmRelay.DiscordBot/Features/Sessions/DiscordNewSessionHandler.cs +++ b/src/GmRelay.DiscordBot/Features/Sessions/DiscordNewSessionHandler.cs @@ -3,6 +3,7 @@ using GmRelay.DiscordBot.Infrastructure.Discord; using GmRelay.Shared.Domain; using GmRelay.Shared.Rendering; using Npgsql; +using System.Globalization; namespace GmRelay.DiscordBot.Features.Sessions; @@ -13,32 +14,38 @@ public sealed class DiscordNewSessionHandler( DiscordPermissionChecker permissionChecker, ILogger logger) { + private static readonly TimeSpan MoscowOffset = TimeSpan.FromHours(3); + public static TimeParseResult ParseTimeInput(string input) { - if (DateTimeOffset.TryParseExact( - input.Trim(), + var trimmed = input.Trim(); + + if (DateTime.TryParseExact( + trimmed, "yyyy-MM-dd HH:mm", - System.Globalization.CultureInfo.InvariantCulture, - System.Globalization.DateTimeStyles.AssumeUniversal, - out var result)) + CultureInfo.InvariantCulture, + DateTimeStyles.None, + out var dt1)) { - if (result < DateTimeOffset.UtcNow) + var offset = new DateTimeOffset(dt1, MoscowOffset).ToUniversalTime(); + if (offset < DateTimeOffset.UtcNow) return new TimeParseResult(false, default, "Дата находится в прошлом."); - return new TimeParseResult(true, result.ToUniversalTime(), null); + return new TimeParseResult(true, offset, null); } - if (DateTimeOffset.TryParseExact( - input.Trim(), + if (DateTime.TryParseExact( + trimmed, "dd.MM.yyyy HH:mm", - System.Globalization.CultureInfo.InvariantCulture, - System.Globalization.DateTimeStyles.AssumeUniversal, - out var altResult)) + CultureInfo.InvariantCulture, + DateTimeStyles.None, + out var dt2)) { - if (altResult < DateTimeOffset.UtcNow) + var offset = new DateTimeOffset(dt2, MoscowOffset).ToUniversalTime(); + if (offset < DateTimeOffset.UtcNow) return new TimeParseResult(false, default, "Дата находится в прошлом."); - return new TimeParseResult(true, altResult.ToUniversalTime(), null); + return new TimeParseResult(true, offset, null); } return new TimeParseResult(false, default, "Некорректный формат даты. Используйте YYYY-MM-DD HH:mm или DD.MM.YYYY HH:mm"); diff --git a/tests/GmRelay.Bot.Tests/Discord/DiscordNewSessionHandlerTests.cs b/tests/GmRelay.Bot.Tests/Discord/DiscordNewSessionHandlerTests.cs index 6cae87f..437dc82 100644 --- a/tests/GmRelay.Bot.Tests/Discord/DiscordNewSessionHandlerTests.cs +++ b/tests/GmRelay.Bot.Tests/Discord/DiscordNewSessionHandlerTests.cs @@ -17,6 +17,17 @@ public sealed class DiscordNewSessionHandlerTests // --- Runtime tests for ParseTimeInput (static, no DB) --- + [Fact] + public void ParseTimeInput_ShouldTreatInputAsMoscowTime() + { + var result = DiscordNewSessionHandler.ParseTimeInput("2026-06-01 15:00"); + Assert.True(result.IsSuccess); + // 15:00 MSK = 12:00 UTC + Assert.Equal(12, result.Value.Hour); + Assert.Equal(0, result.Value.Minute); + Assert.Equal(TimeSpan.Zero, result.Value.Offset); + } + [Fact] public void ParseTimeInput_ShouldParseDiscordDateFormat() { @@ -28,7 +39,8 @@ public sealed class DiscordNewSessionHandlerTests Assert.Equal(expected.Year, result.Value.Year); Assert.Equal(expected.Month, result.Value.Month); Assert.Equal(expected.Day, result.Value.Day); - Assert.Equal(19, result.Value.Hour); + // Input is treated as Moscow time; 19:30 MSK = 16:30 UTC + Assert.Equal(16, result.Value.Hour); Assert.Equal(30, result.Value.Minute); }