fat_core 4.10.0 → 4.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3860db07c85484c1d82e2fafa18d730e7dedb991f33868b21c2c11d3e56c7fde
4
- data.tar.gz: 524ca9b2ecdd017b5f14d8ff70e787d550bdf0a3eb4769be6442c3df914f965c
3
+ metadata.gz: 293a92293691aebd9771c017fa1dfeb9f9935bbb3832a19ec3494df49a75a176
4
+ data.tar.gz: 273ca4995d0e8b5d95cb5103ce520643f5aca983a0d855b083deb7d052033b63
5
5
  SHA512:
6
- metadata.gz: fda77f07f98048d111fdf752b115595f1b3b79e8f2c6b1d73482edfcb9e89ad45f6d46331fca04b3cabab3f1ab2f4da91675de5b8c4965943aa12d4902501839
7
- data.tar.gz: 37a9824e50cdc58880d081007c70b9e5b4d6c338f381139ad178e936d68da24e19a6b188298072da9df7444b88eacfa9ed362bcb2767092abdb6f532e93e7089
6
+ metadata.gz: b6f8fe6dc824a53d5087a2565a16bda84f7c311514346b3c0dcc58d9822b3dc84b3a93f9070dc1d27fce4aba90841556a3251a3e72d065f92b78f82402e9f73a
7
+ data.tar.gz: 2aab09471276331aa009b76250e9dd163ca1512b135170495bd5d899cd46d575d3aa0c29d14a923781ee99606ebffb4a7d434a37c47d10be73c8dba363f254d0
data/lib/fat_core/date.rb CHANGED
@@ -119,6 +119,17 @@ module FatCore
119
119
 
120
120
  # :category: Queries
121
121
 
122
+ # Number of days in self's month
123
+ # @return [Integer]
124
+ def days_in_month
125
+ self.class.days_in_month(year, month)
126
+ end
127
+
128
+ # :category: Queries
129
+ # @group Queries
130
+
131
+ # :category: Queries
132
+
122
133
  # Self's calendar "half" by analogy to calendar quarters: 1 or 2, depending
123
134
  # on whether the date falls in the first or second half of the calendar
124
135
  # year.
@@ -357,10 +368,30 @@ module FatCore
357
368
  # @param from_date [::Date] the middle of the six-month range
358
369
  # @return [Boolean]
359
370
  def within_6mos_of?(from_date)
360
- # ::Date 6 calendar months before self
361
- start_date = self - 6.months + 2.days
362
- end_date = self + 6.months - 2.days
363
- (start_date..end_date).cover?(from_date)
371
+ from_date = Date.parse(from_date) unless from_date.is_a?(Date)
372
+ from_day = from_date.day
373
+ if [28, 29, 30, 31].include?(from_day)
374
+ # Near the end of the month, we need to make sure that when we go
375
+ # forward or backwards 6 months, we do not go past the end of the
376
+ # destination month when finding the "corresponding" day in that
377
+ # month, per Stella v. Graham Page Motors. This refinement was
378
+ # endorsed in the Jammies International case. After we find the
379
+ # corresponding day in the target month, then add two days (for the
380
+ # month six months before the from_date) or subtract two days (for the
381
+ # month six months after the from_date) to get the first and last days
382
+ # of the "within a period of less than six months" date range.
383
+ start_month = from_date.beginning_of_month - 6.months
384
+ start_days = start_month.days_in_month
385
+ start_date = ::Date.new(start_month.year, start_month.month, [start_days, from_day].min) + 2.days
386
+ end_month = from_date.beginning_of_month + 6.months
387
+ end_days = end_month.days_in_month
388
+ end_date = ::Date.new(end_month.year, end_month.month, [end_days, from_day].min) - 2.days
389
+ else
390
+ # ::Date 6 calendar months before self
391
+ start_date = from_date - 6.months + 2.days
392
+ end_date = from_date + 6.months - 2.days
393
+ end
394
+ (start_date..end_date).cover?(self)
364
395
  end
365
396
 
366
397
  # Return whether this date is Easter Sunday for the year in which it falls
@@ -1002,6 +1033,9 @@ module FatCore
1002
1033
  if mon == 1 && mday == 1
1003
1034
  # New Years (January 1),
1004
1035
  true
1036
+ elsif mon == 6 && mday == 19 && year >= 2021
1037
+ # Juneteenth,
1038
+ true
1005
1039
  elsif mon == 7 && mday == 4
1006
1040
  # Independence Day (July 4),
1007
1041
  true
@@ -245,28 +245,44 @@ module FatCore
245
245
  #
246
246
  # 1. Remove all periods, commas, apostrophes, and asterisks (the punctuation
247
247
  # characters) from both self and `matcher`,
248
- # 2. Treat ':' in the matcher as the equivalent of '.*' in a regular
249
- # expression, that is, match anything in self,
250
- # 3. Require each component to match the beginning of a word boundary
251
- # 4. Ignore case in the match
248
+ # 2. Treat internal ':' in the matcher as the equivalent of '.*' in a
249
+ # regular expression, that is, match anything in self,
250
+ # 3. Treat leading ':' in the matcher as anchoring the match to the
251
+ # beginning of the target string,
252
+ # 4. Treat ending ':' in the matcher as anchoring the match to the
253
+ # end of the target string,
254
+ # 5. Require each component to match the beginning of a word boundary
255
+ # 6. Ignore case in the match
252
256
  #
