kronic 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/HISTORY +4 -0
- data/README.rdoc +3 -3
- data/lib/kronic.rb +77 -49
- data/spec/kronic_spec.rb +11 -6
- metadata +47 -4
data/HISTORY
CHANGED
data/README.rdoc
CHANGED
@@ -8,7 +8,7 @@ Chronic and Tickle both parse a *heap* of formats. That's not useful to me when
|
|
8
8
|
|
9
9
|
In addition, Kronic can take a date and give you a human readable form, neither of which Chronic nor Tickle does.
|
10
10
|
|
11
|
-
Oh yeah, Kronic is less than
|
11
|
+
Oh yeah, Kronic is less than 100 lines of code.
|
12
12
|
|
13
13
|
== Usage
|
14
14
|
|
@@ -18,8 +18,8 @@ Oh yeah, Kronic is less than 60 lines of code.
|
|
18
18
|
Kronic.parse("Today") # => Date.today
|
19
19
|
Kronic.format(Date.today) # => "Today"
|
20
20
|
|
21
|
-
Supported formats: Today, yesterday, last thursday, 14 Sep, 14 June 2010
|
21
|
+
Supported formats: Today, yesterday, tomorrow, last thursday, this thursday, 14 Sep, 14 June 2010. Any dates without a year are assumed to be in the past.
|
22
22
|
|
23
23
|
== Future
|
24
24
|
|
25
|
-
|
25
|
+
Kronic will be totally timezone aware. It may or may not be now, I haven't specced it. Maybe a flag to toggle interpretation of dates without years.
|
data/lib/kronic.rb
CHANGED
@@ -1,71 +1,99 @@
|
|
1
1
|
require 'active_support/core_ext'
|
2
|
-
require 'active_support/duration'
|
3
|
-
require 'active_support/time_with_zone'
|
4
2
|
|
5
3
|
class Kronic
|
6
|
-
# Converts a human readable day (Today, yesterday) to a
|
7
|
-
# Supported inputs include Today, yesterday, last thursday, 14 Sep, 14
|
8
|
-
# June 2010, all case-insensitive.
|
4
|
+
# Public: Converts a human readable day (Today, yesterday) to a Date.
|
9
5
|
#
|
10
6
|
# Will call #to_s on the input, so can process Symbols or whatever other
|
11
7
|
# object you wish to throw at it.
|
8
|
+
#
|
9
|
+
# string - The String to convert to a Date. Supported formats are: Today,
|
10
|
+
# yesterday, tomorrow, last thursday, this thursday, 14 Sep,
|
11
|
+
# 14 June 2010. Parsing is case-insensitive.
|
12
|
+
#
|
13
|
+
# Returns the Date, or nil if the input could not be parsed.
|
12
14
|
def self.parse(string)
|
13
|
-
# TODO: You could totally clean this method up, if you wanted
|
14
|
-
def self.month_from_name(month)
|
15
|
-
months = (1..12).map {|x|
|
16
|
-
Date.new(2010, x, 1)
|
17
|
-
}.inject({}) {|a, x|
|
18
|
-
a.update(x.strftime("%B").downcase => x.month)
|
19
|
-
}
|
20
|
-
|
21
|
-
months[month] || months.detect {|name, number| name.starts_with?(month) }.try(:last)
|
22
|
-
end
|
23
|
-
|
24
15
|
string = string.to_s.downcase.strip
|
25
16
|
today = Date.today
|
26
17
|
|
27
|
-
|
28
|
-
|
18
|
+
parse_nearby_days(string, today) ||
|
19
|
+
parse_last_or_this_day(string, today) ||
|
20
|
+
parse_exact_date(string, today)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Public: Converts a date to a human readable string.
|
24
|
+
#
|
25
|
+
# date - The Date to be converted
|
26
|
+
# opts - The Hash options used to customize formatting
|
27
|
+
# :today - The reference point for calculations (default: Date.today)
|
28
|
+
#
|
29
|
+
# Returns a relative string ("Today", "This Monday") if available, otherwise
|
30
|
+
# the full representation of the date ("19 September 2010").
|
31
|
+
def self.format(date, opts = {})
|
32
|
+
case (date.to_date - (opts[:today] || Date.today)).to_i
|
33
|
+
when (2..7) then "This " + date.strftime("%A")
|
34
|
+
when 1 then "Tomorrow"
|
35
|
+
when 0 then "Today"
|
36
|
+
when -1 then "Yesterday"
|
37
|
+
when (-7..-2) then "Last " + date.strftime("%A")
|
38
|
+
else date.strftime("%e %B %Y").strip
|
39
|
+
end
|
40
|
+
end
|
29
41
|
|
30
|
-
|
42
|
+
class << self
|
43
|
+
private
|
31
44
|
|
32
|
-
#
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
45
|
+
# Examples
|
46
|
+
#
|
47
|
+
# month_from_name("January") # => 1
|
48
|
+
# month_from_name("Jan") # => 1
|
49
|
+
def month_from_name(month)
|
50
|
+
return nil unless month
|
51
|
+
|
52
|
+
human_month = month.downcase.humanize
|
53
|
+
Date::MONTHNAMES.index(human_month) || Date::ABBR_MONTHNAMES.index(human_month)
|
40
54
|
end
|
41
55
|
|
42
|
-
#
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
else
|
49
|
-
today.year
|
50
|
-
end
|
56
|
+
# Parse "Today", "Tomorrow" and "Yesterday"
|
57
|
+
def parse_nearby_days(string, today)
|
58
|
+
return today if string == 'today'
|
59
|
+
return today - 1.day if string == 'yesterday'
|
60
|
+
return today + 1.day if string == 'tomorrow'
|
61
|
+
end
|
51
62
|
|
52
|
-
|
63
|
+
# Parse "Last Monday", "This Monday"
|
64
|
+
def parse_last_or_this_day(string, today)
|
65
|
+
tokens = string.split(/\s+/)
|
53
66
|
|
54
|
-
|
55
|
-
|
56
|
-
|
67
|
+
if %w(last this).include?(tokens[0])
|
68
|
+
days = (1..7).map {|x|
|
69
|
+
today + (tokens[0] == 'last' ? -x.days : x.days)
|
70
|
+
}.inject({}) {|a, x|
|
71
|
+
a.update(x.strftime("%A").downcase => x)
|
72
|
+
}
|
73
|
+
|
74
|
+
days[tokens[1]]
|
75
|
+
end
|
57
76
|
end
|
58
77
|
|
59
|
-
|
60
|
-
|
78
|
+
# Parse "14 Sep, 14 September, 14 September 2010"
|
79
|
+
def parse_exact_date(string, today)
|
80
|
+
tokens = string.split(/\s+/)
|
61
81
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
82
|
+
if tokens[0] =~ /^[0-9]+$/ && tokens[1]
|
83
|
+
day = tokens[0].to_i
|
84
|
+
month = month_from_name(tokens[1])
|
85
|
+
year = if tokens[2]
|
86
|
+
tokens[2] =~ /^[0-9]+$/ ? tokens[2].to_i : nil
|
87
|
+
else
|
88
|
+
today.year
|
89
|
+
end
|
90
|
+
|
91
|
+
return nil unless day && month && year
|
92
|
+
|
93
|
+
result = Date.new(year, month, day)
|
94
|
+
result -= 1.year if result > today
|
95
|
+
result
|
96
|
+
end
|
69
97
|
end
|
70
98
|
end
|
71
99
|
end
|
data/spec/kronic_spec.rb
CHANGED
@@ -15,15 +15,16 @@ describe Kronic do
|
|
15
15
|
|
16
16
|
def self.date(key)
|
17
17
|
{
|
18
|
-
:today
|
19
|
-
:
|
18
|
+
:today => Date.new(2010, 9, 18),
|
19
|
+
:last_monday => Date.new(2010, 9, 13),
|
20
|
+
:next_monday => Date.new(2010, 9, 20),
|
20
21
|
:sep_4 => Date.new(2010, 9, 4),
|
21
22
|
:sep_20 => Date.new(2009, 9, 20)
|
22
23
|
}.fetch(key)
|
23
24
|
end
|
24
25
|
|
25
26
|
before :all do
|
26
|
-
Timecop.freeze(
|
27
|
+
Timecop.freeze(self.class.date(:today))
|
27
28
|
end
|
28
29
|
|
29
30
|
should_parse('Today', date(:today))
|
@@ -31,7 +32,9 @@ describe Kronic do
|
|
31
32
|
should_parse('today', date(:today))
|
32
33
|
should_parse(' Today', date(:today))
|
33
34
|
should_parse('Yesterday', date(:today) - 1.day)
|
34
|
-
should_parse('
|
35
|
+
should_parse('Tomorrow', date(:today) + 1.day)
|
36
|
+
should_parse('Last Monday', date(:last_monday))
|
37
|
+
should_parse('This Monday', date(:next_monday))
|
35
38
|
should_parse('4 Sep', date(:sep_4))
|
36
39
|
should_parse('4 Sep', date(:sep_4))
|
37
40
|
should_parse('4 September', date(:sep_4))
|
@@ -40,10 +43,12 @@ describe Kronic do
|
|
40
43
|
should_parse('bogus', nil)
|
41
44
|
should_parse('14', nil)
|
42
45
|
should_parse('14 bogus in', nil)
|
43
|
-
should_parse('14
|
46
|
+
should_parse('14 June oen', nil)
|
44
47
|
|
45
48
|
should_format('Today', date(:today))
|
46
49
|
should_format('Yesterday', date(:today) - 1.day)
|
47
|
-
should_format('
|
50
|
+
should_format('Tomorrow', date(:today) + 1.day)
|
51
|
+
should_format('Last Monday', date(:last_monday))
|
52
|
+
should_format('This Monday', date(:next_monday))
|
48
53
|
should_format('14 September 2008', Date.new(2008, 9, 14))
|
49
54
|
end
|
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 0
|
7
|
-
-
|
8
|
-
-
|
9
|
-
version: 0.
|
7
|
+
- 2
|
8
|
+
- 0
|
9
|
+
version: 0.2.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Xavier Shay
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-09-
|
17
|
+
date: 2010-09-20 00:00:00 +01:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -30,6 +30,49 @@ dependencies:
|
|
30
30
|
version: "0"
|
31
31
|
type: :runtime
|
32
32
|
version_requirements: *id001
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: i18n
|
35
|
+
prerelease: false
|
36
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
37
|
+
none: false
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
segments:
|
42
|
+
- 0
|
43
|
+
version: "0"
|
44
|
+
type: :runtime
|
45
|
+
version_requirements: *id002
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rspec
|
48
|
+
prerelease: false
|
49
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
segments:
|
55
|
+
- 2
|
56
|
+
- 0
|
57
|
+
- 0
|
58
|
+
- beta
|
59
|
+
- 16
|
60
|
+
version: 2.0.0.beta.16
|
61
|
+
type: :development
|
62
|
+
version_requirements: *id003
|
63
|
+
- !ruby/object:Gem::Dependency
|
64
|
+
name: timecop
|
65
|
+
prerelease: false
|
66
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
67
|
+
none: false
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
segments:
|
72
|
+
- 0
|
73
|
+
version: "0"
|
74
|
+
type: :development
|
75
|
+
version_requirements: *id004
|
33
76
|
description:
|
34
77
|
email:
|
35
78
|
- hello@xaviershay.com
|