tickle 1.2.0 → 2.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -1
- data/.travis.yml +6 -4
- data/CHANGES.md +6 -24
- data/Gemfile +13 -4
- data/LICENCE +1 -1
- data/README.md +2 -2
- data/Rakefile +4 -7
- data/benchmarks/main.rb +71 -0
- data/lib/ext/array.rb +6 -0
- data/lib/ext/date_and_time.rb +64 -0
- data/lib/ext/string.rb +39 -0
- data/lib/tickle.rb +24 -117
- data/lib/tickle/filters.rb +58 -0
- data/lib/tickle/handler.rb +223 -80
- data/lib/tickle/helpers.rb +51 -0
- data/lib/tickle/patterns.rb +115 -0
- data/lib/tickle/repeater.rb +232 -116
- data/lib/tickle/tickle.rb +98 -220
- data/lib/tickle/tickled.rb +174 -0
- data/lib/tickle/token.rb +94 -0
- data/lib/tickle/version.rb +1 -1
- data/spec/helpers_spec.rb +36 -0
- data/spec/patterns_spec.rb +240 -0
- data/spec/spec_helper.rb +41 -0
- data/spec/tickle_spec.rb +543 -0
- data/spec/token_spec.rb +82 -0
- data/test/helper.rb +0 -2
- data/test/test_parsing.rb +14 -23
- data/tickle.gemspec +2 -9
- metadata +32 -113
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1d8b35a7f362cfb13d80ef3236f7f1f036cd7ebc
|
4
|
+
data.tar.gz: cec9f257100839052b8ea4e7b7a64fa560eebd01
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d3d2636db14e75ce64453d3bf42bfdb4f22fde65af54a01b5113a58447ab46e9530a39f16771265a28e345a16b22417ae6dca699c8828cd9d4825d756db38af0
|
7
|
+
data.tar.gz: e972f8974b427bcb5ad3be4333b4ac0951a82ada15f9cc8def342ff7414dd636e4169f757f579a3f17baa9c1578a265bc33865ee596f1e1aee2f2a4123be052b
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
@@ -1,12 +1,14 @@
|
|
1
1
|
language: ruby
|
2
2
|
rvm:
|
3
|
-
-
|
4
|
-
-
|
5
|
-
-
|
3
|
+
- 1.9.3
|
4
|
+
- 2.0.0
|
5
|
+
- 2.1.1
|
6
|
+
- ruby-head
|
7
|
+
- jruby-19mode
|
8
|
+
# - rbx-2.1.1 # not working right now, will look at later
|
6
9
|
|
7
10
|
# whitelist
|
8
11
|
branches:
|
9
12
|
only:
|
10
13
|
- master
|
11
14
|
- develop
|
12
|
-
- v1-branch
|
data/CHANGES.md
CHANGED
@@ -1,39 +1,21 @@
|
|
1
1
|
# CH CH CH CH CHANGES! #
|
2
2
|
|
3
|
-
##
|
3
|
+
## Saturday the 7th of November 2015, 2.0.0.rc1 ##
|
4
4
|
|
5
|
-
-
|
6
|
-
-
|
7
|
-
-
|
8
|
-
- Fixed bug with "today and" in the tests
|
9
|
-
- Fixed a bug previously fixed on the v2 branch in 2f2a32ce9 with @start
|
10
|
-
- Added Timecop because we're beyond the date chosen in the tests. Tempus fugit!
|
5
|
+
- Rewrite of the internals.
|
6
|
+
- API should be pretty much the same from the outside, but enough things on the inside change to warrant the major version bump.
|
7
|
+
- Chronic updated to 0.10.2.
|
11
8
|
|
12
9
|
----
|
13
10
|
|
14
|
-
##
|
15
|
-
|
16
|
-
* Bit of easier debugging added in.
|
17
|
-
* Bug fixes, thanks to https://github.com/dodomarocgenex for finding and helping with them.
|
18
|
-
|
19
|
-
----
|
20
|
-
|
21
|
-
## Wednesday the 22nd of February 2017, v1.1.0 ##
|
22
|
-
|
23
|
-
* Numerizer duplication removed. Thanks to https://github.com/bjonord.
|
24
|
-
* Some very minor changes to the project, no other code changes.
|
25
|
-
|
26
|
-
----
|
27
|
-
|
28
|
-
|
29
|
-
## Monday the 11th of November 2015, v1.0.2 ##
|
11
|
+
## Monday the 11th of November 2013, v1.0.2 ##
|
30
12
|
|
31
13
|
* Shoulda and simplecov aren't runtime dependencies, fixed that in the gemfile.
|
32
14
|
* Got the version number right this time ;-)
|
33
15
|
|
34
16
|
----
|
35
17
|
|
36
|
-
## Monday the 11th of November
|
18
|
+
## Monday the 11th of November 2013, v1.0.1 ##
|
37
19
|
|
38
20
|
* Moved library to new maintainer [https://github.com/yb66/tickle](@yb66)
|
39
21
|
* Moved library to [http://semver.org/](semver).
|
data/Gemfile
CHANGED
@@ -2,9 +2,18 @@ source "https://rubygems.org"
|
|
2
2
|
|
3
3
|
gemspec
|
4
4
|
|
5
|
-
group :
|
6
|
-
gem "
|
5
|
+
group :development do
|
6
|
+
gem "wirble"
|
7
|
+
gem "maruku"
|
8
|
+
gem "yard"
|
9
|
+
gem "pry-byebug"
|
10
|
+
gem "activesupport"
|
7
11
|
end
|
8
12
|
|
9
|
-
|
10
|
-
gem "
|
13
|
+
group :test do
|
14
|
+
gem "rspec"
|
15
|
+
gem "rspec-its"
|
16
|
+
gem "timecop"
|
17
|
+
gem "simplecov"
|
18
|
+
gem "rake"
|
19
|
+
end
|
data/LICENCE
CHANGED
data/README.md
CHANGED
@@ -11,7 +11,7 @@ Take a look at the `develop` branch where all this stuff will be happening.
|
|
11
11
|
|
12
12
|
Tickle is a natural language parser for recurring events.
|
13
13
|
|
14
|
-
Tickle is designed to be a complement to [Chronic](https://rubygems.org/gems/chronic) and can interpret things such as "every 2 days, every Sunday, Sundays, Weekly, etc.
|
14
|
+
Tickle is designed to be a complement to [Chronic](https://rubygems.org/gems/chronic) and can interpret things such as "every 2 days", "every Sunday", “Sundays", “Weekly", etc.
|
15
15
|
|
16
16
|
Tickle has one main method, `Tickle.parse`, which returns the next time the event should occur, at which point you simply call `Tickle.parse` again.
|
17
17
|
|
@@ -407,7 +407,7 @@ or if you're using Bundler, add this to the Gemfile:
|
|
407
407
|
|
408
408
|
Chronic gem:
|
409
409
|
|
410
|
-
`gem install
|
410
|
+
`gem install chronic`
|
411
411
|
|
412
412
|
thoughtbot's [shoulda](https://rubygems.org/gems/shoulda):
|
413
413
|
|
data/Rakefile
CHANGED
@@ -1,11 +1,8 @@
|
|
1
|
-
require '
|
2
|
-
|
3
|
-
|
4
|
-
test.pattern = 'test/**/test_*.rb'
|
5
|
-
test.verbose = true
|
6
|
-
end
|
1
|
+
require 'rspec/core/rake_task'
|
2
|
+
|
3
|
+
RSpec::Core::RakeTask.new(:spec)
|
7
4
|
|
8
|
-
task :default => :
|
5
|
+
task :default => :spec
|
9
6
|
|
10
7
|
|
11
8
|
desc "(Re-) generate documentation and place it in the docs/ dir. Open the index.html file in there to read it."
|
data/benchmarks/main.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'benchmark'
|
2
|
+
require_relative "../lib/tickle.rb"
|
3
|
+
warn Benchmark.measure {
|
4
|
+
Tickle.parse('day')
|
5
|
+
# Tickle.parse('week')
|
6
|
+
Tickle.parse('month')
|
7
|
+
Tickle.parse('year')
|
8
|
+
Tickle.parse('daily')
|
9
|
+
# Tickle.parse('weekly')
|
10
|
+
Tickle.parse('monthly')
|
11
|
+
Tickle.parse('yearly')
|
12
|
+
Tickle.parse('3 days')
|
13
|
+
# Tickle.parse('3 weeks')
|
14
|
+
Tickle.parse('3 months')
|
15
|
+
Tickle.parse('3 years')
|
16
|
+
Tickle.parse('other day')
|
17
|
+
# Tickle.parse('other week')
|
18
|
+
Tickle.parse('other month')
|
19
|
+
Tickle.parse('other year')
|
20
|
+
Tickle.parse('Monday')
|
21
|
+
Tickle.parse('Wednesday')
|
22
|
+
Tickle.parse('Friday')
|
23
|
+
Tickle.parse('February', {:start=>Time.parse("2020-04-01 00:00:00 +0000"), :now=>Time.parse("2020-04-01 00:00:00 +0000")})
|
24
|
+
Tickle.parse('May', {:start=>Time.parse("2020-04-01 00:00:00 +0000"), :now=>Time.parse("2020-04-01 00:00:00 +0000")})
|
25
|
+
Tickle.parse('june', {:start=>Time.parse("2020-04-01 00:00:00 +0000"), :now=>Time.parse("2020-04-01 00:00:00 +0000")})
|
26
|
+
Tickle.parse('beginning of the week')
|
27
|
+
Tickle.parse('middle of the week')
|
28
|
+
Tickle.parse('end of the week')
|
29
|
+
Tickle.parse('beginning of the month', {:start=>Time.parse("2020-04-01 00:00:00 +0000"), :now=>Time.parse("2020-04-01 00:00:00 +0000")})
|
30
|
+
Tickle.parse('middle of the month', {:start=>Time.parse("2020-04-01 00:00:00 +0000"), :now=>Time.parse("2020-04-01 00:00:00 +0000")})
|
31
|
+
Tickle.parse('end of the month', {:start=>Time.parse("2020-04-01 00:00:00 +0000"), :now=>Time.parse("2020-04-01 00:00:00 +0000")})
|
32
|
+
Tickle.parse('beginning of the year', {:start=>Time.parse("2020-04-01 00:00:00 +0000"), :now=>Time.parse("2020-04-01 00:00:00 +0000")})
|
33
|
+
Tickle.parse('middle of the year', {:start=>Time.parse("2020-04-01 00:00:00 +0000"), :now=>Time.parse("2020-04-01 00:00:00 +0000")})
|
34
|
+
Tickle.parse('end of the year', {:start=>Time.parse("2020-04-01 00:00:00 +0000"), :now=>Time.parse("2020-04-01 00:00:00 +0000")})
|
35
|
+
Tickle.parse('the 3rd of May', {:start=>Time.parse("2020-04-01 00:00:00 +0000"), :now=>Time.parse("2020-04-01 00:00:00 +0000")})
|
36
|
+
Tickle.parse('the 3rd of February', {:start=>Time.parse("2020-04-01 00:00:00 +0000"), :now=>Time.parse("2020-04-01 00:00:00 +0000")})
|
37
|
+
Tickle.parse('the 3rd of February 2022', {:start=>Time.parse("2020-04-01 00:00:00 +0000"), :now=>Time.parse("2020-04-01 00:00:00 +0000")})
|
38
|
+
Tickle.parse('the 3rd of Feb 2022', {:start=>Time.parse("2020-04-01 00:00:00 +0000"), :now=>Time.parse("2020-04-01 00:00:00 +0000")})
|
39
|
+
Tickle.parse('the 4th of the month', {:start=>Time.parse("2020-04-01 00:00:00 +0000"), :now=>Time.parse("2020-04-01 00:00:00 +0000")})
|
40
|
+
Tickle.parse('the 10th of the month', {:start=>Time.parse("2020-04-01 00:00:00 +0000"), :now=>Time.parse("2020-04-01 00:00:00 +0000")})
|
41
|
+
Tickle.parse('the tenth of the month', {:start=>Time.parse("2020-04-01 00:00:00 +0000"), :now=>Time.parse("2020-04-01 00:00:00 +0000")})
|
42
|
+
Tickle.parse('first', {:start=>Time.parse("2020-04-01 00:00:00 +0000"), :now=>Time.parse("2020-04-01 00:00:00 +0000")})
|
43
|
+
Tickle.parse('the first of the month', {:start=>Time.parse("2020-04-01 00:00:00 +0000"), :now=>Time.parse("2020-04-01 00:00:00 +0000")})
|
44
|
+
Tickle.parse('the thirtieth', {:start=>Time.parse("2020-04-01 00:00:00 +0000"), :now=>Time.parse("2020-04-01 00:00:00 +0000")})
|
45
|
+
Tickle.parse('the fifth', {:start=>Time.parse("2020-04-01 00:00:00 +0000"), :now=>Time.parse("2020-04-01 00:00:00 +0000")})
|
46
|
+
Tickle.parse('the 1st Wednesday of the month', {:start=>Time.parse("2020-04-01 00:00:00 +0000"), :now=>Time.parse("2020-04-01 00:00:00 +0000")})
|
47
|
+
Tickle.parse('the 3rd Sunday of May', {:start=>Time.parse("2020-04-01 00:00:00 +0000"), :now=>Time.parse("2020-04-01 00:00:00 +0000")})
|
48
|
+
Tickle.parse('the 3rd Sunday of the month', {:start=>Time.parse("2020-04-01 00:00:00 +0000"), :now=>Time.parse("2020-04-01 00:00:00 +0000")})
|
49
|
+
Tickle.parse('the 23rd of June', {:start=>Time.parse("2020-04-01 00:00:00 +0000"), :now=>Time.parse("2020-04-01 00:00:00 +0000")})
|
50
|
+
Tickle.parse('the twenty third of June', {:start=>Time.parse("2020-04-01 00:00:00 +0000"), :now=>Time.parse("2020-04-01 00:00:00 +0000")})
|
51
|
+
Tickle.parse('the thirty first of July', {:start=>Time.parse("2020-04-01 00:00:00 +0000"), :now=>Time.parse("2020-04-01 00:00:00 +0000")})
|
52
|
+
Tickle.parse('the twenty first', {:start=>Time.parse("2020-04-01 00:00:00 +0000"), :now=>Time.parse("2020-04-01 00:00:00 +0000")})
|
53
|
+
Tickle.parse('the twenty first of the month', {:start=>Time.parse("2020-04-01 00:00:00 +0000"), :now=>Time.parse("2020-04-01 00:00:00 +0000")})
|
54
|
+
Tickle.parse('starting today and ending one week from now')
|
55
|
+
Tickle.parse('starting tomorrow and ending one week from now')
|
56
|
+
Tickle.parse('starting Monday repeat every month')
|
57
|
+
Tickle.parse('starting May 13th repeat every week')
|
58
|
+
Tickle.parse('starting May 13th repeat every other day')
|
59
|
+
Tickle.parse('every other day starts May 13th')
|
60
|
+
Tickle.parse('every other day starts May 13')
|
61
|
+
Tickle.parse('every other day starting May 13th')
|
62
|
+
Tickle.parse('every other day starting May 13')
|
63
|
+
Tickle.parse('every week starts this wednesday')
|
64
|
+
Tickle.parse('every week starting this wednesday')
|
65
|
+
Tickle.parse('every other day starting May 1st 2021')
|
66
|
+
Tickle.parse('every other day starting May 1 2021')
|
67
|
+
Tickle.parse('every other week starting this Sunday')
|
68
|
+
Tickle.parse('every week starting this wednesday until May 13th')
|
69
|
+
Tickle.parse('every week starting this wednesday ends May 13th')
|
70
|
+
Tickle.parse('every week starting this wednesday ending May 13th')
|
71
|
+
}
|
data/lib/ext/array.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
class Date
|
2
|
+
# returns the days in the sending month
|
3
|
+
def days_in_month
|
4
|
+
d,m,y = mday,month,year
|
5
|
+
d += 1 while Date.valid_civil?(y,m,d)
|
6
|
+
d - 1
|
7
|
+
end
|
8
|
+
|
9
|
+
|
10
|
+
# no idea what this is for
|
11
|
+
def bump(attr, amount=nil)
|
12
|
+
amount ||= 1
|
13
|
+
case attr
|
14
|
+
when :day then
|
15
|
+
Date.civil(self.year, self.month, self.day + amount)
|
16
|
+
when :wday then
|
17
|
+
amount = Date::ABBR_DAYNAMES.index(amount) if amount.is_a?(String)
|
18
|
+
raise Exception, "specified day of week invalid. Use #{Date::ABBR_DAYNAMES}" unless amount
|
19
|
+
diff = (amount > self.wday) ? (amount - self.wday) : (7 - (self.wday - amount))
|
20
|
+
Date.civil(self.year, self.month, self.day + diff)
|
21
|
+
when :week then
|
22
|
+
Date.civil(self.year, self.month, self.day + (7*amount))
|
23
|
+
when :month then
|
24
|
+
Date.civil(self.year, self.month+amount, self.day)
|
25
|
+
when :year then
|
26
|
+
Date.civil(self.year + amount, self.month, self.day)
|
27
|
+
when :sec then
|
28
|
+
Date.civil(self.year, self.month, self.day, self.hour, self.minute, self.sec + amount)
|
29
|
+
else
|
30
|
+
raise Exception, "type \"#{attr}\" not supported."
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class Time
|
36
|
+
|
37
|
+
# same again, no idea what this is for
|
38
|
+
def bump(attr, amount=nil)
|
39
|
+
amount ||= 1
|
40
|
+
case attr
|
41
|
+
when :sec then
|
42
|
+
Time.local(self.year, self.month, self.day, self.hour, self.min, self.sec + amount)
|
43
|
+
when :min then
|
44
|
+
Time.local(self.year, self.month, self.day, self.hour, self.min + amount, self.sec)
|
45
|
+
when :hour then
|
46
|
+
Time.local(self.year, self.month, self.day, self.hour + amount, self.min, self.sec)
|
47
|
+
when :day then
|
48
|
+
Time.local(self.year, self.month, self.day + amount, self.hour, self.min, self.sec)
|
49
|
+
when :wday then
|
50
|
+
amount = Time::RFC2822_DAY_NAME.index(amount) if amount.is_a?(String)
|
51
|
+
raise Exception, "specified day of week invalid. Use #{Time::RFC2822_DAY_NAME}" unless amount
|
52
|
+
diff = (amount > self.wday) ? (amount - self.wday) : (7 - (self.wday - amount))
|
53
|
+
Time.local(self.year, self.month, self.day + diff, self.hour, self.min, self.sec)
|
54
|
+
when :week then
|
55
|
+
Time.local(self.year, self.month, self.day + (amount * 7), self.hour, self.min, self.sec)
|
56
|
+
when :month then
|
57
|
+
Time.local(self.year, self.month + amount, self.day, self.hour, self.min, self.sec)
|
58
|
+
when :year then
|
59
|
+
Time.local(self.year + amount, self.month, self.day, self.hour, self.min, self.sec)
|
60
|
+
else
|
61
|
+
raise Exception, "type \"#{attr}\" not supported."
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/lib/ext/string.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
module Tickle
|
2
|
+
class Ordinal < ::String
|
3
|
+
# returns true if the sending string is a text or numeric ordinal (e.g. first or 1st)
|
4
|
+
def is_ordinal?
|
5
|
+
scanner = %w{first second third fourth fifth sixth seventh eighth ninth tenth eleventh twelfth thirteenth fourteenth fifteenth sixteenth seventeenth eighteenth nineteenth twenty thirty thirtieth}
|
6
|
+
regex = /\b(\d*)(st|nd|rd|th)\b/
|
7
|
+
!(self =~ regex).nil? || scanner.include?(self.downcase)
|
8
|
+
end
|
9
|
+
|
10
|
+
def ordinal_as_number
|
11
|
+
return self unless self.is_ordinal?
|
12
|
+
scanner = {/first/ => '1st',
|
13
|
+
/second/ => '2nd',
|
14
|
+
/third/ => '3rd',
|
15
|
+
/fourth/ => '4th',
|
16
|
+
/fifth/ => '5th',
|
17
|
+
/sixth/ => '6th',
|
18
|
+
/seventh/ => '7th',
|
19
|
+
/eighth/ => '8th',
|
20
|
+
/ninth/ => '9th',
|
21
|
+
/tenth/ => '10th',
|
22
|
+
/eleventh/ => '11th',
|
23
|
+
/twelfth/ => '12th',
|
24
|
+
/thirteenth/ => '13th',
|
25
|
+
/fourteenth/ => '14th',
|
26
|
+
/fifteenth/ => '15th',
|
27
|
+
/sixteenth/ => '16th',
|
28
|
+
/seventeenth/ => '17th',
|
29
|
+
/eighteenth/ => '18th',
|
30
|
+
/nineteenth/ => '19th',
|
31
|
+
/twentieth/ => '20th',
|
32
|
+
/thirtieth/ => '30th',
|
33
|
+
}
|
34
|
+
result = self
|
35
|
+
scanner.keys.each {|scanner_item| result = scanner[scanner_item] if scanner_item =~ self}
|
36
|
+
return result.gsub(/\b(\d*)(st|nd|rd|th)\b/, '\1')
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/tickle.rb
CHANGED
@@ -9,29 +9,40 @@
|
|
9
9
|
|
10
10
|
$LOAD_PATH.unshift(File.dirname(__FILE__)) # For use/testing when no gem is installed
|
11
11
|
|
12
|
-
if ENV["DEBUG"]
|
13
|
-
warn "DEBUG MODE ON"
|
14
|
-
require 'pry-byebug'
|
15
|
-
require 'pry-state'
|
16
|
-
binding.pry
|
17
|
-
end
|
18
|
-
|
19
12
|
require 'date'
|
20
13
|
require 'time'
|
21
|
-
require '
|
14
|
+
require 'chronic'
|
22
15
|
|
23
16
|
require 'tickle/tickle'
|
24
17
|
require 'tickle/handler'
|
25
18
|
require 'tickle/repeater'
|
19
|
+
require_relative "tickle/tickled.rb"
|
20
|
+
require_relative "ext/array.rb"
|
21
|
+
require_relative "ext/date_and_time.rb"
|
22
|
+
require_relative "ext/string.rb"
|
23
|
+
|
24
|
+
# these are required not because they're used by the library
|
25
|
+
# but because they clobber so much that testing
|
26
|
+
# without them will miss possible problems
|
27
|
+
require 'active_support/core_ext/string/conversions'
|
28
|
+
require 'active_support/core_ext/date/calculations'
|
29
|
+
require 'active_support/core_ext/date_time/calculations'
|
30
|
+
require 'active_support/core_ext/time/calculations'
|
26
31
|
|
27
|
-
|
32
|
+
# Tickle is a natural language parser for recurring events.
|
33
|
+
module Tickle
|
28
34
|
|
29
|
-
def self.
|
35
|
+
def self.parse(asked, options = {})
|
36
|
+
# check to see if a datetime was passed
|
37
|
+
# if so, give it back
|
38
|
+
# TODO Consider converting to a Tickled
|
39
|
+
return asked if asked.respond_to? :day
|
30
40
|
|
31
|
-
|
32
|
-
|
41
|
+
tickled = Tickled.new asked.dup, options
|
42
|
+
_parse tickled
|
33
43
|
end
|
34
44
|
|
45
|
+
|
35
46
|
def self.is_date(str)
|
36
47
|
begin
|
37
48
|
Date.parse(str.to_s)
|
@@ -40,108 +51,4 @@ module Tickle #:nodoc:
|
|
40
51
|
return false
|
41
52
|
end
|
42
53
|
end
|
43
|
-
end
|
44
|
-
|
45
|
-
class Date #:nodoc:
|
46
|
-
# returns the days in the sending month
|
47
|
-
def days_in_month
|
48
|
-
d,m,y = mday,month,year
|
49
|
-
d += 1 while Date.valid_civil?(y,m,d)
|
50
|
-
d - 1
|
51
|
-
end
|
52
|
-
|
53
|
-
def bump(attr, amount=nil)
|
54
|
-
amount ||= 1
|
55
|
-
case attr
|
56
|
-
when :day then
|
57
|
-
Date.civil(self.year, self.month, self.day).next_day(amount)
|
58
|
-
when :wday then
|
59
|
-
amount = Date::ABBR_DAYNAMES.index(amount) if amount.is_a?(String)
|
60
|
-
raise Exception, "specified day of week invalid. Use #{Date::ABBR_DAYNAMES}" unless amount
|
61
|
-
diff = (amount > self.wday) ? (amount - self.wday) : (7 - (self.wday - amount))
|
62
|
-
Date.civil(self.year, self.month, self.day).next_day(diff)
|
63
|
-
when :week then
|
64
|
-
Date.civil(self.year, self.month, self.day).next_day(7*amount)
|
65
|
-
when :month then
|
66
|
-
Date.civil(self.year, self.month, self.day).next_month(amount)
|
67
|
-
when :year then
|
68
|
-
Date.civil(self.year, self.month, self.day).next_year(amount)
|
69
|
-
else
|
70
|
-
raise Exception, "type \"#{attr}\" not supported."
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
class Time #:nodoc:
|
76
|
-
def bump(attr, amount=nil)
|
77
|
-
amount ||= 1
|
78
|
-
case attr
|
79
|
-
when :sec then
|
80
|
-
Time.local(self.year, self.month, self.day, self.hour, self.min, self.sec) + amount
|
81
|
-
when :min then
|
82
|
-
Time.local(self.year, self.month, self.day, self.hour, self.min, self.sec) + (amount * 60)
|
83
|
-
when :hour then
|
84
|
-
Time.local(self.year, self.month, self.day, self.hour, self.min, self.sec) + (amount * 60 * 60)
|
85
|
-
when :day then
|
86
|
-
Time.local(self.year, self.month, self.day, self.hour, self.min, self.sec) + (amount * 60 * 60 * 24)
|
87
|
-
when :wday then
|
88
|
-
amount = Time::RFC2822_DAY_NAME.index(amount) if amount.is_a?(String)
|
89
|
-
raise Exception, "specified day of week invalid. Use #{Time::RFC2822_DAY_NAME}" unless amount
|
90
|
-
diff = (amount > self.wday) ? (amount - self.wday) : (7 - (self.wday - amount))
|
91
|
-
DateTime.civil(self.year, self.month, self.day, self.hour, self.min, self.sec, self.zone).next_day(diff)
|
92
|
-
when :week then
|
93
|
-
DateTime.civil(self.year, self.month, self.day, self.hour, self.min, self.sec, self.zone).next_day(amount * 7)
|
94
|
-
when :month then
|
95
|
-
DateTime.civil(self.year, self.month, self.day, self.hour, self.min, self.sec, self.zone).next_month(amount)
|
96
|
-
when :year then
|
97
|
-
DateTime.civil(self.year, self.month, self.day, self.hour, self.min, self.sec, self.zone).next_year(amount)
|
98
|
-
else
|
99
|
-
raise Exception, "type \"#{attr}\" not supported."
|
100
|
-
end.to_time.localtime
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
class String #:nodoc:
|
105
|
-
# returns true if the sending string is a text or numeric ordinal (e.g. first or 1st)
|
106
|
-
def is_ordinal?
|
107
|
-
scanner = %w{first second third fourth fifth sixth seventh eighth ninth tenth eleventh twelfth thirteenth fourteenth fifteenth sixteenth seventeenth eighteenth nineteenth twenty thirty thirtieth}
|
108
|
-
regex = /\b(\d*)(st|nd|rd|th)\b/
|
109
|
-
!(self =~ regex).nil? || scanner.include?(self.downcase)
|
110
|
-
end
|
111
|
-
|
112
|
-
def ordinal_as_number
|
113
|
-
return self unless self.is_ordinal?
|
114
|
-
scanner = {/first/ => '1st',
|
115
|
-
/second/ => '2nd',
|
116
|
-
/third/ => '3rd',
|
117
|
-
/fourth/ => '4th',
|
118
|
-
/fifth/ => '5th',
|
119
|
-
/sixth/ => '6th',
|
120
|
-
/seventh/ => '7th',
|
121
|
-
/eighth/ => '8th',
|
122
|
-
/ninth/ => '9th',
|
123
|
-
/tenth/ => '10th',
|
124
|
-
/eleventh/ => '11th',
|
125
|
-
/twelfth/ => '12th',
|
126
|
-
/thirteenth/ => '13th',
|
127
|
-
/fourteenth/ => '14th',
|
128
|
-
/fifteenth/ => '15th',
|
129
|
-
/sixteenth/ => '16th',
|
130
|
-
/seventeenth/ => '17th',
|
131
|
-
/eighteenth/ => '18th',
|
132
|
-
/nineteenth/ => '19th',
|
133
|
-
/twentieth/ => '20th',
|
134
|
-
/thirtieth/ => '30th',
|
135
|
-
}
|
136
|
-
result = self
|
137
|
-
scanner.keys.each {|scanner_item| result = scanner[scanner_item] if scanner_item =~ self}
|
138
|
-
return result.gsub(/\b(\d*)(st|nd|rd|th)\b/, '\1')
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
|
-
class Array #:nodoc:
|
143
|
-
# compares two arrays to determine if they both contain the same elements
|
144
|
-
def same?(y)
|
145
|
-
self.sort == y.sort
|
146
|
-
end
|
147
|
-
end
|
54
|
+
end
|