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.

34   def is_commons_parliamentary_sitting_day?
35     SittingDay.all.where( 'start_date = ?',  self ).where( house_id: 1 ).first
36   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.

42   def is_lords_parliamentary_sitting_day?
43     SittingDay.all.where( 'start_date = ?',  self ).where( house_id: 2 ).first
44   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.

54   def is_commons_virtual_sitting_day?
55     VirtualSittingDay.all.where( 'start_date <= ?',  self ).where( 'end_date >= ?',  self ).where( house_id: 1 ).first
56   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.

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

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

70   def is_either_house_actual_sitting_day?
71     self.is_commons_actual_sitting_day? or self.is_lords_actual_sitting_day?
72   end

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

76   def is_joint_actual_sitting_day?
77     self.is_commons_actual_sitting_day? and self.is_lords_actual_sitting_day?
78   end

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

82   def is_either_house_parliamentary_sitting_day?
83     self.is_commons_parliamentary_sitting_day? or self.is_lords_parliamentary_sitting_day?
84   end

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

88   def is_joint_parliamentary_sitting_day?
89     self.is_commons_parliamentary_sitting_day? and self.is_lords_parliamentary_sitting_day?
90   end

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

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

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

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

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

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

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

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

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

118   def is_dissolution_day?
119     DissolutionDay.all.where( 'date = ?',  self ).first
120   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.

126   def is_calendar_populated?
127     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?
128   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.

136   def is_calendar_not_populated?
137     !self.is_calendar_populated?
138   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.

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

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

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

170       date = self
171       for i in ( 1..maximum_day_count )

Go forward one day.

175         date = date.next_day

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

178         if date.is_commons_adjournment_day? or date.is_commons_virtual_sitting_day?

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

181           non_sitting_scrutiny_day_count +=1

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

184         else

... stop cycling through following days.

187           break
188         end
189       end

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

192       date = self
193       for i in ( 1..maximum_day_count )

Go back one day.

196         date = date.prev_day

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

199         if date.is_commons_adjournment_day? or date.is_commons_virtual_sitting_day?

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

202           non_sitting_scrutiny_day_count +=1

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

205         else

... stop cycling through preceding days.

208           break
209         end
210       end

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

213       if non_sitting_scrutiny_day_count > maximum_day_count

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

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

219       else

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

222         is_commons_non_sitting_scrutiny_day = true
223       end

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

226       is_commons_non_sitting_scrutiny_day
227     end
228   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.

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

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

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

252       date = self
253       for i in ( 1..maximum_day_count )

Go forward one day.

257         date = date.next_day

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

260         if date.is_lords_adjournment_day? or date.is_lords_virtual_sitting_day?

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

263           non_sitting_scrutiny_day_count +=1

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

266         else

... stop cycling through following days.

269           break
270         end
271       end

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

274       date = self
275       for i in ( 1..maximum_day_count )

Go back one day.

278         date = date.prev_day

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

281         if date.is_lords_adjournment_day? or date.is_lords_virtual_sitting_day?

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

284           non_sitting_scrutiny_day_count +=1

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

287         else

... stop cycling through preceding days.

290           break
291         end
292       end

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

295       if non_sitting_scrutiny_day_count > maximum_day_count

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

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

301       else

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

304         is_lords_non_sitting_scrutiny_day = true
305       end

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

308       is_lords_non_sitting_scrutiny_day
309     end
310   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.

320   def is_commons_scrutiny_day?
321     self.is_commons_actual_sitting_day? or self.is_commons_non_sitting_scrutiny_day?( 4 )
322   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.

330   def is_lords_scrutiny_day?
331     self.is_lords_actual_sitting_day? or self.is_lords_non_sitting_scrutiny_day?( 4 )
332   end

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

336   def is_either_house_scrutiny_day?
337     self.is_commons_scrutiny_day? or self.is_lords_scrutiny_day?
338   end

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

342   def is_joint_scrutiny_day?
343     self.is_commons_scrutiny_day? and self.is_lords_scrutiny_day?
344   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.

357   def first_scrutiny_day_in_either_house

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

360     if self.is_calendar_not_populated?

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

363       return nil

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

366     else

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

369       unless self.is_either_house_scrutiny_day?

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

372         self.next_day.first_scrutiny_day_in_either_house

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

375       else

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

378         self
379       end
380     end
381   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.

389   def first_joint_scrutiny_day

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

392     if self.is_calendar_not_populated?

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

395       return nil

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

398     else

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

401       unless self.is_joint_scrutiny_day?

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

404         self.next_day.first_joint_scrutiny_day

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

407       else

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

410         self
411       end
412     end
413   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.

421   def first_commons_scrutiny_day

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

424     if self.is_calendar_not_populated?

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

427       return nil

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

430     else

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

433       unless self.is_commons_scrutiny_day?

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

436         self.next_day.first_commons_scrutiny_day

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

439       else

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

442         self
443       end
444     end
445   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.

453   def first_joint_parliamentary_sitting_day

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

456     if self.is_calendar_not_populated?

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

459       return nil

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

462     else

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

465       unless self.is_joint_parliamentary_sitting_day?

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

