hizuke 0.0.2 → 0.0.3

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: 064d7574f7151fdf7695b789dd4812f38c6becb6226b7bda807d0339dc0fc8e1
4
- data.tar.gz: 48c23cc779ae564bc3625176f7af74ebb2c0b33a7f4f67cea5098851c35de2e0
3
+ metadata.gz: 21dc88226bf08c961ed63958ab1dc38ca4a9c324ed2259fbfcd5cda208a43247
4
+ data.tar.gz: 9bb2da3217a6b090b0036e4804d4860d156dd6d996aeff8d1b2705f43c146eda
5
5
  SHA512:
6
- metadata.gz: 22380b935099c93601a9ac54837e4378935037d653e5ee557c1fc7cdd31098245fbb3bc4c3e5c6623c431fdb9f54138fb3445dbe18e9197721caf6d082c25e16
7
- data.tar.gz: e00e4fe32d48188761db0f070d55bddf9bd31915f2a4336c2846704aaf4286f1b1e7c132070029a39afa558a264b1badc1734934c47f9d059334a9f06d809416
6
+ metadata.gz: 5c8dc6d727568a43564b707858469fa75639fc93b75c49fc299f0de6a08ac88ce316a3aa27e7efbf3d35d7a26e795ba3881a9d3472628749a75b1e4213cde239
7
+ data.tar.gz: 46bb31b32c3e40b54973757b5e954dbd91841fc7cc09febf50d3f704e6cb312dca3e0f472ae91e2cd876fcdba7aa510fb83a8c5d813e9a524532a1dc2b6049f5
data/CHANGELOG.md CHANGED
@@ -5,6 +5,35 @@ 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.0.3] - 2023-05-09
9
+
10
+ ### Added
11
+ - Support for quarterly date references:
12
+ - "next quarter" / "nextquarter" - returns the first day of the next quarter
13
+ - "last quarter" / "lastquarter" - returns the first day of the last quarter
14
+ - Support for relative day references:
15
+ - "day after tomorrow" / "dayaftertomorrow"
16
+ - "day before yesterday" / "daybeforeyesterday"
17
+ - Support for dynamic time spans:
18
+ - "in X days", "X days ago"
19
+ - "in X weeks", "X weeks ago"
20
+ - "in X months", "X months ago"
21
+ - "in X years", "X years ago"
22
+ - Support for specific days of the week:
23
+ - "this Monday", "this Tuesday", etc.
24
+ - "next Monday", "next Tuesday", etc.
25
+ - "last Monday", "last Tuesday", etc.
26
+ - Support for additional time references:
27
+ - "last week" / "lastweek"
28
+ - "last month" / "lastmonth"
29
+ - "last year" / "lastyear"
30
+ - "end of week" / "endofweek"
31
+ - "end of month" / "endofmonth"
32
+ - "end of year" / "endofyear"
33
+ - "mid week" / "midweek"
34
+ - "mid month" / "midmonth"
35
+ - Updated documentation to reflect all new functionality
36
+
8
37
  ## [0.0.2] - 2025-04-23
9
38
 
10
39
  ### Added
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- hizuke (0.0.1)
4
+ hizuke (0.0.2)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -72,10 +72,33 @@ Currently, the following English date keywords are supported:
72
72
  - `yesterday` - returns yesterday's date
73
73
  - `today` - returns today's date
74
74
  - `tomorrow` - returns tomorrow's date
75
+ - `day after tomorrow` / `dayaftertomorrow` - returns the date two days from today
76
+ - `day before yesterday` / `daybeforeyesterday` - returns the date two days before today
77
+ - `in X days` - returns the date X days from today (where X is any number)
78
+ - `X days ago` - returns the date X days before today (where X is any number)
79
+ - `in X weeks` - returns the date X weeks from today (where X is any number)
80
+ - `X weeks ago` - returns the date X weeks before today (where X is any number)
81
+ - `in X months` - returns the date X months from today (where X is any number)
82
+ - `X months ago` - returns the date X months before today (where X is any number)
83
+ - `in X years` - returns the date X years from today (where X is any number)
84
+ - `X years ago` - returns the date X years before today (where X is any number)
85
+ - `this Monday`, `this Tuesday`, etc. - returns the date of the specified day in the current week
86
+ - `next Monday`, `next Tuesday`, etc. - returns the date of the specified day in the next week
87
+ - `last Monday`, `last Tuesday`, etc. - returns the date of the specified day in the previous week
75
88
  - `next week` / `nextweek` - returns the date of the next Monday
