17 using System.Collections.Generic;
19 using System.Runtime.CompilerServices;
35 private HashSet<long> _holidays;
36 private HashSet<long> _bankHolidays;
37 private IReadOnlyDictionary<DateTime, TimeSpan> _earlyCloses;
38 private IReadOnlyDictionary<DateTime, TimeSpan> _lateOpens;
48 private Dictionary<DayOfWeek, LocalMarketHours> _openHoursByDay;
49 private static List<DayOfWeek> daysOfWeek =
new List<DayOfWeek>() {
62 public DateTimeZone
TimeZone {
get;
private set; }
69 get {
return _holidays.ToHashSet(x =>
new DateTime(x)); }
79 get {
return _bankHolidays.ToHashSet(x =>
new DateTime(x)); }
90 public IReadOnlyDictionary<DayOfWeek, LocalMarketHours>
MarketHours => _openHoursByDay;
95 public IReadOnlyDictionary<DateTime, TimeSpan>
EarlyCloses => _earlyCloses;
100 public IReadOnlyDictionary<DateTime, TimeSpan>
LateOpens => _lateOpens;
120 var dayOfWeeks = Enum.GetValues(typeof (DayOfWeek)).OfType<DayOfWeek>();
122 Enumerable.Empty<DateTime>(),
124 new Dictionary<DateTime, TimeSpan>(),
125 new Dictionary<DateTime, TimeSpan>()
138 DateTimeZone timeZone,
139 IEnumerable<DateTime> holidayDates,
140 Dictionary<DayOfWeek, LocalMarketHours> marketHoursForEachDayOfWeek,
141 IReadOnlyDictionary<DateTime, TimeSpan> earlyCloses,
142 IReadOnlyDictionary<DateTime, TimeSpan> lateOpens,
143 IEnumerable<DateTime> bankHolidayDates =
null)
146 _holidays = holidayDates.Select(x => x.Date.Ticks).ToHashSet();
147 _bankHolidays = (bankHolidayDates ?? Enumerable.Empty<DateTime>()).Select(x => x.Date.Ticks).ToHashSet();
148 _earlyCloses = earlyCloses;
149 _lateOpens = lateOpens;
150 _openHoursByDay = marketHoursForEachDayOfWeek;
152 SetMarketHoursForDay(DayOfWeek.Sunday, out _sunday);
153 SetMarketHoursForDay(DayOfWeek.Monday, out _monday);
154 SetMarketHoursForDay(DayOfWeek.Tuesday, out _tuesday);
155 SetMarketHoursForDay(DayOfWeek.Wednesday, out _wednesday);
156 SetMarketHoursForDay(DayOfWeek.Thursday, out _thursday);
157 SetMarketHoursForDay(DayOfWeek.Friday, out _friday);
158 SetMarketHoursForDay(DayOfWeek.Saturday, out _saturday);
162 .OrderByDescending(grp => grp.Count())
163 .ThenByDescending(grp => grp.Key)
175 public bool IsOpen(DateTime localDateTime,
bool extendedMarketHours)
187 [MethodImpl(MethodImplOptions.AggressiveInlining)]
188 public bool IsOpen(DateTime startLocalDateTime, DateTime endLocalDateTime,
bool extendedMarketHours)
190 if (startLocalDateTime == endLocalDateTime)
193 return IsOpen(startLocalDateTime, extendedMarketHours);
197 var start = startLocalDateTime;
198 var end =
new DateTime(Math.Min(endLocalDateTime.Ticks, start.Date.Ticks +
Time.
OneDay.Ticks - 1));
201 if (!_holidays.Contains(start.Date.Ticks))
205 if (marketHours.IsOpen(start.TimeOfDay, end.TimeOfDay, extendedMarketHours))
211 start = start.Date.AddDays(1);
212 end =
new DateTime(Math.Min(endLocalDateTime.Ticks, end.Ticks +
Time.
OneDay.Ticks));
225 public bool IsDateOpen(DateTime localDateTime,
bool extendedMarketHours =
false)
228 if (marketHours.IsClosedAllDay)
234 if (marketHours.MarketDuration == TimeSpan.Zero)
237 return extendedMarketHours;
272 var time = localDateTime;
276 if (localDateTime == nextMarketOpen)
278 return localDateTime;
282 for (
int i = 0; i < 7; i++)
284 DateTime? potentialResult =
null;
285 foreach(var segment
in marketHours.Segments.Reverse())
287 if ((time.Date + segment.Start <= localDateTime) &&
290 var timeOfDay = time.Date + segment.Start;
293 potentialResult = timeOfDay;
296 else if (
GetNextMarketOpen(timeOfDay.AddTicks(-1), extendedMarketHours) == timeOfDay)
303 if (potentialResult.HasValue)
305 return potentialResult.Value;
308 time = time.AddDays(-1);
323 var time = localDateTime;
324 var oneWeekLater = localDateTime.Date.AddDays(15);
326 var lastDay = time.Date.AddDays(-1);
328 var lastDaySegment = lastDayMarketHours.Segments.LastOrDefault();
332 if (!marketHours.IsClosedAllDay && !_holidays.Contains(time.Date.Ticks))
334 var marketOpenTimeOfDay = marketHours.GetMarketOpen(time.TimeOfDay, extendedMarketHours, lastDaySegment?.End);
335 if (marketOpenTimeOfDay.HasValue)
337 var marketOpen = time.Date + marketOpenTimeOfDay.Value;
338 if (localDateTime < marketOpen)
347 if (_earlyCloses.ContainsKey(time.Date))
349 lastDaySegment =
null;
353 lastDaySegment = marketHours.Segments.LastOrDefault();
358 lastDaySegment =
null;
363 while (time < oneWeekLater);
399 var time = localDateTime;
400 var oneWeekLater = localDateTime.Date.AddDays(15);
404 if (!marketHours.IsClosedAllDay && !_holidays.Contains(time.Date.Ticks))
410 var nextSegment = GetNextOrPreviousSegment(time, isNextDay:
true);
411 var marketCloseTimeOfDay = marketHours.GetMarketClose(time.TimeOfDay, extendedMarketHours, lastClose, nextSegment?.Start);
412 if (marketCloseTimeOfDay.HasValue)
414 var marketClose = time.Date + marketCloseTimeOfDay.Value;
415 if (localDateTime < marketClose)
424 while (time < oneWeekLater);
437 var nextOrPrevious = isNextDay ? 1 : -1;
438 var nextOrPreviousDay = time.Date.AddDays(nextOrPrevious);
439 if (_earlyCloses.ContainsKey(nextOrPreviousDay.Date))
445 return isNextDay ? segments.FirstOrDefault() : segments.LastOrDefault();
452 private bool CheckIsMarketAlwaysOpen()
454 LocalMarketHours marketHours =
null;
455 for (var i = 0; i < daysOfWeek.Count; i++)
457 var day = daysOfWeek[i];
460 case DayOfWeek.Sunday:
461 marketHours = _sunday;
463 case DayOfWeek.Monday:
464 marketHours = _monday;
466 case DayOfWeek.Tuesday:
467 marketHours = _tuesday;
469 case DayOfWeek.Wednesday:
470 marketHours = _wednesday;
472 case DayOfWeek.Thursday:
473 marketHours = _thursday;
475 case DayOfWeek.Friday:
476 marketHours = _friday;
478 case DayOfWeek.Saturday:
479 marketHours = _saturday;
483 if (!marketHours.IsOpenAllDay)
496 private void SetMarketHoursForDay(DayOfWeek dayOfWeek, out LocalMarketHours localMarketHoursForDay)
498 if (!_openHoursByDay.TryGetValue(dayOfWeek, out localMarketHoursForDay))
501 _openHoursByDay[dayOfWeek] = localMarketHoursForDay = LocalMarketHours.
ClosedAllDay(dayOfWeek);
517 if (_holidays.Contains(localDateTime.Date.Ticks))
523 switch (localDateTime.DayOfWeek)
525 case DayOfWeek.Sunday:
526 marketHours = _sunday;
528 case DayOfWeek.Monday:
529 marketHours = _monday;
531 case DayOfWeek.Tuesday:
532 marketHours = _tuesday;
534 case DayOfWeek.Wednesday:
535 marketHours = _wednesday;
537 case DayOfWeek.Thursday:
538 marketHours = _thursday;
540 case DayOfWeek.Friday:
541 marketHours = _friday;
543 case DayOfWeek.Saturday:
544 marketHours = _saturday;
547 throw new ArgumentOutOfRangeException(nameof(localDateTime), localDateTime,
null);
550 var hasEarlyClose = _earlyCloses.TryGetValue(localDateTime.Date, out var earlyCloseTime);
551 var hasLateOpen = _lateOpens.TryGetValue(localDateTime.Date, out var lateOpenTime);
552 if (!hasEarlyClose && !hasLateOpen)
557 IReadOnlyList<MarketHoursSegment> marketHoursSegments = marketHours.
Segments;
562 List<MarketHoursSegment> segmentsEarlyClose =
null;
565 var index = marketHoursSegments.Count;
567 for (var i = 0; i < marketHoursSegments.Count; i++)
569 var segment = marketHoursSegments[i];
570 if (segment.Start <= earlyCloseTime && earlyCloseTime <= segment.End)
576 else if (earlyCloseTime < segment.Start)
584 segmentsEarlyClose =
new List<MarketHoursSegment>(marketHoursSegments.Take(index));
585 if (newSegment !=
null)
587 segmentsEarlyClose.Add(newSegment);
595 if (segmentsEarlyClose !=
null && (!hasLateOpen || earlyCloseTime >= lateOpenTime))
597 marketHoursSegments = segmentsEarlyClose;
603 List<MarketHoursSegment> segmentsLateOpen =
null;
607 segmentsLateOpen =
new List<MarketHoursSegment>();
608 for(var i = 0; i < marketHoursSegments.Count; i++)
610 var segment = marketHoursSegments[i];
611 if (segment.Start <= lateOpenTime && lateOpenTime <= segment.End)
613 segmentsLateOpen.Add(
new (segment.State, lateOpenTime, segment.End));
617 else if (lateOpenTime < segment.Start)
624 segmentsLateOpen.AddRange(marketHoursSegments.TakeLast(marketHoursSegments.Count - index));
625 marketHoursSegments = segmentsLateOpen;
630 if (segmentsEarlyClose !=
null && hasLateOpen && earlyCloseTime <= lateOpenTime)
632 segmentsEarlyClose.AddRange(segmentsLateOpen);
633 marketHoursSegments = segmentsEarlyClose;
646 localDate = localDate.AddDays(-1);
649 localDate = localDate.AddDays(-1);
662 date = date.AddDays(1);
665 date = date.AddDays(1);
682 _holidays = other._holidays;
683 _earlyCloses = other._earlyCloses;
684 _lateOpens = other._lateOpens;
685 _sunday = other._sunday;
686 _monday = other._monday;
687 _tuesday = other._tuesday;
688 _wednesday = other._wednesday;
689 _thursday = other._thursday;
690 _friday = other._friday;
691 _saturday = other._saturday;
692 _openHoursByDay = other._openHoursByDay;