kronic 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. data/HISTORY +4 -0
  2. data/README.rdoc +3 -3
  3. data/lib/kronic.rb +77 -49
  4. data/spec/kronic_spec.rb +11 -6
  5. metadata +47 -4
data/HISTORY CHANGED
@@ -1,3 +1,7 @@
1
+ 0.2.0
2
+ * Support "tomorrow"
3
+ * Support "this monday"
4
+
1
5
  0.1.1 - 19 September 2010
2
6
  * Kronic.parse will explode in fewer scenarios!
3
7
 
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 60 lines of code.
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
- I only need date selection in the past for my application, so Kronic only does that so far. I'm not opposed to support "next monday", patches welcome. Kronic will be totally timezone aware. It may or may not be now, I haven't specced it.
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 date in the past.
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
- return Date.today if string == 'today'
28
- return Date.yesterday if string == 'yesterday'
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
- tokens = string.split(/\s+/)
42
+ class << self
43
+ private
31
44
 
32
- # Last X
33
- if tokens[0] == 'last'
34
- days = (1..7).map {|x|
35
- (Date.today - x.days)
36
- }.inject({}) {|a, x|
37
- a.update(x.strftime("%A").downcase => x)
38
- }
39
- return days[tokens[1]]
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
- # 14 Sep, 14 September, 14 September 2010
43
- if tokens[0] =~ /^[0-9]+$/
44
- day = tokens[0].to_i
45
- month = month_from_name(tokens[1])
46
- year = if tokens[2]
47
- tokens[2] =~ /^[0-9]+$/ ? tokens[2].to_i : nil
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
- return nil unless day && month && year
63
+ # Parse "Last Monday", "This Monday"
64
+ def parse_last_or_this_day(string, today)
65
+ tokens = string.split(/\s+/)
53
66
 
54
- result = Date.new(year, month, day)
55
- result -= 1.year if result > Date.today
56
- return result
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
- nil
60
- end
78
+ # Parse "14 Sep, 14 September, 14 September 2010"
79
+ def parse_exact_date(string, today)
80
+ tokens = string.split(/\s+/)
61
81
 
62
- # Converts a date to a human readable string.
63
- def self.format(date, opts = {})
64
- case ((opts[:today] || Date.today).to_date - date.to_date).to_i
65
- when 0 then "Today"
66
- when 1 then "Yesterday"
67
- when (2..7) then "Last " + date.strftime("%A")
68
- else date.strftime("%e %B %Y").strip
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 => Date.new(2010, 9, 19),
19
- :monday => Date.new(2010, 9, 13),
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(Date.new(2010, 9, 19))
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('Last Monday', date(:monday))
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 September oen', nil)
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('Last Monday', date(:monday))
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
- - 1
8
- - 1
9
- version: 0.1.1
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-19 00:00:00 +01:00
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