17 using System.Collections.Generic;
19 using System.Runtime.CompilerServices;
35 private readonly HashSet<long> _holidays;
36 private readonly IReadOnlyDictionary<DateTime, TimeSpan> _earlyCloses;
37 private readonly IReadOnlyDictionary<DateTime, TimeSpan> _lateOpens;
47 private readonly Dictionary<DayOfWeek, LocalMarketHours> _openHoursByDay;
48 private static List<DayOfWeek> daysOfWeek =
new List<DayOfWeek>() {
68 get {
return _holidays.ToHashSet(x =>
new DateTime(x)); }
74 public IReadOnlyDictionary<DayOfWeek, LocalMarketHours>
MarketHours => _openHoursByDay;
79 public IReadOnlyDictionary<DateTime, TimeSpan>
EarlyCloses => _earlyCloses;
84 public IReadOnlyDictionary<DateTime, TimeSpan>
LateOpens => _lateOpens;
104 var dayOfWeeks = Enum.GetValues(typeof (DayOfWeek)).OfType<DayOfWeek>();
106 Enumerable.Empty<DateTime>(),
108 new Dictionary<DateTime, TimeSpan>(),
109 new Dictionary<DateTime, TimeSpan>()
122 DateTimeZone timeZone,
123 IEnumerable<DateTime> holidayDates,
124 Dictionary<DayOfWeek, LocalMarketHours> marketHoursForEachDayOfWeek,
125 IReadOnlyDictionary<DateTime, TimeSpan> earlyCloses,
126 IReadOnlyDictionary<DateTime, TimeSpan> lateOpens)
129 _holidays = holidayDates.Select(x => x.Date.Ticks).ToHashSet();
130 _earlyCloses = earlyCloses;
131 _lateOpens = lateOpens;
132 _openHoursByDay = marketHoursForEachDayOfWeek;
134 SetMarketHoursForDay(DayOfWeek.Sunday, out _sunday);
135 SetMarketHoursForDay(DayOfWeek.Monday, out _monday);
136 SetMarketHoursForDay(DayOfWeek.Tuesday, out _tuesday);
137 SetMarketHoursForDay(DayOfWeek.Wednesday, out _wednesday);
138 SetMarketHoursForDay(DayOfWeek.Thursday, out _thursday);
139 SetMarketHoursForDay(DayOfWeek.Friday, out _friday);
140 SetMarketHoursForDay(DayOfWeek.Saturday, out _saturday);
144 .OrderByDescending(grp => grp.Count())
145 .ThenByDescending(grp => grp.Key)
157 public bool IsOpen(DateTime localDateTime,
bool extendedMarketHours)
159 if (_holidays.Contains(localDateTime.Date.Ticks) || IsTimeAfterEarlyClose(localDateTime) || IsTimeBeforeLateOpen(localDateTime))
174 [MethodImpl(MethodImplOptions.AggressiveInlining)]
175 public bool IsOpen(DateTime startLocalDateTime, DateTime endLocalDateTime,
bool extendedMarketHours)
177 if (startLocalDateTime == endLocalDateTime)
180 return IsOpen(startLocalDateTime, extendedMarketHours);
184 var start = startLocalDateTime;
185 var end =
new DateTime(Math.Min(endLocalDateTime.Ticks, start.Date.Ticks +
Time.
OneDay.Ticks - 1));
188 if (!_holidays.Contains(start.Date.Ticks))
192 if (marketHours.IsOpen(start.TimeOfDay, end.TimeOfDay, extendedMarketHours))
198 start = start.Date.AddDays(1);
199 end =
new DateTime(Math.Min(endLocalDateTime.Ticks, end.Ticks +
Time.
OneDay.Ticks));
214 if (marketHours.IsClosedAllDay)
221 return !_holidays.Contains(localDateTime.Date.Ticks);
232 var time = localDateTime;
236 if (localDateTime == nextMarketOpen)
238 return localDateTime;
242 for (
int i = 0; i < 7; i++)
244 foreach(var segment
in marketHours.Segments.Reverse())
246 if ((time.Date + segment.Start <= localDateTime) &&
250 var timeOfDay = time.Date + segment.Start;
258 time = time.AddDays(-1);
273 var time = localDateTime;
274 var oneWeekLater = localDateTime.Date.AddDays(15);
276 var lastDay = time.Date.AddDays(-1);
278 var lastDaySegment = lastDayMarketHours.Segments.LastOrDefault();
282 if (!marketHours.IsClosedAllDay && !_holidays.Contains(time.Date.Ticks))
284 var marketOpenTimeOfDay = marketHours.GetMarketOpen(time.TimeOfDay, extendedMarketHours, lastDaySegment?.End);
285 if (marketOpenTimeOfDay.HasValue)
287 var marketOpen = time.Date + marketOpenTimeOfDay.Value;
288 if (localDateTime < marketOpen)
297 if (_earlyCloses.ContainsKey(time.Date))
299 lastDaySegment =
null;
303 lastDaySegment = marketHours.Segments.LastOrDefault();
308 lastDaySegment =
null;
313 while (time < oneWeekLater);
326 var time = localDateTime;
327 var oneWeekLater = localDateTime.Date.AddDays(15);
331 if (!marketHours.IsClosedAllDay && !_holidays.Contains(time.Date.Ticks))
337 var nextSegment = GetNextOrPreviousSegment(time, isNextDay:
true);
338 var marketCloseTimeOfDay = marketHours.GetMarketClose(time.TimeOfDay, extendedMarketHours, nextSegment?.Start);
339 if (marketCloseTimeOfDay.HasValue)
341 var marketClose = time.Date + marketCloseTimeOfDay.Value;
342 if (localDateTime < marketClose)
351 while (time < oneWeekLater);
364 var nextOrPrevious = isNextDay ? 1 : -1;
365 var nextOrPreviousDay = time.Date.AddDays(nextOrPrevious);
366 if (_earlyCloses.ContainsKey(nextOrPreviousDay.Date))
372 return isNextDay ? segments.FirstOrDefault() : segments.LastOrDefault();
379 private bool CheckIsMarketAlwaysOpen()
381 LocalMarketHours marketHours =
null;
382 for (var i = 0; i < daysOfWeek.Count; i++)
384 var day = daysOfWeek[i];
387 case DayOfWeek.Sunday:
388 marketHours = _sunday;
390 case DayOfWeek.Monday:
391 marketHours = _monday;
393 case DayOfWeek.Tuesday:
394 marketHours = _tuesday;
396 case DayOfWeek.Wednesday:
397 marketHours = _wednesday;
399 case DayOfWeek.Thursday:
400 marketHours = _thursday;
402 case DayOfWeek.Friday:
403 marketHours = _friday;
405 case DayOfWeek.Saturday:
406 marketHours = _saturday;
410 if (!marketHours.IsOpenAllDay)
423 private void SetMarketHoursForDay(DayOfWeek dayOfWeek, out LocalMarketHours localMarketHoursForDay)
425 if (!_openHoursByDay.TryGetValue(dayOfWeek, out localMarketHoursForDay))
428 _openHoursByDay[dayOfWeek] = localMarketHoursForDay = LocalMarketHours.ClosedAllDay(dayOfWeek);
439 switch (localDateTime.DayOfWeek)
441 case DayOfWeek.Sunday:
442 marketHours = _sunday;
444 case DayOfWeek.Monday:
445 marketHours = _monday;
447 case DayOfWeek.Tuesday:
448 marketHours = _tuesday;
450 case DayOfWeek.Wednesday:
451 marketHours = _wednesday;
453 case DayOfWeek.Thursday:
454 marketHours = _thursday;
456 case DayOfWeek.Friday:
457 marketHours = _friday;
459 case DayOfWeek.Saturday:
460 marketHours = _saturday;
463 throw new ArgumentOutOfRangeException(nameof(localDateTime), localDateTime,
null);
469 if (_earlyCloses.TryGetValue(localDateTime.Date, out var earlyCloseTime))
471 var index = marketHours.
Segments.Count;
473 for (var i = 0; i < marketHours.
Segments.Count; i++)
475 var segment = marketHours.
Segments[i];
476 if (segment.Start <= earlyCloseTime && earlyCloseTime <= segment.End)
482 else if (earlyCloseTime < segment.Start)
490 var newSegments =
new List<MarketHoursSegment>(marketHours.
Segments.Take(index));
491 if (newSegment !=
null)
493 newSegments.Add(newSegment);
501 if (_lateOpens.TryGetValue(localDateTime.Date, out var lateOpenTime))
504 var newSegments =
new List<MarketHoursSegment>();
505 for(var i = 0; i < marketHours.
Segments.Count; i++)
507 var segment = marketHours.
Segments[i];
508 if (segment.Start <= lateOpenTime && lateOpenTime <= segment.End)
510 newSegments.Add(
new (segment.State, lateOpenTime, segment.End));
514 else if (lateOpenTime < segment.Start)
521 newSegments.AddRange(marketHours.
Segments.TakeLast(marketHours.
Segments.Count - index));
531 private bool IsTimeAfterEarlyClose(DateTime localDateTime)
533 TimeSpan earlyCloseTime;
534 return _earlyCloses.TryGetValue(localDateTime.Date, out earlyCloseTime) && localDateTime.TimeOfDay >= earlyCloseTime;
540 private bool IsTimeBeforeLateOpen(DateTime localDateTime)
542 TimeSpan lateOpenTime;
543 return _lateOpens.TryGetValue(localDateTime.Date, out lateOpenTime) && localDateTime.TimeOfDay <= lateOpenTime;
553 localDate = localDate.AddDays(-1);
556 localDate = localDate.AddDays(-1);
569 date = date.AddDays(1);
572 date = date.AddDays(1);