468         self.next_day.first_joint_parliamentary_sitting_day

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

471       else

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

474         self
475       end
476     end
477   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.

485   def first_joint_actual_sitting_day

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

488     if self.is_calendar_not_populated?

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

491       return nil

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

494     else

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

497       unless self.is_joint_actual_sitting_day?

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

500         self.next_day.first_joint_actual_sitting_day

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

503       else

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

506         self
507       end
508     end
509   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.

517   def first_commons_actual_sitting_day

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

520     if self.is_calendar_not_populated?

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

523       return nil

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

526     else

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

529       unless self.is_commons_actual_sitting_day?

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

532         self.next_day.first_commons_actual_sitting_day

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

535       else

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

538         self
539       end
540     end
541   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.

555   def dissolution_period
556     DissolutionPeriod.all.where( "start_date <= ?", self ).where( "end_date >= ?", self).first
557   end

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

560   def parliament_period
561     ParliamentPeriod.find_by_sql([
562       "
563         SELECT *
564         FROM parliament_periods
565         WHERE start_date <= :the_date
566         AND (
567           end_date >= :the_date
568           OR
569           end_date IS NULL
570         )
571       ",
572       the_date: self
573     ]).first
574   end

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

577   def prorogation_period
578     ProrogationPeriod.all.where( "start_date <= ?", self ).where( "end_date >= ?", self).first
579   end

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

582   def session
583     Session.find_by_sql([
584       "
585         SELECT *
586         FROM sessions
587         WHERE start_date <= :the_date
588         AND (
589           end_date >= :the_date
590           OR
591           end_date IS NULL
592         )
593       ",
594       the_date: self
595     ]).first
596   end

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

599   def is_final_day_of_session?
600     is_final_day_of_session = false
601     session = Session.all.where( "end_date = ?", self )
602     is_final_day_of_session = true unless session.empty?
603     is_final_day_of_session
604   end

We want to find the session immediately preceding this date.

608   def preceding_session
609     Session.all.where( "start_date < ?", self ).order( "start_date DESC" ).first
610   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.

614   def following_session
615     Session.all.where( "start_date > ?", self ).order( "start_date" ).first
616   end

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

619   def commons_day_type
620     if self.is_commons_parliamentary_sitting_day?
621       day_type = 'Parliamentary sitting day'
622     elsif self.is_commons_actual_sitting_day?
623       day_type = "Continuation sitting day"
624     elsif self.is_commons_virtual_sitting_day?
625       day_type = 'Virtual sitting day'
626     elsif self.is_commons_non_sitting_scrutiny_day?( 4 )
627       day_type = 'Scrutiny non-sitting day'
628     elsif self.is_commons_adjournment_day?
629       day_type = self.commons_adjournment_day_label
630     elsif self.session
631       day_type = 'Session day of unknown type'
632     elsif self.is_prorogation_day?
633       day_type = 'Prorogation'
634     elsif self.is_dissolution_day?
635       day_type = 'Dissolution'
636     end
637     day_type
638   end

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

641   def lords_day_type
642     if self.is_lords_parliamentary_sitting_day?
643       day_type = 'Parliamentary sitting day'
644     elsif self.is_lords_actual_sitting_day?
645       day_type = "Continuation sitting day"
646     elsif self.is_lords_virtual_sitting_day?
647       day_type = 'Virtual sitting day'
648     elsif self.is_lords_non_sitting_scrutiny_day?( 4 )
649       day_type = 'Scrutiny non-sitting day'
650     elsif self.is_lords_adjournment_day?
651       day_type = self.lords_adjournment_day_label
652     elsif self.session
653       day_type = 'Session day of unknown type'
654     elsif self.is_prorogation_day?
655       day_type = 'Prorogation'
656     elsif self.is_dissolution_day?
657       day_type = 'Dissolution'
658     end
659     day_type
660   end

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

663   def is_commons_scrutiny_day_label
664     if self.is_commons_scrutiny_day?
665       label = 'True'
666     else
667       label = 'False'
668     end
669     label
670   end

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

673   def is_lords_scrutiny_day_label
674     if self.is_lords_scrutiny_day?
675       label = 'True'
676     else
677       label = 'False'
678     end
679     label
680   end

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

683   def commons_adjournment_day_label
684     commons_adjournment_day_label = 'Adjournment day'

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

687     recess_date = RecessDate
688       .all
689       .where( "start_date <= ?", self )
690       .where( "end_date >= ?", self )
691       .where( house_id: 1 )
692       .first

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

695     if recess_date

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

698       commons_adjournment_day_label += ' (' + recess_date.description + ')'
699     end
700     commons_adjournment_day_label
701   end

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

704   def lords_adjournment_day_label
705     lords_adjournment_day_label = 'Adjournment day'

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

708     recess_date = RecessDate
709       .all
710       .where( "start_date <= ?", self )
711       .where( "end_date >= ?", self )
712       .where( house_id: 2 )
713       .first

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

716     if recess_date

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

719       lords_adjournment_day_label += ' (' + recess_date.description + ')'
720     end
721     lords_adjournment_day_label
722   end
723 end
725 Date.include(DateMonkeyPatch)