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 +4 -4
- data/CHANGELOG.md +14 -0
- data/Gemfile +2 -2
- data/Gemfile.lock +1 -1
- data/README.md +3 -0
- data/Rakefile +6 -6
- data/hizuke.gemspec +21 -18
- data/lib/hizuke/constants.rb +86 -0
- data/lib/hizuke/date_calculator.rb +458 -0
- data/lib/hizuke/parser.rb +67 -500
- data/lib/hizuke/pattern_matcher.rb +495 -0
- data/lib/hizuke/version.rb +2 -2
- data/lib/hizuke.rb +31 -4
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0b0d90523e4927c6aeac7e03575aeb0bbb9cc923e6524cf63eacfbcff6115cb8
|
4
|
+
data.tar.gz: b641b880ad5ac8a67a29437702aea3c7f690695d9a560c039b2eead361b4d30f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
data/Gemfile.lock
CHANGED
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
|
4
|
-
require
|
3
|
+
require 'bundler/gem_tasks'
|
4
|
+
require 'rake/testtask'
|
5
5
|
|
6
6
|
Rake::TestTask.new(:test) do |t|
|
7
|
-
t.libs <<
|
8
|
-
t.libs <<
|
9
|
-
t.test_files = FileList[
|
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
|
3
|
+
require_relative 'lib/hizuke/version'
|
4
4
|
|
5
5
|
Gem::Specification.new do |spec|
|
6
|
-
spec.name =
|
6
|
+
spec.name = 'hizuke'
|
7
7
|
spec.version = Hizuke::VERSION
|
8
|
-
spec.authors = [
|
9
|
-
spec.email = [
|
8
|
+
spec.authors = ['Juraj Maťaše']
|
9
|
+
spec.email = ['juraj@hey.com']
|
10
10
|
|
11
|
-
spec.summary =
|
12
|
-
spec.description =
|
13
|
-
|
14
|
-
|
15
|
-
|
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[
|
18
|
-
spec.metadata[
|
19
|
-
spec.metadata[
|
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 =
|
31
|
+
spec.bindir = 'exe'
|
29
32
|
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
30
|
-
spec.require_paths = [
|
33
|
+
spec.require_paths = ['lib']
|
31
34
|
|
32
35
|
# Add development dependencies here
|
33
|
-
spec.add_development_dependency
|
34
|
-
spec.add_development_dependency
|
35
|
-
spec.add_development_dependency
|
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
|