Module: Holidays

Included in:
Date
Defined in:
lib/holidays.rb,
lib/holidays/se.rb,
lib/holidays/fr.rb,
lib/holidays/be.rb,
lib/holidays/de.rb,
lib/holidays/jp.rb,
lib/holidays/lt.rb,
lib/holidays/vi.rb,
lib/holidays/hr.rb,
lib/holidays/is.rb,
lib/holidays/pl.rb,
lib/holidays/br.rb,
lib/holidays/pt.rb,
lib/holidays/gb.rb,
lib/holidays/hu.rb,
lib/holidays/it.rb,
lib/holidays/li.rb,
lib/holidays/at.rb,
lib/holidays/ar.rb,
lib/holidays/cz.rb,
lib/holidays/sg.rb,
lib/holidays/fi.rb,
lib/holidays/si.rb,
lib/holidays/ma.rb,
lib/holidays/us.rb,
lib/holidays/za.rb,
lib/holidays/ro.rb,
lib/holidays/cr.rb,
lib/holidays/sk.rb,
lib/holidays/ph.rb,
lib/holidays/cl.rb,
lib/holidays/nl.rb,
lib/holidays/el.rb,
lib/holidays/no.rb,
lib/holidays/es.rb,
lib/holidays/au.rb,
lib/holidays/mx.rb,
lib/holidays/dk.rb,
lib/holidays/ch.rb,
lib/holidays/ie.rb,
lib/holidays/ve.rb,
lib/holidays/ca.rb,
lib/holidays/nz.rb,
lib/holidays/ups.rb,
lib/holidays/nyse.rb,
lib/holidays/nerc.rb,
lib/holidays/europe.rb,
lib/holidays/fed_ex.rb,
lib/holidays/version.rb,
lib/holidays/ecb_target.rb,
lib/holidays/scandinavia.rb,
lib/holidays/north_america.rb,
lib/holidays/united_nations.rb,
lib/holidays/federal_reserve.rb

Overview

Region options

Holidays can be defined as belonging to one or more regions and sub regions. The Holidays#on, Holidays#between, Date#holidays and Date#holiday? methods each allow you to specify a specific region.

There are several different ways that you can specify a region:

:region

By region. For example, return holidays in the Canada with :ca.

:region_

By region and sub regions. For example, return holidays in Germany and all its sub regions with :de_.

:region_sub

By sub region. Return national holidays in Spain plus holidays in Spain's Valencia region with :es_v.

:any

Any region. Return holidays from any loaded region.

You can load all the available holiday definition sets by running

Holidays.load_all

Other options

:observed

Return holidays on the day they are observed (e.g. on a Monday if they fall on a Sunday).

:informal

