Skip to content

Bump the nuget-remaining group with 10 updates - #38

Open
dependabot[bot] wants to merge 1 commit into
developfrom
dependabot/nuget/develop/nuget-remaining-9f26278aad
Open

Bump the nuget-remaining group with 10 updates#38
dependabot[bot] wants to merge 1 commit into
developfrom
dependabot/nuget/develop/nuget-remaining-9f26278aad

Conversation

@dependabot

@dependabot dependabot Bot commented on behalf of github Aug 31, 2026

Copy link
Copy Markdown
Contributor

Updated Meziantou.Analyzer from 3.0.177 to 3.0.195.

Release notes

Sourced from Meziantou.Analyzer's releases.

3.0.195

NuGet package: https://www.nuget.org/packages/Meziantou.Analyzer/3.0.195

What's Changed

Full Changelog: meziantou/Meziantou.Analyzer@3.0.194...3.0.195

3.0.194

NuGet package: https://www.nuget.org/packages/Meziantou.Analyzer/3.0.194

What's Changed

Full Changelog: meziantou/Meziantou.Analyzer@3.0.193...3.0.194

3.0.193

NuGet package: https://www.nuget.org/packages/Meziantou.Analyzer/3.0.193

What's Changed

Full Changelog: meziantou/Meziantou.Analyzer@3.0.192...3.0.193

3.0.192

NuGet package: https://www.nuget.org/packages/Meziantou.Analyzer/3.0.192

What's Changed

Full Changelog: meziantou/Meziantou.Analyzer@3.0.191...3.0.192

3.0.191

NuGet package: https://www.nuget.org/packages/Meziantou.Analyzer/3.0.191

What's Changed

Full Changelog: meziantou/Meziantou.Analyzer@3.0.190...3.0.191

3.0.190

NuGet package: https://www.nuget.org/packages/Meziantou.Analyzer/3.0.190

What's Changed

Full Changelog: meziantou/Meziantou.Analyzer@3.0.189...3.0.190

3.0.189

NuGet package: https://www.nuget.org/packages/Meziantou.Analyzer/3.0.189

What's Changed

Full Changelog: meziantou/Meziantou.Analyzer@3.0.188...3.0.189

3.0.188

NuGet package: https://www.nuget.org/packages/Meziantou.Analyzer/3.0.188

What's Changed

Full Changelog: meziantou/Meziantou.Analyzer@3.0.187...3.0.188

3.0.187

NuGet package: https://www.nuget.org/packages/Meziantou.Analyzer/3.0.187

What's Changed

Full Changelog: meziantou/Meziantou.Analyzer@3.0.186...3.0.187

3.0.186

NuGet package: https://www.nuget.org/packages/Meziantou.Analyzer/3.0.186

What's Changed

Full Changelog: meziantou/Meziantou.Analyzer@3.0.185...3.0.186

3.0.185

NuGet package: https://www.nuget.org/packages/Meziantou.Analyzer/3.0.185

What's Changed

Full Changelog: meziantou/Meziantou.Analyzer@3.0.184...3.0.185

3.0.184

NuGet package: https://www.nuget.org/packages/Meziantou.Analyzer/3.0.184

What's Changed

Full Changelog: meziantou/Meziantou.Analyzer@3.0.183...3.0.184

3.0.183

NuGet package: https://www.nuget.org/packages/Meziantou.Analyzer/3.0.183

What's Changed

Full Changelog: meziantou/Meziantou.Analyzer@3.0.182...3.0.183

3.0.182

NuGet package: https://www.nuget.org/packages/Meziantou.Analyzer/3.0.182

What's Changed

Full Changelog: meziantou/Meziantou.Analyzer@3.0.181...3.0.182

3.0.181

NuGet package: https://www.nuget.org/packages/Meziantou.Analyzer/3.0.181

What's Changed

Full Changelog: meziantou/Meziantou.Analyzer@3.0.180...3.0.181

3.0.180

NuGet package: https://www.nuget.org/packages/Meziantou.Analyzer/3.0.180

What's Changed

Full Changelog: meziantou/Meziantou.Analyzer@3.0.179...3.0.180

3.0.179

NuGet package: https://www.nuget.org/packages/Meziantou.Analyzer/3.0.179

What's Changed

Full Changelog: meziantou/Meziantou.Analyzer@3.0.178...3.0.179

3.0.178

NuGet package: https://www.nuget.org/packages/Meziantou.Analyzer/3.0.178

What's Changed

Full Changelog: meziantou/Meziantou.Analyzer@3.0.177...3.0.178

Commits viewable in compare view.

Updated Quartz from 3.19.1 to 3.20.0.

Release notes

Sourced from Quartz's releases.

3.20.0

Quartz.NET 3.20.0 is a feature release: the ADO.NET job store can take part in the application's own database transaction, job instantiation failures finally carry the trigger they died on, and the daylight-saving and calendar arithmetic got a systematic pass that fixed several defects a schedule can actually hit. The public API is additive only — three new members and one new exception type, nothing changed or removed — but this is not quite a drop-in upgrade. Four things to read before you take it:

  1. The database scripts moved. Everything under database/ that used to sit flat is now database/migrations/<version>/<name>_<dialect>.sql, one runnable file per database instead of one file with five commented-out dialect blocks. Old links still resolve against release tags; the mapping is below.
  2. The index migration is optional and performance-only (database/migrations/3.20/). Nothing needs it to run, but PostgreSQL users should read that bullet.
  3. A few fixes change what fires when. Each is marked Behavior change worth noting below.
  4. One rolling-upgrade caveat, for a narrow case involving the Newtonsoft serializer — see the note before What's Changed.