89
+ - `last week` / `lastweek` - returns the date of the previous Monday
76
90
  - `next month` / `nextmonth` - returns the first day of the next month
91
+ - `last month` / `lastmonth` - returns the first day of the previous month
77
92
  - `next year` / `nextyear` - returns the first day of the next year
93
+ - `last year` / `lastyear` - returns the first day of the previous year
78
94
  - `this weekend` / `thisweekend` - returns the date of the upcoming Saturday (or today if it's already the weekend)
95
+ - `end of week` / `endofweek` - returns the date of the upcoming Sunday (end of week)
96
+ - `end of month` / `endofmonth` - returns the date of the last day of the current month
97
+ - `end of year` / `endofyear` - returns the date of December 31st of the current year
98
+ - `mid week` / `midweek` - returns the date of Wednesday of the current week
99
+ - `mid month` / `midmonth` - returns the date of the 15th day of the current month
100
+ - `next quarter` / `nextquarter` - returns the first day of the next quarter
101
+ - `last quarter` / `lastquarter` - returns the first day of the last quarter
79
102
 
80
103
  ## Development
81
104
 
data/lib/hizuke/parser.rb CHANGED
@@ -15,21 +15,71 @@ module Hizuke
15
15
 
16
16
  # Parser class responsible for extracting dates from text
17
17
  class Parser
18
+ # Mapping of day names to their wday values (0-6, Sunday is 0)
19
+ DAYS_OF_WEEK = {
20
+ "monday" => 1,
21
+ "tuesday" => 2,
22
+ "wednesday" => 3,
23
+ "thursday" => 4,
24
+ "friday" => 5,
25
+ "saturday" => 6,
26
+ "sunday" => 0
27
+ }.freeze
28
+
18
29
  # Date keywords mapping
19
30
  DATE_KEYWORDS = {
20
31
  "yesterday" => -1,
21
32
  "today" => 0,
22
33
  "tomorrow" => 1,
34
+ "dayaftertomorrow" => 2,
35
+ "day after tomorrow" => 2,
36
+ "daybeforeyesterday" => -2,
37
+ "day before yesterday" => -2,
23
38
  "nextweek" => :next_week,
24
39
  "next week" => :next_week,
40
+ "lastweek" => :last_week,
41
+ "last week" => :last_week,
25
42
  "nextmonth" => :next_month,
26
43
  "next month" => :next_month,
44
+ "lastmonth" => :last_month,
45
+ "last month" => :last_month,
27
46
  "nextyear" => :next_year,
28
47
  "next year" => :next_year,
48
+ "lastyear" => :last_year,
49
+ "last year" => :last_year,
50
+ "nextquarter" => :next_quarter,
51
+ "next quarter" => :next_quarter,
52
+ "lastquarter" => :last_quarter,
53
+ "last quarter" => :last_quarter,
29
54
  "thisweekend" => :this_weekend,
30
- "this weekend" => :this_weekend
55
+ "this weekend" => :this_weekend,
56
+ "endofweek" => :end_of_week,
57
+ "end of week" => :end_of_week,
58
+ "endofmonth" => :end_of_month,
59
+ "end of month" => :end_of_month,
60
+ "endofyear" => :end_of_year,
61
+ "end of year" => :end_of_year,
62
+ "midweek" => :mid_week,
63
+ "mid week" => :mid_week,
64
+ "midmonth" => :mid_month,
65
+ "mid month" => :mid_month
31
66
  }.freeze
32
67
 
68
+ # Regex patterns for dynamic date references
69
+ IN_X_DAYS_PATTERN = /in (\d+) days?/i
70
+ X_DAYS_AGO_PATTERN = /(\d+) days? ago/i
71
+ IN_X_WEEKS_PATTERN = /in (\d+) weeks?/i
72
+ X_WEEKS_AGO_PATTERN = /(\d+) weeks? ago/i
73
+ IN_X_MONTHS_PATTERN = /in (\d+) months?/i
74
+ X_MONTHS_AGO_PATTERN = /(\d+) months? ago/i
75
+ IN_X_YEARS_PATTERN = /in (\d+) years?/i
76
+ X_YEARS_AGO_PATTERN = /(\d+) years? ago/i
77
+
78
+ # Regex patterns for specific days of the week
79
+ THIS_DAY_PATTERN = /this (monday|tuesday|wednesday|thursday|friday|saturday|sunday)/i
80
+ NEXT_DAY_PATTERN = /next (monday|tuesday|wednesday|thursday|friday|saturday|sunday)/i
81
+ LAST_DAY_PATTERN = /last (monday|tuesday|wednesday|thursday|friday|saturday|sunday)/i
82
+
33
83
  # Parse text containing time references and extract both
34
84
  # the clean text and the date.
35
85
  #
@@ -49,6 +99,14 @@ module Hizuke
49
99
  # Check if text is nil or empty
50
100
  raise ParseError, "Input text cannot be nil or empty" if text.nil? || text.empty?
51
101
 
102
+ # Check for dynamic patterns first (in X days, X days ago)
103
+ result = check_dynamic_patterns(text)
104
+ return result if result
105
+
106
+ # Check for day of week patterns (this Monday, next Tuesday, etc.)
107
+ result = check_day_of_week_patterns(text)
108
+ return result if result
109
+
52
110
  # Try to find compound date expressions (like "next week")
53
111
  compound_matches = {}
54
112
 
@@ -110,6 +168,158 @@ module Hizuke
110
168
 
111
169
  private
112
170
 
171
+ # Check for day of week patterns (this Monday, next Tuesday, last Friday, etc.)
172
+ def check_day_of_week_patterns(text)
173
+ # Check for "this [day]" pattern
174
+ if (match = text.match(THIS_DAY_PATTERN))
175
+ day_name = match[1].downcase
176
+ day_value = DAYS_OF_WEEK[day_name]
177
+ date = calculate_this_day(day_value)
178
+ clean_text = text.gsub(match[0], "").strip
179
+ return Result.new(clean_text, date)
180
+ end
181
+
182
+ # Check for "next [day]" pattern
183
+ if (match = text.match(NEXT_DAY_PATTERN))
184
+ day_name = match[1].downcase
185
+ day_value = DAYS_OF_WEEK[day_name]
186
+ date = calculate_next_day(day_value)
187
+ clean_text = text.gsub(match[0], "").strip
188
+ return Result.new(clean_text, date)
189
+ end
190
+
191
+ # Check for "last [day]" pattern
192
+ if (match = text.match(LAST_DAY_PATTERN))
193
+ day_name = match[1].downcase
194
+ day_value = DAYS_OF_WEEK[day_name]
195
+ date = calculate_last_day(day_value)
196
+ clean_text = text.gsub(match[0], "").strip
197
+ return Result.new(clean_text, date)
198
+ end
199
+
200
+ nil
201
+ end
202
+
203
+ # Check for dynamic date patterns like "in X days" or "X days ago"
204
+ def check_dynamic_patterns(text)
205
+ # Check for "in X days" pattern
206
+ if (match = text.match(IN_X_DAYS_PATTERN))
207
+ days = match[1].to_i
208
+ date = Date.today + days
209
+ clean_text = text.gsub(match[0], "").strip
210
+ return Result.new(clean_text, date)
211
+ end
212
+
213
+ # Check for "X days ago" pattern
214
+ if (match = text.match(X_DAYS_AGO_PATTERN))
215
+ days = match[1].to_i
216
+ date = Date.today - days
217
+ clean_text = text.gsub(match[0], "").strip
218
+ return Result.new(clean_text, date)
219
+ end
220
+
221
+ # Check for "in X weeks" pattern
222
+ if (match = text.match(IN_X_WEEKS_PATTERN))
223
+ weeks = match[1].to_i
224
+ date = Date.today + (weeks * 7)
225
+ clean_text = text.gsub(match[0], "").strip
226
+ return Result.new(clean_text, date)
227
+ end
228
+
229
+ # Check for "X weeks ago" pattern
230
+ if (match = text.match(X_WEEKS_AGO_PATTERN))
231
+ weeks = match[1].to_i
232
+ date = Date.today - (weeks * 7)
233
+ clean_text = text.gsub(match[0], "").strip
234
+ return Result.new(clean_text, date)
235
+ end
236
+
237
+ # Check for "in X months" pattern
238
+ if (match = text.match(IN_X_MONTHS_PATTERN))
239
+ months = match[1].to_i
240
+ date = Date.today >> months
241
+ clean_text = text.gsub(match[0], "").strip
242
+ return Result.new(clean_text, date)
243
+ end
244
+
245
+ # Check for "X months ago" pattern
246
+ if (match = text.match(X_MONTHS_AGO_PATTERN))
247
+ months = match[1].to_i
248
+ date = Date.today << months
249
+ clean_text = text.gsub(match[0], "").strip
250
+ return Result.new(clean_text, date)
251
+ end
252
+
253
+ # Check for "in X years" pattern
254
+ if (match = text.match(IN_X_YEARS_PATTERN))
255
+ years = match[1].to_i
256
+ date = Date.new(Date.today.year + years, Date.today.month, Date.today.day)
257
+ clean_text = text.gsub(match[0], "").strip
258
+ return Result.new(clean_text, date)
259
+ end
260
+
261
+ # Check for "X years ago" pattern
262
+ if (match = text.match(X_YEARS_AGO_PATTERN))
263
+ years = match[1].to_i
264
+ date = Date.new(Date.today.year - years, Date.today.month, Date.today.day)
265
+ clean_text = text.gsub(match[0], "").strip
266
+ return Result.new(clean_text, date)
267
+ end
268
+
269
+ nil
270
+ end
271
+
272
+ # Calculate date for "this [day]" - the current/upcoming day in this week
273
+ def calculate_this_day(target_wday)
274
+ today = Date.today
275
+ today_wday = today.wday
276
+
277
+ # Calculate days until the target day in this week
278
+ days_diff = (target_wday - today_wday) % 7
279
+
280
+ # If it's the same day, return today's date
281
+ if days_diff == 0
282
+ return today
283
+ end
284
+
285
+ # Return the date of the next occurrence in this week
286
+ today + days_diff
287
+ end
288
+
289
+ # Calculate date for "next [day]" - the day in next week
290
+ def calculate_next_day(target_wday)
291
+ today = Date.today
292
+ today_wday = today.wday
293
+
294
+ # Calculate days until the next occurrence
295
+ days_until_target = (target_wday - today_wday) % 7
296
+
297
+ # If today is the target day or the target day is earlier in the week,
298
+ # we want the day next week, so add 7 days
299
+ if days_until_target == 0 || target_wday < today_wday
300
+ days_until_target += 7
301
+ end
302
+
303
+ today + days_until_target
304
+ end
305
+
306
+ # Calculate date for "last [day]" - the day in previous week
307
+ def calculate_last_day(target_wday)
308
+ today = Date.today
309
+ today_wday = today.wday
310
+
311
+ # Calculate days since the last occurrence
312
+ days_since_target = (today_wday - target_wday) % 7
313
+
314
+ # If today is the target day or the target day is later in the week,
315
+ # we want the day last week, so add 7 days
316
+ if days_since_target == 0 || target_wday > today_wday
317
+ days_since_target += 7
318
+ end
319
+
320
+ today - days_since_target
321
+ end
322
+
113
323
  # Calculate the date based on the keyword value
114
324
  def calculate_date(date_value)
115
325
  if date_value.is_a?(Integer)
@@ -120,20 +330,107 @@ module Hizuke
120
330
  # If today is Monday, we want next Monday, not today
121
331
  days_until_monday = 7 if days_until_monday == 0
122
332
  Date.today + days_until_monday
333
+ elsif date_value == :last_week
334
+ # Find last Monday
335
+ days_since_monday = (Date.today.wday - 1) % 7
336
+ # If today is Monday, we want last Monday, not today
337
+ days_since_monday = 7 if days_since_monday == 0
338
+ Date.today - days_since_monday - 7
123
339
  elsif date_value == :next_month
124
340
  # Return the first day of the next month
125
341
  next_month = Date.today >> 1
126
342
  Date.new(next_month.year, next_month.month, 1)
343
+ elsif date_value == :last_month
344
+ # Return the first day of the previous month
345
+ prev_month = Date.today << 1
346
+ Date.new(prev_month.year, prev_month.month, 1)
127
347
  elsif date_value == :next_year
128
348
  # Return the first day of the next year
129
349
  next_year = Date.today.year + 1
130
350
  Date.new(next_year, 1, 1)
351
+ elsif date_value == :last_year
352
+ # Return the first day of the last year
353
+ last_year = Date.today.year - 1
354
+ Date.new(last_year, 1, 1)
355
+ elsif date_value == :next_quarter
356
+ # Return the first day of the next quarter
357
+ today = Date.today
358
+ current_month = today.month
359
+
360
+ # Determine the start month of the next quarter
361
+ next_quarter_month = case
362
+ when current_month <= 3
363
+ 4 # Q2 starts in April
364
+ when current_month <= 6
365
+ 7 # Q3 starts in July
366
+ when current_month <= 9
367
+ 10 # Q4 starts in October
368
+ else
369
+ 1 # Q1 of next year starts in January
370
+ end
371
+
372
+ # If the next quarter is in the next year, increment the year
373
+ next_quarter_year = today.year
374
+ next_quarter_year += 1 if current_month > 9
375
+
376
+ Date.new(next_quarter_year, next_quarter_month, 1)
377
+ elsif date_value == :last_quarter
378
+ # Return the first day of the last quarter
379
+ today = Date.today
380
+ current_month = today.month
381
+
382
+ # Determine the start month of the last quarter
383
+ last_quarter_month = case
384
+ when current_month <= 3
385
+ 10 # Q4 of last year starts in October
386
+ when current_month <= 6
387
+ 1 # Q1 starts in January
388
+ when current_month <= 9
389
+ 4 # Q2 starts in April
390
+ else
391
+ 7 # Q3 starts in July
392
+ end
393
+
394
+ # If the last quarter is in the previous year, decrement the year
395
+ last_quarter_year = today.year
396
+ last_quarter_year -= 1 if current_month <= 3
397
+
398
+ Date.new(last_quarter_year, last_quarter_month, 1)
131
399
  elsif date_value == :this_weekend
132
400
  # Calculate days until Saturday
133
401
  days_until_saturday = (6 - Date.today.wday) % 7
134
402
  # If today is Saturday or Sunday, we're already on the weekend
135
403
  days_until_saturday = 0 if days_until_saturday == 0 || days_until_saturday == 6
136
404
  Date.today + days_until_saturday
405
+ elsif date_value == :end_of_week
406
+ # Calculate days until Sunday (end of week)
407
+ days_until_sunday = (0 - Date.today.wday) % 7
408
+ # If today is Sunday, we're already at the end of the week
409
+ days_until_sunday = 0 if days_until_sunday == 0
410
+ Date.today + days_until_sunday
411
+ elsif date_value == :end_of_month
412
+ # Return the last day of the current month
413
+ # Get the first day of next month
414
+ next_month = Date.today >> 1
415
+ first_day_next_month = Date.new(next_month.year, next_month.month, 1)
416
+ # Subtract one day to get the last day of current month
417
+ first_day_next_month - 1
418
+ elsif date_value == :end_of_year
419
+ # Return the last day of the current year (December 31)
420
+ Date.new(Date.today.year, 12, 31)
421
+ elsif date_value == :mid_week
422
+ # Return Wednesday of the current week
423
+ # Calculate days until/since Wednesday (3)
424
+ today_wday = Date.today.wday
425
+ target_wday = 3 # Wednesday
426
+ days_diff = (target_wday - today_wday) % 7
427
+ # If the difference is more than 3, then Wednesday has passed this week
428
+ # So we need to go back to Wednesday
429
+ days_diff = days_diff - 7 if days_diff > 3
430
+ Date.today + days_diff
431
+ elsif date_value == :mid_month
432
+ # Return the 15th day of the current month
433
+ Date.new(Date.today.year, Date.today.month, 15)
137
434
  end
138
435
  end
139
436
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Hizuke
4
- VERSION = "0.0.2"
4
+ VERSION = "0.0.3"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hizuke
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Juraj Maťaše
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-04-23 00:00:00.000000000 Z
11
+ date: 2025-04-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest