Skip to main content

Calculation code comments Monkey patched date class

On GitHub: config/initializers/monkey_patching.rb

2 module DateMonkeyPatch

See also: Parliamentary Time Period ontology

A Venn diagram of parliamentary time

A Venn diagram of parliamentary time

A set of methods to determine the type of a given day across both Houses.

We want to determine if Parliament is dissolved on this day.

13   def is_dissolution_day?
14     DissolutionDay.all.where( 'date = ?',  self ).first
15   end

We want to determine if Parliament is prorogued on this day.

18   def is_prorogation_day?
19     ProrogationDay.all.where( 'date = ?',  self ).first
20   end

A set of methods to return the parliamentary time periods a calendar day forms part of.

A calendar day forms part of a dissolution period or a Parliament period.

Where a calendar day forms part of a Parliament period, it forms part of a session or a prorogation period.

We want to find which Parliament period a calendar day forms part of, if any.

27   def parliament_period
28     ParliamentPeriod.find_by_sql([
29       "
30         SELECT *
31         FROM parliament_periods
32         WHERE start_date <= :the_date
33         AND (
34           end_date >= :the_date
35           OR
36           end_date IS NULL
37         )
38       ",
39       the_date: self
40     ]).first
41   end

We want to find which dissolution period a calendar day forms part of, if any.

44   def dissolution_period
45     DissolutionPeriod.all.where( "start_date <= ?", self ).where( "end_date >= ?", self).first
46   end

We want to find which session a calendar day forms part of, if any.

49   def session
50     Session.find_by_sql([
51       "
52         SELECT *
53         FROM sessions
54         WHERE start_date <= :the_date
55         AND (
56           end_date >= :the_date
57           OR
58           end_date IS NULL
59         )
60       ",
61       the_date: self
62     ]).first
63   end

We want to find which prorogation period a calendar day forms part of, if any.

66   def prorogation_period
67     ProrogationPeriod.all.where( "start_date <= ?", self ).where( "end_date >= ?", self).first
68   end

A set of methods to determine the type of a given day in a House.

Sitting days

Parliamentary clerks define a sitting day in a House as a day when the House started sitting. Should a House start sitting on a Monday, continue sitting through the night, rise on the Tuesday and not start sitting again on that Tuesday, that would count as one sitting day.

As far as we are aware, this definition made its first appearance in legislation as part of schedule 5, paragraph 44 (2) of the European Union (Withdrawal Agreement) Act 2020.

Clerks have suggested that this definition would apply across all calculations of scrutiny periods. Lawyers have suggested otherwise.

The Parliamentary Time application defines three types of sitting day:

  • a parliamentary sitting day, being a day on which a House started sitting according to the definition given above.
  • a calendar sitting day, being a day on which a House sat, regardless of whether it started sitting on that date.
  • a continuation sitting day, being a day on which a House sat when it did not start sitting on that date.

We want to determine if this is a parliamentary sitting day in the Commons.

84   def is_commons_parliamentary_sitting_day?
85     SittingDay.all.where( 'start_date = ?',  self ).where( house_id: 1 ).first
86   end

We want to determine if this is a parliamentary sitting day in the Lords.

89   def is_lords_parliamentary_sitting_day?
90     SittingDay.all.where( 'start_date = ?',  self ).where( house_id: 2 ).first
91   end

We want to determine if this is a calendar sitting day in the Commons.

This method is never called so is commented out here.

95   def is_commons_calendar_sitting_day?
96     SittingDay.all.where( 'start_date <= ?',  self ).where( 'end_date >= ?',  self ).where( house_id: 1 ).first
97   end

We want to determine if this is a calendar sitting day in the Lords.

This method is never called so is commented out here.

101   def is_lords_calendar_sitting_day?
102     SittingDay.all.where( 'start_date <= ?',  self ).where( 'end_date >= ?',  self ).where( house_id: 2 ).first
103   end

We want to determine if this is a continuation sitting day in the Commons.

106   def is_commons_continuation_sitting_day?
107     SittingDay.all.where( 'start_date < ?',  self ).where( 'end_date >= ?',  self ).where( house_id: 1 ).first
108   end

