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;
237 var time = localDateTime;
241 if (localDateTime == nextMarketOpen)
243 return localDateTime;
247 for (
int i = 0; i < 7; i++)
249 foreach(var segment
in marketHours.Segments.Reverse())
251 if ((time.Date + segment.Start <= localDateTime) &&
255 var timeOfDay = time.Date + segment.Start;
263 time = time.AddDays(-1);
278 var time = localDateTime;
279 var oneWeekLater = localDateTime.Date.AddDays(15);
281 var lastDay = time.Date.AddDays(-1);
283 var lastDaySegment = lastDayMarketHours.Segments.LastOrDefault();
287 if (!marketHours.IsClosedAllDay && !_holidays.Contains(time.Date.Ticks))
289 var marketOpenTimeOfDay = marketHours.GetMarketOpen(time.TimeOfDay, extendedMarketHours, lastDaySegment?.End);
290 if (marketOpenTimeOfDay.HasValue)
292 var marketOpen = time.Date + marketOpenTimeOfDay.Value;
293 if (localDateTime < marketOpen)
302 if (_earlyCloses.ContainsKey(time.Date))
304 lastDaySegment =
null;
308 lastDaySegment = marketHours.Segments.LastOrDefault();
313 lastDaySegment =
null;
318 while (time < oneWeekLater);
331 var time = localDateTime;
332 var oneWeekLater = localDateTime.Date.AddDays(15);
336 if (!marketHours.IsClosedAllDay && !_holidays.Contains(time.Date.Ticks))
342 var nextSegment = GetNextOrPreviousSegment(time, isNextDay:
true);
343 var marketCloseTimeOfDay = marketHours.GetMarketClose(time.TimeOfDay, extendedMarketHours, nextSegment?.Start);
344 if (marketCloseTimeOfDay.HasValue)
346 var marketClose = time.Date + marketCloseTimeOfDay.Value;
347 if (localDateTime < marketClose)
356 while (time < oneWeekLater);
369 var nextOrPrevious = isNextDay ? 1 : -1;
370 var nextOrPreviousDay = time.Date.AddDays(nextOrPrevious);
371 if (_earlyCloses.ContainsKey(nextOrPreviousDay.Date))
377 return isNextDay ? segments.FirstOrDefault() : segments.LastOrDefault();
384 private bool CheckIsMarketAlwaysOpen()
386 LocalMarketHours marketHours =
null;
387 for (var i = 0; i < daysOfWeek.Count; i++)
389 var day = daysOfWeek[i];
392 case DayOfWeek.Sunday:
393 marketHours = _sunday;
395 case DayOfWeek.Monday:
396 marketHours = _monday;
398 case DayOfWeek.Tuesday:
399 marketHours = _tuesday;
401 case DayOfWeek.Wednesday:
402 marketHours = _wednesday;
404 case DayOfWeek.Thursday:
405 marketHours = _thursday;
407 case DayOfWeek.Friday:
408 marketHours = _friday;
410 case DayOfWeek.Saturday:
411 marketHours = _saturday;
415 if (!marketHours.IsOpenAllDay)
428 private void SetMarketHoursForDay(DayOfWeek dayOfWeek, out LocalMarketHours localMarketHoursForDay)
430 if (!_openHoursByDay.TryGetValue(dayOfWeek, out localMarketHoursForDay))
433 _openHoursByDay[dayOfWeek] = localMarketHoursForDay = LocalMarketHours.
ClosedAllDay(dayOfWeek);
449 if (_holidays.Contains(localDateTime.Date.Ticks))
455 switch (localDateTime.DayOfWeek)
457 case DayOfWeek.Sunday:
458 marketHours = _sunday;
460 case DayOfWeek.Monday:
461 marketHours = _monday;
463 case DayOfWeek.Tuesday:
464 marketHours = _tuesday;
466 case DayOfWeek.Wednesday:
467 marketHours = _wednesday;
469 case DayOfWeek.Thursday:
470 marketHours = _thursday;
472 case DayOfWeek.Friday:
473 marketHours = _friday;
475 case DayOfWeek.Saturday:
476 marketHours = _saturday;
479 throw new ArgumentOutOfRangeException(nameof(localDateTime), localDateTime,
null);
482 var hasEarlyClose = _earlyCloses.TryGetValue(localDateTime.Date, out var earlyCloseTime);
483 var hasLateOpen = _lateOpens.TryGetValue(localDateTime.Date, out var lateOpenTime);
484 if (!hasEarlyClose && !hasLateOpen)
489 IReadOnlyList<MarketHoursSegment> marketHoursSegments = marketHours.
Segments;
494 List<MarketHoursSegment> segmentsEarlyClose =
null;
497 var index = marketHoursSegments.Count;
499 for (var i = 0; i < marketHoursSegments.Count; i++)
501 var segment = marketHoursSegments[i];
502 if (segment.Start <= earlyCloseTime && earlyCloseTime <= segment.End)
508 else if (earlyCloseTime < segment.Start)
516 segmentsEarlyClose =
new List<MarketHoursSegment>(marketHoursSegments.Take(index));
517 if (newSegment !=
null)
519 segmentsEarlyClose.Add(newSegment);
527 if (segmentsEarlyClose !=
null && (!hasLateOpen || earlyCloseTime >= lateOpenTime))
529 marketHoursSegments = segmentsEarlyClose;
535 List<MarketHoursSegment> segmentsLateOpen =
null;
539 segmentsLateOpen =
new List<MarketHoursSegment>();
540 for(var i = 0; i < marketHoursSegments.Count; i++)
542 var segment = marketHoursSegments[i];
543 if (segment.Start <= lateOpenTime && lateOpenTime <= segment.End)
545 segmentsLateOpen.Add(
new (segment.State, lateOpenTime, segment.End));
549 else if (lateOpenTime < segment.Start)
556 segmentsLateOpen.AddRange(marketHoursSegments.TakeLast(marketHoursSegments.Count - index));
557 marketHoursSegments = segmentsLateOpen;
562 if (segmentsEarlyClose !=
null && hasLateOpen && earlyCloseTime <= lateOpenTime)
564 segmentsEarlyClose.AddRange(segmentsLateOpen);
565 marketHoursSegments = segmentsEarlyClose;
578 localDate = localDate.AddDays(-1);
581 localDate = localDate.AddDays(-1);
594 date = date.AddDays(1);
597 date = date.AddDays(1);
614 _holidays = other._holidays;
615 _earlyCloses = other._earlyCloses;
616 _lateOpens = other._lateOpens;
617 _sunday = other._sunday;
618 _monday = other._monday;
619 _tuesday = other._tuesday;
620 _wednesday = other._wednesday;
621 _thursday = other._thursday;
622 _friday = other._friday;
623 _saturday = other._saturday;
624 _openHoursByDay = other._openHoursByDay;