relative_date 0.1.1
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 +7 -0
- data/.claude/settings.local.json +17 -0
- data/.rspec +3 -0
- data/CHANGELOG.md +19 -0
- data/INSTALLATION.md +73 -0
- data/LICENSE +21 -0
- data/README.md +139 -0
- data/Rakefile +8 -0
- data/examples/usage.rb +45 -0
- data/exe/relative_date +31 -0
- data/lib/relative_date/parser.rb +151 -0
- data/lib/relative_date/version.rb +5 -0
- data/lib/relative_date.rb +30 -0
- data/relative_date.gemspec +35 -0
- metadata +89 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: eaeed8ffe3ae5a3dfa0045e38310e26f53b35b3433b57798214be3a4919fe770
|
|
4
|
+
data.tar.gz: f4bc3e381c59165a8f3914059fe87cdae148e4056b9a63cbc45db455ef7d7f78
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 6fb18cb20c5021c826cf5fe07e56d7705efb958f3974e4db35eeac844c1f18beeb608987a34a13fd53550c8cfa213d3b2c29ae8283e4f5ecfb8c9bc0fdc400d5
|
|
7
|
+
data.tar.gz: d77917c6aeb27709bcc2eac9aaaf5abb034d46aa03520b738ed1780a32c3808ff0978d9663c180c83f9dc061cf263cc69cb8a57cae04e27568cc5caccb3757fb
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"Bash(bundle gem:*)",
|
|
5
|
+
"Bash(mkdir:*)",
|
|
6
|
+
"Bash(chmod:*)",
|
|
7
|
+
"Bash(bundle install:*)",
|
|
8
|
+
"Bash(bundle exec rspec:*)",
|
|
9
|
+
"Bash(ruby exe/relative_time:*)",
|
|
10
|
+
"Bash(bundle exec rake:*)",
|
|
11
|
+
"Bash(ruby examples/usage.rb:*)",
|
|
12
|
+
"Bash(mv:*)"
|
|
13
|
+
],
|
|
14
|
+
"deny": [],
|
|
15
|
+
"ask": []
|
|
16
|
+
}
|
|
17
|
+
}
|
data/.rspec
ADDED
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.1.0] - 2025-12-06
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Initial release
|
|
12
|
+
- Support for weekday patterns (e.g., "2 Mondays ago", "3 Fridays from now")
|
|
13
|
+
- Support for next/last weekday patterns (e.g., "next Monday", "last Friday")
|
|
14
|
+
- Support for unit-based relative dates (e.g., "3 days ago", "2 weeks from now")
|
|
15
|
+
- Support for simple relative dates (today, tomorrow, yesterday)
|
|
16
|
+
- Command-line interface (`relative_date` executable)
|
|
17
|
+
- Comprehensive test suite with RSpec
|
|
18
|
+
- Case-insensitive parsing
|
|
19
|
+
- Both `parse` and `parse!` methods
|
data/INSTALLATION.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Installation Instructions
|
|
2
|
+
|
|
3
|
+
## Building the Gem
|
|
4
|
+
|
|
5
|
+
To build the gem from source:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
gem build relative_date.gemspec
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
This will create a file like `relative_date-0.1.0.gem`.
|
|
12
|
+
|
|
13
|
+
## Installing Locally
|
|
14
|
+
|
|
15
|
+
To install the gem on your local machine:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
gem install ./relative_date-0.1.0.gem
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Or using rake:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
bundle exec rake install
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Using in a Project
|
|
28
|
+
|
|
29
|
+
Add to your Gemfile:
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
gem 'relative_date'
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Then run:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
bundle install
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Development Setup
|
|
42
|
+
|
|
43
|
+
1. Clone the repository
|
|
44
|
+
2. Install dependencies:
|
|
45
|
+
```bash
|
|
46
|
+
bundle install
|
|
47
|
+
```
|
|
48
|
+
3. Run tests:
|
|
49
|
+
```bash
|
|
50
|
+
bundle exec rspec
|
|
51
|
+
# or
|
|
52
|
+
bundle exec rake
|
|
53
|
+
```
|
|
54
|
+
4. Try the CLI:
|
|
55
|
+
```bash
|
|
56
|
+
ruby exe/relative_date "2 Mondays ago"
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Publishing to RubyGems (for maintainers)
|
|
60
|
+
|
|
61
|
+
1. Update version in `lib/relative_date/version.rb`
|
|
62
|
+
2. Update `CHANGELOG.md`
|
|
63
|
+
3. Commit changes
|
|
64
|
+
4. Run:
|
|
65
|
+
```bash
|
|
66
|
+
bundle exec rake release
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
This will:
|
|
70
|
+
- Build the gem
|
|
71
|
+
- Create a git tag for the version
|
|
72
|
+
- Push the gem to RubyGems.org
|
|
73
|
+
- Push git commits and tags
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Thomas Powell
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# RelativeDate
|
|
2
|
+
|
|
3
|
+
A Ruby gem for parsing natural language relative date expressions like "2 Mondays ago", "next Friday", "3 weeks from now", and more.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add this line to your application's Gemfile:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
gem 'relative_date'
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
And then execute:
|
|
14
|
+
|
|
15
|
+
$ bundle install
|
|
16
|
+
|
|
17
|
+
Or install it yourself as:
|
|
18
|
+
|
|
19
|
+
$ gem install relative_date
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
### Ruby API
|
|
24
|
+
|
|
25
|
+
```ruby
|
|
26
|
+
require 'relative_date'
|
|
27
|
+
|
|
28
|
+
# Parse a relative date expression
|
|
29
|
+
date = RelativeDate.parse("2 Mondays ago")
|
|
30
|
+
#=> #<Date: 2025-11-24>
|
|
31
|
+
|
|
32
|
+
# Use a custom reference date (defaults to today)
|
|
33
|
+
date = RelativeDate.parse("next Friday", reference_date: Date.new(2024, 1, 15))
|
|
34
|
+
#=> #<Date: 2024-01-19>
|
|
35
|
+
|
|
36
|
+
# Use parse! to raise an error on invalid input
|
|
37
|
+
date = RelativeDate.parse!("tomorrow")
|
|
38
|
+
#=> #<Date: 2025-12-07>
|
|
39
|
+
|
|
40
|
+
RelativeDate.parse!("invalid input")
|
|
41
|
+
#=> RelativeDate::Error: Unable to parse 'invalid input'
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Command Line
|
|
45
|
+
|
|
46
|
+
The gem includes a CLI tool:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
$ relative_date "2 Mondays ago"
|
|
50
|
+
2025-11-24
|
|
51
|
+
|
|
52
|
+
$ relative_date "next Friday"
|
|
53
|
+
2025-12-12
|
|
54
|
+
|
|
55
|
+
$ relative_date "3 weeks from now"
|
|
56
|
+
2025-12-27
|
|
57
|
+
|
|
58
|
+
$ relative_date "tomorrow"
|
|
59
|
+
2025-12-07
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Supported Patterns
|
|
63
|
+
|
|
64
|
+
### Weekday Patterns
|
|
65
|
+
|
|
66
|
+
- `N <weekday>s ago` - e.g., "2 Mondays ago", "3 Fridays ago"
|
|
67
|
+
- `N <weekday>s from now` - e.g., "2 Tuesdays from now", "4 Wednesdays from now"
|
|
68
|
+
- `N <weekday> ago` - e.g., "1 Monday ago" (singular form also works)
|
|
69
|
+
|
|
70
|
+
### Next/Last Weekday
|
|
71
|
+
|
|
72
|
+
- `next <weekday>` - e.g., "next Monday", "next Friday"
|
|
73
|
+
- `last <weekday>` - e.g., "last Monday", "last Friday"
|
|
74
|
+
|
|
75
|
+
### Unit-Based Relative Dates
|
|
76
|
+
|
|
77
|
+
- `N days ago` / `N days from now` - e.g., "5 days ago", "3 days from now"
|
|
78
|
+
- `N weeks ago` / `N weeks from now` - e.g., "2 weeks ago", "1 week from now"
|
|
79
|
+
- `N months ago` / `N months from now` - e.g., "3 months ago", "6 months from now"
|
|
80
|
+
- `N years ago` / `N years from now` - e.g., "1 year ago", "2 years from now"
|
|
81
|
+
|
|
82
|
+
### Simple Relative Dates
|
|
83
|
+
|
|
84
|
+
- `today`
|
|
85
|
+
- `tomorrow`
|
|
86
|
+
- `yesterday`
|
|
87
|
+
|
|
88
|
+
## Features
|
|
89
|
+
|
|
90
|
+
- Case insensitive parsing (e.g., "NEXT FRIDAY", "Next Friday", "next friday" all work)
|
|
91
|
+
- Handles both singular and plural forms (e.g., "1 day ago" and "2 days ago")
|
|
92
|
+
- Returns `Date` objects for easy manipulation
|
|
93
|
+
- Includes both `parse` (returns nil on failure) and `parse!` (raises error on failure) methods
|
|
94
|
+
- No external dependencies beyond Ruby's standard library
|
|
95
|
+
|
|
96
|
+
## Examples
|
|
97
|
+
|
|
98
|
+
```ruby
|
|
99
|
+
# Today is Friday, December 6, 2025
|
|
100
|
+
|
|
101
|
+
RelativeDate.parse("2 Mondays ago")
|
|
102
|
+
#=> #<Date: 2025-11-24> (Monday, November 24, 2025)
|
|
103
|
+
|
|
104
|
+
RelativeDate.parse("next Friday")
|
|
105
|
+
#=> #<Date: 2025-12-12> (Friday, December 12, 2025)
|
|
106
|
+
|
|
107
|
+
RelativeDate.parse("last Sunday")
|
|
108
|
+
#=> #<Date: 2025-11-30> (Sunday, November 30, 2025)
|
|
109
|
+
|
|
110
|
+
RelativeDate.parse("3 weeks from now")
|
|
111
|
+
#=> #<Date: 2025-12-27> (Saturday, December 27, 2025)
|
|
112
|
+
|
|
113
|
+
RelativeDate.parse("5 days ago")
|
|
114
|
+
#=> #<Date: 2025-12-01> (Monday, December 1, 2025)
|
|
115
|
+
|
|
116
|
+
RelativeDate.parse("2 months ago")
|
|
117
|
+
#=> #<Date: 2025-10-06> (Monday, October 6, 2025)
|
|
118
|
+
|
|
119
|
+
RelativeDate.parse("tomorrow")
|
|
120
|
+
#=> #<Date: 2025-12-07> (Saturday, December 7, 2025)
|
|
121
|
+
|
|
122
|
+
# With a custom reference date
|
|
123
|
+
RelativeDate.parse("2 Mondays ago", reference_date: Date.new(2024, 1, 15))
|
|
124
|
+
#=> #<Date: 2024-01-01> (Monday, January 1, 2024)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Development
|
|
128
|
+
|
|
129
|
+
After checking out the repo, run `bundle install` to install dependencies. Then, run `bundle exec rspec` to run the tests.
|
|
130
|
+
|
|
131
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
|
132
|
+
|
|
133
|
+
## Contributing
|
|
134
|
+
|
|
135
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/stringsn88keys/relative_date.
|
|
136
|
+
|
|
137
|
+
## License
|
|
138
|
+
|
|
139
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/examples/usage.rb
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative "../lib/relative_date"
|
|
5
|
+
|
|
6
|
+
puts "RelativeDate Gem Examples"
|
|
7
|
+
puts "=" * 50
|
|
8
|
+
puts
|
|
9
|
+
|
|
10
|
+
# Today's date
|
|
11
|
+
puts "Today is: #{Date.today.strftime('%A, %B %d, %Y')}"
|
|
12
|
+
puts
|
|
13
|
+
|
|
14
|
+
# Various examples
|
|
15
|
+
examples = [
|
|
16
|
+
"2 Mondays ago",
|
|
17
|
+
"next Friday",
|
|
18
|
+
"last Sunday",
|
|
19
|
+
"3 weeks from now",
|
|
20
|
+
"5 days ago",
|
|
21
|
+
"2 months ago",
|
|
22
|
+
"tomorrow",
|
|
23
|
+
"yesterday",
|
|
24
|
+
"1 year from now"
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
examples.each do |expression|
|
|
28
|
+
result = RelativeDate.parse(expression)
|
|
29
|
+
if result
|
|
30
|
+
puts "#{expression.ljust(20)} => #{result.strftime('%Y-%m-%d (%A)')}"
|
|
31
|
+
else
|
|
32
|
+
puts "#{expression.ljust(20)} => [Could not parse]"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
puts
|
|
37
|
+
puts "=" * 50
|
|
38
|
+
puts
|
|
39
|
+
|
|
40
|
+
# Example with custom reference date
|
|
41
|
+
puts "Using custom reference date: Monday, January 15, 2024"
|
|
42
|
+
reference = Date.new(2024, 1, 15)
|
|
43
|
+
expression = "2 Mondays ago"
|
|
44
|
+
result = RelativeDate.parse(expression, reference_date: reference)
|
|
45
|
+
puts "#{expression} => #{result.strftime('%Y-%m-%d (%A)')}"
|
data/exe/relative_date
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative "../lib/relative_date"
|
|
5
|
+
|
|
6
|
+
if ARGV.empty?
|
|
7
|
+
puts "Usage: relative_date <expression>"
|
|
8
|
+
puts ""
|
|
9
|
+
puts "Examples:"
|
|
10
|
+
puts " relative_date '2 Mondays ago'"
|
|
11
|
+
puts " relative_date 'next Friday'"
|
|
12
|
+
puts " relative_date '3 weeks from now'"
|
|
13
|
+
puts " relative_date 'yesterday'"
|
|
14
|
+
puts ""
|
|
15
|
+
puts "Supported patterns:"
|
|
16
|
+
puts " - N <weekday>s ago/from now (e.g., '2 Mondays ago', '3 Fridays from now')"
|
|
17
|
+
puts " - next/last <weekday> (e.g., 'next Monday', 'last Friday')"
|
|
18
|
+
puts " - N days/weeks/months/years ago/from now (e.g., '5 days ago', '2 weeks from now')"
|
|
19
|
+
puts " - today/tomorrow/yesterday"
|
|
20
|
+
exit 0
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
input = ARGV.join(" ")
|
|
24
|
+
result = RelativeDate.parse(input)
|
|
25
|
+
|
|
26
|
+
if result
|
|
27
|
+
puts result.strftime("%Y-%m-%d")
|
|
28
|
+
else
|
|
29
|
+
warn "Error: Unable to parse '#{input}'"
|
|
30
|
+
exit 1
|
|
31
|
+
end
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "date"
|
|
4
|
+
require "time"
|
|
5
|
+
|
|
6
|
+
module RelativeDate
|
|
7
|
+
class Parser
|
|
8
|
+
WEEKDAYS = {
|
|
9
|
+
"monday" => 1,
|
|
10
|
+
"tuesday" => 2,
|
|
11
|
+
"wednesday" => 3,
|
|
12
|
+
"thursday" => 4,
|
|
13
|
+
"friday" => 5,
|
|
14
|
+
"saturday" => 6,
|
|
15
|
+
"sunday" => 0
|
|
16
|
+
}.freeze
|
|
17
|
+
|
|
18
|
+
UNITS = {
|
|
19
|
+
"day" => :days,
|
|
20
|
+
"days" => :days,
|
|
21
|
+
"week" => :weeks,
|
|
22
|
+
"weeks" => :weeks,
|
|
23
|
+
"month" => :months,
|
|
24
|
+
"months" => :months,
|
|
25
|
+
"year" => :years,
|
|
26
|
+
"years" => :years
|
|
27
|
+
}.freeze
|
|
28
|
+
|
|
29
|
+
def initialize(input, reference_date: Date.today)
|
|
30
|
+
@input = input.downcase.strip
|
|
31
|
+
@reference_date = reference_date
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def parse
|
|
35
|
+
return nil if @input.empty?
|
|
36
|
+
|
|
37
|
+
# Try to match different patterns
|
|
38
|
+
parse_weekday_pattern ||
|
|
39
|
+
parse_simple_relative ||
|
|
40
|
+
parse_unit_relative ||
|
|
41
|
+
parse_next_last_weekday ||
|
|
42
|
+
parse_today_tomorrow_yesterday ||
|
|
43
|
+
nil
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
# Matches patterns like "2 Mondays ago", "3 fridays from now"
|
|
49
|
+
def parse_weekday_pattern
|
|
50
|
+
match = @input.match(/^(\d+)\s+(monday|tuesday|wednesday|thursday|friday|saturday|sunday)s?\s+(ago|from\s+now)$/i)
|
|
51
|
+
return nil unless match
|
|
52
|
+
|
|
53
|
+
count = match[1].to_i
|
|
54
|
+
weekday = match[2].downcase
|
|
55
|
+
direction = match[3].downcase
|
|
56
|
+
|
|
57
|
+
target_wday = WEEKDAYS[weekday]
|
|
58
|
+
current_date = @reference_date
|
|
59
|
+
found_count = 0
|
|
60
|
+
|
|
61
|
+
if direction == "ago"
|
|
62
|
+
# Go backwards
|
|
63
|
+
loop do
|
|
64
|
+
current_date -= 1
|
|
65
|
+
found_count += 1 if current_date.wday == target_wday
|
|
66
|
+
break if found_count == count
|
|
67
|
+
end
|
|
68
|
+
else
|
|
69
|
+
# Go forwards
|
|
70
|
+
loop do
|
|
71
|
+
current_date += 1
|
|
72
|
+
found_count += 1 if current_date.wday == target_wday
|
|
73
|
+
break if found_count == count
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
current_date
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Matches patterns like "next Monday", "last Friday"
|
|
81
|
+
def parse_next_last_weekday
|
|
82
|
+
match = @input.match(/^(next|last)\s+(monday|tuesday|wednesday|thursday|friday|saturday|sunday)$/i)
|
|
83
|
+
return nil unless match
|
|
84
|
+
|
|
85
|
+
direction = match[1].downcase
|
|
86
|
+
weekday = match[2].downcase
|
|
87
|
+
target_wday = WEEKDAYS[weekday]
|
|
88
|
+
|
|
89
|
+
current_date = @reference_date
|
|
90
|
+
|
|
91
|
+
if direction == "next"
|
|
92
|
+
# Find the next occurrence of this weekday
|
|
93
|
+
loop do
|
|
94
|
+
current_date += 1
|
|
95
|
+
break if current_date.wday == target_wday
|
|
96
|
+
end
|
|
97
|
+
else # last
|
|
98
|
+
# Find the previous occurrence of this weekday
|
|
99
|
+
loop do
|
|
100
|
+
current_date -= 1
|
|
101
|
+
break if current_date.wday == target_wday
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
current_date
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Matches patterns like "3 days ago", "2 weeks from now"
|
|
109
|
+
def parse_unit_relative
|
|
110
|
+
match = @input.match(/^(\d+)\s+(day|days|week|weeks|month|months|year|years)\s+(ago|from\s+now)$/i)
|
|
111
|
+
return nil unless match
|
|
112
|
+
|
|
113
|
+
count = match[1].to_i
|
|
114
|
+
unit = match[2].downcase
|
|
115
|
+
direction = match[3].downcase
|
|
116
|
+
|
|
117
|
+
unit_type = UNITS[unit]
|
|
118
|
+
offset = direction == "ago" ? -count : count
|
|
119
|
+
|
|
120
|
+
case unit_type
|
|
121
|
+
when :days
|
|
122
|
+
@reference_date + offset
|
|
123
|
+
when :weeks
|
|
124
|
+
@reference_date + (offset * 7)
|
|
125
|
+
when :months
|
|
126
|
+
@reference_date >> offset
|
|
127
|
+
when :years
|
|
128
|
+
@reference_date >> (offset * 12)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Matches simple patterns like "tomorrow", "yesterday", "today"
|
|
133
|
+
def parse_simple_relative
|
|
134
|
+
case @input
|
|
135
|
+
when "today"
|
|
136
|
+
@reference_date
|
|
137
|
+
when "tomorrow"
|
|
138
|
+
@reference_date + 1
|
|
139
|
+
when "yesterday"
|
|
140
|
+
@reference_date - 1
|
|
141
|
+
else
|
|
142
|
+
nil
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Matches "today", "tomorrow", "yesterday"
|
|
147
|
+
def parse_today_tomorrow_yesterday
|
|
148
|
+
parse_simple_relative
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "relative_date/version"
|
|
4
|
+
require_relative "relative_date/parser"
|
|
5
|
+
|
|
6
|
+
module RelativeDate
|
|
7
|
+
class Error < StandardError; end
|
|
8
|
+
|
|
9
|
+
# Parse a relative time expression and return a Date
|
|
10
|
+
#
|
|
11
|
+
# @param input [String] the relative time expression (e.g., "2 Mondays ago")
|
|
12
|
+
# @param reference_date [Date] the date to calculate from (defaults to today)
|
|
13
|
+
# @return [Date, nil] the calculated date, or nil if the input can't be parsed
|
|
14
|
+
def self.parse(input, reference_date: Date.today)
|
|
15
|
+
Parser.new(input, reference_date: reference_date).parse
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Parse a relative time expression and return a Date, raising an error if parsing fails
|
|
19
|
+
#
|
|
20
|
+
# @param input [String] the relative time expression
|
|
21
|
+
# @param reference_date [Date] the date to calculate from (defaults to today)
|
|
22
|
+
# @return [Date] the calculated date
|
|
23
|
+
# @raise [Error] if the input can't be parsed
|
|
24
|
+
def self.parse!(input, reference_date: Date.today)
|
|
25
|
+
result = parse(input, reference_date: reference_date)
|
|
26
|
+
raise Error, "Unable to parse '#{input}'" if result.nil?
|
|
27
|
+
|
|
28
|
+
result
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "lib/relative_date/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "relative_date"
|
|
7
|
+
spec.version = RelativeDate::VERSION
|
|
8
|
+
spec.authors = ["Thomas Powell"]
|
|
9
|
+
spec.email = ["twilliampowell@gmail.com"]
|
|
10
|
+
|
|
11
|
+
spec.summary = "Parse and calculate relative dates like '2 Mondays ago'"
|
|
12
|
+
spec.description = "A Ruby gem for parsing natural language relative date expressions like '2 Mondays ago', 'next Friday', '3 weeks from now', etc."
|
|
13
|
+
spec.homepage = "https://github.com/stringsn88keys/relative_date"
|
|
14
|
+
spec.license = "MIT"
|
|
15
|
+
spec.required_ruby_version = ">= 2.7.0"
|
|
16
|
+
|
|
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
|
+
|
|
21
|
+
# Specify which files should be added to the gem when it is released.
|
|
22
|
+
spec.files = Dir.chdir(__dir__) do
|
|
23
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
|
24
|
+
(File.expand_path(f) == __FILE__) ||
|
|
25
|
+
f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor Gemfile])
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
spec.bindir = "exe"
|
|
29
|
+
spec.executables = ["relative_date"]
|
|
30
|
+
spec.require_paths = ["lib"]
|
|
31
|
+
|
|
32
|
+
# Dependencies
|
|
33
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
|
34
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
|
35
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: relative_date
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Thomas Powell
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2025-12-06 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: rake
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '13.0'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '13.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rspec
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '3.0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '3.0'
|
|
41
|
+
description: A Ruby gem for parsing natural language relative date expressions like
|
|
42
|
+
'2 Mondays ago', 'next Friday', '3 weeks from now', etc.
|
|
43
|
+
email:
|
|
44
|
+
- twilliampowell@gmail.com
|
|
45
|
+
executables:
|
|
46
|
+
- relative_date
|
|
47
|
+
extensions: []
|
|
48
|
+
extra_rdoc_files: []
|
|
49
|
+
files:
|
|
50
|
+
- ".claude/settings.local.json"
|
|
51
|
+
- ".rspec"
|
|
52
|
+
- CHANGELOG.md
|
|
53
|
+
- INSTALLATION.md
|
|
54
|
+
- LICENSE
|
|
55
|
+
- README.md
|
|
56
|
+
- Rakefile
|
|
57
|
+
- examples/usage.rb
|
|
58
|
+
- exe/relative_date
|
|
59
|
+
- lib/relative_date.rb
|
|
60
|
+
- lib/relative_date/parser.rb
|
|
61
|
+
- lib/relative_date/version.rb
|
|
62
|
+
- relative_date.gemspec
|
|
63
|
+
homepage: https://github.com/stringsn88keys/relative_date
|
|
64
|
+
licenses:
|
|
65
|
+
- MIT
|
|
66
|
+
metadata:
|
|
67
|
+
homepage_uri: https://github.com/stringsn88keys/relative_date
|
|
68
|
+
source_code_uri: https://github.com/stringsn88keys/relative_date
|
|
69
|
+
changelog_uri: https://github.com/stringsn88keys/relative_date/blob/main/CHANGELOG.md
|
|
70
|
+
post_install_message:
|
|
71
|
+
rdoc_options: []
|
|
72
|
+
require_paths:
|
|
73
|
+
- lib
|
|
74
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
75
|
+
requirements:
|
|
76
|
+
- - ">="
|
|
77
|
+
- !ruby/object:Gem::Version
|
|
78
|
+
version: 2.7.0
|
|
79
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
80
|
+
requirements:
|
|
81
|
+
- - ">="
|
|
82
|
+
- !ruby/object:Gem::Version
|
|
83
|
+
version: '0'
|
|
84
|
+
requirements: []
|
|
85
|
+
rubygems_version: 3.5.16
|
|
86
|
+
signing_key:
|
|
87
|
+
specification_version: 4
|
|
88
|
+
summary: Parse and calculate relative dates like '2 Mondays ago'
|
|
89
|
+
test_files: []
|