We want to determine if this is a continuation sitting day in the Lords.

111   def is_lords_continuation_sitting_day?
112     SittingDay.all.where( 'start_date < ?',  self ).where( 'end_date >= ?',  self ).where( house_id: 2 ).first
113   end

Virtual sitting days

During the COVID pandemic, the House of Lords sat virtually for 11 days.

As of February 2026, the House of Commons has never sat virtually.

In guidance issued on 16 April 2020 the Lords Procedure Committee stated:

"A Virtual Proceeding is not a sitting of the House. There is no Mace present and the Virtual Proceeding will not be empowered to make decisions. Virtual Proceedings can debate and sc[r]utinise an issue but when a decision is needed that must be taken by the House."

Whilst clerks state that a virtual sitting day does not count as a sitting day for the purposes of calculating scrutiny periods, lawyers imply this would need to be tested in court.

Unless and until this is resolved, these methods use the clerks' definition of non-sitting scrutiny day.

We want to determine if this is a virtual sitting day in the Commons.

126   def is_commons_virtual_sitting_day?
127     VirtualSittingDay.all.where( 'start_date <= ?',  self ).where( 'end_date >= ?',  self ).where( house_id: 1 ).first
128   end

We want to determine if this is a virtual sitting day in the Lords.

131   def is_lords_virtual_sitting_day?
132     VirtualSittingDay.all.where( 'start_date <= ?',  self ).where( 'end_date >= ?',  self ).where( house_id: 2 ).first
133   end

Adjournment days

We want to determine if this is an adjournment day in the Commons.

139   def is_commons_adjournment_day?
140     AdjournmentDay.all.where( 'date = ?',  self ).where( house_id: 1 ).first
141   end

We want to determine if this is an adjournment day in the Lords.

144   def is_lords_adjournment_day?
145     AdjournmentDay.all.where( 'date = ?',  self ).where( house_id: 2 ).first
146   end

Adjourned days

Days on which a House is adjourned encompass more day types than those declared as adjournment days in the calendar.

According to the guidance set out by the Lords Procedure Committee - given above - days on which a House sat virtually also count as adjourned days.

According to the definition of a parliamentary sitting day - also given above - as understood by clerks and as set out in some legislation, continuation sitting days also count as adjourned days.

We want to determine if the day counts as the Commons being adjourned.

155   def counts_as_commons_adjourned_day?
156     self.is_commons_adjournment_day? \
157     or self.is_commons_virtual_sitting_day? \
158     or self.is_commons_continuation_sitting_day?
159   end

We want to determine if the day counts as the Lords being adjourned.

162   def counts_as_lords_adjourned_day?
163     self.is_lords_adjournment_day? \
164     or self.is_lords_virtual_sitting_day? \
165     or self.is_lords_continuation_sitting_day?
166   end

A set of methods to determine the type of a given day in either House.

We want to determine if this is a parliamentary sitting day in either House.

172   def is_either_house_parliamentary_sitting_day?
173     SittingDay.all.where( 'start_date = ?',  self ).first
174   end

We want to determine if this is a calendar sitting day in either House.

177   def is_either_house_calendar_sitting_day?
178     SittingDay.all.where( 'start_date <= ?',  self ).where( 'end_date >= ?', self).first
179   end

We want to determine if this is an adjournment day in either House.

182   def is_either_house_adjournment_day?
183     AdjournmentDay.all.where( 'date = ?',  self ).first
184   end

A set of methods to determine if this is a joint sitting day.

We want to check if this is a parliamentary sitting day in both Houses.

190   def is_joint_parliamentary_sitting_day?
191     self.is_commons_parliamentary_sitting_day? and self.is_lords_parliamentary_sitting_day?
192   end

We want to determine if this is a calendar sitting day in both Houses.

195   def is_joint_calendar_sitting_day?
196     self.is_commons_calendar_sitting_day? and self.is_lords_calendar_sitting_day?
197   end

A set of methods to determine if the calendar is populated.

A method to determine if this is a day for which we have something in the calendar.

That may be:

  • a day within dissolution
  • a day within prorogation
  • a parliamentary sitting day
  • a calendar sitting day
  • a virtual sitting day
  • an adjournment day
