Skip to content

Commit a656d4b

Browse files
authored
Use safe DateTime conversion (#861)
1 parent fcc5409 commit a656d4b

File tree

6 files changed

+65
-37
lines changed

6 files changed

+65
-37
lines changed

Daybreak/Services/Logging/JsonLogsManager.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using Daybreak.Configuration.Options;
22
using Daybreak.Services.Database;
33
using Daybreak.Services.Logging.Models;
4+
using Daybreak.Utils;
45
using Microsoft.Extensions.Logging;
56
using System;
67
using System.Collections.Generic;
@@ -65,7 +66,7 @@ public async void WriteLog(Log log)
6566
Message = log.Exception is null ? log.Message : $"{log.Message}{Environment.NewLine}{log.Exception}",
6667
Category = log.Category,
6768
LogLevel = log.LogLevel,
68-
LogTime = log.LogTime,
69+
LogTime = log.LogTime.ToSafeDateTimeOffset(),
6970
CorrelationVector = log.CorrelationVector
7071
};
7172

Daybreak/Services/Notifications/NotificationService.cs

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
using Daybreak.Models.Notifications;
22
using Daybreak.Models.Notifications.Handling;
33
using Daybreak.Services.Notifications.Models;
4+
using Daybreak.Utils;
45
using Microsoft.Extensions.Logging;
56
using Slim;
67
using System;
78
using System.Collections.Concurrent;
89
using System.Collections.Generic;
910
using System.Core.Extensions;
1011
using System.Extensions;
11-
using System.Extensions.Core;
1212
using System.Linq;
1313
using System.Runtime.CompilerServices;
1414
using System.Threading;
@@ -80,27 +80,19 @@ public NotificationToken NotifyError<THandlingType>(string title,
8080

8181
void INotificationProducer.OpenNotification(Notification notification, bool storeNotification)
8282
{
83-
var scopedLogger = this.logger.CreateScopedLogger();
8483
if (storeNotification)
8584
{
86-
try
85+
this.storage.OpenNotification(new NotificationDTO
8786
{
88-
this.storage.OpenNotification(new NotificationDTO
89-
{
90-
Title = notification.Title,
91-
Description = notification.Description,
92-
Id = notification.Id,
93-
Level = (int)notification.Level,
94-
MetaData = notification.Metadata,
95-
HandlerType = notification.HandlingType?.AssemblyQualifiedName,
96-
ExpirationTime = notification.ExpirationTime,
97-
Closed = true
98-
});
99-
}
100-
catch(ArgumentOutOfRangeException exception) when (exception.Message.Contains("The UTC time represented when the offset is applied must be between year 0 and 10000"))
101-
{
102-
scopedLogger.LogError(exception, "Encountered DateTime to DateTimeOffset exception. Failed to store notification and will discard it. DateTime: {dateTime}", notification.ExpirationTime);
103-
}
87+
Title = notification.Title,
88+
Description = notification.Description,
89+
Id = notification.Id,
90+
Level = (int)notification.Level,
91+
MetaData = notification.Metadata,
92+
HandlerType = notification.HandlingType?.AssemblyQualifiedName,
93+
ExpirationTime = notification.ExpirationTime.ToSafeDateTimeOffset(),
94+
Closed = true
95+
});
10496
}
10597

10698
if (notification.HandlingType is null)
@@ -213,8 +205,8 @@ private static NotificationDTO ToDTO(Notification notification)
213205
Level = (int)notification.Level,
214206
Title = notification.Title,
215207
Description = notification.Description,
216-
ExpirationTime = notification.ExpirationTime,
217-
CreationTime = notification.CreationTime,
208+
ExpirationTime = notification.ExpirationTime.ToSafeDateTimeOffset(),
209+
CreationTime = notification.CreationTime.ToSafeDateTimeOffset(),
218210
MetaData = notification.Metadata,
219211
Dismissible = notification.Dismissible,
220212
Closed = notification.Closed,

Daybreak/Services/TradeChat/PriceHistoryDatabase.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using Daybreak.Models.Guildwars;
22
using Daybreak.Services.Database;
33
using Daybreak.Services.TradeChat.Models;
4+
using Daybreak.Utils;
45
using Microsoft.Extensions.Logging;
56
using System;
67
using System.Collections.Generic;
@@ -48,10 +49,10 @@ public IEnumerable<TraderQuoteDTO> GetQuoteHistory(ItemBase item, DateTime? from
4849
{
4950
var scopedLogger = this.logger.CreateScopedLogger(nameof(this.GetQuoteHistory), item.Id.ToString());
5051
var fromO = fromTimestamp.HasValue ?
51-
new DateTimeOffset(fromTimestamp.Value) :
52+
fromTimestamp.Value.ToSafeDateTimeOffset() :
5253
DateTimeOffset.MinValue;
5354
var toO = toTimestamp.HasValue ?
54-
new DateTimeOffset(toTimestamp.Value) :
55+
toTimestamp.Value.ToSafeDateTimeOffset() :
5556
DateTimeOffset.MaxValue;
5657
scopedLogger.LogDebug($"Retrieving quotes for item {item.Id} with timestamp between [{fromO}] and [{toO}]");
5758
var modifiersHash = item.Modifiers is not null ? this.itemHashService.ComputeHash(item) : default;
@@ -69,10 +70,10 @@ public IEnumerable<TraderQuoteDTO> GetQuotesByTimestamp(TraderQuoteType type, Da
6970
{
7071
var scopedLogger = this.logger.CreateScopedLogger(nameof(this.GetQuotesByTimestamp), string.Empty);
7172
var fromO = from.HasValue ?
72-
new DateTimeOffset(from.Value) :
73+
from.Value.ToSafeDateTimeOffset() :
7374
DateTimeOffset.MinValue;
7475
var toO = to.HasValue ?
75-
new DateTimeOffset(to.Value) :
76+
to.Value.ToSafeDateTimeOffset() :
7677
DateTimeOffset.Now;
7778
scopedLogger.LogDebug($"Retrieving all quotes by timestamp between [{fromO}] and [{toO}]");
7879
var items = this.collection.FindAll(t => t.TimeStamp >= fromO && t.TimeStamp <= toO && t.TraderQuoteType == (int)type);
@@ -84,10 +85,10 @@ public IEnumerable<TraderQuoteDTO> GetQuotesByInsertionTime(TraderQuoteType type
8485
{
8586
var scopedLogger = this.logger.CreateScopedLogger(nameof(this.GetQuotesByInsertionTime), string.Empty);
8687
var fromO = from.HasValue ?
87-
new DateTimeOffset(from.Value) :
88+
from.Value.ToSafeDateTimeOffset() :
8889
DateTimeOffset.MinValue;
8990
var toO = to.HasValue ?
90-
new DateTimeOffset(to.Value) :
91+
to.Value.ToSafeDateTimeOffset() :
9192
DateTimeOffset.Now;
9293
scopedLogger.LogDebug($"Retrieving all quotes by insertion time between [{fromO}] and [{toO}]");
9394
var items = this.collection.FindAll(t => t.InsertionTime >= fromO && t.InsertionTime <= toO && t.TraderQuoteType == (int)type);

Daybreak/Services/TradeChat/TradeAlertingService.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using Daybreak.Services.Notifications;
55
using Daybreak.Services.TradeChat.Models;
66
using Daybreak.Services.TradeChat.Notifications;
7+
using Daybreak.Utils;
78
using Microsoft.Extensions.Logging;
89
using Newtonsoft.Json;
910
using System;
@@ -105,7 +106,7 @@ public void OnStartup()
105106
private async void StartAlertingService(CancellationToken cancellationToken)
106107
{
107108
var lastCheckTime = this.options.Value.LastCheckTime;
108-
var timeSinceLastCheckTime = DateTimeOffset.UtcNow - lastCheckTime;
109+
var timeSinceLastCheckTime = DateTimeOffset.UtcNow - lastCheckTime.ToSafeDateTimeOffset();
109110
if (timeSinceLastCheckTime > this.options.Value.MaxLookbackPeriod)
110111
{
111112
timeSinceLastCheckTime = this.options.Value.MaxLookbackPeriod;
@@ -146,7 +147,7 @@ private async Task CheckLiveTrades<T>(ITradeChatService<T> tradeChatService, Tra
146147
{
147148
var traderMessageDTO = new TraderMessageDTO
148149
{
149-
Timestamp = message.Timestamp,
150+
Timestamp = message.Timestamp.ToSafeDateTimeOffset(),
150151
Id = message.Timestamp.Ticks,
151152
Message = message.Message,
152153
Sender = message.Sender,
@@ -274,7 +275,7 @@ private static bool CheckMatch(string toCheck, string toMatch, bool isRegex)
274275
}
275276
else
276277
{
277-
return toCheck.ToLower().Contains(toMatch.ToLower());
278+
return toCheck.Contains(toMatch, StringComparison.CurrentCultureIgnoreCase);
278279
}
279280
}
280281

@@ -284,7 +285,7 @@ private static async Task<IEnumerable<TraderMessageDTO>> GetTraderMessages<T>(IT
284285
var trades = await tradeChatService.GetLatestTrades(cancellationToken);
285286
var orderedTrades = trades.OrderBy(t => t.Timestamp).Select(t => new TraderMessageDTO
286287
{
287-
Timestamp = t.Timestamp,
288+
Timestamp = t.Timestamp.ToSafeDateTimeOffset(),
288289
Id = t.Timestamp.Ticks,
289290
Message = t.Message,
290291
Sender = t.Sender,
@@ -308,7 +309,7 @@ private static async Task<IEnumerable<TraderMessageDTO>> GetTraderMessagesSince<
308309
var trades = await tradeChatService.GetLatestTrades(cancellationToken, since);
309310
var orderedTrades = trades.OrderBy(t => t.Timestamp).Select(t => new TraderMessageDTO
310311
{
311-
Timestamp = t.Timestamp,
312+
Timestamp = t.Timestamp.ToSafeDateTimeOffset(),
312313
Id = t.Timestamp.Ticks,
313314
Message = t.Message,
314315
Sender = t.Sender,

Daybreak/Services/TradeChat/TraderQuoteService.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using Daybreak.Models.Guildwars;
33
using Daybreak.Models.Trade;
44
using Daybreak.Services.TradeChat.Models;
5+
using Daybreak.Utils;
56
using Microsoft.Extensions.Logging;
67
using Newtonsoft.Json;
78
using System;
@@ -121,7 +122,7 @@ private async Task<IEnumerable<TraderQuote>> GetSellQuotesInternal(CancellationT
121122
{
122123
var buyQuotes = await this.FetchBuyQuotesInternal(cancellationToken);
123124
var sellQuotes = await this.FetchSellQuotesInternal(cancellationToken);
124-
var insertionTime = DateTime.UtcNow;
125+
var insertionTime = DateTimeOffset.UtcNow;
125126
this.priceHistoryDatabase.AddTraderQuotes(buyQuotes
126127
.Select(
127128
quote => new TraderQuoteDTO
@@ -130,7 +131,7 @@ private async Task<IEnumerable<TraderQuote>> GetSellQuotesInternal(CancellationT
130131
ItemId = quote.Item?.Id ?? 0,
131132
ModifiersHash = quote.Item?.Modifiers is null ? string.Empty : this.itemHashService.ComputeHash(quote.Item),
132133
InsertionTime = insertionTime,
133-
TimeStamp = quote.Timestamp ?? insertionTime,
134+
TimeStamp = quote.Timestamp.ToSafeDateTimeOffset() ?? insertionTime,
134135
TraderQuoteType = (int)TraderQuoteType.Buy
135136
}));
136137

@@ -142,11 +143,11 @@ private async Task<IEnumerable<TraderQuote>> GetSellQuotesInternal(CancellationT
142143
ItemId = quote.Item?.Id ?? 0,
143144
ModifiersHash = quote.Item?.Modifiers is null ? string.Empty : this.itemHashService.ComputeHash(quote.Item),
144145
InsertionTime = insertionTime,
145-
TimeStamp = quote.Timestamp ?? insertionTime,
146+
TimeStamp = quote.Timestamp.ToSafeDateTimeOffset() ?? insertionTime,
146147
TraderQuoteType = (int)TraderQuoteType.Sell
147148
}));
148149

149-
this.options.Value.LastCheckTime = insertionTime;
150+
this.options.Value.LastCheckTime = insertionTime.UtcDateTime;
150151
this.options.UpdateOption();
151152
return (buyQuotes, sellQuotes);
152153
}

Daybreak/Utils/DateTimeExtensions.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using System;
2+
3+
namespace Daybreak.Utils;
4+
5+
public static class DateTimeExtensions
6+
{
7+
/// <summary>
8+
/// Converts <see cref="DateTime"/> to <see cref="DateTimeOffset"/> safely and clamps the values between <see cref="DateTimeOffset.MinValue"/> and <see cref="DateTimeOffset.MaxValue"/>
9+
/// </summary>
10+
public static DateTimeOffset ToSafeDateTimeOffset(this DateTime dateTime)
11+
{
12+
var utcDateTime = DateTime.SpecifyKind(dateTime, DateTimeKind.Utc);
13+
return utcDateTime.ToUniversalTime() <= DateTimeOffset.MinValue.UtcDateTime
14+
? DateTimeOffset.MinValue
15+
: utcDateTime.ToUniversalTime() >= DateTimeOffset.MaxValue.UtcDateTime
16+
? DateTimeOffset.MaxValue
17+
: new DateTimeOffset(utcDateTime);
18+
}
19+
20+
/// <summary>
21+
/// Converts <see cref="DateTime"/> to <see cref="DateTimeOffset"/> safely and clamps the values between <see cref="DateTimeOffset.MinValue"/> and <see cref="DateTimeOffset.MaxValue"/>
22+
/// </summary>
23+
public static DateTimeOffset? ToSafeDateTimeOffset(this DateTime? dateTime)
24+
{
25+
if (!dateTime.HasValue)
26+
{
27+
return default;
28+
}
29+
30+
return dateTime.Value.ToSafeDateTimeOffset();
31+
}
32+
}

0 commit comments

Comments
 (0)