Include informal holidays (e.g. Valentine's Day)

Examples

Return all holidays in the :ca and :us regions on the day that they are observed.

Holidays.between(from, to, :ca, :us, :observed)

Return all holidays in :ca and any :ca sub-region.

Holidays.between(from, to, :ca_)

Return all holidays in :ca_bc sub-region (which includes the :ca), including informal holidays.

Holidays.between(from, to, :ca_bc, :informal)

Defined Under Namespace

Modules: AR, AT, AU, BE, BR, CA, CH, CL, CR, CZ, DE, DK, ECB_TARGET, EL, ES, EUROPE, FEDERAL_RESERVE, FED_EX, FI, FR, GB, HR, HU, IE, IS, IT, JP, LI, LT, MA, MX, NERC, NL, NO, NORTH_AMERICA, NYSE, NZ, PH, PL, PT, RO, SCANDINAVIA, SE, SG, SI, SK, UNITED_NATIONS, UPS, US, VE, VI, ZA Classes: UnknownRegionError

Constant Summary

WEEKS =
{:first => 1, :second => 2, :third => 3, :fourth => 4, :fifth => 5, :last => -1, :second_last => -2, :third_last => -3}
MONTH_LENGTHS =
[31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
DAY_SYMBOLS =
Date::DAYNAMES.collect { |n| n.downcase.intern }
DEFINITION_PATH =
File.expand_path(File.dirname(__FILE__) + '/holidays/')
VERSION =
'1.2.0'
@@regions =
[]
@@holidays_by_month =
{}
@@proc_cache =
{}
@@cache =
{}
@@cache_range =
{}

Class Method Summary collapse

Class Method Details

.available(full_path = false) ⇒ Object

Returns an array of symbols all the available holiday definitions.

Optional `full_path` param is used internally for loading all the definitions.



312
313
314
315
# File 'lib/holidays.rb', line 312

def self.available(full_path = false)
  paths = Dir.glob(DEFINITION_PATH + '/*.rb')
  full_path ? paths : paths.collect { |path| path.match(/([a-z_-]+)\.rb/i)[1].to_sym }
end

.between(start_date, end_date, *options) ⇒ Object

Get all holidays occuring between two dates, inclusively.

Returns an array of hashes or nil.

Each holiday is returned as a hash with the following fields:

start_date

Ruby Date object.

end_date

Ruby Date object.

options

One or more region symbols, :informal and/or :observed.

Example

from = Date.civil(2008,7,1)
to   = Date.civil(2008,7,31)

Holidays.between(from, to, :ca, :us)
=> [{:name => 'Canada Day', :regions => [:ca]...}
    {:name => 'Independence Day'', :regions => [:us], ...}]


112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/holidays.rb', line 112

def self.between(start_date, end_date, *options)
  # remove the timezone
  start_date = start_date.new_offset(0) + start_date.offset if start_date.respond_to?(:new_offset)
  end_date = end_date.new_offset(0) + end_date.offset if end_date.respond_to?(:new_offset)

  # get simple dates
  start_date, end_date = get_date(start_date), get_date(end_date)

  if range = @@cache_range[options]
    if range.begin < start_date && range.end > end_date
      return @@cache[options].select do |holiday|
        holiday[:date] >= start_date && holiday[:date] <= end_date
      end
    end
  end

  regions, observed, informal = parse_options(options)
  holidays = []

  dates = {}
  (start_date..end_date).each do |date|
    # Always include month '0' for variable-month holidays
    dates[date.year] = [0] unless dates[date.year]
    # TODO: test this, maybe should push then flatten
    dates[date.year] << date.month unless dates[date.year].include?(date.month)
  end

  dates.each do |year, months|
    months.each do |month|
      next unless hbm = @@holidays_by_month[month]

      hbm.each do |h|
        next unless in_region?(regions, h[:regions])

        # Skip informal holidays unless they have been requested
        next if h[:type] == :informal and not informal

        if h[:function]
          # Holiday definition requires a calculation
          result = call_proc(h[:function], year)

          # Procs may return either Date or an integer representing mday
          if result.kind_of?(Date)
            month = result.month
            mday = result.mday
          else
            mday = result
          end
        else
          # Calculate the mday
          mday = h[:mday] || Date.calculate_mday(year, month, h[:week], h[:wday])
        end

        # Silently skip bad mdays
        begin
          date = Date.civil(year, month, mday)
        rescue; next; end

        # If the :observed option is set, calculate the date when the holiday
        # is observed.
        if observed and h[:observed]
          date = call_proc(h[:observed], date)
        end

        if date.between?(start_date, end_date)
          holidays << {:date => date, :name => h[:name], :regions => h[:regions]}
        end

      end
    end
  end

  holidays.sort{|a, b| a[:date] <=> b[:date] }
end

.ca_victoria_day(year) ⇒ Object

Monday on or before May 24



66
67
68
69
70
71
72
73
74
# File 'lib/holidays/ca.rb', line 66

def self.ca_victoria_day(year)
  date = Date.civil(year,5,24)
  if date.wday > 1
    date -= (date.wday - 1)
  elsif date.wday == 0
    date -= 6
  end
  date
end

.cacheObject



58
# File 'lib/holidays.rb', line 58

def cache; @@cache; end

.cache_between(start_date, end_date, *options) ⇒ Object

Allows a developer to explicitly calculate and cache holidays within a given period



188
189
190
191
192
# File 'lib/holidays.rb', line 188

def self.cache_between(start_date, end_date, *options)
  start_date, end_date = get_date(start_date), get_date(end_date)
  @@cache[options]       = between(start_date, end_date, *options)
  @@cache_range[options] = start_date..end_date
end

.cache_rangeObject



57
# File 'lib/holidays.rb', line 57

def cache_range; @@cache_range; end

.ch_ge_jeune_genevois(year) ⇒ Object

Thursday after the first Sunday of September



64
65
66
67
68
69
70
71
72
# File 'lib/holidays/ch.rb', line 64

def self.ch_ge_jeune_genevois(year)
  date = Date.civil(year,9,1)
  # Find the first Sunday of September
  until date.wday.eql? 0 do
    date += 1
  end
  # Thursday is four days after Sunday
  date + 4
end

.ch_gl_naefelser_fahrt(year) ⇒ Object

First Thursday of April. If the first Thursday of April is in the week before easter, then a week later.



76
77
78
79
80
81
82
83
84
85
86
# File 'lib/holidays/ch.rb', line 76

def self.ch_gl_naefelser_fahrt(year)
  date = Date.civil(year,4,1)
  # Find the first Thursday of April
  until date.wday.eql? 4 do
    date += 1
  end
  if date.eql?(easter(year)-3)
    date += 7
  end
  date
end

.ch_vd_lundi_du_jeune_federal(year) ⇒ Object

Monday after the third Sunday of September



51
52
53
54
55
56
57
58
59
60
# File 'lib/holidays/ch.rb', line 51

def self.ch_vd_lundi_du_jeune_federal(year)
  date = Date.civil(year,9,1)
  # Find the first Sunday of September
  until date.wday.eql? 0 do
    date += 1
  end
  # There are 15 days between the first Sunday
  # and the Monday after the third Sunday
  date + 15
end

.closest_monday(date) ⇒ Object



49
50
51
52
53
54
55
56
57
58
# File 'lib/holidays/nz.rb', line 49

def self.closest_monday(date)
  if [1, 2, 3, 4].include?(date.wday)
    date -= (date.wday - 1)
  elsif 0 == date.wday
    date += 1
  else
    date += 8 - date.wday
  end
  date
end

.de_buss_und_bettag(year) ⇒ Object

Germany: Wednesday before November 23



41
42
43
44
45
46
47
48
49
# File 'lib/holidays/de.rb', line 41

def self.de_buss_und_bettag(year)
  date = Date.civil(year,11,23)
  if date.wday > 3
    date -= (date.wday - 3)
  else
    date -= (date.wday + 4)
  end
  date
end

.easter(year) ⇒ Object

Get the date of Easter Sunday in a given year. From Easter Sunday, it is possible to calculate many traditional holidays in Western countries. Returns a Date object.



228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
# File 'lib/holidays.rb', line 228

def self.easter(year)
  y = year
  a = y % 19
  b = y / 100
  c = y % 100
  d = b / 4
  e = b % 4
  f = (b + 8) / 25
  g = (b - f + 1) / 3
  h = (19 * a + b - d - g + 15) % 30
  i = c / 4
  k = c % 4
  l = (32 + 2 * e + 2 * i - h - k) % 7
  m = (a + 11 * h + 22 * l) / 451
  month = (h + l - 7 * m + 114) / 31
  day = ((h + l - 7 * m + 114) % 31) + 1
  Date.civil(year, month, day)
end

.fi_juhannusaatto(year) ⇒ Object

Finland: Mid-summer eve (Friday between June 19–25)



41
42
43
44
45
46
47
48
49
# File 'lib/holidays/fi.rb', line 41

def self.fi_juhannusaatto(year)
  date = Date.civil(year,6,19)
  if date.wday > 5 #if 19.6 is saturday
    date += 6
  else 
    date += (5 - date.wday)
  end
  date
end

.fi_juhannuspaiva(year) ⇒ Object

Finland: Mid-summer (Saturday between June 20–26)



53
54
55
56
57
# File 'lib/holidays/fi.rb', line 53

def self.fi_juhannuspaiva(year)
  date = Date.civil(year,6,20)
  date += (6 - date.wday)
  date
end

.fi_pyhainpaiva(year) ⇒ Object

Finland: All Saint's Day (Saturday between Oct 31 and Nov 6)



61
62
63
64
65
# File 'lib/holidays/fi.rb', line 61

def self.fi_pyhainpaiva(year)
  date = Date.civil(year,10,31)
  date += (6 - date.wday)
  date
end

.full_week?(date, *options) ⇒ Boolean

Does the given work-week have any holidays?

date

A Date object.

:options

One or more region symbols, and/or :informal. Automatically includes :observed. If you don't want this, pass :no_observed

The given Date can be any day of the week. Returns true if any holidays fall on Monday - Friday of the given week.

Returns:

  • (Boolean)


86
87
88
89
90
91
92
93
94
# File 'lib/holidays.rb', line 86

def self.full_week?(date, *options)
  days_to_monday = date.wday - 1
  days_to_friday = 5 - date.wday
  start_date = date - days_to_monday
  end_date = date + days_to_friday
  options += [:observed] unless options.include?(:no_observed)
  options.delete(:no_observed)
  self.between(start_date, end_date, options).empty?
end

.g20_day_2014_only(year) ⇒ Object



78
79
80
# File 'lib/holidays/au.rb', line 78

def self.g20_day_2014_only(year)
  year == 2014 ? 14 : nil
end

.hobart_show_day(year) ⇒ Object

worksafe.tas.gov.au/__data/assets/pdf_file/0008/287036/Public_Holidays_2014.pdf The Thursday before the fourth Saturday in October.



85
86
87
88
# File 'lib/holidays/au.rb', line 85

def self.hobart_show_day(year)
  fourth_sat_in_oct = Date.civil(year, 10, Date.calculate_mday(year, 10, 4, :saturday))
  fourth_sat_in_oct - 2 # the thursday before
end

.ie_st_stephens_day(date) ⇒ Object

Ireland - Stephens Day is always the day after christmas day



35
36
37
38
39
40
# File 'lib/holidays/ie.rb', line 35

def self.ie_st_stephens_day(date)
  date += 2 if date.wday == 6
  date += 2 if date.wday == 0
  date += 1 if date.wday == 1
  date
end

.is_sumardagurinn_fyrsti(year) ⇒ Object

Iceland: first day of summer (Thursday after 18 April)



52
53
54
55
56
57
58
59
60
# File 'lib/holidays/is.rb', line 52

def self.is_sumardagurinn_fyrsti(year)
  date = Date.civil(year,4,18)
  if date.wday < 4
    date += (4 - date.wday)
  else date
    date += (11 - date.wday)
  end
  date
end

.jp_citizons_holiday(year) ⇒ Object



94
95
96
97
98
99
100
101
102
# File 'lib/holidays/jp.rb', line 94

def self.jp_citizons_holiday(year)
  year < 2003 and return nil
  ncd = Holidays.jp_national_culture_day(year)
  if ncd.wday == 3
    ncd - 1
  else
    nil
  end
end

.jp_national_culture_day(year) ⇒ Object



74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/holidays/jp.rb', line 74

def self.jp_national_culture_day(year)
  day =
    case year
    when 1851..1899
      22.2588
    when 1900..1979
      23.2588
    when 1980..2099
      23.2488
    when 2100..2150
      24.2488
    else
      raise IndexError.new("Out of range")
    end
  day += 0.242194 * (year - 1980) - ((year - 1980)/4).floor
  day = day.floor
  Date.civil(year, 9, day)
end

.jp_substitute_holiday(*date) ⇒ Object



105
106
107
108
# File 'lib/holidays/jp.rb', line 105

def self.jp_substitute_holiday(*date)
  date = date[0].kind_of?(Date) ? date.first : Date.civil(*date)
  date.wday == 0 ? date+1 : nil
end

.jp_vernal_equinox_day(year) ⇒ Object



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/holidays/jp.rb', line 54

def self.jp_vernal_equinox_day(year)
  day =
    case year
    when 1851..1899
      19.8277
    when 1900..1979
      20.8357
    when 1980..2099
      20.8431
    when 2100..2150
      21.8510
    else
      raise IndexError.new("Out of range")
    end
  day += 0.242194 * (year - 1980) - ((year - 1980)/4).floor
  day = day.floor
  Date.civil(year, 3, day)
end

.load_allObject

Load all available holiday definitions



323
324
325
# File 'lib/holidays.rb', line 323

def self.load_all
  self.available(true).each { |path| require path }
end

.load_custom(*files) ⇒ Object

Parses provided holiday definition file(s) and loads them so that they are immediately available.



328
329
330
331
# File 'lib/holidays.rb', line 328

def self.load_custom(*files)
  regions, rules_by_month, custom_methods, tests = self.parse_definition_files(files)
  merge_defs(regions, rules_by_month)
end

.merge_defs(regions, holidays) ⇒ Object

Merge a new set of definitions into the Holidays module.

This method is automatically called when including holiday definition files.



198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/holidays.rb', line 198

def self.merge_defs(regions, holidays) # :nodoc:
  @@regions = @@regions | regions
  @@regions.uniq!

  holidays.each do |month, holiday_defs|
    @@holidays_by_month[month] = [] unless @@holidays_by_month[month]
    holiday_defs.each do |holiday_def|

        exists = false
        @@holidays_by_month[month].each do |ex|
          # TODO: gross.
          if ex[:name] == holiday_def[:name] and ex[:wday] == holiday_def[:wday] and ex[:mday] == holiday_def[:mday] and ex[:week] == holiday_def[:week] and ex[:function_id] == holiday_def[:function_id] and ex[:type] == holiday_def[:type] and ex[:observed_id] == holiday_def[:observed_id]
            # append regions
            ex[:regions] << holiday_def[:regions]

            # Should do this once we're done
            ex[:regions].flatten!
            ex[:regions].uniq!
            exists = true
          end
        end

        @@holidays_by_month[month] << holiday_def  unless exists
    end
  end
end

.next_week(date) ⇒ Object



66
67
68
# File 'lib/holidays/nz.rb', line 66

def self.next_week(date)
  date + 7
end

.on(date, *options) ⇒ Object

Get all holidays on a given date.

date

A Date object.

:options

One or more region symbols, :informal and/or :observed.

Returns an array of hashes or nil. See Holidays#between for the output format.

Also available via Date#holidays.



75
76
77
# File 'lib/holidays.rb', line 75

def self.on(date, *options)
  self.between(date, date, options)
end

.orthodox_easter(year) ⇒ Object

A method to calculate the orthodox easter date, returns date in the Gregorian (western) calendar Safe until appr. 4100 AD, when one leap day will be removed. Returns a Date object.



250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
# File 'lib/holidays.rb', line 250

def self.orthodox_easter(year)
  y = year
  g = y % 19
  i = (19 * g + 15) % 30
  j = (year + year/4 + i) % 7
  j_month = 3 + (i - j + 40) / 44
  j_day = i - j + 28 - 31 * (j_month / 4)
  j_date = Date.civil(year, j_month, j_day)
  case
    # up until 1582, julian and gregorian easter dates were identical
    when year <= 1582
      offset = 0
    # between the years 1583 and 1699 10 days are added to the julian day count
    when (year >= 1583 and year <= 1699)
      offset = 10
    # after 1700, 1 day is added for each century, except if the century year is exactly divisible by 400 (in which case no days are added).
    # Safe until 4100 AD, when one leap day will be removed.
    when year >= 1700
      offset = (year - 1700).divmod(100)[0] + ((year - year.divmod(100)[1]).divmod(400)[1] == 0 ? 0 : 1) - (year - year.divmod(100)[1] - 1700).divmod(400)[0] + 10
  end
  # add offset to the julian day
  return Date.jd(j_date.jd + offset)
end

.parse_definition_files_and_return_source(module_name, *files) ⇒ Object

Parses provided holiday definition file(s) and returns strings containing the generated module and test source



334
335
336
337
338
339
# File 'lib/holidays.rb', line 334

def self.parse_definition_files_and_return_source(module_name, *files)
  regions, rules_by_month, custom_methods, tests = self.parse_definition_files(files)
  module_src, test_src = self.generate_definition_source(module_name, files, regions, rules_by_month, custom_methods, tests)

  return module_src, test_src
end

.ph_heroes_day(year) ⇒ Object

last Monday of August



42
43
44
45
46
# File 'lib/holidays/ph.rb', line 42

def self.ph_heroes_day(year)
  date = Date.new(year, 8, -1)
  return date if date.wday == 1
  date -= date.wday - 1
end

.pl_trzech_kroli(year) ⇒ Object

Poland: January 6 is holiday since 2011



66
67
68
# File 'lib/holidays/pl.rb', line 66

def self.pl_trzech_kroli(year)
  year >= 2011 ? 6 : nil
end

.pl_trzech_kroli_informal(year) ⇒ Object

Poland: January 6 wasn't holiday before 2011



72
73
74
# File 'lib/holidays/pl.rb', line 72

def self.pl_trzech_kroli_informal(year)
  year < 2011 ? 6 : nil
end

.previous_friday(date) ⇒ Object



61
62
63
# File 'lib/holidays/nz.rb', line 61

def self.previous_friday(date)
  date - 3
end

.qld_labour_day_may(year) ⇒ Object



64
65
66
# File 'lib/holidays/au.rb', line 64

def self.qld_labour_day_may(year)
  year <= 2012 ? Date.calculate_mday(year, 5, 1, 1) : nil
end

.qld_labour_day_october(year) ⇒ Object



71
72
73
# File 'lib/holidays/au.rb', line 71

def self.qld_labour_day_october(year)
  year <= 2012 ? nil : Date.calculate_mday(year, 10, 1, 1)
end

.qld_queens_bday_october(year) ⇒ Object



57
58
59
# File 'lib/holidays/au.rb', line 57

def self.qld_queens_bday_october(year)
  year == 2012 ? 1 : nil
end

.regionsObject

Returns an array of symbols of all the available holiday regions.



318
319
320
# File 'lib/holidays.rb', line 318

def self.regions
  @@regions
end

.se_alla_helgons_dag(year) ⇒ Object

Sweden: All Saint's Day (Saturday between Oct 31 and Nov 6)



51
52
53
54
55
# File 'lib/holidays/se.rb', line 51

def self.se_alla_helgons_dag(year)
  date = Date.civil(year,10,31)
  date += (6 - date.wday)
  date
end

.se_midsommardagen(year) ⇒ Object

Sweden: Mid-summer (Saturday between June 20–26)



43
44
45
46
47
# File 'lib/holidays/se.rb', line 43

def self.se_midsommardagen(year)
  date = Date.civil(year,6,20)
  date += (6 - date.wday)
  date
end

.to_monday_if_sunday(date) ⇒ Object

Move date to Monday if it occurs on a Sunday. Used as a callback function.



276
277
278
279
# File 'lib/holidays.rb', line 276

def self.to_monday_if_sunday(date)
  date += 1 if date.wday == 0
  date
end

.to_monday_if_weekend(date) ⇒ Object

Move date to Monday if it occurs on a Saturday on Sunday. Used as a callback function.



283
284
285
286
287
# File 'lib/holidays.rb', line 283

def self.to_monday_if_weekend(date)
  date += 1 if date.wday == 0
  date += 2 if date.wday == 6
  date
end

.to_weekday_if_boxing_weekend(date) ⇒ Object

Move Boxing Day if it falls on a weekend, leaving room for Christmas. Used as a callback function.



291
292
293
294
295
296
297
298
# File 'lib/holidays.rb', line 291

def self.to_weekday_if_boxing_weekend(date)
  if date.wday == 6 or date.wday == 0
    date += 2
  elsif date.wday == 1
    date += 1
  end
  date
end

.to_weekday_if_weekend(date) ⇒ Object

Move date to Monday if it occurs on a Sunday or to Friday if it occurs on a Saturday. Used as a callback function.



303
304
305
306
307
# File 'lib/holidays.rb', line 303

def self.to_weekday_if_weekend(date)
  date += 1 if date.wday == 0
  date -= 1 if date.wday == 6
  date
end

.us_inauguration_day(year) ⇒ Object

January 20, every fourth year, following Presidential election



50
51
52
# File 'lib/holidays/us.rb', line 50

def self.us_inauguration_day(year)
  year % 4 == 1 ? 20 : nil
end