210   def is_calendar_populated?
211     self.is_dissolution_day? \
212     or self.is_prorogation_day? \
213     or self.is_commons_calendar_sitting_day? \
214     or self.is_lords_calendar_sitting_day? \
215     or self.is_commons_virtual_sitting_day? \
216     or self.is_lords_virtual_sitting_day? \
217     or self.is_commons_adjournment_day? \
218     or self.is_lords_adjournment_day?
219   end

We want to determine if this is a day for which we do not have anything in the calendar.

This method is used to determine whether a calculation has enough calendar data to proceed.

If the calendar is not populated for a date, calculations cannot proceed past that date.

224   def is_calendar_not_populated?
225     !self.is_calendar_populated?
226   end

A set of methods to determine whether a date forms part of a break in sitting days of a determined length.

Under some legislation, periods where the Houses are adjourned for a defined number of days count as scrutiny days for instruments laid under powers in that legislation.

In the legislation we've encountered where this is set out, such periods are defined as being not more than four days.

These methods allow the calculation to be adjusted by passing in a maximum day count.

Methods to calculate non-sitting scrutiny days in both Houses.

We want to check if this is a non-sitting scrutiny day in the Commons.

237   def is_commons_non_sitting_scrutiny_day?( maximum_day_count )

If this counts as an adjourned day in the Commons ...

240     if self.counts_as_commons_adjourned_day?

... we start the non-sitting scrutiny day count at 1.

243       non_sitting_scrutiny_day_count = 1

We want to cycle through the following days until we reach the maximum day count passed into this function.

246       date = self

For each number between 1 and the maximum day count ...

249       for i in ( 1 .. maximum_day_count )

... we go forward one day.

252         date = date.next_day

If this day counts as an adjourned day in the Commons ...

255         if date.counts_as_commons_adjourned_day?

... we add one to the non-sitting scrutiny day count.

258           non_sitting_scrutiny_day_count += 1

Otherwise, if this day does not count as an adjourned day in the Commons ...

261         else

... we stop cycling through following days.

264           break
265         end
266       end

We want to cycle through the preceding days until we reach the maximum day count passed into this function.

269       date = self

For each number between 1 and the maximum day count ...

272       for i in ( 1 .. maximum_day_count )

... we go back one day.

275         date = date.prev_day

If this day counts as an adjourned day in the Commons ...

278         if date.counts_as_commons_adjourned_day?

... we add one to the non-sitting scrutiny day count.

281           non_sitting_scrutiny_day_count += 1

Otherwise, if this day does not count as an adjourned day in the Commons ...

284         else

... we stop cycling through preceding days.

287           break
288         end
289       end

If the total number of non-sitting scrutiny days is more than the maximum day count ...

292       if non_sitting_scrutiny_day_count > maximum_day_count

... then this day does not count as a non-sitting scrutiny day.

295         is_commons_non_sitting_scrutiny_day = false

If the total number of non-sitting scrutiny days is less than or the same as the maximum day count ...

298       else

... then this day does count as a non-sitting scrutiny day.

301         is_commons_non_sitting_scrutiny_day = true
302       end

We return if this day is a Commons non-sitting scrutiny day

305       is_commons_non_sitting_scrutiny_day
306     end
307   end

We want to check if this is a non-sitting scrutiny day in the Lords.

310   def is_lords_non_sitting_scrutiny_day?( maximum_day_count )

If this counts as an adjourned day in the Lords ...

313     if self.counts_as_lords_adjourned_day?

... we start the non-sitting scrutiny day count at 1.

316       non_sitting_scrutiny_day_count = 1

We want to cycle through the following days until we reach the maximum day count passed into this function.

319       date = self

For each number between 1 and the maximum day count ...

322       for i in ( 1 .. maximum_day_count )

... we go forward one day.

325         date = date.next_day

If this day counts as an adjourned day in the Lords ...

328         if date.counts_as_lords_adjourned_day?

... we add one to the non-sitting scrutiny day count.

331           non_sitting_scrutiny_day_count += 1

Otherwise, if this day does not count as an adjourned day in the Lords ...

