Describe the bug
Calendar.addHoliday() mutates global shared state, causing all instances of the same calendar type to be affected. For example, calling addHoliday() on one TARGET instance makes that date a holiday for every other TARGET instance in the process, including instances created afterwards.
This happens because calendar constructors pass a static singleton CalendarImpl to the base class (e.g. public TARGET() : base(Impl.Singleton) {}), and the mutable addedHolidays/removedHolidays sets live on that shared CalendarImpl rather than on each Calendar instance.
I'm not sure if this is intended or expected, however for me it caused some unexpected issues as i was processing different currencies for different markets.
To Reproduce
using QLNet;
var date = new Date(15, Month.June, 2026); // A Monday — normal business day
var calendar1 = new TARGET();
var calendar2 = new TARGET();
Console.WriteLine($"calendar1.isBusinessDay({date}): {calendar1.isBusinessDay(date)}"); // True
Console.WriteLine($"calendar2.isBusinessDay({date}): {calendar2.isBusinessDay(date)}"); // True
// Add a holiday to calendar1 only
calendar1.addHoliday(date);
Console.WriteLine($"calendar1.isBusinessDay({date}): {calendar1.isBusinessDay(date)}"); // False (expected)
Console.WriteLine($"calendar2.isBusinessDay({date}): {calendar2.isBusinessDay(date)}"); // False (BUG)
// Even a brand new instance sees the holiday
var calendar3 = new TARGET();
Console.WriteLine($"calendar3.isBusinessDay({date}): {calendar3.isBusinessDay(date)}"); // False (BUG)
Output:
calendar1.isBusinessDay(6/15/2026): True
calendar2.isBusinessDay(6/15/2026): True
calendar1.isBusinessDay(6/15/2026): False
calendar2.isBusinessDay(6/15/2026): False
calendar3.isBusinessDay(6/15/2026): False
The issue affects all calendar types (TARGET, UnitedKingdom, UnitedStates, etc.) since they all use the same singleton pattern.
Expected behavior
addHoliday() on one calendar instance should not affect other instances. calendar2 and calendar3 should still return True for isBusinessDay() since addHoliday() was only called on calendar1.
Desktop:
- OS: Linux (Almalinux 9)
- .Net version: .NET 10.0
- QLNet version: 1.13.1 (also present in 1.12.0)
Additional context
The root cause is in Calendar.cs and the calendar implementation classes (e.g. TARGET.cs):
TARGET.cs:40 — public TARGET() : base(Impl.Singleton) {} passes a static singleton to the base constructor
TARGET.cs:44 — internal static readonly Impl Singleton = new(); is a single instance for the entire process
CalendarImpl at Calendar.cs:39-40 — the mutable state lives on the shared impl:
public SortedSet<Date> addedHolidays { get; set; } = new SortedSet<Date>();
public SortedSet<Date> removedHolidays { get; set; } = new SortedSet<Date>();
Calendar.addHoliday() at Calendar.cs:176-186 — writes to _impl.addedHolidays (the singleton's set)
Calendar.isBusinessDay() at Calendar.cs:116-127 — reads from the same shared set
A possible fix would be to move addedHolidays and removedHolidays from CalendarImpl to Calendar, so each instance has its own mutable state while still sharing the immutable business day rules via the singleton.
Describe the bug
Calendar.addHoliday()mutates global shared state, causing all instances of the same calendar type to be affected. For example, callingaddHoliday()on oneTARGETinstance makes that date a holiday for every otherTARGETinstance in the process, including instances created afterwards.This happens because calendar constructors pass a static singleton
CalendarImplto the base class (e.g.public TARGET() : base(Impl.Singleton) {}), and the mutableaddedHolidays/removedHolidayssets live on that sharedCalendarImplrather than on eachCalendarinstance.I'm not sure if this is intended or expected, however for me it caused some unexpected issues as i was processing different currencies for different markets.
To Reproduce
Output:
The issue affects all calendar types (
TARGET,UnitedKingdom,UnitedStates, etc.) since they all use the same singleton pattern.Expected behavior
addHoliday()on one calendar instance should not affect other instances.calendar2andcalendar3should still returnTrueforisBusinessDay()sinceaddHoliday()was only called oncalendar1.Desktop:
Additional context
The root cause is in
Calendar.csand the calendar implementation classes (e.g.TARGET.cs):TARGET.cs:40—public TARGET() : base(Impl.Singleton) {}passes a static singleton to the base constructorTARGET.cs:44—internal static readonly Impl Singleton = new();is a single instance for the entire processCalendarImplatCalendar.cs:39-40— the mutable state lives on the shared impl:Calendar.addHoliday()atCalendar.cs:176-186— writes to_impl.addedHolidays(the singleton's set)Calendar.isBusinessDay()atCalendar.cs:116-127— reads from the same shared setA possible fix would be to move
addedHolidaysandremovedHolidaysfromCalendarImpltoCalendar, so each instance has its own mutable state while still sharing the immutable business day rules via the singleton.