diff --git a/AsbCloudApp/ValidationAttributes/DateValidationAttribute.cs b/AsbCloudApp/ValidationAttributes/DateValidationAttribute.cs
new file mode 100644
index 00000000..3ad6fce0
--- /dev/null
+++ b/AsbCloudApp/ValidationAttributes/DateValidationAttribute.cs
@@ -0,0 +1,80 @@
+using System;
+using System.ComponentModel.DataAnnotations;
+
+namespace AsbCloudApp.ValidationAttributes
+{
+ ///
+ /// Атрибут валидации даты-времени
+ ///
+ [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property)]
+ public class DateValidationAttribute : ValidationAttribute
+ {
+ private DateTime? gtDate;
+
+ ///
+ /// null разрешен
+ ///
+ public bool AllowNull { get; set; } = true;
+
+ ///
+ /// Разрешена только дат.
+ /// При наличии времени в DateTime инвалидирует.
+ /// При наличии UTC тоже инвалидирует.
+ ///
+ public bool IsDateOnly { get; set; } = false;
+
+ ///
+ /// Допустима дата-время в UTC
+ ///
+ public bool AllowUtc { get; set; } = true;
+
+ ///
+ /// Дата больше которой должно быть проверяемое значение.
+ /// Формат строки - любой поддерживаемый DateTime.Parse.
+ /// Желательно использовать ISO 8601 формат
+ ///
+ public string? GtDate {
+ get => gtDate.ToString();
+ set
+ {
+ if(value is null)
+ gtDate = null;
+ else
+ gtDate = DateTime.Parse(value);
+ }
+ }
+
+ ///
+ /// Проверка значения
+ ///
+ ///
+ ///
+ public override bool IsValid(object? value)
+ {
+ if (value is null)
+ return AllowNull;
+
+ if (value is not DateTime dateTime)
+ return false;
+
+ if (IsDateOnly)
+ {
+ if (dateTime.Hour > 0 ||
+ dateTime.Minute > 0 ||
+ dateTime.Second > 0 ||
+ dateTime.Millisecond > 0 ||
+ dateTime.Kind == DateTimeKind.Utc)
+ return false;
+ }
+
+ if (!AllowUtc && dateTime.Kind == DateTimeKind.Utc)
+ return false;
+
+ if (gtDate.HasValue && dateTime <= gtDate)
+ return false;
+
+ return true;
+ }
+
+ }
+}
diff --git a/AsbCloudWebApi.Tests/AttributeTest/DateValidationAttributeTest.cs b/AsbCloudWebApi.Tests/AttributeTest/DateValidationAttributeTest.cs
new file mode 100644
index 00000000..3e4990a2
--- /dev/null
+++ b/AsbCloudWebApi.Tests/AttributeTest/DateValidationAttributeTest.cs
@@ -0,0 +1,108 @@
+using AsbCloudApp.ValidationAttributes;
+using System;
+using Xunit;
+
+namespace AsbCloudWebApi.Tests.AttributeTest
+{
+ public class DateValidationAttributeTest
+ {
+ [Fact]
+ public void AllowNull_true_on_null_valid()
+ {
+ var attribute = new DateValidationAttribute { AllowNull = true };
+ var result = attribute.IsValid(null);
+ Assert.True(result);
+ }
+
+ [Fact]
+ public void AllowNull_false_on_null_invalid()
+ {
+ var attribute = new DateValidationAttribute { AllowNull = false };
+ var result = attribute.IsValid(null);
+ Assert.False(result);
+ }
+
+ [Fact]
+ public void IsDateOnly_true_on_empty_timePart_valid()
+ {
+ var attribute = new DateValidationAttribute { IsDateOnly = true };
+ var date = new DateTime(2023, 01, 01, 00, 00, 00);
+ var result = attribute.IsValid(date);
+ Assert.True(result);
+ }
+
+ [Fact]
+ public void IsDateOnly_true_on_timePart_invalid()
+ {
+ var attribute = new DateValidationAttribute { IsDateOnly = true };
+ var date = new DateTime(2023, 01, 01, 01, 01, 01);
+ var result = attribute.IsValid(date);
+ Assert.False(result);
+ }
+
+ [Fact]
+ public void IsDateOnly_true_on_utc_invalid()
+ {
+ var attribute = new DateValidationAttribute { IsDateOnly = true };
+ var date = new DateTime(2023, 01, 01, 00, 00, 00, DateTimeKind.Utc);
+ var result = attribute.IsValid(date);
+ Assert.False(result);
+ }
+
+ [Fact]
+ public void AllowUtc_true_on_unspecified_valid()
+ {
+ var attribute = new DateValidationAttribute { AllowUtc = true };
+ var date = new DateTime(2023, 01, 01, 00, 00, 00, DateTimeKind.Unspecified);
+ var result = attribute.IsValid(date);
+ Assert.True(result);
+ }
+
+ [Fact]
+ public void AllowUtc_true_on_utc_valid()
+ {
+ var attribute = new DateValidationAttribute { AllowUtc = true };
+ var date = new DateTime(2023, 01, 01, 00, 00, 00, DateTimeKind.Utc);
+ var result = attribute.IsValid(date);
+ Assert.True(result);
+ }
+
+ [Fact]
+ public void AllowUtc_false_on_utc_invalid()
+ {
+ var attribute = new DateValidationAttribute { AllowUtc = false };
+ var date = new DateTime(2023, 01, 01, 00, 00, 00, DateTimeKind.Utc);
+ var result = attribute.IsValid(date);
+ Assert.False(result);
+ }
+
+ [Fact]
+ public void AllowUtc_false_on_unspecified_valid()
+ {
+ var attribute = new DateValidationAttribute { AllowUtc = false };
+ var date = new DateTime(2023, 01, 01, 00, 00, 00, DateTimeKind.Unspecified);
+ var result = attribute.IsValid(date);
+ Assert.True(result);
+ }
+
+ [Fact]
+ public void GtDate_on_same_date_invalid()
+ {
+ var gtDate = "2023-01-01T00:00:00";
+ var date = DateTime.Parse(gtDate);
+ var attribute = new DateValidationAttribute { GtDate = gtDate };
+ var result = attribute.IsValid(date);
+ Assert.False(result);
+ }
+
+ [Fact]
+ public void GtDate_on_greater_date_valid()
+ {
+ var gtDate = "2023-01-01T00:00:00";
+ var date = DateTime.Parse(gtDate).AddMilliseconds(1);
+ var attribute = new DateValidationAttribute { GtDate = gtDate };
+ var result = attribute.IsValid(date);
+ Assert.True(result);
+ }
+ }
+}