Make sure Job date times are stored in the database as UTC. This is required if using Postgres or you will get an exception with a message of “Cannot write DateTime with Kind=Unspecified to PostgreSQL type 'timestamp with time zone', only UTC is supported.”.
This commit is contained in:
parent
62eca2aedc
commit
ba97f63338
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ else
|
|||
<td>@context.Name</td>
|
||||
<td>@DisplayStatus(context.IsEnabled, context.IsExecuting)</td>
|
||||
<td>@DisplayFrequency(context.Interval, context.Frequency)</td>
|
||||
<td>@context.NextExecution</td>
|
||||
<td>@context.NextExecution?.ToLocalTime()</td>
|
||||
<td>
|
||||
@if (context.IsStarted)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user