hizuke 0.0.4 → 0.1.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: 6a44032e09036e25847a067394cf50b60f47197e5fe4d29b068c0e822960db9d
4
- data.tar.gz: 8f6a2d515fe0b1b64e970ef5ced8aad5aaa96febfd6c44448dc765d78521a709
3
+ metadata.gz: 0b0d90523e4927c6aeac7e03575aeb0bbb9cc923e6524cf63eacfbcff6115cb8
4
+ data.tar.gz: b641b880ad5ac8a67a29437702aea3c7f690695d9a560c039b2eead361b4d30f
5
5
  SHA512:
6
- metadata.gz: be87cac6f8fdf4ca69ca42641ad214804787a498b019d80b80b263835ce5fed52215af0d1f6e04e30e12889c710cb56db7df19a2c20c056c4969c97cfddbbd90
7
- data.tar.gz: 8e36da5a76ea3f9940b3970bab64a6dd2fa33a050caf02d9dd743e2052773ea4f28678f142dc82ce4b16dd436d0396b07afcb8bf961f9168e5d9d8fed2c733e6
6
+ metadata.gz: 788e7bcd5b09046f865771f7b9fc3ef55bab5ed6b1374891ef24006d67db8e3759fadaf34d21fd9ed60bce27331ef1ca6545a0fe9cd8c272ac74265b7f97772f
7
+ data.tar.gz: c8a4b2c5c5d07cf3c8f3d4233121a0f92c379ff2d8caaa05804e021b19da07271deec84af4ffa2061fa98e3cf84419113a09307ae25ee402fa194e874e607f81
data/CHANGELOG.md CHANGED
@@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.1.0] - 2025-05-08
9
+
10
+ ### Added
11
+ - Support for Christmas date parsing:
12
+ - "christmas" / "xmas" - returns December 25th of the current year
13
+ - "last christmas" / "lastchristmas" - returns December 25th of the previous year
14
+ - "next christmas" / "nextchristmas" - returns December 25th of the next year
15
+
16
+ ### Changed
17
+ - Major refactoring of the entire codebase:
18
+ - Improved code organization and structure
19
+ - Better error handling and edge case management
20
+ - Cleaner API design for better developer experience
21
+
8
22
  ## [0.0.4] - 2025-04-29
9
23
 
10
24
  ### Added
data/Gemfile CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- source "https://rubygems.org"
3
+ source 'https://rubygems.org'
4
4
 
5
5
  # Specify your gem's dependencies in hizuke.gemspec