334         else

... we stop cycling through following days.

337           break
338         end
339       end

We want to cycle through the preceding days until we reach the maximum day count passed into this function.

342       date = self

For each number between 1 and the maximum day count ...

345       for i in ( 1 .. maximum_day_count )

... we go back one day.

348         date = date.prev_day

If this day counts as an adjourned day in the Lords ...

351         if date.counts_as_lords_adjourned_day?

... we add one to the non-sitting scrutiny day count.

354           non_sitting_scrutiny_day_count += 1

Otherwise, if this day does not count as an adjourned day in the Lords ...

357         else

... we stop cycling through preceding days.

360           break
361         end
362       end

If the total number of non-sitting scrutiny days is more than the maximum day count ...

365       if non_sitting_scrutiny_day_count > maximum_day_count

... then this day does not count as a non-sitting scrutiny day.

368         is_lords_non_sitting_scrutiny_day = false

If the total number of non-sitting scrutiny days is less than or the same as the maximum day count ...

371       else

... then this day does count as a non-sitting scrutiny day.

374         is_lords_non_sitting_scrutiny_day = true
375       end

We return if this day is a Lord non-sitting scrutiny day

378       is_lords_non_sitting_scrutiny_day
379     end
380   end

A set of methods to determine if this is a scrutiny day in a House, in both Houses and in either House.

We want to determine if this is a scrutiny day in the Commons.

385   def is_commons_scrutiny_day?

We set the Commons is scrutiny day boolean to false.

388     is_commons_scrutiny_day = false

If this is a parliamentary sitting day in the Commons ...

... or this is a non-sitting scrutiny day in the Commons ...

392     if self.is_commons_parliamentary_sitting_day? or self.is_commons_non_sitting_scrutiny_day?( 4 ) # We pass '4' as the maximum day count to the non-sitting scrutiny day calculation, because non-sitting scrutiny days are days within a series of not more than four non-sitting days.

... we set the is Commons scrutiny day boolean to true.

395       is_commons_scrutiny_day = true
396     end

We return the is Commons scrutiny day boolean.

399     is_commons_scrutiny_day
400   end

We want to determine if this is a scrutiny day in the Lords.

403   def is_lords_scrutiny_day?

We set the Lords is scrutiny day boolean to false.

406     is_lords_scrutiny_day = false

If this is a parliamentary sitting day in the Lords ...

... or this is a non-sitting scrutiny day in the Lords ...

410     if self.is_lords_parliamentary_sitting_day? or self.is_lords_non_sitting_scrutiny_day?( 4 ) # We pass '4' as the maximum day count to the non-sitting scrutiny day calculation, because non-sitting scrutiny days are days within a series of not more than four non-sitting days.

... we set the Lords is scrutiny day boolean to true.

413       is_lords_scrutiny_day = true
414     end

We return the Lords is scrutiny day boolean.

417     is_lords_scrutiny_day
418   end

We want to check if this is a scrutiny day in both Houses.

421   def is_joint_scrutiny_day?

We set the is joint scrutiny day boolean to false.

424     is_joint_scrutiny_day = false

If this is a scrutiny day in the Commons and this is a scrutiny day in the Lords ...

427     if self.is_commons_scrutiny_day? and self.is_lords_scrutiny_day?

... we set the is joint scrutiny day boolean to true.

430       is_joint_scrutiny_day = true
431     end

We return the is joint scrutiny day boolean.

434     is_joint_scrutiny_day
435   end

We want to check if this is a scrutiny day in either House.

438   def is_either_house_scrutiny_day?

We set the is either house scrutiny day boolean to false.

441     is_either_house_scrutiny_day = false

If this is a scrutiny day in the Commons or this is a scrutiny day in the Lords ...

444     if self.is_commons_scrutiny_day? or self.is_lords_scrutiny_day?

... we set the is either house scrutiny day boolean to true.

447       is_either_house_scrutiny_day = true
448     end

We return the is either house scrutiny day boolean.

451     is_either_house_scrutiny_day
452   end

A set of methods to determine the relationship of a day to its containing or preceding session.

We want to determine if this is the final day of a session.

