fat_core 4.10.0 → 4.12.0

Sign up to get free protection for your applications and to get access to all the features.
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: