Skip to content

Commit

Permalink
Merge pull request connamara#853 from gbirchmeier/weekdays-i844
Browse files Browse the repository at this point in the history
implement Weekdays config setting
  • Loading branch information
gbirchmeier authored May 23, 2024
2 parents 37ba497 + 6b086a4 commit c609125
Show file tree
Hide file tree
Showing 6 changed files with 459 additions and 294 deletions.
113 changes: 85 additions & 28 deletions QuickFIXn/SessionSchedule.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#nullable enable
using System;
using System.Collections.Generic;

namespace QuickFix
{
Expand All @@ -12,6 +13,9 @@ public class SessionSchedule
public DayOfWeek? StartDay { get; }
public DayOfWeek? EndDay { get; }

private readonly bool _isWeekdaysSession;
private readonly HashSet<DayOfWeek> _weekdays;

public bool NonStopSession { get; }

public bool UseLocalTime { get; }
Expand All @@ -28,9 +32,7 @@ public class SessionSchedule
public bool IsNewSession(DateTime oldtimeUtc, DateTime testtimeUtc)
{
if (NonStopSession)
{
return false;
}

if (oldtimeUtc.Kind != DateTimeKind.Utc)
throw new ArgumentException("Only UTC time is supported", nameof(oldtimeUtc));
Expand Down Expand Up @@ -72,6 +74,9 @@ public bool IsSessionTime(DateTime utc)

DateTime adjusted = AdjustUtcDateTime(utc);

if (_isWeekdaysSession)
return CheckWeekdays(adjusted);

return WeeklySession ? CheckDay(adjusted) : CheckTime(adjusted.TimeOfDay);
}

Expand All @@ -83,7 +88,7 @@ public bool IsSessionTime(DateTime utc)
public DateTime NextEndTime(DateTime utc)
{
if (NonStopSession)
throw new NotSupportedException("NonStopSession");
throw new InvalidOperationException("NonStopSession is set; this statement should be unreachable");

TimeSpan vEndTime = EndTime ?? throw new QuickFix.ConfigError("EndTime is null");

Expand All @@ -93,7 +98,14 @@ public DateTime NextEndTime(DateTime utc)
DateTime d = AdjustUtcDateTime(utc);
DateTime end = DateTime.MinValue;

if (WeeklySession)

if (_isWeekdaysSession)
{
end = new DateTime(d.Year, d.Month, d.Day, vEndTime.Hours, vEndTime.Minutes, vEndTime.Seconds, d.Kind);
if (DateTime.Compare(d, end) > 0) // d is later than end
end = end.AddDays(1);
}
else if (WeeklySession)
{
end = new DateTime(d.Year, d.Month, d.Day, vEndTime.Hours, vEndTime.Minutes, vEndTime.Seconds, d.Kind);
while (end.DayOfWeek != EndDay)
Expand All @@ -114,12 +126,12 @@ public DateTime NextEndTime(DateTime utc)
/// <summary>
/// return true if time falls within StartTime/EndTime
/// </summary>
/// <param name="time"></param>
/// <param name="dt"></param>
/// <returns></returns>
private bool CheckDay(DateTime time)
private bool CheckDay(DateTime dt)
{
if (NonStopSession)
throw new InvalidOperationException("NonStopSession is set -- this should never be called.");
throw new InvalidOperationException("NonStopSession is set; this statement should be unreachable");

DayOfWeek vStartDay = StartDay ?? throw new QuickFix.ConfigError("StartDay is null");
DayOfWeek vEndDay = EndDay ?? throw new QuickFix.ConfigError("EndDay is null");
Expand All @@ -128,31 +140,31 @@ private bool CheckDay(DateTime time)

if (vStartDay < vEndDay)
{
if (time.DayOfWeek < vStartDay || time.DayOfWeek > vEndDay)
if (dt.DayOfWeek < vStartDay || dt.DayOfWeek > vEndDay)
return false;

if (time.DayOfWeek < vEndDay)
return (vStartDay < time.DayOfWeek) || (vStartTime.CompareTo(time.TimeOfDay) <= 0);
if (dt.DayOfWeek < vEndDay)
return (vStartDay < dt.DayOfWeek) || (vStartTime.CompareTo(dt.TimeOfDay) <= 0);

return (time.DayOfWeek < vEndDay) || (vEndTime.CompareTo(time.TimeOfDay) >= 0);
return (dt.DayOfWeek < vEndDay) || (vEndTime.CompareTo(dt.TimeOfDay) >= 0);
}

if (vEndDay < vStartDay)
{
if (vEndDay < time.DayOfWeek && time.DayOfWeek < vStartDay)
if (vEndDay < dt.DayOfWeek && dt.DayOfWeek < vStartDay)
return false;

if (time.DayOfWeek < vStartDay)
return (time.DayOfWeek < vEndDay) || (vEndTime.CompareTo(time.TimeOfDay) >= 0);
if (dt.DayOfWeek < vStartDay)
return (dt.DayOfWeek < vEndDay) || (vEndTime.CompareTo(dt.TimeOfDay) >= 0);

return (time.DayOfWeek > vStartDay) || (vStartTime.CompareTo(time.TimeOfDay) <= 0);
return (dt.DayOfWeek > vStartDay) || (vStartTime.CompareTo(dt.TimeOfDay) <= 0);
}

//start day must be same as end day
if (vStartTime >= vEndTime)
return time.DayOfWeek != vStartDay || CheckTime(time.TimeOfDay);
return dt.DayOfWeek != vStartDay || CheckTime(dt.TimeOfDay);

return time.DayOfWeek == vStartDay && CheckTime(time.TimeOfDay);
return dt.DayOfWeek == vStartDay && CheckTime(dt.TimeOfDay);
}

/// <summary>
Expand All @@ -170,29 +182,74 @@ private bool CheckTime(TimeSpan time)

if (vStartTime.CompareTo(vEndTime) < 0)
{
return (time.CompareTo(vStartTime) >= 0 &&
time.CompareTo(vEndTime) <= 0);
return time.CompareTo(vStartTime) >= 0 &&
time.CompareTo(vEndTime) <= 0;
}

if (vStartTime.CompareTo(vEndTime) > 0)
{
return time.CompareTo(vStartTime) >= 0 ||
time.CompareTo(vEndTime) <= 0;
return time.CompareTo(vStartTime) >= 0 ||
time.CompareTo(vEndTime) <= 0;
}

private bool CheckWeekdays(DateTime dt)
{
if (NonStopSession)
throw new InvalidOperationException("NonStopSession is set; this statement should be unreachable");

TimeSpan vStartTime = StartTime ?? throw new QuickFix.ConfigError("StartTime is null");
TimeSpan vEndTime = EndTime ?? throw new QuickFix.ConfigError("EndTime is null");

TimeSpan tod = dt.TimeOfDay;

// session spans midnight
if (vStartTime.CompareTo(vEndTime) < 0) {
return _weekdays.Contains(dt.DayOfWeek) &&
tod.CompareTo(vStartTime) >= 0 &&
tod.CompareTo(vEndTime) < 0;
}

return true;
// session doesn't span midnight
if (tod.CompareTo(vEndTime) >= 0 && tod.CompareTo(vStartTime) < 0)
return false;
var targetDay = tod.CompareTo(vStartTime) >= 0
? dt.DayOfWeek
: PreviousDay(dt.DayOfWeek);
return _weekdays.Contains(targetDay);
}

private DayOfWeek PreviousDay(DayOfWeek d) {
return d == DayOfWeek.Sunday
? DayOfWeek.Saturday
: d - 1;
}

/// <summary>
/// </summary>
/// <param name="settings"></param>
public SessionSchedule(QuickFix.SettingsDictionary settings)
public SessionSchedule(SettingsDictionary settings)

Check warning on line 228 in QuickFIXn/SessionSchedule.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable field '_weekdays' must contain a non-null value when exiting constructor. Consider declaring the field as nullable.

Check warning on line 228 in QuickFIXn/SessionSchedule.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable field '_weekdays' must contain a non-null value when exiting constructor. Consider declaring the field as nullable.
{
if (settings.Has(SessionSettings.NON_STOP_SESSION))
NonStopSession = settings.GetBool(SessionSettings.NON_STOP_SESSION);
if (settings.IsBoolPresentAndTrue(SessionSettings.NON_STOP_SESSION)) {
NonStopSession = true;

if (settings.Has(SessionSettings.START_DAY)
|| settings.Has(SessionSettings.END_DAY)
|| settings.Has(SessionSettings.START_TIME)
|| settings.Has(SessionSettings.END_TIME))
{
throw new ConfigError(
"NonStopSession is not compatible with StartDay/EndDay and StartTime/EndTime");
}

if (NonStopSession)
return;
}

if (settings.Has(SessionSettings.WEEKDAYS))
{
_isWeekdaysSession = true;
if (settings.Has(SessionSettings.START_DAY) || settings.Has(SessionSettings.END_DAY) )
throw new ConfigError("StartDay/EndDay are not compatible with 'Weekdays' setting");

_weekdays = settings.GetDays(SessionSettings.WEEKDAYS);
}

if (!settings.Has(SessionSettings.START_DAY) && settings.Has(SessionSettings.END_DAY))
throw new QuickFix.ConfigError("EndDay used without StartDay");
Expand Down
1 change: 1 addition & 0 deletions QuickFIXn/SessionSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public class SessionSettings
public const string TIME_ZONE = "TimeZone";
public const string START_DAY = "StartDay";
public const string END_DAY = "EndDay";
public const string WEEKDAYS = "Weekdays";
public const string START_TIME = "StartTime";
public const string END_TIME = "EndTime";
public const string HEARTBTINT = "HeartBtInt";
Expand Down
43 changes: 29 additions & 14 deletions QuickFIXn/SettingsDictionary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,25 @@ public bool IsBoolPresentAndTrue(string key) {
return Has(key) && GetBool(key);
}

public HashSet<DayOfWeek> GetDays(string key)
{
string[] weekdayNameArray = GetString(key).Split(",");
var result = new HashSet<DayOfWeek>(weekdayNameArray.Length);
foreach (var weekDayName in weekdayNameArray)
{
string abbr = weekDayName.Trim().Substring(0, 2).ToUpper();
result.Add(DeduceDay(abbr));
}

return result;
}

public DayOfWeek GetDay(string key) {
string abbr = GetString(key).Substring(0, 2).ToUpperInvariant();
return DeduceDay(GetString(key));
}

private static DayOfWeek DeduceDay(string dayStr) {
string abbr = dayStr.Substring(0, 2).ToUpperInvariant();
return abbr switch
{
"SU" => DayOfWeek.Sunday,
Expand All @@ -161,7 +178,7 @@ public DayOfWeek GetDay(string key) {
"TH" => DayOfWeek.Thursday,
"FR" => DayOfWeek.Friday,
"SA" => DayOfWeek.Saturday,
_ => throw new ConfigError($"Illegal value {GetString(key)} for {key}")
_ => throw new ArgumentException($"Cannot recognize this day: '{dayStr}'")
};
}

Expand Down Expand Up @@ -199,21 +216,19 @@ public void SetBool(string key, bool val)
SetString(key, BoolConverter.Convert(val));
}

public void SetDay(string key, DayOfWeek val)
{
switch(val)
{
case DayOfWeek.Sunday: SetString(key, "SU"); break;
case DayOfWeek.Monday: SetString(key, "MO"); break;
case DayOfWeek.Tuesday: SetString(key, "TU"); break;
case DayOfWeek.Wednesday: SetString(key, "WE"); break;
case DayOfWeek.Thursday: SetString(key, "TH"); break;
case DayOfWeek.Friday: SetString(key, "FR"); break;
case DayOfWeek.Saturday: SetString(key, "SA"); break;
default: throw new ConfigError($"Illegal value {val} for {key}");
public void SetDay(string key, DayOfWeek val) {
if (Enum.IsDefined(val)) {
SetString(key, val.ToString());
return;
}
throw new ArgumentException("Not a valid DayOfWeek value");
}

/// <summary>
/// returns true if this SettingsDictionary has a value set for this key
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public bool Has(string key)
{
return _data.ContainsKey(key.ToUpperInvariant());
Expand Down
1 change: 1 addition & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ What's New
* Also refactor the heck out of DateTimeConverter & tests: many functions renamed/deprecated
* #847 - remove setting MillisecondsInTimeStamp (gbirchmeier)
* Use TimestampPrecision instead (same as QF/j)
* #844 - implement "Weekdays" setting (MichalUssuri/gbirchmeier)

**Non-breaking changes**
* #400 - added DDTool, a C#-based codegen, and deleted Ruby-based generator (gbirchmeier)
Expand Down
Loading

0 comments on commit c609125

Please sign in to comment.