457   def is_final_day_of_session?

We set the is final day of session boolean to false.

460     is_final_day_of_session = false

We attempt to find a session ending on this date.

463     session = Session.all.where( "end_date = ?", self ).first

If we find a session ending on this data ...

466     if session

... we set the is final day of session boolean to true.

469       is_final_day_of_session = true
470     end

We return the is final day of session boolean.

473     is_final_day_of_session
474   end

We want to find the session immediately preceding this day.

477   def preceding_session

We attempt to find the first session starting before this day.

480     Session.all.where( "start_date < ?", self ).order( "start_date DESC" ).first
481   end

We want to find the session immediately following this day.

This method is used to determine which session papers laid in prorogation are recorded in.

485   def following_session

We attempt to find the next session starting after this day.

488     Session.all.where( "start_date > ?", self ).order( "start_date" ).first
489   end

A set of methods to find the first following and first preceding joint sitting days.

We attempt to find the first following joint sitting day.

This method is used in the forwards calculations for a Proposed Negative Statutory Instrument.

Even if a PNSI is laid on a joint sitting day, the clock does not start until the next joint sitting day.

496   def first_joint_parliamentary_sitting_day

If this is a day on which the calendar is not yet populated ...

499     if self.is_calendar_not_populated?

... we cannot find a following joint sitting day, so we stop looking.

502       return nil

Otherwise, if this is a day on which the calendar is populated ...

505     else

... if this is not a joint sitting day ...

508       unless self.is_joint_parliamentary_sitting_day?

... we go to the next day and check that.

511         self.next_day.first_joint_parliamentary_sitting_day

Otherwise, if this is a joint sitting day ...

514       else

... we return this day as the first following joint sitting day.

517         self
518       end
519     end
520   end

We want to find the first preceding joint sitting day.

This method is used in the backwards calculations for a Proposed Negative Statutory Instrument.

Even if a PNSI is laid on a joint sitting day, the clock does not start until the next joint sitting day.

525   def last_joint_parliamentary_sitting_day

If this is a day on which the calendar is not yet populated ...

528     if self.is_calendar_not_populated?

... we cannot find a preceding joint sitting day, so we stop looking.

531       return nil

Otherwise, if this is a day on which the calendar is populated ...

534     else

... if this is not a joint sitting day ...

537       unless self.is_joint_parliamentary_sitting_day?

... we go to the previous day and check that.

540         self.prev_day.last_joint_parliamentary_sitting_day

Otherwise, if this is a joint sitting day ...

543       else

... we return this day as the first preceding joint sitting day.

546         self
547       end
548     end
549   end

A set of methods to apply labels to a day in the calendar views.

A method to generate a label for the type of a day in the Commons in a session.

554   def commons_day_type

If the day is a parliamentary sitting day in the Commons ...

557     if self.is_commons_parliamentary_sitting_day?

... we set the day type label to 'Sitting day'.

560       day_type = 'Sitting day'

Otherwise, if the day is a continuation sitting day in the Commons ...

563     elsif self.is_commons_continuation_sitting_day?

... we set the day type label to 'Continuation sitting day'.

566       day_type = "Continuation sitting day"

Otherwise, if the day is a virtual sitting day in the Commons ...

569     elsif self.is_commons_virtual_sitting_day?

... we set the day type label to 'Virtual sitting day'.

572       day_type = 'Virtual sitting day'

Otherwise, if the day is a adjournment day in the Commons ...

575     elsif self.is_commons_adjournment_day?

... we want to display if the adjournment day forms part of a named recess ...

... so we call the Commons adjournment day label method.

579       day_type = self.commons_adjournment_day_label

Otherwise, if the day is in a session but has no defined type ...

582     elsif self.session

... we set the day type label to 'Session day of unknown type'.

585       day_type = 'Session day of unknown type'

Otherwise, if the day is a prorogation day ...

588     elsif self.is_prorogation_day?

... we set the day type label to 'Prorogation'.

591       day_type = 'Prorogation'

Otherwise, if the day is a dissolution day ...

594     elsif self.is_dissolution_day?

... we set the day type label to 'Dissolution'.