253
257
  # @example
254
258
  # "St. Luke's Hospital".fuzzy_match('st lukes') #=> 'St Lukes'
255
259
  # "St. Luke's Hospital".fuzzy_match('luk:hosp') #=> 'Lukes Hosp'
256
260
  # "St. Luke's Hospital".fuzzy_match('st:spital') #=> 'St Lukes Hospital'
257
261
  # "St. Luke's Hospital".fuzzy_match('st:laks') #=> nil
262
+ # "St. Luke's Hospital".fuzzy_match(':lukes') #=> nil
263
+ # "St. Luke's Hospital".fuzzy_match('lukes:hospital:') #=> 'Lukes Hospital'
258
264
  #
259
265
  # @param matcher [String] pattern to test against where ':' is wildcard
260
266
  # @return [String] the unpunctuated part of self that matched
261
267
  # @return [nil] if self did not match matcher
262
268
  def fuzzy_match(matcher)
263
269
  # Remove periods, asterisks, commas, and apostrophes
264
- matcher = matcher.gsub(/[\*.,']/, '')
270
+ matcher = matcher.strip.gsub(/[\*.,']/, '')
271
+ if matcher.start_with?(':')
272
+ begin_anchor = true
273
+ matcher.sub!(/\A:/, '')
274
+ end
275
+ if matcher.end_with?(':')
276
+ end_anchor = true
277
+ matcher.sub!(/:\z/, '')
278
+ end
265
279
  target = gsub(/[\*.,']/, '')
266
280
  matchers = matcher.split(/[: ]+/)
267
281
  regexp_string = matchers.map { |m| ".*?\\b#{Regexp.escape(m)}.*?" }.join('[: ]')
268
282
  regexp_string.sub!(/^\.\*\?/, '')
269
283
  regexp_string.sub!(/\.\*\?$/, '')
284
+ regexp_string.sub!(/\A/, '\\A') if begin_anchor
285
+ regexp_string.sub!(/\z/, '\\z') if end_anchor
270
286
  regexp = /#{regexp_string}/i
271
287
  matched_text =
272
288
  if (match = regexp.match(target))
@@ -2,7 +2,7 @@
2
2
 
3
3
  module FatCore
4
4
  MAJOR = 4
5
- MINOR = 10
5
+ MINOR = 12
6
6
  PATCH = 0
7
7
 
8
8
  # FatCore version number
@@ -837,6 +837,65 @@ describe Date do
837
837
  expect(Date.parse('2014-01-12'))
838
838
  .to be_within_6mos_of(Date.parse('2014-07-10'))
839
839
  end
840
+
841
+ it "knows if it's within 6 months of another date if it's near end of month" do
842
+ # This tests for the Jammies Interntional twist where there is no
843
+ # corresponding day in the sixth month before or after the given date.
844
+
845
+ # Looking backward to Feb
846
+ expect(Date.parse('2014-02-28'))
847
+ .not_to be_within_6mos_of(Date.parse('2014-08-31'))
848
+ expect(Date.parse('2014-03-01'))
849
+ .not_to be_within_6mos_of(Date.parse('2014-08-31'))
850
+ expect(Date.parse('2014-03-02'))
851
+ .to be_within_6mos_of(Date.parse('2014-08-31'))
852
+ # Looking forward to Feb
853
+ expect(Date.parse('2015-02-28'))
854
+ .not_to be_within_6mos_of(Date.parse('2014-08-31'))
855
+ expect(Date.parse('2015-02-27'))
856
+ .not_to be_within_6mos_of(Date.parse('2014-08-31'))
857
+ expect(Date.parse('2015-02-26'))
858
+ .to be_within_6mos_of(Date.parse('2014-08-31'))
859
+ # Same in a leap year, backward
860
+ expect(Date.parse('2012-02-29'))
861
+ .not_to be_within_6mos_of(Date.parse('2012-08-31'))
862
+ expect(Date.parse('2012-03-01'))
863
+ .not_to be_within_6mos_of(Date.parse('2012-08-31'))
864
+ expect(Date.parse('2012-03-02'))
865
+ .to be_within_6mos_of(Date.parse('2012-08-31'))
866
+ # Same in a leap year, forward
867
+ expect(Date.parse('2012-02-29'))
868
+ .not_to be_within_6mos_of(Date.parse('2011-08-31'))
869
+ expect(Date.parse('2012-02-28'))
870
+ .not_to be_within_6mos_of(Date.parse('2011-08-31'))
871
+ expect(Date.parse('2012-02-27'))
872
+ .to be_within_6mos_of(Date.parse('2011-08-31'))
873
+
874
+ # Now try from October to April, as 31->30 test.
875
+ expect(Date.parse('2012-04-30'))
876
+ .not_to be_within_6mos_of(Date.parse('2012-10-31'))
877
+ expect(Date.parse('2012-05-01'))
878
+ .not_to be_within_6mos_of(Date.parse('2012-10-31'))
879
+ expect(Date.parse('2012-05-02'))
880
+ .to be_within_6mos_of(Date.parse('2012-10-31'))
881
+ # And forward
882
+ expect(Date.parse('2013-04-30'))
883
+ .not_to be_within_6mos_of(Date.parse('2012-10-31'))
884
+ expect(Date.parse('2013-04-29'))
885
+ .not_to be_within_6mos_of(Date.parse('2012-10-31'))
886
+ expect(Date.parse('2013-04-28'))
887
+ .to be_within_6mos_of(Date.parse('2012-10-31'))
888
+
889
+ # It's not symmetrical: notice the second example here is within six
890
+ # months if measured from April, but not if measured from October.
891
+ expect(Date.parse('2012-10-31'))
892
+ .not_to be_within_6mos_of(Date.parse('2013-04-30'))
893
+ expect(Date.parse('2012-10-31'))
894
+ .to be_within_6mos_of(Date.parse('2013-04-29'))
895
+ expect(Date.parse('2012-10-31'))
896
+ .to be_within_6mos_of(Date.parse('2013-04-28'))
897
+
898
+ end
840
899
  end
841
900
 
842
901
  describe 'holidays' do
@@ -948,6 +1007,18 @@ describe Date do
948
1007
  expect(Date.parse('2014-11-23')).to be_fed_holiday
949
1008
  end
950
1009
 
1010
+ it 'knows that Juneteenth is a federal holiday from 2021' do
1011
+ expect(Date.parse('2020-06-19')).not_to be_fed_holiday
1012
+ # Saturday
1013
+ expect(Date.parse('2021-06-19')).to be_fed_holiday
1014
+ # Observed Friday
1015
+ expect(Date.parse('2021-06-18')).to be_fed_holiday
1016
+ # Sunday
1017
+ expect(Date.parse('2022-06-19')).to be_fed_holiday
1018
+ # Observed Monday
1019
+ expect(Date.parse('2022-06-20')).to be_fed_holiday
1020
+ end
1021
+
951
1022
  it 'should know if its an NYSE holiday' do
952
1023
  ################# 2014 2015 2016
953
1024
  # New Year's Day January 1 January 1 January 1
@@ -269,6 +269,21 @@ the people, for the people, shall not perish from the earth."
269
269
  expect('Hello:world'.fuzzy_match('hel:ox')).to be_falsy
270
270
  end
271
271
 
272
+ it 'requires end-anchor for ending colon' do
273
+ expect('Hello, to the world'.fuzzy_match('hel:world:')).to eq('Hello to the world')
274
+ expect('Hello, to the world today'.fuzzy_match('to:world:')).to be_nil
275
+ end
276
+
277
+ it 'requires start-anchor for leading colon' do
278
+ expect('Hello, to the world'.fuzzy_match(':hel:the')).to eq('Hello to the')
279
+ expect('Hello, to the world today'.fuzzy_match(':world:today')).to be_nil
280
+ end
281
+
282
+ it 'requires start-anchor and end-anchor for leading and ending colon' do
283
+ expect('Hello, to the world'.fuzzy_match(':hel:world:')).to eq('Hello to the world')
284
+ expect('Hello, to the world today'.fuzzy_match('hel:world:')).to be_falsy
285
+ end
286
+
272
287
  it 'should return the matched text' do
273
288
  expect('Hello:world'.fuzzy_match('hel')).to eq('Hel')
274
289
  expect('Hello:world'.fuzzy_match('hel:wor')).to eq('Hello:wor')
@@ -280,6 +295,9 @@ the people, for the people, shall not perish from the earth."
280
295
  expect('St Lukes'.fuzzy_match('st. luke\'s')).to eq('St Lukes')
281
296
  expect('St Lukes, Inc.'.fuzzy_match('st luke inc')).to eq('St Lukes Inc')
282
297
  expect('E*TRADE'.fuzzy_match('etrade')).to eq('ETRADE')
298
+ # Does not recognize non-alphanumerics as start of string.
299
+ expect('The 1 Dollar Store'.fuzzy_match('1 stor')).to be_truthy
300
+ expect('The $1 Dollar Store'.fuzzy_match('$1 stor')).to be_falsy
283
301
  end
284
302
  end
285
303
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fat_core
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.10.0
4
+ version: 4.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel E. Doherty
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-06-05 00:00:00.000000000 Z
11
+ date: 2022-09-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: simplecov
@@ -219,7 +219,7 @@ licenses:
219
219
  - MIT
220
220
  metadata:
221
221
  yard.run: yri
222
- post_install_message:
222
+ post_install_message:
223
223
  rdoc_options: []
224
224
  require_paths:
225
225
  - lib
@@ -234,8 +234,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
234
234
  - !ruby/object:Gem::Version
235
235
  version: '0'
236
236
  requirements: []
237
- rubygems_version: 3.3.3
238
- signing_key:
237
+ rubygems_version: 3.3.7
238
+ signing_key:
239
239
  specification_version: 4
240
240
  summary: fat_core provides some useful core extensions
241
241
  test_files: