Skip to content

Calendar.addHoliday() mutates global singleton state, affecting all instances of the same calendar type #313

@egnirra

Description

@egnirra

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:40public TARGET() : base(Impl.Singleton) {} passes a static singleton to the base constructor
  • TARGET.cs:44internal 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions