diff --git a/Oqtane.Client/Modules/Admin/Jobs/Edit.razor b/Oqtane.Client/Modules/Admin/Jobs/Edit.razor
index 120ede22..34780aa0 100644
--- a/Oqtane.Client/Modules/Admin/Jobs/Edit.razor
+++ b/Oqtane.Client/Modules/Admin/Jobs/Edit.razor
@@ -132,22 +132,10 @@
_isEnabled = job.IsEnabled.ToString();
_interval = job.Interval.ToString();
_frequency = job.Frequency;
- _startDate = job.StartDate;
- if (job.StartDate != null && job.StartDate.Value.TimeOfDay.TotalSeconds != 0)
- {
- _startTime = job.StartDate.Value.ToString("HH:mm");
- }
- _endDate = job.EndDate;
- if (job.EndDate != null && job.EndDate.Value.TimeOfDay.TotalSeconds != 0)
- {
- _endTime = job.EndDate.Value.ToString("HH:mm");
- }
+ (_startDate, _startTime) = Utilities.UtcAsLocalDateAndTime(job.StartDate);
+ (_endDate, _endTime) = Utilities.UtcAsLocalDateAndTime(job.EndDate);
_retentionHistory = job.RetentionHistory.ToString();
- _nextDate = job.NextExecution;
- if (job.NextExecution != null && job.NextExecution.Value.TimeOfDay.TotalSeconds != 0)
- {
- _nextTime = job.NextExecution.Value.ToString("HH:mm");
- }
+ (_nextDate, _nextTime) = Utilities.UtcAsLocalDateAndTime(job.NextExecution);
createdby = job.CreatedBy;
createdon = job.CreatedOn;
modifiedby = job.ModifiedBy;
@@ -180,50 +168,27 @@
{
job.Interval = int.Parse(_interval);
}
- job.StartDate = _startDate;
- if (job.StartDate != null)
- {
- job.StartDate = job.StartDate.Value.Date;
- if (!string.IsNullOrEmpty(_startTime))
- {
- job.StartDate = DateTime.Parse(job.StartDate.Value.ToShortDateString() + " " + _startTime);
- }
- }
- job.EndDate = _endDate;
- if (job.EndDate != null)
- {
- job.EndDate = job.EndDate.Value.Date;
- if (!string.IsNullOrEmpty(_endTime))
- {
- job.EndDate = DateTime.Parse(job.EndDate.Value.ToShortDateString() + " " + _endTime);
- }
- }
- job.RetentionHistory = int.Parse(_retentionHistory);
- job.NextExecution = _nextDate;
- if (job.NextExecution != null)
- {
- job.NextExecution = job.NextExecution.Value.Date;
- if (!string.IsNullOrEmpty(_nextTime))
- {
- job.NextExecution = DateTime.Parse(job.NextExecution.Value.ToShortDateString() + " " + _nextTime);
- }
- }
+ job.StartDate = Utilities.LocalDateAndTimeAsUtc(_startDate, _startTime);
+ job.EndDate = Utilities.LocalDateAndTimeAsUtc(_endDate, _endTime);
+ job.RetentionHistory = int.Parse(_retentionHistory);
+ job.NextExecution = Utilities.LocalDateAndTimeAsUtc(_nextDate, _nextTime);
+
+ try
+ {
+ job = await JobService.UpdateJobAsync(job);
+ await logger.LogInformation("Job Updated {Job}", job);
+ NavigationManager.NavigateTo(NavigateUrl());
+ }
+ catch (Exception ex)
+ {
+ await logger.LogError(ex, "Error Udate Job {Job} {Error}", job, ex.Message);
+ AddModuleMessage(Localizer["Error.Job.Update"], MessageType.Error);
+ }
+ }
+ else
+ {
+ AddModuleMessage(Localizer["Message.Required.JobInfo"], MessageType.Warning);
+ }
+ }
- try
- {
- job = await JobService.UpdateJobAsync(job);
- await logger.LogInformation("Job Updated {Job}", job);
- NavigationManager.NavigateTo(NavigateUrl());
- }
- catch (Exception ex)
- {
- await logger.LogError(ex, "Error Udate Job {Job} {Error}", job, ex.Message);
- AddModuleMessage(Localizer["Error.Job.Update"], MessageType.Error);
- }
- }
- else
- {
- AddModuleMessage(Localizer["Message.Required.JobInfo"], MessageType.Warning);
- }
- }
}
diff --git a/Oqtane.Client/Modules/Admin/Jobs/Index.razor b/Oqtane.Client/Modules/Admin/Jobs/Index.razor
index 3553d973..2ae6e15c 100644
--- a/Oqtane.Client/Modules/Admin/Jobs/Index.razor
+++ b/Oqtane.Client/Modules/Admin/Jobs/Index.razor
@@ -33,7 +33,7 @@ else
@context.Name |
@DisplayStatus(context.IsEnabled, context.IsExecuting) |
@DisplayFrequency(context.Interval, context.Frequency) |
- @context.NextExecution |
+ @context.NextExecution?.ToLocalTime() |
@if (context.IsStarted)
{
diff --git a/Oqtane.Server/Infrastructure/Jobs/PurgeJob.cs b/Oqtane.Server/Infrastructure/Jobs/PurgeJob.cs
index 8eb22a97..ff4a88f8 100644
--- a/Oqtane.Server/Infrastructure/Jobs/PurgeJob.cs
+++ b/Oqtane.Server/Infrastructure/Jobs/PurgeJob.cs
@@ -17,7 +17,7 @@ namespace Oqtane.Infrastructure
Name = "Purge Job";
Frequency = "d"; // daily
Interval = 1;
- StartDate = DateTime.ParseExact("03:00", "H:mm", null, System.Globalization.DateTimeStyles.None); // 3 AM
+ StartDate = DateTime.ParseExact("03:00", "H:mm", null, System.Globalization.DateTimeStyles.AssumeLocal).ToUniversalTime(); // 3 AM
IsEnabled = true;
}
diff --git a/Oqtane.Shared/Shared/Utilities.cs b/Oqtane.Shared/Shared/Utilities.cs
index 8717c6b2..03a6cabc 100644
--- a/Oqtane.Shared/Shared/Utilities.cs
+++ b/Oqtane.Shared/Shared/Utilities.cs
@@ -446,5 +446,50 @@ namespace Oqtane.Shared
{
return $"[{@class.GetType()}] {message}";
}
+
+ public static DateTime? LocalDateAndTimeAsUtc(DateTime? date, string time, TimeZoneInfo localTimeZone = null)
+ {
+ localTimeZone ??= TimeZoneInfo.Local;
+ if (date != null)
+ {
+ if (!string.IsNullOrEmpty(time))
+ {
+ return TimeZoneInfo.ConvertTime(DateTime.Parse(date.Value.Date.ToShortDateString() + " " + time), localTimeZone, TimeZoneInfo.Utc);
+ }
+ return TimeZoneInfo.ConvertTime(date.Value.Date, localTimeZone, TimeZoneInfo.Utc);
+ }
+ return null;
+ }
+
+ public static (DateTime? date, string time) UtcAsLocalDateAndTime(DateTime? dateTime, TimeZoneInfo timeZone = null)
+ {
+ timeZone ??= TimeZoneInfo.Local;
+ DateTime? localDateTime = null;
+ string localTime = string.Empty;
+
+ if (dateTime.HasValue && dateTime?.Kind != DateTimeKind.Local)
+ {
+ if (dateTime?.Kind == DateTimeKind.Unspecified)
+ {
+ // Treat Unspecified as Utc not Local. This is due to EF Core, on some databases, after retrieval will have DateTimeKind as Unspecified.
+ // All values in database should be UTC.
+ // Normal .net conversion treats Unspecified as local.
+ // https://docs.microsoft.com/en-us/dotnet/api/system.timezoneinfo.converttime?view=net-6.0
+ localDateTime = TimeZoneInfo.ConvertTime(new DateTime(dateTime.Value.Ticks, DateTimeKind.Utc), timeZone);
+ }
+ else
+ {
+ localDateTime = TimeZoneInfo.ConvertTime(dateTime.Value, timeZone);
+ }
+ }
+
+ if (localDateTime != null && localDateTime.Value.TimeOfDay.TotalSeconds != 0)
+ {
+ localTime = localDateTime.Value.ToString("HH:mm");
+ }
+
+ return (localDateTime?.Date, localTime);
+ }
+
}
}
diff --git a/Oqtane.Test/Oqtane.Shared.Tests/UtilitiesTests.cs b/Oqtane.Test/Oqtane.Shared.Tests/UtilitiesTests.cs
index 29adc5d4..0a2dc47c 100644
--- a/Oqtane.Test/Oqtane.Shared.Tests/UtilitiesTests.cs
+++ b/Oqtane.Test/Oqtane.Shared.Tests/UtilitiesTests.cs
@@ -1,3 +1,5 @@
+using System;
+using System.Globalization;
using Oqtane.Shared;
using Xunit;
@@ -27,5 +29,61 @@ namespace Oqtane.Test.Oqtane.Shared.Tests
// Assert
Assert.Equal(expectedUrl, navigatedUrl);
}
+
+ [Theory]
+ [InlineData(2022, 02, 01, "21:00", "Eastern Standard Time", 2022, 2, 2, 2)]
+ [InlineData(2022, 02, 02, "15:00", "Eastern Standard Time", 2022, 2, 2, 20)]
+ [InlineData(2022, 02, 02, "", "Eastern Standard Time", 2022, 2, 2, 5)]
+ [InlineData(0, 0, 0, "", "Eastern Standard Time", 0, 0, 0, 0)]
+ public void LocalDateAndTimeAsUtcTest(int yr, int mo, int day, string timeString, string zone, int yrUtc, int moUtc, int dayUtc, int hrUtc)
+ {
+ // Arrange
+ DateTime? srcDate = null;
+ if (yr > 0)
+ {
+ srcDate = new DateTime(yr, mo, day);
+ }
+
+ // Act
+ var dateTime = Utilities.LocalDateAndTimeAsUtc(srcDate, timeString, TimeZoneInfo.FindSystemTimeZoneById(zone));
+
+ // Assert
+ DateTime? expected = null;
+ if (yrUtc > 0)
+ {
+ expected = new DateTime(yrUtc, moUtc, dayUtc, hrUtc, 0, 0, DateTimeKind.Utc);
+ }
+ Assert.Equal(expected, dateTime);
+ }
+
+ [Theory]
+ // Standard Time
+ [InlineData(2022, 2, 2, 2, DateTimeKind.Unspecified, "Eastern Standard Time", "2022/02/01", "21:00")]
+ [InlineData(2022, 2, 2, 2, DateTimeKind.Utc, "Eastern Standard Time", "2022/02/01", "21:00")]
+ [InlineData(2022, 2, 2, 20, DateTimeKind.Unspecified, "Eastern Standard Time", "2022/02/02", "15:00")]
+ [InlineData(2022, 2, 2, 20, DateTimeKind.Utc, "Eastern Standard Time", "2022/02/02", "15:00")]
+ [InlineData(2022, 2, 2, 5, DateTimeKind.Unspecified, "Eastern Standard Time", "2022/02/02", "")]
+ [InlineData(2022, 2, 2, 5, DateTimeKind.Utc, "Eastern Standard Time", "2022/02/02", "")]
+ // Daylight Savings Time
+ [InlineData(2022, 7, 2, 20, DateTimeKind.Unspecified, "Eastern Standard Time", "2022/07/02", "16:00")]
+ [InlineData(2022, 7, 2, 20, DateTimeKind.Utc, "Eastern Standard Time", "2022/07/02", "16:00")]
+ [InlineData(2022, 7, 2, 4, DateTimeKind.Unspecified, "Eastern Standard Time", "2022/07/02", "")]
+ [InlineData(2022, 7, 2, 4, DateTimeKind.Utc, "Eastern Standard Time", "2022/07/02", "")]
+ public void UtcAsLocalDateAndTimeTest(int yr, int mo, int day, int hr, DateTimeKind dateTimeKind, string zone, string expectedDate, string expectedTime)
+ {
+ // Arrange
+ DateTime? srcDate = null;
+ if (yr > 0)
+ {
+ srcDate = new DateTime(yr, mo, day, hr, 0, 0, dateTimeKind);
+ }
+
+ // Act
+ var dateAndTime = Utilities.UtcAsLocalDateAndTime(srcDate, TimeZoneInfo.FindSystemTimeZoneById(zone));
+
+ // Assert
+ Assert.Equal(expectedDate, dateAndTime.date.Value.ToString("yyyy/MM/dd"));
+ Assert.Equal(expectedTime, dateAndTime.time);
+ }
}
}
|