A monkey patched Ruby date class to handle UK Parliament specific day types.

 3 class Date

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.

 15   def is_commons_actual_sitting_day?
 16     SittingDay.all.where( 'start_date <= ?',  self ).where( 'end_date >= ?',  self ).where( house_id: 1 ).first
 17   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.

 27   def is_lords_actual_sitting_day?
 28     SittingDay.all.where( 'start_date <= ?',  self ).where( 'end_date >= ?',  self ).where( house_id: 2 ).first
 29   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.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Go forward one day.

 176         date = date.next_day

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

 179         if date.is_commons_adjournment_day? or date.is_commons_virtual_sitting_day?

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

 182           non_sitting_scrutiny_day_count +=1

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

 185         else

... stop cycling through following days.

 188           break
 189         end
 190       end

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

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

Go back one day.

 197         date = date.prev_day

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

 200         if date.is_commons_adjournment_day? or date.is_commons_virtual_sitting_day?

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

 203           non_sitting_scrutiny_day_count +=1

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

 206         else  

... stop cycling through preceding days.

 209           break
 210         end
 211       end

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

 214       if non_sitting_scrutiny_day_count > maximum_day_count

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

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

 220       else

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

 223         is_commons_non_sitting_scrutiny_day = true
 224       end

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

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

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

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

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

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

Go forward one day.

 258         date = date.next_day

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

 261         if date.is_lords_adjournment_day? or date.is_lords_virtual_sitting_day?

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

 264           non_sitting_scrutiny_day_count +=1

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

 267         else

... stop cycling through following days.

 270           break
 271         end
 272       end

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

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

Go back one day.

 279         date = date.prev_day

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

 282         if date.is_lords_adjournment_day? or date.is_lords_virtual_sitting_day?

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

 285           non_sitting_scrutiny_day_count +=1

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

 288         else  

... stop cycling through preceding days.

 291           break
 292         end
 293       end

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

 296       if non_sitting_scrutiny_day_count > maximum_day_count

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

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

 302       else

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

 305         is_lords_non_sitting_scrutiny_day = true
 306       end

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

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

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

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

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

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

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

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

 358   def first_scrutiny_day_in_either_house

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

 361     if self.is_calendar_not_populated?

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

 364       return nil

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

 367     else

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

 370       unless self.is_either_house_scrutiny_day?

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

 373         self.next_day.first_scrutiny_day_in_either_house

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

 376       else

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

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

 390   def first_joint_scrutiny_day

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

 393     if self.is_calendar_not_populated?

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

 396       return nil

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

 399     else

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

 402       unless self.is_joint_scrutiny_day?

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

 405         self.next_day.first_joint_scrutiny_day

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

 408       else

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

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

 422   def first_commons_scrutiny_day

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

 425     if self.is_calendar_not_populated?

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

 428       return nil

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

 431     else

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

 434       unless self.is_commons_scrutiny_day?

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

 437         self.next_day.first_commons_scrutiny_day

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

 440       else

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

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

 454   def first_joint_parliamentary_sitting_day

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

 457     if self.is_calendar_not_populated?

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

 460       return nil

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

 463     else

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

 466       unless self.is_joint_parliamentary_sitting_day?

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

 469         self.next_day.first_joint_parliamentary_sitting_day

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

 472       else

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

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

 486   def first_joint_actual_sitting_day

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

 489     if self.is_calendar_not_populated?

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

 492       return nil

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

 495     else

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

 498       unless self.is_joint_actual_sitting_day?

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

 501         self.next_day.first_joint_actual_sitting_day

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

 504       else

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

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

 518   def first_commons_actual_sitting_day

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

 521     if self.is_calendar_not_populated?

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

 524       return nil

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

 527     else

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

 530       unless self.is_commons_actual_sitting_day?

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

 533         self.next_day.first_commons_actual_sitting_day

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

 536       else

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

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

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

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

 561   def parliament_period
 562     ParliamentPeriod.find_by_sql(
 563       "
 564         SELECT *
 565         FROM parliament_periods
 566         WHERE start_date <= '#{self}'
 567         AND (
 568           end_date >= '#{self}'
 569           OR
 570           end_date IS NULL
 571         )
 572       "
 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 <= '#{self}'
 588         AND (
 589           end_date >= '#{self}'
 590           OR
 591           end_date IS NULL
 592         )
 593       "
 594     ).first
 595   end

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

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

We want to find the session immediately preceding this date.

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

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

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

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

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

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

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

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

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

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

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

 682   def commons_adjournment_day_label
 683     commons_adjournment_day_label = 'Adjournment day'

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

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

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

 694     if recess_date

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

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

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

 703   def lords_adjournment_day_label
 704     lords_adjournment_day_label = 'Adjournment day'

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

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

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

 715     if recess_date

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

 718       lords_adjournment_day_label += ' (' + recess_date.description + ')'
 719     end
 720     lords_adjournment_day_label
 721   end
 722 end