kronic 0.1.1 → 0.2.0
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.
- 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
|