Skip to main content

Calculation code comments Monkey patched date class

On GitHub: config/initializers/monkey_patching.rb

2 module DateMonkeyPatch

A set of methods to work out the type of a given day.

We want to check if this is an actual sitting day in the Commons.

We use a naive definition of a sitting day: this includes a calendar day when the Commons sits, together with following calendar days if the Commons sat through the night.

For example: if the Commons sat on a Tuesday and continued to sit overnight into Wednesday, both Tuesday and Wednesday would count as actual sitting days.

If the Tuesday sitting lasted long enough to overlap the starting time of the Wednesday sitting, the Tuesday would be a parliamentary sitting day, but the Wednesday would not.

14   def is_commons_actual_sitting_day?
15     SittingDay.all.where( 'start_date <= ?',  self ).where( 'end_date >= ?',  self ).where( house_id: 1 ).first
16   end

We want to check if this is an actual sitting day in the Lords.

We use a naive definition of a sitting day: this includes a calendar day when the Lords sits, together with following calendar days if the Lords sat through the night.

For example: if the Lords sat on a Tuesday and continued to sit overnight into Wednesday, both Tuesday and Wednesday would count as actual sitting days.

If the Tuesday sitting lasted long enough to overlap the starting time of the Wednesday sitting, the Tuesday would be a parliamentary sitting day, but the Wednesday would not.

26   def is_lords_actual_sitting_day?
27     SittingDay.all.where( 'start_date <= ?',  self ).where( 'end_date >= ?',  self ).where( house_id: 2 ).first
28   end

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

We use a more strict definition of a sitting day. We don’t include dates for which the Commons continued sitting from a previous day, where the preceding day’s sitting overlapped with the next day’s programmed sitting.

33   def is_commons_parliamentary_sitting_day?
34     SittingDay.all.where( 'start_date = ?',  self ).where( house_id: 1 ).first
35   end

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

We use a more strict definition of a sitting day. We don’t include dates for which the Lords continued sitting from a previous day, where the preceding day’s sitting overlapped with the next day’s programmed sitting.

41   def is_lords_parliamentary_sitting_day?
42     SittingDay.all.where( 'start_date = ?',  self ).where( house_id: 2 ).first
43   end

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

This is a day where all Members of the House sit ‘digitally’, rather than physically.

A virtual sitting may continue over more than one calendar day. We count any continuation, where the preceding day’s sitting overlapped with the next day’s programmed sitting, as also being a virtual sitting day.

As of the end of June 2020, the Commons has had no virtual sitting days.

53   def is_commons_virtual_sitting_day?
54     VirtualSittingDay.all.where( 'start_date <= ?',  self ).where( 'end_date >= ?',  self ).where( house_id: 1 ).first
55   end

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

This is a day where all Members of the House sit ‘digitally’, rather than physically.

A virtual sitting may continue over more than one calendar day. We count any continuation, where the preceding day’s sitting overlapped with the next day’s programmed sitting, as also being a virtual sitting day.

62   def is_lords_virtual_sitting_day?
63     VirtualSittingDay.all.where( 'start_date <= ?',  self ).where( 'end_date >= ?',  self ).where( house_id: 2 ).first
64   end

We want to check if this is an actual sitting day in either House.

68   def is_either_house_actual_sitting_day?
69     self.is_commons_actual_sitting_day? or self.is_lords_actual_sitting_day?
70   end

We want to check if this is an actual sitting day in both Houses.

74   def is_joint_actual_sitting_day?
75     self.is_commons_actual_sitting_day? and self.is_lords_actual_sitting_day?
76   end

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

80   def is_either_house_parliamentary_sitting_day?
81     self.is_commons_parliamentary_sitting_day? or self.is_lords_parliamentary_sitting_day?
82   end

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

86   def is_joint_parliamentary_sitting_day?
87     self.is_commons_parliamentary_sitting_day? and self.is_lords_parliamentary_sitting_day?
88   end

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

92   def is_adjournment_day?
93     AdjournmentDay.all.where( 'date = ?',  self ).first
94   end

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

98   def is_commons_adjournment_day?
99     AdjournmentDay.all.where( 'date = ?',  self ).where( house_id: 1 ).first
100   end

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

104   def is_lords_adjournment_day?
105     AdjournmentDay.all.where( 'date = ?',  self ).where( house_id: 2 ).first
106   end

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

110   def is_prorogation_day?
111     ProrogationDay.all.where( 'date = ?',  self ).first
112   end

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

116   def is_dissolution_day?
117     DissolutionDay.all.where( 'date = ?',  self ).first
118   end

We want to check if this is a day for which we have something in the calendar.

That may be an actual sitting day - including parliamentary sitting days, a virtual sitting day, an adjournment day, a day within prorogation or a day within dissolution.

124   def is_calendar_populated?
125     self.is_commons_actual_sitting_day? or self.is_lords_actual_sitting_day? or self.is_commons_virtual_sitting_day? or self.is_lords_virtual_sitting_day? or self.is_adjournment_day? or self.is_prorogation_day? or self.is_dissolution_day?
126   end

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

We use this to check if we've "run out of calendar" so we don't keep cycling into future days and loop infinitely.

This is our event horizon.

134   def is_calendar_not_populated?
135     !self.is_calendar_populated?
136   end

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

In guidance issued on 16-04-2020 the Lords Procedure Committee stated, "A Virtual Proceeding is not a sitting of 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 check if this is a non-sitting scrutiny day in the Commons.

During a short adjournment, adjournment days count toward the scrutiny period.

This method allows the definition of a “short” adjournment to be adjusted by passing in a maximum day count.

In all known cases “short” is defined as not more than four days.

For the purposes of calculating non-sitting scrutiny days, virtual sitting days also count.

156   def is_commons_non_sitting_scrutiny_day?( maximum_day_count )

We want to check if this is a Commons adjournnment day or a Commons virtual sitting day.

160     if self.is_commons_adjournment_day? or self.is_commons_virtual_sitting_day?

Having found that this is a Commons adjournnment day or a Commons virtual sitting day, we start the adjournment day count at 1.

164       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.

168       date = self
169       for i in ( 1..maximum_day_count )

Go forward one day.

173         date = date.next_day

If this is a Commons adjournnment day or a Commons virtual sitting day ...

176         if date.is_commons_adjournment_day? or date.is_commons_virtual_sitting_day?

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

179           non_sitting_scrutiny_day_count +=1

If this is not a Commons adjournnment day or a Commons virtual sitting day ...

182         else

... stop cycling through following days.

185           break
186         end
187       end

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

190       date = self
191       for i in ( 1..maximum_day_count )

Go back one day.

194         date = date.prev_day

If this is a Commons adjournnment day or a Commons virtual sitting day ...

197         if date.is_commons_adjournment_day? or date.is_commons_virtual_sitting_day?

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

200           non_sitting_scrutiny_day_count +=1

If this is not a Commons adjournnment day or a Commons virtual sitting day ...

203         else

... stop cycling through preceding days.

206           break
207         end
208       end

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

211       if non_sitting_scrutiny_day_count > maximum_day_count

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

214         is_commons_non_sitting_scrutiny_day = false

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

217       else

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

220         is_commons_non_sitting_scrutiny_day = true
221       end

Returns if this day is a Commons non-sitting scrutiny day

224       is_commons_non_sitting_scrutiny_day
225     end
226   end

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

During a short adjournment, adjournment days count toward the scrutiny period.

This method allows the definition of a “short” adjournment to be adjusted by passing in a maximum day count.

In all known cases “short” is defined as not more than four days.

For the purposes of calculating non-sitting scrutiny days, virtual sitting days also count.

238   def is_lords_non_sitting_scrutiny_day?( maximum_day_count )

We want to check if this is a Lords adjournnment day or a Lords virtual sitting day.

242     if self.is_lords_adjournment_day? or self.is_lords_virtual_sitting_day?

Having found that this is a Lords adjournnment day or a Lords virtual sitting day, we start the adjournment day count at 1.

246       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.

250       date = self
251       for i in ( 1..maximum_day_count )

Go forward one day.

255         date = date.next_day

If this is a Lords adjournnment day or a Lords virtual sitting day ...

258         if date.is_lords_adjournment_day? or date.is_lords_virtual_sitting_day?

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

261           non_sitting_scrutiny_day_count +=1

