time_pup 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +79 -0
- data/Rakefile +1 -0
- data/lib/time_pup.rb +24 -0
- data/lib/time_pup/adverb_parser.rb +104 -0
- data/lib/time_pup/date_time_parser.rb +24 -0
- data/lib/time_pup/incremental_time.rb +47 -0
- data/lib/time_pup/time_parser.rb +48 -0
- data/lib/time_pup/version.rb +3 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/time_pup/adverb_parser_spec.rb +75 -0
- data/spec/time_pup/date_time_parser_spec.rb +23 -0
- data/spec/time_pup/incremental_time_spec.rb +117 -0
- data/spec/time_pup/time_parser_spec.rb +61 -0
- data/spec/time_pup_spec.rb +36 -0
- data/time_pup.gemspec +24 -0
- metadata +126 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e6853e36eb663fcd250e211152181b56f540f137
|
4
|
+
data.tar.gz: 9babb3d3625d59a0dd1668282617f951e6ac485a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e4a71b79772ad63d1717ac7387e3e00ce97f05bf72f97d7433dee85fc1190342072789395af00b49d21b435d77137e6c591d1d024d154cc21b65c6242939ddd8
|
7
|
+
data.tar.gz: e7a45f81fc0c4a9ef59125f9be5ef1a2cb695b1ab8ca6a2461a56a7bec0a823f0a51879721bd16347f4efe8b28ce60d31e8723c0135b17781d23b56edc8a56d8
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Siyelo Software
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
# TimePup
|
2
|
+
|
3
|
+
A simple natural language date time parser extracted from [hound.cc](http://www.hound.cc). It is perfect for parsing the local part of an email address.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'time_pup'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install time_pup
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TimePup.parse( 'tomorrow', timezone = UTC )
|
22
|
+
|
23
|
+
The time is always returned in UTC, it is just adjusted based on the time zone provided e.g:
|
24
|
+
|
25
|
+
* TimePup.parse( '8am' ) #=> 8 AM UTC
|
26
|
+
* TimePup.parse( '8am', 'Harare' ) #=> 6 AM UTC
|
27
|
+
|
28
|
+
### Examples
|
29
|
+
|
30
|
+
**From now**
|
31
|
+
|
32
|
+
> 2minutes <br/>
|
33
|
+
> 3hours <br/>
|
34
|
+
> 1day <br/>
|
35
|
+
> 2weeks <br/>
|
36
|
+
> 3month <br/>
|
37
|
+
|
38
|
+
**Composite**
|
39
|
+
|
40
|
+
> 1day2hours <br/>
|
41
|
+
|
42
|
+
**Weekdays**
|
43
|
+
|
44
|
+
> friday <br/>
|
45
|
+
> monday9am <br/>
|
46
|
+
> nexttuesday <br/>
|
47
|
+
> nextweek <br/>
|
48
|
+
|
49
|
+
**Handy**
|
50
|
+
|
51
|
+
> tomorrow <br/>
|
52
|
+
> tomorrow1030am <br/>
|
53
|
+
> endofday <br/>
|
54
|
+
|
55
|
+
**Actual Date**
|
56
|
+
|
57
|
+
> 04july <br/>
|
58
|
+
> aug-10 <br/>
|
59
|
+
> 16sept1030am <br/>
|
60
|
+
|
61
|
+
**Actual Time**
|
62
|
+
|
63
|
+
> 1030 <br/>
|
64
|
+
> 22 <br/>
|
65
|
+
> 1030am <br/>
|
66
|
+
|
67
|
+
**Abbreviations**
|
68
|
+
|
69
|
+
> 2m <br/>
|
70
|
+
> 5d <br/>
|
71
|
+
> 1mo <br/>
|
72
|
+
|
73
|
+
## Contributing
|
74
|
+
|
75
|
+
1. Fork it
|
76
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
77
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
78
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
79
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/lib/time_pup.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require "time_pup/version"
|
2
|
+
|
3
|
+
module TimePup
|
4
|
+
require 'active_support/core_ext/numeric/time'
|
5
|
+
require 'active_support/core_ext/integer/time'
|
6
|
+
require 'active_support/values/time_zone'
|
7
|
+
require 'active_support/time_with_zone'
|
8
|
+
require 'active_support/core_ext/time/calculations'
|
9
|
+
require 'active_support/core_ext/date_time/calculations'
|
10
|
+
require 'active_support/core_ext/date/calculations'
|
11
|
+
require 'active_support/core_ext/date_time/zones'
|
12
|
+
|
13
|
+
require 'time_pup/adverb_parser'
|
14
|
+
require 'time_pup/time_parser'
|
15
|
+
require 'time_pup/incremental_time'
|
16
|
+
require 'time_pup/date_time_parser'
|
17
|
+
|
18
|
+
def self.parse(time, timezone = 'UTC')
|
19
|
+
TimePup::AdverbParser.parse(time, timezone) ||
|
20
|
+
TimePup::DateTimeParser.parse(time, timezone) ||
|
21
|
+
TimePup::IncrementalTime.parse(time) ||
|
22
|
+
TimePup::TimeParser.parse(time, timezone)
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module TimePup
|
2
|
+
class AdverbParser
|
3
|
+
STARTOFDAY = 8
|
4
|
+
ENDOFDAY = 17
|
5
|
+
DAYSOFWEEK = [:monday, :tuesday, :wednesday,
|
6
|
+
:thursday, :friday, :saturday, :sunday]
|
7
|
+
MATCHERS = {
|
8
|
+
tomorrow: /tomorrow/,
|
9
|
+
endofday: /endofday/,
|
10
|
+
endofmonth: /endofmonth/,
|
11
|
+
nextmonday: /nextmonday/,
|
12
|
+
monday: /monday/,
|
13
|
+
nexttuesday: /nexttuesday/,
|
14
|
+
tuesday: /tuesday/,
|
15
|
+
nextwednesday: /nextwednesday/,
|
16
|
+
wednesday: /wednesday/,
|
17
|
+
nextthursday: /nextthursday/,
|
18
|
+
thursday: /thursday/,
|
19
|
+
nextfriday: /nextfriday/,
|
20
|
+
friday: /friday/,
|
21
|
+
nextsaturday: /nextsaturday/,
|
22
|
+
saturday: /saturday/,
|
23
|
+
nextsunday: /nextsunday/,
|
24
|
+
sunday: /sunday/,
|
25
|
+
nextweek: /nextweek/
|
26
|
+
}
|
27
|
+
|
28
|
+
class << self
|
29
|
+
def parse(parsable, zone = 'UTC')
|
30
|
+
matches = match_key_words(parsable)
|
31
|
+
unless matches.empty?
|
32
|
+
time = parse_time(parsable, matches.first)
|
33
|
+
if time
|
34
|
+
hour = time.hour
|
35
|
+
min = time.min
|
36
|
+
end
|
37
|
+
|
38
|
+
match = self.send(matches.first)
|
39
|
+
return utc_offset(match, zone, hour, min)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def parse_time(parsable, matcher)
|
44
|
+
parsable_parts = parsable.partition(matcher)
|
45
|
+
time_part = nil
|
46
|
+
[0, 2].each do |index|
|
47
|
+
parsed_time = TimePup::TimeParser.parse(parsable_parts[index])
|
48
|
+
time_part = parsed_time if parsed_time
|
49
|
+
end
|
50
|
+
|
51
|
+
time_part
|
52
|
+
end
|
53
|
+
|
54
|
+
def match_key_words(time)
|
55
|
+
MATCHERS.keys.inject([]) do
|
56
|
+
|result, m| result << time.scan(MATCHERS[m])
|
57
|
+
end.flatten
|
58
|
+
end
|
59
|
+
|
60
|
+
def tomorrow
|
61
|
+
1.day.from_now.utc.change hour: STARTOFDAY
|
62
|
+
end
|
63
|
+
|
64
|
+
def endofday
|
65
|
+
Time.now.utc.change hour: ENDOFDAY
|
66
|
+
end
|
67
|
+
|
68
|
+
def endofmonth
|
69
|
+
Time.now.end_of_month.utc.change hour: STARTOFDAY
|
70
|
+
end
|
71
|
+
|
72
|
+
def nextweek
|
73
|
+
Time.now.next_week.utc.change hour: STARTOFDAY
|
74
|
+
end
|
75
|
+
|
76
|
+
DAYSOFWEEK.each do |d|
|
77
|
+
define_method d do
|
78
|
+
date_of_next(d.to_s)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
DAYSOFWEEK.each do |d|
|
83
|
+
define_method "next#{d}" do
|
84
|
+
date_of_next(d.to_s)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def date_of_next(day)
|
89
|
+
date = DateTime.parse(day).to_time
|
90
|
+
delta = date > Date.today ? 0 : 7
|
91
|
+
(date + delta.days).change hour: STARTOFDAY
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def utc_offset(utc_time, zone, hour, min)
|
97
|
+
tz_time = utc_time.in_time_zone(ActiveSupport::TimeZone[zone])
|
98
|
+
tz_time = tz_time.change hour: hour || utc_time.hour
|
99
|
+
tz_time = tz_time.change min: min || 0
|
100
|
+
tz_time.utc
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module TimePup
|
2
|
+
class DateTimeParser
|
3
|
+
MONTHS = /jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec/i
|
4
|
+
TIME = /\d{1,4}[a|p]m/i
|
5
|
+
|
6
|
+
def self.parse(parsable, timezone = 'UTC')
|
7
|
+
return nil unless parsable.match(MONTHS)
|
8
|
+
date = DateTime.parse(parsable).to_time
|
9
|
+
date = date.in_time_zone(ActiveSupport::TimeZone[timezone])
|
10
|
+
time = find_time(parsable)
|
11
|
+
if time
|
12
|
+
date.change(hour: time.hour, min: time.min).utc
|
13
|
+
else
|
14
|
+
date.change(hour: 8).utc
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
def self.find_time(parsable)
|
20
|
+
time_part = parsable.slice(TIME)
|
21
|
+
TimePup::TimeParser.parse(time_part) if time_part
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module TimePup
|
2
|
+
# Parses time with the following keywords;
|
3
|
+
# year y
|
4
|
+
# month mo
|
5
|
+
# week w
|
6
|
+
# day d
|
7
|
+
# hour h
|
8
|
+
# minute m
|
9
|
+
|
10
|
+
class IncrementalTime
|
11
|
+
MATCHERS = {
|
12
|
+
years: /(\d{1,2})ye?a?r?s?/,
|
13
|
+
months: /(\d{1,2})mo[a-zA-Z]*/,
|
14
|
+
weeks: /(\d{1,2})we?e?k?s?/,
|
15
|
+
days: /(\d{1,2}(\.\d{1,2})?)da?y?s?/,
|
16
|
+
hours: /(\d{1,2}(\.\d{1,2})?)ho?u?r?s?/,
|
17
|
+
minutes: /(\d{1,2})(?!(mo|mar))m[a-zA-Z]*/
|
18
|
+
}
|
19
|
+
|
20
|
+
class << self
|
21
|
+
def parse(time)
|
22
|
+
increment = match_and_return_time(time.downcase)
|
23
|
+
increment == 0.seconds ? nil : increment.from_now
|
24
|
+
end
|
25
|
+
|
26
|
+
def match_and_return_time(time)
|
27
|
+
MATCHERS.keys.inject(0.seconds) do |result, m|
|
28
|
+
result += scan_increments(time, m)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def scan_increments(time, unit)
|
33
|
+
increments = time.scan MATCHERS[unit]
|
34
|
+
if increments.empty?
|
35
|
+
0.seconds
|
36
|
+
else
|
37
|
+
if unit == :hours || unit == :days
|
38
|
+
increments.first[0].to_f.send(unit.to_sym)
|
39
|
+
else
|
40
|
+
increments.first[0].to_i.send(unit.to_sym)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module TimePup
|
2
|
+
class TimeParser
|
3
|
+
MATCHERS = {
|
4
|
+
twelve_hour_with_period: /\d{1,4}[a|p]m/i,
|
5
|
+
twenty_four_hour_with_minutes: /[0-2]{,1}\d[0-5]\d/,
|
6
|
+
twenty_four_hour_without_minutes: /\d\d{,1}/,
|
7
|
+
}
|
8
|
+
AM_PM = /[a|p]m/i
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def parse(parsable, zone = 'UTC')
|
12
|
+
matches = match_key_words(parsable)
|
13
|
+
if matches.empty?
|
14
|
+
return nil
|
15
|
+
else
|
16
|
+
time_part = matches.first
|
17
|
+
case time_part.length
|
18
|
+
when 1..2
|
19
|
+
time_part = time_part + ':00'
|
20
|
+
when 3..4
|
21
|
+
unless time_part.match(AM_PM)
|
22
|
+
time_part = time_part.insert(-3,':')
|
23
|
+
end
|
24
|
+
when 5..6
|
25
|
+
time_part = time_part.insert(-5,':')
|
26
|
+
end
|
27
|
+
|
28
|
+
return set_time_in_future(time_part, zone)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def match_key_words(time)
|
33
|
+
MATCHERS.keys.inject([]) { |result, m| result << time.scan(MATCHERS[m]) }.flatten
|
34
|
+
end
|
35
|
+
|
36
|
+
def set_time_in_future(time_part, zone)
|
37
|
+
time = DateTime.parse(time_part)
|
38
|
+
time_for_today = DateTime.now.to_time.in_time_zone(ActiveSupport::TimeZone[zone])
|
39
|
+
time_for_today = time_for_today.change(hour: time.hour, min: time.min).utc
|
40
|
+
if time_for_today < DateTime.now.to_time.utc
|
41
|
+
time_for_today + 1.day
|
42
|
+
else
|
43
|
+
time_for_today
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'time_pup'
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe TimePup::AdverbParser do
|
4
|
+
it "should return the reminder time" do
|
5
|
+
time = "tomorrow"
|
6
|
+
TimePup::AdverbParser.parse(time).should == 1.day.from_now.utc.change(hour: 8)
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should offset the time based on the timezone" do
|
10
|
+
time = "tomorrow"
|
11
|
+
TimePup::AdverbParser.parse(time, 'Harare').should == 1.day.from_now.utc.change(hour: 6) # 6am UTC = 8am Harare
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should default to 'UTC' if given time zone is not valid" do
|
15
|
+
time = "tomorrow"
|
16
|
+
TimePup::AdverbParser.parse(time, 'THISisntAtimezone').should == 1.day.from_now.utc.change(hour: 8)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should handle timezone > GMT+8" do
|
20
|
+
time = "tomorrow"
|
21
|
+
# Today 11pm UTC == Tomorrow 8am IRKT
|
22
|
+
TimePup::AdverbParser.parse(time, 'Irkutsk').should == Time.now.utc.change(hour: 23)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should return 8am the next day" do
|
26
|
+
time = "tomorrow"
|
27
|
+
TimePup::AdverbParser.parse(time) == 1.day.from_now.utc.change(hour: 8)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should return the next day (mon tues ect)" do
|
31
|
+
time = "nextmonday"
|
32
|
+
send_at = TimePup::AdverbParser.parse(time)
|
33
|
+
send_at.wday.should == 1
|
34
|
+
send_at.hour.should == 8
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should return the next day (mon tues ect)" do
|
38
|
+
time = "tuesday"
|
39
|
+
send_at = TimePup::AdverbParser.parse(time)
|
40
|
+
send_at.wday.should == 2
|
41
|
+
send_at.hour.should == 8
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should return 8am the next day" do
|
45
|
+
time = "endofday"
|
46
|
+
TimePup::AdverbParser.parse(time).should == Time.now.utc.change(hour: 17)
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should return 8am the last day of the month" do
|
50
|
+
time = "endofmonth"
|
51
|
+
t = Time.now.utc
|
52
|
+
resulting_time = (t.change month: t.month + 1, day: 1, hour: 8) - 1.day
|
53
|
+
TimePup::AdverbParser.parse(time).should == resulting_time
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should return nil if not parseable" do
|
57
|
+
time = 'eeee10am'
|
58
|
+
TimePup::AdverbParser.parse(time).should == nil
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should parse trailing 12-hour time with period" do
|
62
|
+
time = "tomorrow10am"
|
63
|
+
TimePup::AdverbParser.parse(time).should == 1.day.from_now.utc.change(hour: 10)
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should parse leading 12-hour time with period" do
|
67
|
+
time = "10pmtomorrow"
|
68
|
+
TimePup::AdverbParser.parse(time).should == 1.day.from_now.utc.change(hour: 22)
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should parse 12-hour time with minutes and period" do
|
72
|
+
time = "1030amtomorrow"
|
73
|
+
TimePup::AdverbParser.parse(time).should == 1.day.from_now.utc.change(hour: 10, min:30)
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe TimePup::DateTimeParser do
|
4
|
+
|
5
|
+
it "barfs on bad local parts" do
|
6
|
+
TimePup::DateTimeParser.parse('pimpin').should be_nil
|
7
|
+
end
|
8
|
+
|
9
|
+
it "parses day month combinations (defaulting to UTC)" do
|
10
|
+
TimePup::DateTimeParser.parse('31Mar').should == DateTime.parse('31 Mar 08:00')
|
11
|
+
TimePup::DateTimeParser.parse('3July').should == DateTime.parse('3 July 08:00')
|
12
|
+
end
|
13
|
+
|
14
|
+
it "parses day month time combinations (defaulting to UTC)" do
|
15
|
+
TimePup::DateTimeParser.parse('31Mar1030am').should == DateTime.parse('31 Mar 10:30')
|
16
|
+
TimePup::DateTimeParser.parse('3July10pm').should == DateTime.parse('3 July 22:00')
|
17
|
+
TimePup::DateTimeParser.parse('10pm3July2013').should == DateTime.parse('3 July 2013 22:00')
|
18
|
+
end
|
19
|
+
|
20
|
+
it "returns as UTC with the hour equal to 8am in given timezone" do
|
21
|
+
TimePup::DateTimeParser.parse('1Nov', 'Harare').should == DateTime.parse('1Nov 06:00')
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe TimePup::IncrementalTime do
|
4
|
+
def incremental_time(time)
|
5
|
+
TimePup::IncrementalTime.parse(time).to_i
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should detect the minutes" do
|
9
|
+
incremental_time("2minutes").should == 2.minutes.from_now.to_i
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should detect the minutes (m) (complex)" do
|
13
|
+
incremental_time("5m").should == 5.minutes.from_now.to_i
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should detect the minutes(mi) (complex)" do
|
17
|
+
incremental_time("5mi").should == 5.minutes.from_now.to_i
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should detect the minutes(min) (complex)" do
|
21
|
+
incremental_time("5min").should == 5.minutes.from_now.to_i
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should detect the hours" do
|
25
|
+
incremental_time("2hours").should == 2.hours.from_now.to_i
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should detect the hours (complex)" do
|
29
|
+
incremental_time("5h").should == 5.hours.from_now.to_i
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should detect the days" do
|
33
|
+
incremental_time("2days").should == 2.days.from_now.to_i
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should detect the days (complex)" do
|
37
|
+
incremental_time("5d").should == 5.days.from_now.to_i
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should detect the weeks" do
|
41
|
+
incremental_time("2weeks").should == 2.weeks.from_now.to_i
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should detect the weeks (complex)" do
|
45
|
+
incremental_time("5w").should == 5.weeks.from_now.to_i
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should detect the months" do
|
49
|
+
incremental_time("2months").should == 2.months.from_now.to_i || 2.months.from_now.to_i + 1
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should detect the months (complex)" do
|
53
|
+
incremental_time("5mo").should == 5.months.from_now.to_i
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should detect the years" do
|
57
|
+
incremental_time("2years").should == 2.years.from_now.to_i
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should detect the years (complex)" do
|
61
|
+
incremental_time("5y").should == 5.years.from_now.to_i
|
62
|
+
end
|
63
|
+
|
64
|
+
context 'combinations' do
|
65
|
+
let(:duration) { (2.years + 3.months + 12.days + 23.hours + 1.minute).from_now.to_i }
|
66
|
+
it "should detect everything" do
|
67
|
+
incremental_time("2years3months12days23hours1minute").to_i.
|
68
|
+
should == duration
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should work with composite times" do
|
72
|
+
incremental_time("2y3mo12d23h1m").to_i.
|
73
|
+
should == duration
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should work with a mixture of composite and non times" do
|
77
|
+
incremental_time("2years3mo12days23h1min").to_i.
|
78
|
+
should == duration
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# if today is 30th day in a month of 31 days (and next month has 30 days)
|
83
|
+
# then Time.now + 1.day + 1.month != Time.now + 1.month + 1.day
|
84
|
+
# i.e.:
|
85
|
+
# 30 March + 1.day == 31 March
|
86
|
+
# 31 March + 1.month = 30 April
|
87
|
+
# -------------------------------
|
88
|
+
# 30 March + 1.month == 30 April
|
89
|
+
# 30 April + 1.day == 31 April
|
90
|
+
|
91
|
+
it "should match increments in decending order (years to minutes) regardless of email order" do
|
92
|
+
Time.stub(:now).and_return Time.parse '30-03-2011'
|
93
|
+
incremental_time("1d1mo1y").to_i.
|
94
|
+
should == Time.parse('31-04-2012').to_i
|
95
|
+
end
|
96
|
+
|
97
|
+
it "should return nil if unparseable" do
|
98
|
+
TimePup::IncrementalTime.parse("jiosj").should be_nil
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should not try parse Mar / March as minutes" do
|
102
|
+
TimePup::IncrementalTime.parse("31mar").should be_nil
|
103
|
+
TimePup::IncrementalTime.parse("31march").should be_nil
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should downcase email before parsing" do
|
107
|
+
incremental_time("5Min").should == 5.minutes.from_now.to_i
|
108
|
+
end
|
109
|
+
|
110
|
+
it "can parse decimals for days" do
|
111
|
+
incremental_time('1.5d').should == (1.5).days.from_now.to_i
|
112
|
+
end
|
113
|
+
|
114
|
+
it "can parse decimals for days" do
|
115
|
+
incremental_time('3.5h').should == (3.5).hours.from_now.to_i
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe TimePup::TimeParser do
|
4
|
+
it "should return the reminder time - 12 hour format with period" do
|
5
|
+
time = "10am"
|
6
|
+
TimePup::TimeParser.parse(time).hour.should == 10
|
7
|
+
TimePup::TimeParser.parse(time).min.should == 00
|
8
|
+
time = "10pm"
|
9
|
+
TimePup::TimeParser.parse(time).hour.should == 22
|
10
|
+
TimePup::TimeParser.parse(time).min.should == 00
|
11
|
+
time = "9pm"
|
12
|
+
TimePup::TimeParser.parse(time).hour.should == 21
|
13
|
+
TimePup::TimeParser.parse(time).min.should == 00
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should return the reminder time - 12 hour format with period and minutes" do
|
17
|
+
time = "1030am"
|
18
|
+
TimePup::TimeParser.parse(time).hour.should == 10
|
19
|
+
TimePup::TimeParser.parse(time).min.should == 30
|
20
|
+
time = "1030pm"
|
21
|
+
TimePup::TimeParser.parse(time).hour.should == 22
|
22
|
+
TimePup::TimeParser.parse(time).min.should == 30
|
23
|
+
time = "930pm"
|
24
|
+
TimePup::TimeParser.parse(time).hour.should == 21
|
25
|
+
TimePup::TimeParser.parse(time).min.should == 30
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should return the reminder time - 24 hour format" do
|
29
|
+
time = "22"
|
30
|
+
TimePup::TimeParser.parse(time).hour.should == 22
|
31
|
+
TimePup::TimeParser.parse(time).min.should == 00
|
32
|
+
time = "10"
|
33
|
+
TimePup::TimeParser.parse(time).hour.should == 10
|
34
|
+
TimePup::TimeParser.parse(time).min.should == 00
|
35
|
+
time = "9"
|
36
|
+
TimePup::TimeParser.parse(time).hour.should == 9
|
37
|
+
TimePup::TimeParser.parse(time).min.should == 00
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should return the reminder time - 24 hour format with minutes" do
|
41
|
+
time = "2230"
|
42
|
+
TimePup::TimeParser.parse(time).hour.should == 22
|
43
|
+
TimePup::TimeParser.parse(time).min.should == 30
|
44
|
+
time = "1030"
|
45
|
+
TimePup::TimeParser.parse(time).hour.should == 10
|
46
|
+
TimePup::TimeParser.parse(time).min.should == 30
|
47
|
+
time = "930"
|
48
|
+
TimePup::TimeParser.parse(time).hour.should == 9
|
49
|
+
TimePup::TimeParser.parse(time).min.should == 30
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should set the time in the future" do
|
53
|
+
time = (Time.now.utc.hour - 1).to_s
|
54
|
+
TimePup::TimeParser.parse("#{time}am").day.should == Time.now.utc.day + 1
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should adjust the UTC time based on the given time zone" do
|
58
|
+
time = '10am'
|
59
|
+
TimePup::TimeParser.parse(time, 'Harare').hour.should == 8 #10am CAT == 8am UTC
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe TimePup do
|
4
|
+
it "parses adverb addresses (defaulting to 8am)" do
|
5
|
+
Time.zone = "Harare"
|
6
|
+
TimePup.parse('tomorrow').should == (Time.now.utc + 1.day).change(hour: 8)
|
7
|
+
end
|
8
|
+
|
9
|
+
it "parses incremental addresses" do
|
10
|
+
TimePup.parse('2d').to_i.should == (Time.now + 2.days).to_i
|
11
|
+
end
|
12
|
+
|
13
|
+
it "parses calendar addresses w/o times: defaults to 8am" do
|
14
|
+
TimePup.parse('12May2014').should == DateTime.parse('12May2014 08:00')
|
15
|
+
end
|
16
|
+
|
17
|
+
it "parses full month names" do
|
18
|
+
TimePup.parse('12November2013').should == DateTime.parse('12Nov2013 08:00')
|
19
|
+
end
|
20
|
+
|
21
|
+
it "parses adverbs and times" do
|
22
|
+
TimePup.parse('tomorrow1030pm').should == (Time.now.utc + 1.day).change(hour: 22, min: 30)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "parses times" do
|
26
|
+
TimePup.parse('1030pm').should == (Time.now.utc).change(hour: 22, min: 30)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "returns nil or barfs on bad local parts" do
|
30
|
+
lambda do
|
31
|
+
TimePup.parse('99Apr999 aa:00')
|
32
|
+
end.should raise_exception ArgumentError
|
33
|
+
TimePup.parse('pimping').should be_nil
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
data/time_pup.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'time_pup/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "time_pup"
|
8
|
+
gem.version = TimePup::VERSION
|
9
|
+
gem.authors = ["Sachin Ranchod"]
|
10
|
+
gem.email = ["sachin.ranchod@gmail.com"]
|
11
|
+
gem.description = %q{A simple natural language date time parser extracted from hound.cc. Perfect for parsing the local part of an email address}
|
12
|
+
gem.summary = %q{Parses time from now (1day2h), weekdays (mon9am), actual dates (15sep2013), actual time (1030) and more. See hound.cc/how-to for examples. }
|
13
|
+
gem.homepage = "http://www.hound.cc"
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.add_development_dependency "rspec"
|
21
|
+
gem.add_development_dependency "debugger"
|
22
|
+
gem.add_dependency "activesupport", "~> 3.2"
|
23
|
+
gem.add_dependency "tzinfo"
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: time_pup
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Sachin Ranchod
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-05-13 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: debugger
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: activesupport
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.2'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.2'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: tzinfo
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: A simple natural language date time parser extracted from hound.cc. Perfect
|
70
|
+
for parsing the local part of an email address
|
71
|
+
email:
|
72
|
+
- sachin.ranchod@gmail.com
|
73
|
+
executables: []
|
74
|
+
extensions: []
|
75
|
+
extra_rdoc_files: []
|
76
|
+
files:
|
77
|
+
- .gitignore
|
78
|
+
- .travis.yml
|
79
|
+
- Gemfile
|
80
|
+
- LICENSE.txt
|
81
|
+
- README.md
|
82
|
+
- Rakefile
|
83
|
+
- lib/time_pup.rb
|
84
|
+
- lib/time_pup/adverb_parser.rb
|
85
|
+
- lib/time_pup/date_time_parser.rb
|
86
|
+
- lib/time_pup/incremental_time.rb
|
87
|
+
- lib/time_pup/time_parser.rb
|
88
|
+
- lib/time_pup/version.rb
|
89
|
+
- spec/spec_helper.rb
|
90
|
+
- spec/time_pup/adverb_parser_spec.rb
|
91
|
+
- spec/time_pup/date_time_parser_spec.rb
|
92
|
+
- spec/time_pup/incremental_time_spec.rb
|
93
|
+
- spec/time_pup/time_parser_spec.rb
|
94
|
+
- spec/time_pup_spec.rb
|
95
|
+
- time_pup.gemspec
|
96
|
+
homepage: http://www.hound.cc
|
97
|
+
licenses: []
|
98
|
+
metadata: {}
|
99
|
+
post_install_message:
|
100
|
+
rdoc_options: []
|
101
|
+
require_paths:
|
102
|
+
- lib
|
103
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
104
|
+
requirements:
|
105
|
+
- - '>='
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
version: '0'
|
108
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
109
|
+
requirements:
|
110
|
+
- - '>='
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: '0'
|
113
|
+
requirements: []
|
114
|
+
rubyforge_project:
|
115
|
+
rubygems_version: 2.0.3
|
116
|
+
signing_key:
|
117
|
+
specification_version: 4
|
118
|
+
summary: Parses time from now (1day2h), weekdays (mon9am), actual dates (15sep2013),
|
119
|
+
actual time (1030) and more. See hound.cc/how-to for examples.
|
120
|
+
test_files:
|
121
|
+
- spec/spec_helper.rb
|
122
|
+
- spec/time_pup/adverb_parser_spec.rb
|
123
|
+
- spec/time_pup/date_time_parser_spec.rb
|
124
|
+
- spec/time_pup/incremental_time_spec.rb
|
125
|
+
- spec/time_pup/time_parser_spec.rb
|
126
|
+
- spec/time_pup_spec.rb
|