6
- gemspec
6
+ gemspec
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- hizuke (0.0.4)
4
+ hizuke (0.1.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -165,6 +165,9 @@ Currently, the following English date keywords are supported:
165
165
  - `mid month` / `midmonth` - returns the date of the 15th day of the current month
166
166
  - `next quarter` / `nextquarter` - returns the first day of the next quarter
167
167
  - `last quarter` / `lastquarter` - returns the first day of the last quarter
168
+ - `christmas` / `xmas` - returns the date of Christmas (December 25th) of the current year (or next year if Christmas has already passed)
169
+ - `next christmas` / `nextchristmas` - returns the date of Christmas (December 25th) of the next year
170
+ - `last christmas` / `lastchristmas` - returns the date of Christmas (December 25th) of the previous year
168
171
 
169
172
  ## Supported Time Formats
170
173
 
data/Rakefile CHANGED
@@ -1,12 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "bundler/gem_tasks"
4
- require "rake/testtask"
3
+ require 'bundler/gem_tasks'
4
+ require 'rake/testtask'
5
5
 
6
6
  Rake::TestTask.new(:test) do |t|
7
- t.libs << "test"
8
- t.libs << "lib"
9
- t.test_files = FileList["test/**/test_*.rb"]
7
+ t.libs << 'test'
8
+ t.libs << 'lib'
9
+ t.test_files = FileList['test/**/test_*.rb']
10
10
  end
11
11
 
12
- task default: :test
12
+ task default: :test
data/hizuke.gemspec CHANGED
@@ -1,22 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "lib/hizuke/version"
3
+ require_relative 'lib/hizuke/version'
4
4
 
5
5
  Gem::Specification.new do |spec|
6
- spec.name = "hizuke"
6
+ spec.name = 'hizuke'
7
7
  spec.version = Hizuke::VERSION
8
- spec.authors = ["Juraj Maťaše"]
9
- spec.email = ["juraj@hey.com"]
8
+ spec.authors = ['Juraj Maťaše']
9
+ spec.email = ['juraj@hey.com']
10
10
 
11
- spec.summary = "A simple date parser for natural language time references"
12
- spec.description = "Hizuke is a lightweight utility that extracts dates from text by recognizing common time expressions. It cleans the original text and returns both the parsed date and the text without the date reference."
13
- spec.homepage = "https://github.com/majur/hizuke"
14
- spec.license = "MIT"
15
- spec.required_ruby_version = ">= 2.6.0"
11
+ spec.summary = 'A simple date parser for natural language time references'
12
+ spec.description = 'Hizuke is a lightweight utility that extracts dates from text ' \
13
+ 'by recognizing common time expressions. ' \
14
+ 'It cleans the original text and returns both the parsed date ' \
15
+ 'and the text without the date reference.'
16
+ spec.homepage = 'https://github.com/majur/hizuke'
17
+ spec.license = 'MIT'
18
+ spec.required_ruby_version = '>= 2.6.0'
16
19
 
17
- spec.metadata["homepage_uri"] = spec.homepage
18
- spec.metadata["source_code_uri"] = spec.homepage
19
- spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
20
+ spec.metadata['homepage_uri'] = spec.homepage
21
+ spec.metadata['source_code_uri'] = spec.homepage
22
+ spec.metadata['changelog_uri'] = "#{spec.homepage}/blob/main/CHANGELOG.md"
20
23
 
21
24
  # Specify which files should be added to the gem when it is released.
22
25
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
@@ -25,12 +28,12 @@ Gem::Specification.new do |spec|
25
28
  (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
26
29
  end
27
30
  end
28
- spec.bindir = "exe"
31
+ spec.bindir = 'exe'
29
32
  spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
30
- spec.require_paths = ["lib"]
33
+ spec.require_paths = ['lib']
31
34
 
32
35
  # Add development dependencies here
33
- spec.add_development_dependency "minitest", "~> 5.0"
34
- spec.add_development_dependency "rake", "~> 13.0"
35
- spec.add_development_dependency "rubocop", "~> 1.21"
36
- end
36
+ spec.add_development_dependency 'minitest', '~> 5.0'
37
+ spec.add_development_dependency 'rake', '~> 13.0'
38
+ spec.add_development_dependency 'rubocop', '~> 1.21'
39
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hizuke
4
+ # Module containing constants used throughout Hizuke
5
+ module Constants
6
+ # Mapping of day names to their wday values (0-6, Sunday is 0)
7
+ DAYS_OF_WEEK = {
8
+ 'monday' => 1,
9
+ 'tuesday' => 2,
10
+ 'wednesday' => 3,
11
+ 'thursday' => 4,
12
+ 'friday' => 5,
13
+ 'saturday' => 6,
14
+ 'sunday' => 0
15
+ }.freeze
16
+
17
+ # Date keywords mapping
18
+ DATE_KEYWORDS = {
19
+ 'yesterday' => -1,
20
+ 'today' => 0,
21
+ 'tomorrow' => 1,
22
+ 'dayaftertomorrow' => 2,
23
+ 'day after tomorrow' => 2,
24
+ 'daybeforeyesterday' => -2,
25
+ 'day before yesterday' => -2,
26
+ 'nextweek' => :next_week,
27
+ 'next week' => :next_week,
28
+ 'lastweek' => :last_week,
29
+ 'last week' => :last_week,
30
+ 'nextmonth' => :next_month,
31
+ 'next month' => :next_month,
32
+ 'lastmonth' => :last_month,
33
+ 'last month' => :last_month,
34
+ 'nextyear' => :next_year,
35
+ 'next year' => :next_year,
36
+ 'lastyear' => :last_year,
37
+ 'last year' => :last_year,
38
+ 'nextquarter' => :next_quarter,
39
+ 'next quarter' => :next_quarter,
40
+ 'lastquarter' => :last_quarter,
41
+ 'last quarter' => :last_quarter,
42
+ 'thisweekend' => :this_weekend,
43
+ 'this weekend' => :this_weekend,
44
+ 'endofweek' => :end_of_week,
45
+ 'end of week' => :end_of_week,
46
+ 'endofmonth' => :end_of_month,
47
+ 'end of month' => :end_of_month,
48
+ 'endofyear' => :end_of_year,
49
+ 'end of year' => :end_of_year,
50
+ 'midweek' => :mid_week,
51
+ 'mid week' => :mid_week,
52
+ 'midmonth' => :mid_month,
53
+ 'mid month' => :mid_month,
54
+ 'christmas' => :christmas,
55
+ 'xmas' => :christmas,
56
+ 'nextchristmas' => :next_christmas,
57
+ 'next christmas' => :next_christmas,
58
+ 'lastchristmas' => :last_christmas,
59
+ 'last christmas' => :last_christmas
60
+ }.freeze
61
+
62
+ # Regex patterns for dynamic date references
63
+ IN_X_DAYS_PATTERN = /in (\d+) days?/i.freeze
64
+ X_DAYS_AGO_PATTERN = /(\d+) days? ago/i.freeze
65
+ IN_X_WEEKS_PATTERN = /in (\d+) weeks?/i.freeze
66
+ X_WEEKS_AGO_PATTERN = /(\d+) weeks? ago/i.freeze
67
+ IN_X_MONTHS_PATTERN = /in (\d+) months?/i.freeze
68
+ X_MONTHS_AGO_PATTERN = /(\d+) months? ago/i.freeze
69
+ IN_X_YEARS_PATTERN = /in (\d+) years?/i.freeze
70
+ X_YEARS_AGO_PATTERN = /(\d+) years? ago/i.freeze
71
+
72
+ # Regex patterns for specific days of the week
73
+ THIS_DAY_PATTERN = /this (monday|tuesday|wednesday|thursday|friday|saturday|sunday)/i.freeze
74
+ NEXT_DAY_PATTERN = /next (monday|tuesday|wednesday|thursday|friday|saturday|sunday)/i.freeze
75
+ LAST_DAY_PATTERN = /last (monday|tuesday|wednesday|thursday|friday|saturday|sunday)/i.freeze
76
+
77
+ # Regex patterns for time references
78
+ TIME_PATTERN = /(?:at|@)\s*(\d{1,2})(?::(\d{1,2}))?(?::(\d{1,2}))?\s*(am|pm)?/i.freeze
79
+
80
+ # Regex patterns for word-based time references
81
+ NOON_PATTERN = /at\s+noon/i.freeze
82
+ MIDNIGHT_PATTERN = /at\s+midnight/i.freeze
83
+ MORNING_PATTERN = /in\s+the\s+morning/i.freeze
84
+ EVENING_PATTERN = /in\s+the\s+evening/i.freeze
85
+ end
86
+ end
@@ -0,0 +1,458 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hizuke
4
+ # Base module for date calculations
5
+ module BaseCalculator
6
+ # Calculate date for "this [day]" - the current/upcoming day in this week
7
+ # @param target_wday [Integer] the target day of week (0-6, Sunday is 0)
8
+ # @return [Date] the calculated date
9
+ def calculate_this_day(target_wday)
10
+ today = Date.today
11
+ today_wday = today.wday
12
+
13
+ # Calculate days until the target day in this week
14
+ days_diff = (target_wday - today_wday) % 7
15
+
16
+ # If it's the same day, return today's date
17
+ return today if days_diff.zero?
18
+
19
+ # Return the date of the next occurrence in this week
20
+ today + days_diff
21
+ end
22
+
23
+ # Calculate date for "next [day]" - the day in next week
24
+ # @param target_wday [Integer] the target day of week (0-6, Sunday is 0)
25
+ # @return [Date] the calculated date
26
+ def calculate_next_day(target_wday)
27
+ today = Date.today
28
+ today_wday = today.wday
29
+
30
+ # Calculate days until the next occurrence
31
+ days_until_target = (target_wday - today_wday) % 7
32
+
33
+ # If today is the target day or the target day is earlier in the week,
34
+ # we want the day next week, so add 7 days
35
+ days_until_target += 7 if days_until_target.zero? || target_wday < today_wday
36
+
37
+ today + days_until_target
38
+ end
39
+
40
+ # Calculate date for "last [day]" - the day in previous week
41
+ # @param target_wday [Integer] the target day of week (0-6, Sunday is 0)
42
+ # @return [Date] the calculated date
43
+ def calculate_last_day(target_wday)
44
+ today = Date.today
45
+ today_wday = today.wday
46
+
47
+ # Calculate days since the last occurrence
48
+ days_since_target = (today_wday - target_wday) % 7
49
+
50
+ # If today is the target day or the target day is later in the week,
51
+ # we want the day last week, so add 7 days
52
+ days_since_target += 7 if days_since_target.zero? || target_wday > today_wday
53
+
54
+ today - days_since_target
55
+ end
56
+
57
+ # Handles date calculations for integer values (relative days)
58
+ # @param date_value [Integer] number of days from today
59
+ # @return [Date] calculated date
60
+ def calculate_relative_days(date_value)
61
+ Date.today + date_value
62
+ end
63
+
64
+ # Return mapping for week and month related date keywords
65
+ # @return [Hash] Mapping of keywords to method names
66
+ def temporal_period_methods
67
+ {
68
+ next_week: :calculate_week_date,
69
+ last_week: :calculate_week_date,
70
+ next_month: :calculate_month_date,
71
+ last_month: :calculate_month_date
72
+ }
73
+ end
74
+
75
+ # Return mapping for year and quarter related date keywords
76
+ # @return [Hash] Mapping of keywords to method names
77
+ def yearly_period_methods
78
+ {
79
+ next_year: :calculate_year_date,
80
+ last_year: :calculate_year_date,
81
+ next_quarter: :calculate_quarter_date,
82
+ last_quarter: :calculate_quarter_date
83
+ }
84
+ end
85
+
86
+ # Return mapping for special period related date keywords
87
+ # @return [Hash] Mapping of keywords to method names
88
+ def special_period_methods
89
+ {
90
+ this_weekend: :calculate_period_end_date,
91
+ end_of_week: :calculate_period_end_date,
92
+ end_of_month: :calculate_period_end_date,
93
+ end_of_year: :calculate_period_end_date,
94
+ mid_week: :calculate_period_mid_date,
95
+ mid_month: :calculate_period_mid_date
96
+ }
97
+ end
98
+
99
+ # Return mapping for holiday related date keywords
100
+ # @return [Hash] Mapping of keywords to method names
101
+ def holiday_methods
102
+ {
103
+ christmas: :calculate_holiday_date,
104
+ next_christmas: :calculate_holiday_date,
105
+ last_christmas: :calculate_holiday_date
106
+ }
107
+ end
108
+
109
+ # Maps date value symbols to calculation methods
110
+ # @return [Hash] the mapping of symbols to method names
111
+ def date_calculation_methods
112
+ temporal_period_methods.merge(yearly_period_methods)
113
+ .merge(special_period_methods)
114
+ .merge(holiday_methods)
115
+ end
116
+
117
+ # Calculate the date based on the keyword value
118
+ # @param date_value [Symbol, Integer] the date value to calculate from
119
+ # @return [Date] the calculated date
120
+ def calculate_date(date_value)
121
+ return calculate_relative_days(date_value) if date_value.is_a?(Integer)
122
+
123
+ # Look up which method to call for this symbol
124
+ method_name = date_calculation_methods[date_value]
125
+ return unless method_name
126
+
127
+ # Call the appropriate method with the date_value
128
+ send(method_name, date_value)
129
+ end
130
+ end
131
+
132
+ # Module for week-related date calculations
133
+ module WeekCalculator
134
+ # Calculate week-related dates
135
+ # @param date_value [Symbol] the date keyword (:next_week or :last_week)
136
+ # @return [Date] the calculated date
137
+ def calculate_week_date(date_value)
138
+ case date_value
139
+ when :next_week
140
+ calculate_next_week_date
141
+ when :last_week
142
+ calculate_last_week_date
143
+ end
144
+ end
145
+
146
+ # Calculate date for next week
147
+ # @return [Date] the date for next week (following Monday)
148
+ def calculate_next_week_date
149
+ # Find next Monday
150
+ days_until_monday = (1 - Date.today.wday) % 7
151
+ # If today is Monday, we want next Monday, not today
152
+ days_until_monday = 7 if days_until_monday.zero?
153
+ Date.today + days_until_monday
154
+ end
155
+
156
+ # Calculate date for last week
157
+ # @return [Date] the date for last week (previous Monday)
158
+ def calculate_last_week_date
159
+ # Find last Monday
160
+ days_since_monday = (Date.today.wday - 1) % 7
161
+ # If today is Monday, we want last Monday, not today
162
+ days_since_monday = 7 if days_since_monday.zero?
163
+ Date.today - days_since_monday - 7
164
+ end
165
+ end
166
+
167
+ # Module for month-related date calculations
168
+ module MonthCalculator
169
+ # Calculate month-related dates
170
+ # @param date_value [Symbol] the date keyword (:next_month or :last_month)
171
+ # @return [Date] the calculated date
172
+ def calculate_month_date(date_value)
173
+ case date_value
174
+ when :next_month
175
+ calculate_next_month_date
176
+ when :last_month
177
+ calculate_last_month_date
178
+ end
179
+ end
180
+
181
+ # Calculate date for next month
182
+ # @return [Date] the first day of the next month
183
+ def calculate_next_month_date
184
+ # Return the first day of the next month
185
+ next_month = Date.today >> 1
186
+ Date.new(next_month.year, next_month.month, 1)
187
+ end
188
+
189
+ # Calculate date for last month
190
+ # @return [Date] the first day of the previous month
191
+ def calculate_last_month_date
192
+ # Return the first day of the previous month
193
+ prev_month = Date.today << 1
194
+ Date.new(prev_month.year, prev_month.month, 1)
195
+ end
196
+ end
197
+
198
+ # Module for year-related date calculations
199
+ module YearCalculator
200
+ # Calculate year-related dates
201
+ # @param date_value [Symbol] the date keyword (:next_year or :last_year)
202
+ # @return [Date] the calculated date
203
+ def calculate_year_date(date_value)
204
+ case date_value
205
+ when :next_year
206
+ calculate_next_year_date
207
+ when :last_year
208
+ calculate_last_year_date
209
+ end
210
+ end
211
+
212
+ # Calculate date for next year
213
+ # @return [Date] the first day of the next year
214
+ def calculate_next_year_date
215
+ # Return the first day of the next year
216
+ next_year = Date.today.year + 1
217
+ Date.new(next_year, 1, 1)
218
+ end
219
+
220
+ # Calculate date for last year
221
+ # @return [Date] the first day of the last year
222
+ def calculate_last_year_date
223
+ # Return the first day of the last year
224
+ last_year = Date.today.year - 1
225
+ Date.new(last_year, 1, 1)
226
+ end
227
+ end
228
+
229
+ # Module for quarter-related date calculations
230
+ module QuarterCalculator
231
+ # Calculate quarter-related dates
232
+ # @param date_value [Symbol] the date keyword (:next_quarter or :last_quarter)
233
+ # @return [Date] the calculated date
234
+ def calculate_quarter_date(date_value)
235
+ case date_value
236
+ when :next_quarter
237
+ calculate_next_quarter_date
238
+ when :last_quarter
239
+ calculate_last_quarter_date
240
+ end
241
+ end
242
+
243
+ # Calculate the date for next quarter
244
+ # @return [Date] the first day of the next quarter
245
+ def calculate_next_quarter_date
246
+ today = Date.today
247
+ current_month = today.month
248
+
249
+ next_quarter_month = determine_next_quarter_month(current_month)
250
+ next_quarter_year = determine_next_quarter_year(today.year, current_month)
251
+
252
+ Date.new(next_quarter_year, next_quarter_month, 1)
253
+ end
254
+
255
+ # Determine the start month of the next quarter
256
+ # @param current_month [Integer] the current month (1-12)
257
+ # @return [Integer] the month number of the next quarter star
258
+ def determine_next_quarter_month(current_month)
259
+ if current_month <= 3
260
+ 4 # Q2 starts in April
261
+ elsif current_month <= 6
262
+ 7 # Q3 starts in July
263
+ elsif current_month <= 9
264
+ 10 # Q4 starts in October
265
+ else
266
+ 1 # Q1 of next year starts in January
267
+ end
268
+ end
269
+
270
+ # Determine the year of the next quarter
271
+ # @param current_year [Integer] the current year
272
+ # @param current_month [Integer] the current month (1-12)
273
+ # @return [Integer] the year of the next quarter
274
+ def determine_next_quarter_year(current_year, current_month)
275
+ current_year + (current_month > 9 ? 1 : 0)
276
+ end
277
+
278
+ # Calculate the date for last quarter
279
+ # @return [Date] the first day of the last quarter
280
+ def calculate_last_quarter_date
281
+ today = Date.today
282
+ current_month = today.month
283
+
284
+ last_quarter_month = determine_last_quarter_month(current_month)
285
+ last_quarter_year = determine_last_quarter_year(today.year, current_month)
286
+
287
+ Date.new(last_quarter_year, last_quarter_month, 1)
288
+ end
289
+
290
+ # Determine the start month of the last quarter
291
+ # @param current_month [Integer] the current month (1-12)
292
+ # @return [Integer] the month number of the last quarter star
293
+ def determine_last_quarter_month(current_month)
294
+ if current_month <= 3
295
+ 10 # Q4 of last year starts in October
296
+ elsif current_month <= 6
297
+ 1 # Q1 starts in January
298
+ elsif current_month <= 9
299
+ 4 # Q2 starts in April
300
+ else
301
+ 7 # Q3 starts in July
302
+ end
303
+ end
304
+
305
+ # Determine the year of the last quarter
306
+ # @param current_year [Integer] the current year
307
+ # @param current_month [Integer] the current month (1-12)
308
+ # @return [Integer] the year of the last quarter
309
+ def determine_last_quarter_year(current_year, current_month)
310
+ current_year - (current_month <= 3 ? 1 : 0)
311
+ end
312
+ end
313
+
314
+ # Module for period-related date calculations
315
+ module PeriodCalculator
316
+ # Calculate end of period dates
317
+ # @param date_value [Symbol] the date keyword
318
+ # @return [Date] the calculated date
319
+ def calculate_period_end_date(date_value)
320
+ case date_value
321
+ when :this_weekend
322
+ calculate_weekend_date
323
+ when :end_of_week
324
+ calculate_end_of_week_date
325
+ when :end_of_month
326
+ calculate_end_of_month_date
327
+ when :end_of_year
328
+ calculate_end_of_year_date
329
+ end
330
+ end
331
+
332
+ # Calculate date for weekend (Saturday)
333
+ # @return [Date] the date of the upcoming weekend
334
+ def calculate_weekend_date
335
+ # Calculate days until Saturday
336
+ days_until_saturday = (6 - Date.today.wday) % 7
337
+ # If today is Saturday or Sunday, we're already on the weekend
338
+ days_until_saturday = 0 if [0, 6].include?(Date.today.wday)
339
+ Date.today + days_until_saturday
340
+ end
341
+
342
+ # Calculate date for end of week (Sunday)
343
+ # @return [Date] the date of the upcoming Sunday
344
+ def calculate_end_of_week_date
345
+ # Calculate days until Sunday (end of week)
346
+ days_until_sunday = (0 - Date.today.wday) % 7
347
+ # If today is Sunday, we're already at the end of the week
348
+ days_until_sunday = 0 if days_until_sunday.zero?
349
+ Date.today + days_until_sunday
350
+ end
351
+
352
+ # Calculate date for end of month
353
+ # @return [Date] the last day of the current month
354
+ def calculate_end_of_month_date
355
+ # Return the last day of the current month
356
+ # Get the first day of next month
357
+ next_month = Date.today >> 1
358
+ first_day_next_month = Date.new(next_month.year, next_month.month, 1)
359
+ # Subtract one day to get the last day of current month
360
+ first_day_next_month - 1
361
+ end
362
+
363
+ # Calculate date for end of year
364
+ # @return [Date] the last day of the current year
365
+ def calculate_end_of_year_date
366
+ # Return the last day of the current year (December 31)
367
+ Date.new(Date.today.year, 12, 31)
368
+ end
369
+
370
+ # Calculate mid-period dates
371
+ # @param date_value [Symbol] the date keyword
372
+ # @return [Date] the calculated date
373
+ def calculate_period_mid_date(date_value)
374
+ case date_value
375
+ when :mid_week
376
+ calculate_mid_week_date
377
+ when :mid_month
378
+ calculate_mid_month_date
379
+ end
380
+ end
381
+
382
+ # Calculate date for mid-week (Wednesday)
383
+ # @return [Date] the date of the current week's Wednesday
384
+ def calculate_mid_week_date
385
+ # Return Wednesday of the current week
386
+ today_wday = Date.today.wday
387
+ target_wday = 3 # Wednesday
388
+ days_diff = (target_wday - today_wday) % 7
389
+ # If the difference is more than 3, then Wednesday has passed this week
390
+ # So we need to go back to Wednesday
391
+ days_diff -= 7 if days_diff > 3
392
+ Date.today + days_diff
393
+ end
394
+
395
+ # Calculate date for mid-month (15th)
396
+ # @return [Date] the 15th day of the current month
397
+ def calculate_mid_month_date
398
+ # Return the 15th day of the current month
399
+ Date.new(Date.today.year, Date.today.month, 15)
400
+ end
401
+ end
402
+
403
+ # Module for holiday-related date calculations
404
+ module HolidayCalculator
405
+ # Calculate holiday-related dates
406
+ # @param date_value [Symbol] the date keyword (:christmas, :next_christmas, or :last_christmas)
407
+ # @return [Date] the calculated date
408
+ def calculate_holiday_date(date_value)
409
+ case date_value
410
+ when :christmas
411
+ calculate_christmas_date
412
+ when :next_christmas
413
+ calculate_next_christmas_date
414
+ when :last_christmas
415
+ calculate_last_christmas_date
416
+ end
417
+ end
418
+
419
+ # Calculate this year's Christmas date (December 25)
420
+ # @return [Date] the date for Christmas this year
421
+ def calculate_christmas_date
422
+ current_year = Date.today.year
423
+ christmas_date = Date.new(current_year, 12, 25)
424
+
425
+ # If Christmas has already passed this year, return next year's Christmas
426
+ if Date.today > christmas_date
427
+ Date.new(current_year + 1, 12, 25)
428
+ else
429
+ christmas_date
430
+ end
431
+ end
432
+
433
+ # Calculate next year's Christmas date (December 25 of next year)
434
+ # @return [Date] the date for Christmas next year
435
+ def calculate_next_christmas_date
436
+ current_year = Date.today.year
437
+ Date.new(current_year + 1, 12, 25)
438
+ end
439
+
440
+ # Calculate last year's Christmas date (December 25 of previous year)
441
+ # @return [Date] the date for Christmas last year
442
+ def calculate_last_christmas_date
443
+ current_year = Date.today.year
444
+ Date.new(current_year - 1, 12, 25)
445
+ end
446
+ end
447
+
448
+ # Main module for date calculations
449
+ module DateCalculator
450
+ include BaseCalculator
451
+ include WeekCalculator
452
+ include MonthCalculator
453
+ include YearCalculator
454
+ include QuarterCalculator
455
+ include PeriodCalculator
456
+ include HolidayCalculator
457
+ end
458
+ end