If this is not a Lords adjournnment day or a Lords virtual sitting day ...

264         else

... stop cycling through following days.

267           break
268         end
269       end

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

272       date = self
273       for i in ( 1..maximum_day_count )

Go back one day.

276         date = date.prev_day

If this is a Lords adjournnment day or a Lords virtual sitting day ...

279         if date.is_lords_adjournment_day? or date.is_lords_virtual_sitting_day?

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

282           non_sitting_scrutiny_day_count +=1

If this is not a Lords adjournnment day or a Lords virtual sitting day ...

285         else

... stop cycling through preceding days.

288           break
289         end
290       end

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

293       if non_sitting_scrutiny_day_count > maximum_day_count

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

296         is_lords_non_sitting_scrutiny_day = false

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

299       else

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

302         is_lords_non_sitting_scrutiny_day = true
303       end

Returns if this day is a Lords non-sitting scrutiny day

306       is_lords_non_sitting_scrutiny_day
307     end
308   end

(End of methods to calculate non-sitting scrutiny days in both Houses.)

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

A scrutiny day in the Commons is either an actual sitting day in the Commons, or a non-sitting scrutiny day in the Commons.

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.

318   def is_commons_scrutiny_day?
319     self.is_commons_actual_sitting_day? or self.is_commons_non_sitting_scrutiny_day?( 4 )
320   end

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

A scrutiny day in the Lords is either an actual sitting day in the Lords, or a non-sitting scrutiny day in the Lords.

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.

328   def is_lords_scrutiny_day?
329     self.is_lords_actual_sitting_day? or self.is_lords_non_sitting_scrutiny_day?( 4 )
330   end

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

334   def is_either_house_scrutiny_day?
335     self.is_commons_scrutiny_day? or self.is_lords_scrutiny_day?
336   end

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

340   def is_joint_scrutiny_day?
341     self.is_commons_scrutiny_day? and self.is_lords_scrutiny_day?
342   end

(End of set of methods to work out the type of a given day.)

A set of methods to find the first day of a given type.

We want to find the first scrutiny day in either House.

This method is used when a Statutory Instrument is laid outside of a scrutiny period, that is: during a non-sitting period of more than four days, or during a period in which Parliament is prorogued. In such cases, the clock starts from the first actual sitting day in either House following the laying.

This method is used for bicameral negative SIs - and bicameral made affirmatives where the enabling legislation specifies that either House can be sitting.

355   def first_scrutiny_day_in_either_house

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

358     if self.is_calendar_not_populated?

... then we cannot find a first scrutiny day so we stop looking.

361       return nil

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

364     else

... then if this is not a scrutiny day in either House ...

367       unless self.is_either_house_scrutiny_day?

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

370         self.next_day.first_scrutiny_day_in_either_house

... then if this is a scrutiny day in either House ...

373       else

... then return this day as the first scrutiny day in either House.

376         self
377       end
378     end
379   end

We want to find the first scrutiny day in both Houses.

This method is used when a Statutory Instrument is laid outside of a scrutiny period, that is: during an adjournment of more than four days, or during a period in which Parliament is prorogued. The clock starts from the first actual sitting day in both Houses following the laying.

This method is used for bicameral made affirmatives where the enabling legislation specifies that both Houses must be sitting.

387   def first_joint_scrutiny_day

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

390     if self.is_calendar_not_populated?

... then we cannot find a first scrutiny day so we stop looking.

393       return nil

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

396     else

... then if this is not a scrutiny day in both Houses ...

399       unless self.is_joint_scrutiny_day?

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

402         self.next_day.first_joint_scrutiny_day

... then if this is a scrutiny day in both Houses ...

405       else

... then return this day as the first scrutiny day in both Houses.

408         self
409       end
410     end
411   end

We want to find the first scrutiny day in the Commons.

This method is used when a Statutory Instrument is laid outside of a scrutiny period, that is: during an adjournment of more than four days, or during a period in which Parliament is prorogued. The clock starts from the first actual sitting day in the Commons following the laying.

This method is used for negative or made affirmative SIs - laid in the Commons and not laid in the Lords.

419   def first_commons_scrutiny_day

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

422     if self.is_calendar_not_populated?

... then we cannot find a first Commons scrutiny day so we stop looking.

425       return nil

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

428     else

... then if this is not a scrutiny day in the Commons ...

431       unless self.is_commons_scrutiny_day?

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

434         self.next_day.first_commons_scrutiny_day

... then if this is a scrutiny day in the Commons ...

437       else

... then return this day as the first scrutiny day in the Commons.

440         self
441       end
442     end
443   end

We want to find the first parliamentary sitting day in both Houses.

This method is used when a Proposed Negative Statutory Instrument is laid.

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

451   def first_joint_parliamentary_sitting_day

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

454     if self.is_calendar_not_populated?

... then we cannot find a first parliamentary sitting day in both Houses so we stop looking.

457       return nil

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

460     else

... then if this is not a parliamentary sitting day in both Houses ...

463       unless self.is_joint_parliamentary_sitting_day?

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

466         self.next_day.first_joint_parliamentary_sitting_day

... then if this is a parliamentary sitting day in both Houses ...

469       else

... then return this day as the first parliamentary sitting day in both Houses.

472         self
473       end
474     end
475   end

We want to find the last preceding parliamentary sitting day in both Houses.

This method is used when a Proposed Negative Statutory Instrument is laid.

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

483   def last_joint_parliamentary_sitting_day

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

486     if self.is_calendar_not_populated?

... then we cannot find a first parliamentary sitting day in both Houses so we stop looking.

489       return nil

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

492     else

... then if this is not a parliamentary sitting day in both Houses ...

495       unless self.is_joint_parliamentary_sitting_day?

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

498         self.prev_day.last_joint_parliamentary_sitting_day

... then if this is a parliamentary sitting day in both Houses ...

501       else

... then return this day as the first parliamentary sitting day in both Houses.

504         self
505       end
506     end
507   end

We want to find the first actual sitting day in both Houses.

This method is used to calculate periods A and B for treaties.

Even if a treaty is laid or a ministerial statement is made on a joint actual sitting day, the clock does not start until the next joint actual sitting day.

515   def first_joint_actual_sitting_day

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

518     if self.is_calendar_not_populated?

... then we cannot find a first actual sitting day in both Houses so we stop looking.

521       return nil

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

524     else

... then if this is not an actual sitting day in both Houses ...

527       unless self.is_joint_actual_sitting_day?

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

530         self.next_day.first_joint_actual_sitting_day

... then if this is an actual sitting day in both Houses ...

533       else

... then return this day as the first actual sitting day in both Houses.

536         self
537       end
538     end
539   end

We want to find the first actual sitting day in the House of Commons.

This method is used by the House of Commons only sitting day calculation.

Even if an instrument is laid on a House of Commons actual sitting day, the clock does not start until the next House of Commons actual sitting day.

547   def first_commons_actual_sitting_day

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

550     if self.is_calendar_not_populated?

... then we cannot find a first actual sitting day in the House of Commons so we stop looking.

553       return nil

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

556     else

... then if this is not an actual sitting day in the House of Commons ...

559       unless self.is_commons_actual_sitting_day?

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

562         self.next_day.first_commons_actual_sitting_day

... then if this is an actual sitting day in the House of Commons ...

565       else

... then return this day as the first actual sitting day in the House of Commons.

568         self
569       end
570     end
571   end

(End of set of methods to find the first day of a given type.)

A set of methods to return which higher level parliamentary time periods a calendar day sits in.

A calendar day may sit in either a dissolution period or a Parliament period.

If a calendar day sits inside a Parliament period, it may sit inside either a session or a prorogation period.

We want to find which dissolution period a calendar day sits in, if any.

585   def dissolution_period
586     DissolutionPeriod.all.where( "start_date <= ?", self ).where( "end_date >= ?", self).first
587   end

We want to find which Parliament period a calendar day sits in, if any.

590   def parliament_period
591     ParliamentPeriod.find_by_sql([
592       "
593         SELECT *
594         FROM parliament_periods
595         WHERE start_date <= :the_date
596         AND (
597           end_date >= :the_date
598           OR
599           end_date IS NULL
600         )
601       ",
602       the_date: self
603     ]).first
604   end

We want to find which prorogoration period a calendar day sits in, if any.

607   def prorogation_period
608     ProrogationPeriod.all.where( "start_date <= ?", self ).where( "end_date >= ?", self).first
609   end

We want to find which session a calendar day sits in, if any.

612   def session
613     Session.find_by_sql([
614       "
615         SELECT *
616         FROM sessions
617         WHERE start_date <= :the_date
618         AND (
619           end_date >= :the_date
620           OR
621           end_date IS NULL
622         )
623       ",
624       the_date: self
625     ]).first
626   end

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

629   def is_final_day_of_session?
630     is_final_day_of_session = false
631     session = Session.all.where( "end_date = ?", self )
632     is_final_day_of_session = true unless session.empty?
633     is_final_day_of_session
634   end

We want to find the session immediately preceding this date.

638   def preceding_session
639     Session.all.where( "start_date < ?", self ).order( "start_date DESC" ).first
640   end

We want to find the session immediately following this date.

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

644   def following_session
645     Session.all.where( "start_date > ?", self ).order( "start_date" ).first
646   end

Generate label for the day type in the Commons in a session.

649   def commons_day_type
650     if self.is_commons_parliamentary_sitting_day?
651       day_type = 'Parliamentary sitting day'
652     elsif self.is_commons_actual_sitting_day?
653       day_type = "Continuation sitting day"
654     elsif self.is_commons_virtual_sitting_day?
655       day_type = 'Virtual sitting day'
656     elsif self.is_commons_non_sitting_scrutiny_day?( 4 )
657       day_type = 'Scrutiny non-sitting day'
658     elsif self.is_commons_adjournment_day?
659       day_type = self.commons_adjournment_day_label
660     elsif self.session
661       day_type = 'Session day of unknown type'
662     elsif self.is_prorogation_day?
663       day_type = 'Prorogation'
664     elsif self.is_dissolution_day?
665       day_type = 'Dissolution'
666     end
667     day_type
668   end

Generate label for the day type in the Lords in a session.

671   def lords_day_type
672     if self.is_lords_parliamentary_sitting_day?
673       day_type = 'Parliamentary sitting day'
674     elsif self.is_lords_actual_sitting_day?
675       day_type = "Continuation sitting day"
676     elsif self.is_lords_virtual_sitting_day?
677       day_type = 'Virtual sitting day'
678     elsif self.is_lords_non_sitting_scrutiny_day?( 4 )
679       day_type = 'Scrutiny non-sitting day'
680     elsif self.is_lords_adjournment_day?
681       day_type = self.lords_adjournment_day_label
682     elsif self.session
683       day_type = 'Session day of unknown type'
684     elsif self.is_prorogation_day?
685       day_type = 'Prorogation'
686     elsif self.is_dissolution_day?
687       day_type = 'Dissolution'
688     end
689     day_type
690   end

Generate a label to say whether it's a scrutiny day in the Commons or not.

693   def is_commons_scrutiny_day_label
694     if self.is_commons_scrutiny_day?
695       label = 'True'
696     else
697       label = 'False'
698     end
699     label
700   end

Generate a label to say whether it's a scrutiny day in the Lords or not.

703   def is_lords_scrutiny_day_label
704     if self.is_lords_scrutiny_day?
705       label = 'True'
706     else
707       label = 'False'
708     end
709     label
710   end

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

713   def commons_adjournment_day_label
714     commons_adjournment_day_label = 'Adjournment day'

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

717     recess_date = RecessDate
718       .all
719       .where( "start_date <= ?", self )
720       .where( "end_date >= ?", self )
721       .where( house_id: 1 )
722       .first

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

725     if recess_date

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

728       commons_adjournment_day_label += ' (' + recess_date.description + ')'
729     end
730     commons_adjournment_day_label
731   end

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

734   def lords_adjournment_day_label
735     lords_adjournment_day_label = 'Adjournment day'

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

738     recess_date = RecessDate
739       .all
740       .where( "start_date <= ?", self )
741       .where( "end_date >= ?", self )
742       .where( house_id: 2 )
743       .first

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

746     if recess_date

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

749       lords_adjournment_day_label += ' (' + recess_date.description + ')'
750     end
751     lords_adjournment_day_label
752   end
753 end
755 Date.include(DateMonkeyPatch)