Highlights

  • The job store can take part in the application's transaction — scheduling and your own database work can now commit or roll back together, without subclassing the job store. Turn it on with quartz.jobStore.acceptEnlistedTransactions or AcceptEnlistedTransactions(), then hand the store a connection for the duration of a scope with IScheduler.EnlistTransaction(DbTransaction) or IScheduler.EnlistConnection(DbConnection). The application owns the commit; the job store neither commits nor rolls back, and the enlistment flows with the current asynchronous context the way Transaction.Current does — which is what makes it work while IJobStore is a singleton and a DbContext is scoped. Nothing about it is EF Core specific. Taking part always means handing over a connection: an ambient TransactionScope alone is not enough, because a connection the job store opens for itself would be a second connection in that transaction and would force promotion to a distributed one — which needs MSDTC (Windows only, and on modern .NET also an explicit opt-in through TransactionManager.ImplicitDistributedTransactions) and is impossible on Npgsql, which has no distributed transaction support at all. Sharing the one connection is what keeps the transaction local. Opt-in, because joining the application's transaction changes when, and whether, scheduling commits; JobStoreCMT is untouched, since running inside a container-managed transaction is that store's whole contract. (#​3204, fixes #​2038)
    • Known limits, all documented on the pull request: the job store's locks are held until the caller's transaction completes, so a long application transaction blocks trigger acquisition, the misfire handler and cluster check-in; there is no savepoint, so a half-failed operation leaves its statements in the caller's transaction; a connection opened before the ambient scope it is enlisted under is not really in that scope and ADO.NET offers no portable way to detect it; and txIsolationLevelSerializable applies only to the job store's own connections.
  • Job instantiation failures carry the trigger, the job and the fire instance id — when IJobFactory cannot produce a job the trigger has already fired and been committed, but there is no IJobExecutionContext yet, so no trigger or job listener can be raised and ISchedulerListener.SchedulerError is the only notification. It carried the job key as interpolated message text and nothing else, leaving callers parsing a string to find out which firing died. The new Quartz.Core.JobInstantiationException : SchedulerException carries Trigger, JobDetail and FireInstanceId — the same shape JobExecutionProcessException has for execution-time failures — and both catch blocks in JobRunShell.Run raise it, so the DI and non-DI paths are enriched alike. Message text is byte-identical in both paths, including a long-standing misplaced quote, so anything parsing it today keeps working while it migrates. (#​3215, closes #​3213)
    • Behavior change worth noting: in the SchedulerException path the exception handed to listeners is now a JobInstantiationException wrapping the factory's exception rather than being it; the original is reachable as InnerException. The choice between NoInstruction and SetAllJobTriggersError still tests the original exception, so cancellation and disposal races behave exactly as before.
  • Daylight-saving resolution is centralised, and two latent defects are gone — the trigger-wide DST policy existed in one explicit place and was otherwise implicit, working only by the accident of TimeZoneInfo.GetUtcOffset returning the pre-gap offset for positive-delta zones. It is now two internal TimeZoneUtil helpers — ResolveLocal (ambiguous → the first/daylight occurrence; nonexistent → paired with the pre-gap offset, found by scanning backwards a minute at a time so 30-minute deltas and negative-delta zone models work) and WalkToGapEnd — with all five call-site families migrated onto them. Two defects fell out: a negative-daylight-delta gap hazard, where a zone modelled with a negative delta (Europe/Dublin on TZif data, so Linux and macOS) produced an instant before the gap — a scheduler hot-loop hazard the ambiguity demotion does not cover; and a sub-second demotion defect, where CronExpression.GetTimeAfter compared its whole-second candidate against the untruncated after-time, so a sub-second after-time inside the repeated fall-back hour demoted a valid fire a whole hour forward. The migrations are behaviour-neutral for every positive-delta zone, guarded by a minute-by-minute differential test across ±3 hours of all eight test zones' transitions. (#​3198, building on #​3195)
    • Behavior change worth noting: the sub-second fix makes GetTimeAfter monotonic in its argument again, and stops GetTimeBefore returning instants the expression never fires at. Fire times inside the repeated fall-back hour can therefore differ from 3.19.
  • DailyTimeIntervalTrigger no longer fires past its EndTimeUtc — the day-of-week advance resolved the advanced day's start-of-day through the instant-based offset overload while every sibling call site used the wall-clock policy overload, so the offset carried through the AddDays walk was stale once the walk crossed a transition, and an in-gap start-of-day resolved one transition delta too early. The EndTimeUtc check inside the same method consumed the mis-resolved instant before the downstream rescue corrected it, so with an end time between the wrong instant and the right one the trigger fired once past its configured end. Only triggers with a partial DaysOfWeek set were affected — the existing tests all use a full week, where the two resolutions coincide. (#​3196)
  • Calendars that exclude whole days mean the local day, in every zoneHolidayCalendar, AnnualCalendar, MonthlyCalendar and WeeklyCalendar built the boundary they walk as midnight at the offset the queried instant happened to carry, and advanced it with AddDays, which keeps that offset. Both assume a local day begins at midnight at the same offset the rest of the day carries, and a transition day is exactly the day where it does not. In America/Santiago, where the clocks move at midnight, the 23-hour day (2019-09-08) answered with the excluded instant it was asked about, and the 25-hour day (2019-04-06) overshot by 23 hours; in a zone that moves its clocks later in the day the answer lands a whole date out (Europe/Helsinki on 2024-03-31, asked from the afternoon, answered 2024-03-30); and a run of excluded days crossing a transition drifts by the transition delta. The boundary now resolves local midnight by naming the date rather than by adding twenty-four hours. CronCalendar never had the shape. (#​3478, fixes #​3457)
  • DailyCalendar.GetNextIncludedTimeUtc is jumped to rather than walked up to — it named the window's edges on the date the argument fell on and paired them with the argument's offset, while IsTimeIncluded converts into the calendar's zone first, so a question asked in UTC about a calendar keeping another zone's hours had the two disagree. The jump then landed on a window an offset away from the one being tested, the loop fell through to its last resort — one precision step at a time, bounded only by where the wrong window happened to end — and it could step clean over an included stretch. The measured reproduction, an inverted 21:00–22:00 America/Santiago calendar asked at 2019-04-07T01:30Z: 2019-09-09T00:00:59.999Z, five months late, reached a minute of wall clock at a time, against 2019-04-08T01:00Z in two passes now. The whole daily-calendar half of the new test fixture takes 1 m 52 s against the unfixed file and milliseconds against this one. The answer is now named from the window's own edges — the instant the day opens at, the second reading of that edge where the clock repeats it, the instant the clocks moved where a fall-back takes the clock back to before the window opened, and the day's own first instant — each checked against the calendar's own rule before it is taken. (#​3478, fixes #​3466)
    • Behavior change worth noting: the precisionStepMillis optimisation (#​2285) is removed, because a jump has nothing left to speed up and it was rounding the answer up by as much as a minute. GetNextIncludedTimeUtc answers are now exact: a 06:0022:00 calendar asked at 21:59Z returns 22:00:00.001Z where it returned 22:01:00.000Z, and asking about an instant the calendar already includes gives back the next millisecond rather than the next minute. The NestedCalendarTests performance test that came with #​2285 is unchanged and passes.
    • Behavior change worth noting: GetTimeRangeStartingTimeUtc and GetTimeRangeEndingTimeUtc are public and carried the same offset assumption in public — asked in UTC about a Santiago calendar they answered about the UTC date at +00:00, which no caller can use. They now read the date of the local day the instant falls in. For a calendar left on the default TimeZoneInfo.Local asked with a local value, the conversion is a no-op. No signature changed.
  • A trigger written as a plain object graph keeps its time zoneUseNewtonsoftJsonSerializer leaves RegisterTriggerConverters off by default, and with it off Json.NET's default contract wrote a TimeZoneInfo as its whole public surface (Id, DisplayName, BaseUtcOffset, all read-only), so reading it back set nothing and the trigger's getter fell through to TimeZoneInfo.Local. A trigger stored under Tokyo fired on whichever zone the reading machine was in, silently. Measured before the fix: CalendarIntervalTriggerImpl, RecurrenceTriggerImpl and CronTriggerImpl all came back on the reading machine's zone. An internal TimeZoneInfoConverter writes the id and reads both the id and the old object form, attached per property to members typed as a TimeZoneInfo — deliberately not on the serializer's converter list, since that list is consulted for a value's runtime type wherever it appears and a zone held in a job data map value would then lose the $type that path carries. The four private timeZoneInfoId helpers that were meant for this and never worked (DefaultContractResolver does not serialize private members) are gone. BLOB_TRIGGERS payloads written by BinaryObjectSerializer are unaffected: they are computed properties, so there was never a backing field for BinaryFormatter to match. (#​3505)
    • Found and deliberately not fixed: with the converters off, a DailyTimeIntervalTriggerImpl cannot be read back at all — TimeOfDay has no parameterless constructor, so Json.NET fails loudly on EndTimeOfDay. It predates this change, it fails rather than corrupting, and the ADO store reaches that path only for a trigger it serializes as a blob, so a shipped DailyTimeIntervalTrigger does not go through it.
  • The interrupt monitor interrupts only the fire instance it was created forJobInterruptMonitorPlugin interrupted by job key, so a monitor that elapsed cancelled every running execution of that job rather than the one it was watching. Worse, a vetoed fire's monitor was never cancelled at all, because the only cleanup point was TriggerComplete, which a vetoed fire never reaches — so every veto leaked a live monitor that would later interrupt an unrelated, healthy execution of the same job. It now calls Interrupt(fireInstanceId), cancels the monitor on veto through a job listener of its own, does not start a second monitor for a re-executed job sharing a fire instance id, and removes its own bookkeeping entry when it elapses. Both scenarios are exactly as analysed in the report. (#​3249, fixes #​3248)
    • Behavior change worth noting: IScheduler.Interrupt(fireInstanceId) now raises ISchedulerListener.JobInterrupted, matching the Interrupt(JobKey) overload — relevant to anyone calling the fire-instance overload directly. And AutoInterruptable is now read from the merged job data map, consistent with MaxRunTime, so a trigger's data map can opt a fire in or out of auto-interruption rather than only override the timeout.
  • A blank calendar name is no calendar name — a trigger whose CALENDAR_NAME was written as '' rather than NULL stopped firing entirely: every job store gates its calendar lookup on CalendarName is not null, so the empty string passed the gate, the lookup found nothing, and the fire was silently dropped. AbstractTrigger.CalendarName now stores a blank name as null, without trimming — the name is a lookup key against whatever AddCalendar stored, so trimming " holidays " would break a calendar registered with padding and create the same bug from the other direction. That one setter is the choke point for TriggerBuilder, both JSON converters and the ADO read-back, so databases already holding '' self-heal: the row rehydrates as "no calendar", the trigger fires again on the next acquisition, and the column is written back as NULL next time it is persisted. No migration script. TriggerDetailsUpdate.WithCalendarName normalizes separately, since both stores check the calendar exists before the value reaches the trigger setter. Both stores now also log when they skip a fire over a missing calendar, which turns this class of report from a mystery into a one-line diagnosis. The dashboard, which produced the empty string by rebuilding the trigger field by field out of its display projection, now edits the trigger JSON it was already handed — which also preserves the node pin and the real trigger type it used to drop. (#​3295, fixes #​3294)
  • Six bug fixes found by the 4.0 campaign, backported — each confirmed on 3.x with exact locations and mutation-verified on both test target frameworks: CronCalendar.GetNextIncludedTimeUtc never terminated from an excluded instant (it advanced with GetNextValidTimeAfter, which lands on another excluded time); CronCalendar's three-argument constructor dropped its timeZone, so the calendar evaluated its exclusion cron in machine-local time; transient-error classification short-circuited on DbException.IsTransient, which on any .NET 6+ host matches every driver exception — so the deadlock-1205 list, the SQLite busy/locked check and the timeout fallback were all dead code and retryable failures were classified permanent; a prefix trigger-group pause paused only the first matching group and a prefix resume could never clear what a prefix pause recorded; JobInterruptMonitorPlugin silently ignored a numeric MaxRunTime, falling back to the five-minute default with no log; and Newtonsoft deserialization populated read-only collections through their getters, so on the default configuration a DaysOfWeek subset came back as all seven days. (#​3334)
    • Behavior change worth noting: three of those change what fires when. Transient database failures are now retried where they were previously treated as permanent; a prefix trigger-group pause or resume now affects every matching group rather than the first; and a DaysOfWeek subset survives the plain Newtonsoft round trip. Deliberately not backported: the 4.x store-parity semantic alignments (pause-over-Error, ResumeAll marker clearing, sentinel visibility, exception re-wrap types), which change observable maintenance-branch behaviour.
  • ADO delegate parameter bindings, cluster recovery priority and LIKE wildcard handling — cluster failover recovery triggers lost their priority, because SelectInstancesFiredTriggerRecords was the only fired-trigger reader that never read PRIORITY and ClusterRecover assigns from exactly that reader, so every recovery trigger ran at priority 0, below the default 5, deprioritising recovery work precisely when a node has died. Four IDriverDelegate members failed on every provider because their bound parameter names never matched their SQL; nothing in the scheduler calls them, which is why it never surfaced, but they are public surface reachable from JobStoreSupport subclasses. And group matcher values are now escaped, with ! as the escape character because ESCAPE '\' is a MySQL syntax error while ! is a plain literal on all six supported databases. (#​3202)
    • Behavior change worth noting: group names containing % or _ now match literally in group matcher queries. Previously those characters acted as LIKE wildcards, so a matcher could list, pause, resume or delete groups it was not meant to match. The same correction means ResumeAll deletes only the _$_ALL_GROUPS_PAUSED_$_ sentinel row rather than a pattern in which all eight underscores were single-character wildcards.
  • PostgreSQL index realignment, and prefix-redundant indexes dropped everywhere — the PostgreSQL script had 9 of its 11 indexes not leading with sched_name, which every Quartz statement filters on first, and idx_qrtz_t_nft_st had its columns reversed(next_fire_time, trigger_state) against an acquire query that is two equalities then a range, so the index could never bound the scan on state or scheduler name. This is the shape behind slow-acquire-on-PostgreSQL reports. Verified on live PostgreSQL 16: the upgrade script and a fresh install converge on byte-identical pg_indexes sets, and a 160,000-trigger acquire-plan comparison moves all three predicates from post-filter into the index condition — buffer hits 136 → 57, discarded rows 61 → 0. Across the other dialects, indexes whose columns are a leftmost prefix of a wider same-table index are dropped, with the coverer named on every drop. IDX_QRTZ_J_GRP deliberately stays on 3.x, unlike 4.x, because its 4.x coverer does not exist here and it serves the group listings. The migration is optional and performance-onlydatabase/migrations/3.20/index_alignment_<dialect>.sql. (#​3203)
  • Database scripts are per-version, per-dialect migrations, and they are generated — every migration now lives in database/migrations/<version>/<name>_<dialect>.sql, one directly-runnable file per database instead of one file carrying five commented-out dialect blocks, with every statement guarded so re-running is a no-op (SQLite ADD COLUMN is the documented exception — SQLite has no conditional DDL). The scripts are generated from one description in the build, because six hand-written dialect variants is how they drift, and VerifyMigrations fails a checked-in script that no longer matches. database/README.md is the new index: run order, per-version status, and the old-path → new-path map reproduced below. The migrations previously had no test coverage at all; MigrationScriptTest now builds a 3.16-era schema from a checked-in baseline, applies 3.17 → 3.18 → 3.19 → 3.20 in order, applies each twice so the guards are exercised, and asserts the result matches what the current tables_<dialect>.sql produces, table for table, column for column, index for index — on all six databases. It caught two real defects on the way: PostgreSQL's index alignment used CREATE INDEX IF NOT EXISTS for three indexes that already exist in a 3.16 schema under the same name with different columns, so the guard silently kept the wrong shape — including idx_qrtz_t_nft_st; and MySQL's QRTZ_BLOB_TRIGGERS carried an InnoDB-auto-named inline index duplicating its primary key that the migration had left in place. It also backports the 2.5→2.6 QRTZ_CRON_TRIGGERS.TIME_ZONE_ID fix (#​1985), which never reached this branch. (#​3219, fixes #​3218)
  • The 3.x → 4.0 upgrade scripts live on main, and this branch links to them — they were briefly mirrored here, and a mirror goes stale silently every time 4.x's schema moves. A confidently wrong upgrade script is worse than one that is plainly somewhere else, so database/migrations/4.0/ is not on this branch and database/README.md points at https://github.com/quartznet/quartznet/tree/main/database/migrations/4.0 in every place it used to link to the folder. (#​3373, and #​3326, which stopped the SQLite 4.0 upgrade claiming it can be re-run when its five ADD COLUMNs are unguarded)
  • Infrastructure that makes the above checkable — the public API surface of every shipped assembly is now snapshotted with PublicApiGenerator and Verify, so an accidental API change in a patch release fails a test on the branch where that matters most, and the 4.0 delta is a git diff (#​3226). The UnitTest target now asks each project which frameworks it declares and runs it once per framework: dotnet test --framework X against a project that does not target X exits 0 having run nothing, so the Ubuntu and macOS legs had been running 116 tests and silently skipping the 1,753 in Quartz.Tests.Unit while the green check said otherwise (#​3228). CI was unbroken by moving to Testcontainers 4.14.0, which references a patched SSH.NET, and by pinning NuGet.Frameworks forward for SDK 10.0.400 (#​3278). The build orchestrator moved to Fallout 10.4.0 (#​3252), changelog.md was retired in favour of the GitHub releases that had already superseded it (#​3224), sonar-project.properties was removed because SonarCloud's automatic analysis never read it (#​3221), and a test that had been writing files into the application directory on every CI run stopped (#​3293).

Public API

Additive only. No existing signature changed, nothing was removed, and the new baselines are byte-identical across every other change in this release.

  • Quartz.Core.JobInstantiationException : SchedulerException, carrying Trigger, JobDetail and FireInstanceId (#​3215)
  • IScheduler.EnlistTransaction(DbTransaction) and IScheduler.EnlistConnection(DbConnection), plus AcceptEnlistedTransactions() on the persistent store builder and the quartz.jobStore.acceptEnlistedTransactions key (#​3204)

Where the database scripts moved

The scripts used to sit flat in database/, with the dialects other than SQL Server commented out inside each file. Those paths are gone from the branch tip. Old links keep working against release tags — for example https://github.com/quartznet/quartznet/blob/v3.19.1/database/schema_30_add_preferred_node.sql.

Old path New path
database/sqlserver_schema_10_to_20_upgrade.sql
database/schema_10_to_20_upgrade.sql
migrations/2.0/schema_10_to_20_upgrade_sqlServer.sql
database/schema_20_to_22_upgrade.sql migrations/2.2/schema_20_to_22_upgrade_<db>.sql
database/schema_25_to_26_upgrade.sql migrations/2.6/schema_25_to_26_upgrade_<db>.sql
... (truncated)

Commits viewable in compare view.

Updated Quartz.Extensions.Hosting from 3.19.1 to 3.20.0.

Release notes

Sourced from Quartz.Extensions.Hosting's releases.

3.20.0

Quartz.NET 3.20.0 is a feature release: the ADO.NET job store can take part in the application's own database transaction, job instantiation failures finally carry the trigger they died on, and the daylight-saving and calendar arithmetic got a systematic pass that fixed several defects a schedule can actually hit. The public API is additive only — three new members and one new exception type, nothing changed or removed — but this is not quite a drop-in upgrade. Four things to read before you take it:

  1. The database scripts moved. Everything under database/ that used to sit flat is now database/migrations/<version>/<name>_<dialect>.sql, one runnable file per database instead of one file with five commented-out dialect blocks. Old links still resolve against release tags; the mapping is below.
  2. The index migration is optional and performance-only (database/migrations/3.20/). Nothing needs it to run, but PostgreSQL users should read that bullet.
  3. A few fixes change what fires when. Each is marked Behavior change worth noting below.
  4. One rolling-upgrade caveat, for a narrow case involving the Newtonsoft serializer — see the note before What's Changed.

Highlights

  • The job store can take part in the application's transaction — scheduling and your own database work can now commit or roll back together, without subclassing the job store. Turn it on with quartz.jobStore.acceptEnlistedTransactions or AcceptEnlistedTransactions(), then hand the store a connection for the duration of a scope with IScheduler.EnlistTransaction(DbTransaction) or IScheduler.EnlistConnection(DbConnection). The application owns the commit; the job store neither commits nor rolls back, and the enlistment flows with the current asynchronous context the way Transaction.Current does — which is what makes it work while IJobStore is a singleton and a DbContext is scoped. Nothing about it is EF Core specific. Taking part always means handing over a connection: an ambient TransactionScope alone is not enough, because a connection the job store opens for itself would be a second connection in that transaction and would force promotion to a distributed one — which needs MSDTC (Windows only, and on modern .NET also an explicit opt-in through TransactionManager.ImplicitDistributedTransactions) and is impossible on Npgsql, which has no distributed transaction support at all. Sharing the one connection is what keeps the transaction local. Opt-in, because joining the application's transaction changes when, and whether, scheduling commits; JobStoreCMT is untouched, since running inside a container-managed transaction is that store's whole contract. (#​3204, fixes #​2038)
    • Known limits, all documented on the pull request: the job store's locks are held until the caller's transaction completes, so a long application transaction blocks trigger acquisition, the misfire handler and cluster check-in; there is no savepoint, so a half-failed operation leaves its statements in the caller's transaction; a connection opened before the ambient scope it is enlisted under is not really in that scope and ADO.NET offers no portable way to detect it; and txIsolationLevelSerializable applies only to the job store's own connections.
  • Job instantiation failures carry the trigger, the job and the fire instance id — when IJobFactory cannot produce a job the trigger has already fired and been committed, but there is no IJobExecutionContext yet, so no trigger or job listener can be raised and ISchedulerListener.SchedulerError is the only notification. It carried the job key as interpolated message text and nothing else, leaving callers parsing a string to find out which firing died. The new Quartz.Core.JobInstantiationException : SchedulerException carries Trigger, JobDetail and FireInstanceId — the same shape JobExecutionProcessException has for execution-time failures — and both catch blocks in JobRunShell.Run raise it, so the DI and non-DI paths are enriched alike. Message text is byte-identical in both paths, including a long-standing misplaced quote, so anything parsing it today keeps working while it migrates. (#​3215, closes #​3213)
    • Behavior change worth noting: in the SchedulerException path the exception handed to listeners is now a JobInstantiationException wrapping the factory's exception rather than being it; the original is reachable as InnerException. The choice between NoInstruction and SetAllJobTriggersError still tests the original exception, so cancellation and disposal races behave exactly as before.
  • Daylight-saving resolution is centralised, and two latent defects are gone — the trigger-wide DST policy existed in one explicit place and was otherwise implicit, working only by the accident of TimeZoneInfo.GetUtcOffset returning the pre-gap offset for positive-delta zones. It is now two internal TimeZoneUtil helpers — ResolveLocal (ambiguous → the first/daylight occurrence; nonexistent → paired with the pre-gap offset, found by scanning backwards a minute at a time so 30-minute deltas and negative-delta zone models work) and WalkToGapEnd — with all five call-site families migrated onto them. Two defects fell out: a negative-daylight-delta gap hazard, where a zone modelled with a negative delta (Europe/Dublin on TZif data, so Linux and macOS) produced an instant before the gap — a scheduler hot-loop hazard the ambiguity demotion does not cover; and a sub-second demotion defect, where CronExpression.GetTimeAfter compared its whole-second candidate against the untruncated after-time, so a sub-second after-time inside the repeated fall-back hour demoted a valid fire a whole hour forward. The migrations are behaviour-neutral for every positive-delta zone, guarded by a minute-by-minute differential test across ±3 hours of all eight test zones' transitions. (#​3198, building on #​3195)
    • Behavior change worth noting: the sub-second fix makes GetTimeAfter monotonic in its argument again, and stops GetTimeBefore returning instants the expression never fires at. Fire times inside the repeated fall-back hour can therefore differ from 3.19.
  • DailyTimeIntervalTrigger no longer fires past its EndTimeUtc — the day-of-week advance resolved the advanced day's start-of-day through the instant-based offset overload while every sibling call site used the wall-clock policy overload, so the offset carried through the AddDays walk was stale once the walk crossed a transition, and an in-gap start-of-day resolved one transition delta too early. The EndTimeUtc check inside the same method consumed the mis-resolved instant before the downstream rescue corrected it, so with an end time between the wrong instant and the right one the trigger fired once past its configured end. Only triggers with a partial DaysOfWeek set were affected — the existing tests all use a full week, where the two resolutions coincide. (#​3196)
  • Calendars that exclude whole days mean the local day, in every zoneHolidayCalendar, AnnualCalendar, MonthlyCalendar and WeeklyCalendar built the boundary they walk as midnight at the offset the queried instant happened to carry, and advanced it with AddDays, which keeps that offset. Both assume a local day begins at midnight at the same offset the rest of the day carries, and a transition day is exactly the day where it does not. In America/Santiago, where the clocks move at midnight, the 23-hour day (2019-09-08) answered with the excluded instant it was asked about, and the 25-hour day (2019-04-06) overshot by 23 hours; in a zone that moves its clocks later in the day the answer lands a whole date out (Europe/Helsinki on 2024-03-31, asked from the afternoon, answered 2024-03-30); and a run of excluded days crossing a transition drifts by the transition delta. The boundary now resolves local midnight by naming the date rather than by adding twenty-four hours. CronCalendar never had the shape. (#​3478, fixes #​3457)
  • DailyCalendar.GetNextIncludedTimeUtc is jumped to rather than walked up to — it named the window's edges on the date the argument fell on and paired them with the argument's offset, while IsTimeIncluded converts into the calendar's zone first, so a question asked in UTC about a calendar keeping another zone's hours had the two disagree. The jump then landed on a window an offset away from the one being tested, the loop fell through to its last resort — one precision step at a time, bounded only by where the wrong window happened to end — and it could step clean over an included stretch. The measured reproduction, an inverted 21:00–22:00 America/Santiago calendar asked at 2019-04-07T01:30Z: 2019-09-09T00:00:59.999Z, five months late, reached a minute of wall clock at a time, against 2019-04-08T01:00Z in two passes now. The whole daily-calendar half of the new test fixture takes 1 m 52 s against the unfixed file and milliseconds against this one. The answer is now named from the window's own edges — the instant the day opens at, the second reading of that edge where the clock repeats it, the instant the clocks moved where a fall-back takes the clock back to before the window opened, and the day's own first instant — each checked against the calendar's own rule before it is taken. (#​3478, fixes #​3466)
    • Behavior change worth noting: the precisionStepMillis optimisation (#​2285) is removed, because a jump has nothing left to speed up and it was rounding the answer up by as much as a minute. GetNextIncludedTimeUtc answers are now exact: a 06:0022:00 calendar asked at 21:59Z returns 22:00:00.001Z where it returned 22:01:00.000Z, and asking about an instant the calendar already includes gives back the next millisecond rather than the next minute. The NestedCalendarTests performance test that came with #​2285 is unchanged and passes.
    • Behavior change worth noting: GetTimeRangeStartingTimeUtc and GetTimeRangeEndingTimeUtc are public and carried the same offset assumption in public — asked in UTC about a Santiago calendar they answered about the UTC date at +00:00, which no caller can use. They now read the date of the local day the instant falls in. For a calendar left on the default TimeZoneInfo.Local asked with a local value, the conversion is a no-op. No signature changed.
  • A trigger written as a plain object graph keeps its time zoneUseNewtonsoftJsonSerializer leaves RegisterTriggerConverters off by default, and with it off Json.NET's default contract wrote a TimeZoneInfo as its whole public surface (Id, DisplayName, BaseUtcOffset, all read-only), so reading it back set nothing and the trigger's getter fell through to TimeZoneInfo.Local. A trigger stored under Tokyo fired on whichever zone the reading machine was in, silently. Measured before the fix: CalendarIntervalTriggerImpl, RecurrenceTriggerImpl and CronTriggerImpl all came back on the reading machine's zone. An internal TimeZoneInfoConverter writes the id and reads both the id and the old object form, attached per property to members typed as a TimeZoneInfo — deliberately not on the serializer's converter list, since that list is consulted for a value's runtime type wherever it appears and a zone held in a job data map value would then lose the $type that path carries. The four private timeZoneInfoId helpers that were meant for this and never worked (DefaultContractResolver does not serialize private members) are gone. BLOB_TRIGGERS payloads written by BinaryObjectSerializer are unaffected: they are computed properties, so there was never a backing field for BinaryFormatter to match. (#​3505)
    • Found and deliberately not fixed: with the converters off, a DailyTimeIntervalTriggerImpl cannot be read back at all — TimeOfDay has no parameterless constructor, so Json.NET fails loudly on EndTimeOfDay. It predates this change, it fails rather than corrupting, and the ADO store reaches that path only for a trigger it serializes as a blob, so a shipped DailyTimeIntervalTrigger does not go through it.
  • The interrupt monitor interrupts only the fire instance it was created forJobInterruptMonitorPlugin interrupted by job key, so a monitor that elapsed cancelled every running execution of that job rather than the one it was watching. Worse, a vetoed fire's monitor was never cancelled at all, because the only cleanup point was TriggerComplete, which a vetoed fire never reaches — so every veto leaked a live monitor that would later interrupt an unrelated, healthy execution of the same job. It now calls Interrupt(fireInstanceId), cancels the monitor on veto through a job listener of its own, does not start a second monitor for a re-executed job sharing a fire instance id, and removes its own bookkeeping entry when it elapses. Both scenarios are exactly as analysed in the report. (#​3249, fixes #​3248)
    • Behavior change worth noting: IScheduler.Interrupt(fireInstanceId) now raises ISchedulerListener.JobInterrupted, matching the Interrupt(JobKey) overload — relevant to anyone calling the fire-instance overload directly. And AutoInterruptable is now read from the merged job data map, consistent with MaxRunTime, so a trigger's data map can opt a fire in or out of auto-interruption rather than only override the timeout.
  • A blank calendar name is no calendar name — a trigger whose CALENDAR_NAME was written as '' rather than NULL stopped firing entirely: every job store gates its calendar lookup on CalendarName is not null, so the empty string passed the gate, the lookup found nothing, and the fire was silently dropped. AbstractTrigger.CalendarName now stores a blank name as null, without trimming — the name is a lookup key against whatever AddCalendar stored, so trimming " holidays " would break a calendar registered with padding and create the same bug from the other direction. That one setter is the choke point for TriggerBuilder, both JSON converters and the ADO read-back, so databases already holding '' self-heal: the row rehydrates as "no calendar", the trigger fires again on the next acquisition, and the column is written back as NULL next time it is persisted. No migration script. TriggerDetailsUpdate.WithCalendarName normalizes separately, since both stores check the calendar exists before the value reaches the trigger setter. Both stores now also log when they skip a fire over a missing calendar, which turns this class of report from a mystery into a one-line diagnosis. The dashboard, which produced the empty string by rebuilding the trigger field by field out of its display projection, now edits the trigger JSON it was already handed — which also preserves the node pin and the real trigger type it used to drop. (#​3295, fixes #​3294)
  • Six bug fixes found by the 4.0 campaign, backported — each confirmed on 3.x with exact locations and mutation-verified on both test target frameworks: CronCalendar.GetNextIncludedTimeUtc never terminated from an excluded instant (it advanced with GetNextValidTimeAfter, which lands on another excluded time); CronCalendar's three-argument constructor dropped its timeZone, so the calendar evaluated its exclusion cron in machine-local time; transient-error classification short-circuited on DbException.IsTransient, which on any .NET 6+ host matches every driver exception — so the deadlock-1205 list, the SQLite busy/locked check and the timeout fallback were all dead code and retryable failures were classified permanent; a prefix trigger-group pause paused only the first matching group and a prefix resume could never clear what a prefix pause recorded; JobInterruptMonitorPlugin silently ignored a numeric MaxRunTime, falling back to the five-minute default with no log; and Newtonsoft deserialization populated read-only collections through their getters, so on the default configuration a DaysOfWeek subset came back as all seven days. (#​3334)
    • Behavior change worth noting: three of those change what fires when. Transient database failures are now retried where they were previously treated as permanent; a prefix trigger-group pause or resume now affects every matching group rather than the first; and a DaysOfWeek subset survives the plain Newtonsoft round trip. Deliberately not backported: the 4.x store-parity semantic alignments (pause-over-Error, ResumeAll marker clearing, sentinel visibility, exception re-wrap types), which change observable maintenance-branch behaviour.
  • ADO delegate parameter bindings, cluster recovery priority and LIKE wildcard handling — cluster failover recovery triggers lost their priority, because SelectInstancesFiredTriggerRecords was the only fired-trigger reader that never read PRIORITY and ClusterRecover assigns from exactly that reader, so every recovery trigger ran at priority 0, below the default 5, deprioritising recovery work precisely when a node has died. Four IDriverDelegate members failed on every provider because their bound parameter names never matched their SQL; nothing in the scheduler calls them, which is why it never surfaced, but they are public surface reachable from JobStoreSupport subclasses. And group matcher values are now escaped, with ! as the escape character because ESCAPE '\' is a MySQL syntax error while ! is a plain literal on all six supported databases. (#​3202)
    • Behavior change worth noting: group names containing % or _ now match literally in group matcher queries. Previously those characters acted as LIKE wildcards, so a matcher could list, pause, resume or delete groups it was not meant to match. The same correction means ResumeAll deletes only the _$_ALL_GROUPS_PAUSED_$_ sentinel row rather than a pattern in which all eight underscores were single-character wildcards.
  • PostgreSQL index realignment, and prefix-redundant indexes dropped everywhere — the PostgreSQL script had 9 of its 11 indexes not leading with sched_name, which every Quartz statement filters on first, and idx_qrtz_t_nft_st had its columns reversed(next_fire_time, trigger_state) against an acquire query that is two equalities then a range, so the index could never bound the scan on state or scheduler name. This is the shape behind slow-acquire-on-PostgreSQL reports. Verified on live PostgreSQL 16: the upgrade script and a fresh install converge on byte-identical pg_indexes sets, and a 160,000-trigger acquire-plan comparison moves all three predicates from post-filter into the index condition — buffer hits 136 → 57, discarded rows 61 → 0. Across the other dialects, indexes whose columns are a leftmost prefix of a wider same-table index are dropped, with the coverer named on every drop. IDX_QRTZ_J_GRP deliberately stays on 3.x, unlike 4.x, because its 4.x coverer does not exist here and it serves the group listings. The migration is optional and performance-onlydatabase/migrations/3.20/index_alignment_<dialect>.sql. (#​3203)
  • Database scripts are per-version, per-dialect migrations, and they are generated — every migration now lives in database/migrations/<version>/<name>_<dialect>.sql, one directly-runnable file per database instead of one file carrying five commented-out dialect blocks, with every statement guarded so re-running is a no-op (SQLite ADD COLUMN is the documented exception — SQLite has no conditional DDL). The scripts are generated from one description in the build, because six hand-written dialect variants is how they drift, and VerifyMigrations fails a checked-in script that no longer matches. database/README.md is the new index: run order, per-version status, and the old-path → new-path map reproduced below. The migrations previously had no test coverage at all; MigrationScriptTest now builds a 3.16-era schema from a checked-in baseline, applies 3.17 → 3.18 → 3.19 → 3.20 in order, applies each twice so the guards are exercised, and asserts the result matches what the current tables_<dialect>.sql produces, table for table, column for column, index for index — on all six databases. It caught two real defects on the way: PostgreSQL's index alignment used CREATE INDEX IF NOT EXISTS for three indexes that already exist in a 3.16 schema under the same name with different columns, so the guard silently kept the wrong shape — including idx_qrtz_t_nft_st; and MySQL's QRTZ_BLOB_TRIGGERS carried an InnoDB-auto-named inline index duplicating its primary key that the migration had left in place. It also backports the 2.5→2.6 QRTZ_CRON_TRIGGERS.TIME_ZONE_ID fix (#​1985), which never reached this branch. (#​3219, fixes #​3218)
  • The 3.x → 4.0 upgrade scripts live on main, and this branch links to them — they were briefly mirrored here, and a mirror goes stale silently every time 4.x's schema moves. A confidently wrong upgrade script is worse than one that is plainly somewhere else, so database/migrations/4.0/ is not on this branch and database/README.md points at https://github.com/quartznet/quartznet/tree/main/database/migrations/4.0 in every place it used to link to the folder. (#​3373, and #​3326, which stopped the SQLite 4.0 upgrade claiming it can be re-run when its five ADD COLUMNs are unguarded)
  • Infrastructure that makes the above checkable — the public API surface of every shipped assembly is now snapshotted with PublicApiGenerator and Verify, so an accidental API change in a patch release fails a test on the branch where that matters most, and the 4.0 delta is a git diff (#​3226). The UnitTest target now asks each project which frameworks it declares and runs it once per framework: dotnet test --framework X against a project that does not target X exits 0 having run nothing, so the Ubuntu and macOS legs had been running 116 tests and silently skipping the 1,753 in Quartz.Tests.Unit while the green check said otherwise (#​3228). CI was unbroken by moving to Testcontainers 4.14.0, which references a patched SSH.NET, and by pinning NuGet.Frameworks forward for SDK 10.0.400 (#​3278). The build orchestrator moved to Fallout 10.4.0 (#​3252), changelog.md was retired in favour of the GitHub releases that had already superseded it (#​3224), sonar-project.properties was removed because SonarCloud's automatic analysis never read it (#​3221), and a test that had been writing files into the application directory on every CI run stopped (#​3293).

Public API

Additive only. No existing signature changed, nothing was removed, and the new baselines are byte-identical across every other change in this release.

  • Quartz.Core.JobInstantiationException : SchedulerException, carrying Trigger, JobDetail and FireInstanceId (#​3215)
  • IScheduler.EnlistTransaction(DbTransaction) and IScheduler.EnlistConnection(DbConnection), plus AcceptEnlistedTransactions() on the persistent store builder and the quartz.jobStore.acceptEnlistedTransactions key (#​3204)

Where the database scripts moved

The scripts used to sit flat in database/, with the dialects other than SQL Server commented out inside each file. Those paths are gone from the branch tip. Old links keep working against release tags — for example https://github.com/quartznet/quartznet/blob/v3.19.1/database/schema_30_add_preferred_node.sql.

Old path New path
database/sqlserver_schema_10_to_20_upgrade.sql
database/schema_10_to_20_upgrade.sql
migrations/2.0/schema_10_to_20_upgrade_sqlServer.sql
database/schema_20_to_22_upgrade.sql migrations/2.2/schema_20_to_22_upgrade_<db>.sql
database/schema_25_to_26_upgrade.sql migrations/2.6/schema_25_to_26_upgrade_<db>.sql
... (truncated)

Commits viewable in compare view.

Updated Quartz.Plugins from 3.19.1 to 3.20.0.

Release notes

Sourced from Quartz.Plugins's releases.

3.20.0

Quartz.NET 3.20.0 is a feature release: the ADO.NET job store can take part in the application's own database transaction, job instantiation failures finally carry the trigger they died on, and the daylight-saving and calendar arithmetic got a systematic pass that fixed several defects a schedule can actually hit. The public API is additive only — three new members and one new exception type, nothing changed or removed — but this is not quite a drop-in upgrade. Four things to read before you take it:

  1. The database scripts moved. Everything under database/ that used to sit flat is now database/migrations/<version>/<name>_<dialect>.sql, one runnable file per database instead of one file with five commented-out dialect blocks. Old links still resolve against release tags; the mapping is below.
  2. The index migration is optional and performance-only (database/migrations/3.20/). Nothing needs it to run, but PostgreSQL users should read that bullet.
  3. A few fixes change what fires when. Each is marked Behavior change worth noting below.
  4. One rolling-upgrade caveat, for a narrow case involving the Newtonsoft serializer — see the note before What's Changed.

Highlights

  • The job store can take part in the application's transaction — scheduling and your own database work can now commit or roll back together, without subclassing the job store. Turn it on with quartz.jobStore.acceptEnlistedTransactions or AcceptEnlistedTransactions(), then hand the store a connection for the duration of a scope with IScheduler.EnlistTransaction(DbTransaction) or IScheduler.EnlistConnection(DbConnection). The application owns the commit; the job store neither commits nor rolls back, and the enlistment flows with the current asynchronous context the way Transaction.Current does — which is what makes it work while IJobStore is a singleton and a DbContext is scoped. Nothing about it is EF Core specific. Taking part always means handing over a connection: an ambient TransactionScope alone is not enough, because a connection the job store opens for itself would be a second connection in that transaction and would force promotion to a distributed one — which needs MSDTC (Windows only, and on modern .NET also an explicit opt-in through TransactionManager.ImplicitDistributedTransactions) and is impossible on Npgsql, which has no distributed transaction support at all. Sharing the one connection is what keeps the transaction local. Opt-in, because joining the application's transaction changes when, and whether, scheduling commits; JobStoreCMT is untouched, since running inside a container-managed transaction is that store's whole contract. (#​3204, fixes #​2038)
    • Known limits, all documented on the pull request: the job store's locks are held until the caller's transaction completes, so a long application transaction blocks trigger acquisition, the misfire handler and cluster check-in; there is no savepoint, so a half-failed operation leaves its statements in the caller's transaction; a connection opened before the ambient scope it is enlisted under is not really in that scope and ADO.NET offers no portable way to detect it; and txIsolationLevelSerializable applies only to the job store's own connections.
  • Job instantiation failures carry the trigger, the job and the fire instance id — when IJobFactory cannot produce a job the trigger has already fired and been committed, but there is no IJobExecutionContext yet, so no trigger or job listener can be raised and ISchedulerListener.SchedulerError is the only notification. It carried the job key as interpolated message text and nothing else, leaving callers parsing a string to find out which firing died. The new Quartz.Core.JobInstantiationException : SchedulerException carries Trigger, JobDetail and FireInstanceId — the same shape JobExecutionProcessException has for execution-time failures — and both catch blocks in JobRunShell.Run raise it, so the DI and non-DI paths are enriched alike. Message text is byte-identical in both paths, including a long-standing misplaced quote, so anything parsing it today keeps working while it migrates. (#​3215, closes #​3213)
    • Behavior change worth noting: in the SchedulerException path the exception handed to listeners is now a JobInstantiationException wrapping the factory's exception rather than being it; the original is reachable as InnerException. The choice between NoInstruction and SetAllJobTriggersError still tests the original exception, so cancellation and disposal races behave exactly as before.
  • Daylight-saving resolution is centralised, and two latent defects are gone — the trigger-wide DST policy existed in one explicit place and was otherwise implicit, working only by the accident of TimeZoneInfo.GetUtcOffset returning the pre-gap offset for positive-delta zones. It is now two internal TimeZoneUtil helpers — ResolveLocal (ambiguous → the first/daylight occurrence; nonexistent → paired with the pre-gap offset, found by scanning backwards a minute at a time so 30-minute deltas and negative-delta zone models work) and WalkToGapEnd — with all five call-site families migrated onto them. Two defects fell out: a negative-daylight-delta gap hazard, where a zone modelled with a negative delta (Europe/Dublin on TZif data, so Linux and macOS) produced an instant before the gap — a scheduler hot-loop hazard the ambiguity demotion does not cover; and a sub-second demotion defect, where CronExpression.GetTimeAfter compared its whole-second candidate against the untruncated after-time, so a sub-second after-time inside the repeated fall-back hour demoted a valid fire a whole hour forward. The migrations are behaviour-neutral for every positive-delta zone, guarded by a minute-by-minute differential test across ±3 hours of all eight test zones' transitions. (#​3198, building on #​3195)
    • Behavior change worth noting: the sub-second fix makes GetTimeAfter monotonic in its argument again, and stops GetTimeBefore returning instants the expression never fires at. Fire times inside the repeated fall-back hour can therefore differ from 3.19.
  • DailyTimeIntervalTrigger no longer fires past its EndTimeUtc — the day-of-week advance resolved the advanced day's start-of-day through the instant-based offset overload while every sibling call site used the wall-clock policy overload, so the offset carried through the AddDays walk was stale once the walk crossed a transition, and an in-gap start-of-day resolved one transition delta too early. The EndTimeUtc check inside the same method consumed the mis-resolved instant before the downstream rescue corrected it, so with an end time between the wrong instant and the right one the trigger fired once past its configured end. Only triggers with a partial DaysOfWeek set were affected — the existing tests all use a full week, where the two resolutions coincide. (#​3196)
  • Calendars that exclude whole days mean the local day, in every zoneHolidayCalendar, AnnualCalendar, MonthlyCalendar and WeeklyCalendar built the boundary they walk as midnight at the offset the queried instant happened to carry, and advanced it with AddDays, which keeps that offset. Both assume a local day begins at midnight at the same offset the rest of the day carries, and a transition day is exactly the day where it does not. In America/Santiago, where the clocks move at midnight, the 23-hour day (2019-09-08) answered with the excluded instant it was asked about, and the 25-hour day (2019-04-06) overshot by 23 hours; in a zone that moves its clocks later in the day the answer lands a whole date out (Europe/Helsinki on 2024-03-31, asked from the afternoon, answered 2024-03-30); and a run of excluded days crossing a transition drifts by the transition delta. The boundary now resolves local midnight by naming the date rather than by adding twenty-four hours. CronCalendar never had the shape. (#​3478, fixes #​3457)
  • DailyCalendar.GetNextIncludedTimeUtc is jumped to rather than walked up to — it named the window's edges on the date the argument fell on and paired them with the argument's offset, while IsTimeIncluded converts into the calendar's zone first, so a question asked in UTC about a calendar keeping another zone's hours had the two disagree. The jump then landed on a window an offset away from the one being tested, the loop fell through to its last resort — one precision step at a time, bounded only by where the wrong window happened to end — and it could step clean over an included stretch. The measured reproduction, an inverted 21:00–22:00 America/Santiago calendar asked at 2019-04-07T01:30Z: **2019-09-09T00:00:59.999Z, five months late, reach...

Description has been truncated

Bumps Meziantou.Analyzer from 3.0.177 to 3.0.195
Bumps Quartz from 3.19.1 to 3.20.0
Bumps Quartz.Extensions.Hosting from 3.19.1 to 3.20.0
Bumps Quartz.Plugins from 3.19.1 to 3.20.0
Bumps Quartz.Plugins.TimeZoneConverter from 3.19.1 to 3.20.0
Bumps Quartz.Serialization.SystemTextJson from 3.19.1 to 3.20.0
Bumps Scalar.AspNetCore from 2.16.10 to 2.17.2
Bumps System.Security.Cryptography.ProtectedData from 10.0.9 to 10.0.11
Bumps TUnit from 1.65.38 to 1.65.68
Bumps TUnit.Playwright from 1.58.0 to 1.65.68

---
updated-dependencies:
- dependency-name: Meziantou.Analyzer
  dependency-version: 3.0.195
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: nuget-remaining
- dependency-name: Quartz
  dependency-version: 3.20.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: nuget-remaining
- dependency-name: Quartz.Extensions.Hosting
  dependency-version: 3.20.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: nuget-remaining
- dependency-name: Quartz.Plugins
  dependency-version: 3.20.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: nuget-remaining
- dependency-name: Quartz.Plugins.TimeZoneConverter
  dependency-version: 3.20.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: nuget-remaining
- dependency-name: Quartz.Serialization.SystemTextJson
  dependency-version: 3.20.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: nuget-remaining
- dependency-name: Scalar.AspNetCore
  dependency-version: 2.17.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: nuget-remaining
- dependency-name: System.Security.Cryptography.ProtectedData
  dependency-version: 10.0.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: nuget-remaining
- dependency-name: TUnit
  dependency-version: 1.65.68
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: nuget-remaining
- dependency-name: TUnit.Playwright
  dependency-version: 1.65.68
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: nuget-remaining
...

Signed-off-by: dependabot[bot] <support@github.com>
@dependabot dependabot Bot added .NET Pull requests that update .NET code dependencies Pull requests that update a dependency file labels Aug 31, 2026
@dependabot
dependabot Bot requested a review from w0rldx as a code owner August 31, 2026 04:48
@dependabot dependabot Bot added dependencies Pull requests that update a dependency file .NET Pull requests that update .NET code labels Aug 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file .NET Pull requests that update .NET code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants