17 using System.Collections.Generic;
19 using System.Runtime.CompilerServices;
35 private HashSet<long> _holidays;
36 private IReadOnlyDictionary<DateTime, TimeSpan> _earlyCloses;
37 private IReadOnlyDictionary<DateTime, TimeSpan> _lateOpens;
47 private Dictionary<DayOfWeek, LocalMarketHours> _openHoursByDay;
48 private static List<DayOfWeek> daysOfWeek =
new List<DayOfWeek>() {
61 public DateTimeZone
TimeZone {
get;
private set; }
68 get {
return _holidays.ToHashSet(x =>
new DateTime(x)); }
79 public IReadOnlyDictionary<DayOfWeek, LocalMarketHours>
MarketHours => _openHoursByDay;
84 public IReadOnlyDictionary<DateTime, TimeSpan>
EarlyCloses => _earlyCloses;
89 public IReadOnlyDictionary<DateTime, TimeSpan>
LateOpens => _lateOpens;
109 var dayOfWeeks = Enum.GetValues(typeof (DayOfWeek)).OfType<DayOfWeek>();
111 Enumerable.Empty<DateTime>(),
113 new Dictionary<DateTime, TimeSpan>(),
114 new Dictionary<DateTime, TimeSpan>()
127 DateTimeZone timeZone,
128 IEnumerable<DateTime> holidayDates,
129 Dictionary<DayOfWeek, LocalMarketHours> marketHoursForEachDayOfWeek,
130 IReadOnlyDictionary<DateTime, TimeSpan> earlyCloses,
131 IReadOnlyDictionary<DateTime, TimeSpan> lateOpens)
134 _holidays = holidayDates.Select(x => x.Date.Ticks).ToHashSet();
135 _earlyCloses = earlyCloses;
136 _lateOpens = lateOpens;
137 _openHoursByDay = marketHoursForEachDayOfWeek;
139 SetMarketHoursForDay(DayOfWeek.Sunday, out _sunday);
140 SetMarketHoursForDay(DayOfWeek.Monday, out _monday);
141 SetMarketHoursForDay(DayOfWeek.Tuesday, out _tuesday);
142 SetMarketHoursForDay(DayOfWeek.Wednesday, out _wednesday);
143 SetMarketHoursForDay(DayOfWeek.Thursday, out _thursday);
144 SetMarketHoursForDay(DayOfWeek.Friday, out _friday);
145 SetMarketHoursForDay(DayOfWeek.Saturday, out _saturday);
149 .OrderByDescending(grp => grp.Count())
150 .ThenByDescending(grp => grp.Key)
162 public bool IsOpen(DateTime localDateTime,
bool extendedMarketHours)
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));
212 public bool IsDateOpen(DateTime localDateTime,
bool extendedMarketHours =
false)
215 if (marketHours.IsClosedAllDay)
221 if (marketHours.MarketDuration == TimeSpan.Zero)
224 return extendedMarketHours;
259 var time = localDateTime;
263 if (localDateTime == nextMarketOpen)
265 return localDateTime;
269 for (
int i = 0; i < 7; i++)
271 DateTime? potentialResult =
null;
272 foreach(var segment
in marketHours.Segments.Reverse())
274 if ((time.Date + segment.Start <= localDateTime) &&
277 var timeOfDay = time.Date + segment.Start;
280 potentialResult = timeOfDay;
283 else if (
GetNextMarketOpen(timeOfDay.AddTicks(-1), extendedMarketHours) == timeOfDay)
290 if (potentialResult.HasValue)
292 return potentialResult.Value;
295 time = time.AddDays(-1);
310 var time = localDateTime;
311 var oneWeekLater = localDateTime.Date.AddDays(15);
313 var lastDay = time.Date.AddDays(-1);
315 var lastDaySegment = lastDayMarketHours.Segments.LastOrDefault();
319 if (!marketHours.IsClosedAllDay && !_holidays.Contains(time.Date.Ticks))
321 var marketOpenTimeOfDay = marketHours.GetMarketOpen(time.TimeOfDay, extendedMarketHours, lastDaySegment?.End);
322 if (marketOpenTimeOfDay.HasValue)
324 var marketOpen = time.Date + marketOpenTimeOfDay.Value;
325 if (localDateTime < marketOpen)
334 if (_earlyCloses.ContainsKey(time.Date))
336 lastDaySegment =
null;
340 lastDaySegment = marketHours.Segments.LastOrDefault();
345 lastDaySegment =
null;
350 while (time < oneWeekLater);
386 var time = localDateTime;
387 var oneWeekLater = localDateTime.Date.AddDays(15);
391 if (!marketHours.IsClosedAllDay && !_holidays.Contains(time.Date.Ticks))
397 var nextSegment = GetNextOrPreviousSegment(time, isNextDay:
true);
398 var marketCloseTimeOfDay = marketHours.GetMarketClose(time.TimeOfDay, extendedMarketHours, lastClose, nextSegment?.Start);
399 if (marketCloseTimeOfDay.HasValue)
401 var marketClose = time.Date + marketCloseTimeOfDay.Value;
402 if (localDateTime < marketClose)
411 while (time < oneWeekLater);
424 var nextOrPrevious = isNextDay ? 1 : -1;
425 var nextOrPreviousDay = time.Date.AddDays(nextOrPrevious);
426 if (_earlyCloses.ContainsKey(nextOrPreviousDay.Date))
432 return isNextDay ? segments.FirstOrDefault() : segments.LastOrDefault();
439 private bool CheckIsMarketAlwaysOpen()
441 LocalMarketHours marketHours =
null;
442 for (var i = 0; i < daysOfWeek.Count; i++)
444 var day = daysOfWeek[i];
447 case DayOfWeek.Sunday:
448 marketHours = _sunday;
450 case DayOfWeek.Monday:
451 marketHours = _monday;
453 case DayOfWeek.Tuesday:
454 marketHours = _tuesday;
456 case DayOfWeek.Wednesday:
457 marketHours = _wednesday;
459 case DayOfWeek.Thursday:
460 marketHours = _thursday;
462 case DayOfWeek.Friday:
463 marketHours = _friday;
465 case DayOfWeek.Saturday:
466 marketHours = _saturday;
470 if (!marketHours.IsOpenAllDay)
483 private void SetMarketHoursForDay(DayOfWeek dayOfWeek, out LocalMarketHours localMarketHoursForDay)
485 if (!_openHoursByDay.TryGetValue(dayOfWeek, out localMarketHoursForDay))
488 _openHoursByDay[dayOfWeek] = localMarketHoursForDay = LocalMarketHours.
ClosedAllDay(dayOfWeek);
504 if (_holidays.Contains(localDateTime.Date.Ticks))
510 switch (localDateTime.DayOfWeek)
512 case DayOfWeek.Sunday:
513 marketHours = _sunday;
515 case DayOfWeek.Monday:
516 marketHours = _monday;
518 case DayOfWeek.Tuesday:
519 marketHours = _tuesday;
521 case DayOfWeek.Wednesday:
522 marketHours = _wednesday;
524 case DayOfWeek.Thursday:
525 marketHours = _thursday;
527 case DayOfWeek.Friday:
528 marketHours = _friday;
530 case DayOfWeek.Saturday:
531 marketHours = _saturday;
534 throw new ArgumentOutOfRangeException(nameof(localDateTime), localDateTime,
null);
537 var hasEarlyClose = _earlyCloses.TryGetValue(localDateTime.Date, out var earlyCloseTime);
538 var hasLateOpen = _lateOpens.TryGetValue(localDateTime.Date, out var lateOpenTime);
539 if (!hasEarlyClose && !hasLateOpen)
544 IReadOnlyList<MarketHoursSegment> marketHoursSegments = marketHours.
Segments;
549 List<MarketHoursSegment> segmentsEarlyClose =
null;
552 var index = marketHoursSegments.Count;
554 for (var i = 0; i < marketHoursSegments.Count; i++)
556 var segment = marketHoursSegments[i];
557 if (segment.Start <= earlyCloseTime && earlyCloseTime <= segment.End)
563 else if (earlyCloseTime < segment.Start)
571 segmentsEarlyClose =
new List<MarketHoursSegment>(marketHoursSegments.Take(index));
572 if (newSegment !=
null)
574 segmentsEarlyClose.Add(newSegment);
582 if (segmentsEarlyClose !=
null && (!hasLateOpen || earlyCloseTime >= lateOpenTime))
584 marketHoursSegments = segmentsEarlyClose;
590 List<MarketHoursSegment> segmentsLateOpen =
null;
594 segmentsLateOpen =
new List<MarketHoursSegment>();
595 for(var i = 0; i < marketHoursSegments.Count; i++)
597 var segment = marketHoursSegments[i];
598 if (segment.Start <= lateOpenTime && lateOpenTime <= segment.End)
600 segmentsLateOpen.Add(
new (segment.State, lateOpenTime, segment.End));
604 else if (lateOpenTime < segment.Start)
611 segmentsLateOpen.AddRange(marketHoursSegments.TakeLast(marketHoursSegments.Count - index));
612 marketHoursSegments = segmentsLateOpen;
617 if (segmentsEarlyClose !=
null && hasLateOpen && earlyCloseTime <= lateOpenTime)
619 segmentsEarlyClose.AddRange(segmentsLateOpen);
620 marketHoursSegments = segmentsEarlyClose;
633 localDate = localDate.AddDays(-1);
636 localDate = localDate.AddDays(-1);
649 date = date.AddDays(1);
652 date = date.AddDays(1);
669 _holidays = other._holidays;
670 _earlyCloses = other._earlyCloses;
671 _lateOpens = other._lateOpens;
672 _sunday = other._sunday;
673 _monday = other._monday;
674 _tuesday = other._tuesday;
675 _wednesday = other._wednesday;
676 _thursday = other._thursday;
677 _friday = other._friday;
678 _saturday = other._saturday;
679 _openHoursByDay = other._openHoursByDay;