597       day_type = 'Dissolution'
598     end

We return the day type label.

601     day_type
602   end

A method to generate a label for the type of a day in the Lords in a session.

605   def lords_day_type

If the day is a parliamentary sitting day in the Lords ...

608     if self.is_lords_parliamentary_sitting_day?

... we set the day type label to 'Sitting day'.

611       day_type = 'Sitting day'

Otherwise, if the day is a continuation sitting day in the Lords ...

614     elsif self.is_lords_continuation_sitting_day?

... we set the day type label to 'Continuation sitting day'.

617       day_type = "Continuation sitting day"

Otherwise, if the day is a virtual sitting day in the Lords ...

620     elsif self.is_lords_virtual_sitting_day?

... we set the day type label to 'Virtual sitting day'.

623       day_type = 'Virtual sitting day'

Otherwise, if the day is a adjournment day in the Lords ...

626     elsif self.is_lords_adjournment_day?

... we want to display if the adjournment day forms part of a named recess ...

... so we call the Lords adjournment day label method.

630       day_type = self.lords_adjournment_day_label

Otherwise, if the day is in a session but has no defined type ...

633     elsif self.session

... we set the day type label to 'Session day of unknown type'.

636       day_type = 'Session day of unknown type'

Otherwise, if the day is a prorogation day ...

639     elsif self.is_prorogation_day?

... we set the day type label to 'Prorogation'.

642       day_type = 'Prorogation'

Otherwise, if the day is a dissolution day ...

645     elsif self.is_dissolution_day?

... we set the day type label to 'Dissolution'.

648       day_type = 'Dissolution'
649     end

We return the day type label.

652     day_type
653   end

A set of methods to decorate a day label for an adjournment day with the name of a recess where applicable.

A method to label a Commons adjournment day, with the name of a recess if applicable.

658   def commons_adjournment_day_label

We set the day type label to 'Adjournment day'

661     day_type = 'Adjournment day'

We attempt to find a recess on this date, in this House.

664     recess_date = RecessDate
665       .all
666       .where( "start_date <= ?", self )
667       .where( "end_date >= ?", self )
668       .where( house_id: 1 ) # 1 being the ID of the House of Commons.
669       .first

If we find a recess date on this day, in this House ...

672     if recess_date

... we append the description of the recess date to the day type label.

675       day_type += ' (' + recess_date.description + ')'
676     end

We return the day type label.

679     day_type
680   end

A method to label a Lords adjournment day, with the name of a recess if applicable.

683   def lords_adjournment_day_label

We set the day type label to 'Adjournment day'

686     day_type = 'Adjournment day'

We attempt to find a recess on this date, in this House.

689     recess_date = RecessDate
690       .all
691       .where( "start_date <= ?", self )
692       .where( "end_date >= ?", self )
693       .where( house_id: 2 ) # 2 being the ID of the House of Lords.
694       .first

If we find a recess date on this day, in this House ...

697     if recess_date

... we append the description of the recess date to the day type label.

700       day_type += ' (' + recess_date.description + ')'
701     end

We return the day type label.

704     day_type
705   end

A set of methods to generate a label for whether this day counts as a scrutiny day in a House, or not.

A method to generate a label for whether this day counts as a scrutiny day in the Commons, or not.

710   def is_commons_scrutiny_day_label

If this is a scrutiny day in the Commons ...

713     if self.is_commons_scrutiny_day?

... we set the label to 'True'.

716       label = 'True'

Otherwise, if this is not a scrutiny day in the Commons ...

719     else

... we set the label to 'False'.

722       label = 'False'
723     end

We return the label.

726     label
727   end

A method to generate a label for whether this day counts as a scrutiny day in the Lords, or not.

730   def is_lords_scrutiny_day_label

If this is a scrutiny day in the Lords ...

733     if self.is_lords_scrutiny_day?

... we set the label to 'True'.

736       label = 'True'

Otherwise, if this is not a scrutiny day in the Lords ...

739     else

... we set the label to 'False'.

742       label = 'False'
743     end

We return the label.

746     label
747   end
748 end

We include these methods in the Ruby Data class.

751 Date.include( DateMonkeyPatch )