hizuke 0.0.3 → 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 +4 -4
- data/CHANGELOG.md +37 -1
- data/Gemfile +2 -2
- data/Gemfile.lock +1 -1
- data/README.md +91 -2
- data/Rakefile +6 -6
- data/hizuke.gemspec +39 -0
- data/lib/hizuke/constants.rb +86 -0
- data/lib/hizuke/date_calculator.rb +458 -0
- data/lib/hizuke/parser.rb +83 -411
- data/lib/hizuke/pattern_matcher.rb +495 -0
- data/lib/hizuke/version.rb +2 -2
- data/lib/hizuke.rb +31 -4
- metadata +6 -2
@@ -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
|