horai 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ -fs --color
data/Gemfile ADDED
@@ -0,0 +1,17 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+
4
+ gem "i18n", ">= 0.6.0"
5
+ gem "activesupport", ">= 3.0.0"
6
+ gem "rparsec-ruby19", ">= 1.0"
7
+
8
+ # Add dependencies to develop your gem here.
9
+ # Include everything needed to run rake, tests, features, etc.
10
+ group :development do
11
+ gem "rspec", "~> 2.8.0"
12
+ gem "rdoc", "~> 3.12"
13
+ gem "jeweler", "~> 1.8.3"
14
+ gem 'guard-shell'
15
+ gem 'guard-rspec'
16
+ gem 'pry'
17
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,55 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ activesupport (3.0.12)
5
+ coderay (1.0.6)
6
+ diff-lcs (1.1.3)
7
+ ffi (1.0.11)
8
+ git (1.2.5)
9
+ guard (1.0.1)
10
+ ffi (>= 0.5.0)
11
+ thor (~> 0.14.6)
12
+ guard-rspec (0.7.0)
13
+ guard (>= 0.10.0)
14
+ guard-shell (0.4.0)
15
+ guard (>= 0.2.0)
16
+ i18n (0.6.0)
17
+ jeweler (1.8.3)
18
+ bundler (~> 1.0)
19
+ git (>= 1.2.5)
20
+ rake
21
+ rdoc
22
+ json (1.6.6)
23
+ method_source (0.7.1)
24
+ pry (0.9.9.3)
25
+ coderay (~> 1.0.5)
26
+ method_source (~> 0.7.1)
27
+ slop (>= 2.4.4, < 3)
28
+ rake (0.9.2.2)
29
+ rdoc (3.12)
30
+ json (~> 1.4)
31
+ rparsec-ruby19 (1.0)
32
+ rspec (2.8.0)
33
+ rspec-core (~> 2.8.0)
34
+ rspec-expectations (~> 2.8.0)
35
+ rspec-mocks (~> 2.8.0)
36
+ rspec-core (2.8.0)
37
+ rspec-expectations (2.8.0)
38
+ diff-lcs (~> 1.1.2)
39
+ rspec-mocks (2.8.0)
40
+ slop (2.4.4)
41
+ thor (0.14.6)
42
+
43
+ PLATFORMS
44
+ ruby
45
+
46
+ DEPENDENCIES
47
+ activesupport (>= 3.0.0)
48
+ guard-rspec
49
+ guard-shell
50
+ i18n (>= 0.6.0)
51
+ jeweler (~> 1.8.3)
52
+ pry
53
+ rdoc (~> 3.12)
54
+ rparsec-ruby19 (>= 1.0)
55
+ rspec (~> 2.8.0)
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 AOKI Yuuto
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # What is Horai
2
+
3
+ Horai は日本語の時刻表現をパースし、DateTime型に変換することを目標としている Gem です。(beta)
data/Rakefile ADDED
@@ -0,0 +1,49 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "horai"
18
+ gem.homepage = "http://github.com/wneko/horai"
19
+ gem.license = "MIT"
20
+ gem.summary = "日本語の時刻表現をパースし、DateTime型に変換する Gem"
21
+ gem.description = "Horai は日本語の時刻表現をパースし、DateTime型に変換することを目標としている Gem です。"
22
+ gem.email = "aoki@u-ne.co"
23
+ gem.authors = ["AOKI Yuuto"]
24
+ # dependencies defined in Gemfile
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
27
+
28
+ require 'rspec/core'
29
+ require 'rspec/core/rake_task'
30
+ RSpec::Core::RakeTask.new(:spec) do |spec|
31
+ spec.pattern = FileList['spec/**/*_spec.rb']
32
+ end
33
+
34
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
35
+ spec.pattern = 'spec/**/*_spec.rb'
36
+ spec.rcov = true
37
+ end
38
+
39
+ task :default => :spec
40
+
41
+ require 'rdoc/task'
42
+ Rake::RDocTask.new do |rdoc|
43
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
44
+
45
+ rdoc.rdoc_dir = 'rdoc'
46
+ rdoc.title = "horai #{version}"
47
+ rdoc.rdoc_files.include('README*')
48
+ rdoc.rdoc_files.include('lib/**/*.rb')
49
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.7.0
data/horai.gemspec ADDED
@@ -0,0 +1,76 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "horai"
8
+ s.version = "0.7.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["AOKI Yuuto"]
12
+ s.date = "2012-04-25"
13
+ s.description = "Horai \u{306f}\u{65e5}\u{672c}\u{8a9e}\u{306e}\u{6642}\u{523b}\u{8868}\u{73fe}\u{3092}\u{30d1}\u{30fc}\u{30b9}\u{3057}\u{3001}DateTime\u{578b}\u{306b}\u{5909}\u{63db}\u{3059}\u{308b}\u{3053}\u{3068}\u{3092}\u{76ee}\u{6a19}\u{3068}\u{3057}\u{3066}\u{3044}\u{308b} Gem \u{3067}\u{3059}\u{3002}"
14
+ s.email = "aoki@u-ne.co"
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README.md"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".rspec",
22
+ "Gemfile",
23
+ "Gemfile.lock",
24
+ "LICENSE.txt",
25
+ "README.md",
26
+ "Rakefile",
27
+ "VERSION",
28
+ "horai.gemspec",
29
+ "lib/horai.rb",
30
+ "lib/ja_number.rb",
31
+ "spec/horai_spec.rb",
32
+ "spec/spec_helper.rb"
33
+ ]
34
+ s.homepage = "http://github.com/wneko/horai"
35
+ s.licenses = ["MIT"]
36
+ s.require_paths = ["lib"]
37
+ s.rubygems_version = "1.8.11"
38
+ s.summary = "\u{65e5}\u{672c}\u{8a9e}\u{306e}\u{6642}\u{523b}\u{8868}\u{73fe}\u{3092}\u{30d1}\u{30fc}\u{30b9}\u{3057}\u{3001}DateTime\u{578b}\u{306b}\u{5909}\u{63db}\u{3059}\u{308b} Gem"
39
+
40
+ if s.respond_to? :specification_version then
41
+ s.specification_version = 3
42
+
43
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
44
+ s.add_runtime_dependency(%q<i18n>, [">= 0.6.0"])
45
+ s.add_runtime_dependency(%q<activesupport>, [">= 3.0.0"])
46
+ s.add_runtime_dependency(%q<rparsec-ruby19>, [">= 1.0"])
47
+ s.add_development_dependency(%q<rspec>, ["~> 2.8.0"])
48
+ s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
49
+ s.add_development_dependency(%q<jeweler>, ["~> 1.8.3"])
50
+ s.add_development_dependency(%q<guard-shell>, [">= 0"])
51
+ s.add_development_dependency(%q<guard-rspec>, [">= 0"])
52
+ s.add_development_dependency(%q<pry>, [">= 0"])
53
+ else
54
+ s.add_dependency(%q<i18n>, [">= 0.6.0"])
55
+ s.add_dependency(%q<activesupport>, [">= 3.0.0"])
56
+ s.add_dependency(%q<rparsec-ruby19>, [">= 1.0"])
57
+ s.add_dependency(%q<rspec>, ["~> 2.8.0"])
58
+ s.add_dependency(%q<rdoc>, ["~> 3.12"])
59
+ s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
60
+ s.add_dependency(%q<guard-shell>, [">= 0"])
61
+ s.add_dependency(%q<guard-rspec>, [">= 0"])
62
+ s.add_dependency(%q<pry>, [">= 0"])
63
+ end
64
+ else
65
+ s.add_dependency(%q<i18n>, [">= 0.6.0"])
66
+ s.add_dependency(%q<activesupport>, [">= 3.0.0"])
67
+ s.add_dependency(%q<rparsec-ruby19>, [">= 1.0"])
68
+ s.add_dependency(%q<rspec>, ["~> 2.8.0"])
69
+ s.add_dependency(%q<rdoc>, ["~> 3.12"])
70
+ s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
71
+ s.add_dependency(%q<guard-shell>, [">= 0"])
72
+ s.add_dependency(%q<guard-rspec>, [">= 0"])
73
+ s.add_dependency(%q<pry>, [">= 0"])
74
+ end
75
+ end
76
+
data/lib/horai.rb ADDED
@@ -0,0 +1,272 @@
1
+ # coding: utf-8
2
+
3
+ require 'active_support/time'
4
+ require "ja_number"
5
+
6
+ class Horai
7
+ NORMALIZE_FROM = '0-9a-zA-Z:'
8
+ NORMALIZE_TO = '0-9a-zA-Z:'
9
+
10
+ DATE_DELIMITER = (/[\/-]/)
11
+ TIME_DELIMITER = (/[:]/)
12
+
13
+ AFTERNOON_PATTERN = (/午後|ごご|夜|よる|(?<![a-z])pm(?![a-z])/i)
14
+ RELATIVE_KEYWORDS_PATTERN = (/(?<![明午])後(?![年月日])|(?:[経た]っ|し)たら/)
15
+ RELATIVE_PATTERN = (/(\d+)(?:(?:年|ねん)|(?:月|がつ)|(?:日|にち)|(?:分|ふん|ぷん)|(?:時間?|じ(?:かん)?)|(?:秒|びょう))#{RELATIVE_KEYWORDS_PATTERN}/)
16
+
17
+ def self.filter(pattern, type = :absolute, &block)
18
+ @filters = [] if @filters.nil?
19
+ @filters << { pattern: pattern, type => block }
20
+ end
21
+
22
+ def self.truncate_time(date)
23
+ datetime_delta(date, hour: 0, minute: 0, second: 0)
24
+ end
25
+
26
+ def self.datetime_delta(date, methods = {})
27
+ date = date.dup
28
+ methods.each_pair do |key, value|
29
+ converter = 1.0.respond_to?(key) ? :to_f : :to_i;
30
+ date += value.send(converter).send(key) - date.send(key).send(key)
31
+ end
32
+ date
33
+ end
34
+
35
+ def self.register_filters
36
+ dd = DATE_DELIMITER
37
+ td = TIME_DELIMITER
38
+
39
+ # 年 (絶対)
40
+ filter /(\d+)年/, :absolute do |text, matches, date|
41
+ year = year_normalize(matches[1].to_i)
42
+ truncate_time(datetime_delta(date, year: year, month: 1, day: 1))
43
+ end
44
+
45
+ filter /一昨[々昨]年|さきおととし|いっさくさくねん/, :absolute do |text, matches, date|
46
+ truncate_time(datetime_delta(date - 3.year, month: 1, day: 1))
47
+ end
48
+
49
+ filter /一昨年|おととし|いっさくねん/, :absolute do |text, matches, date|
50
+ truncate_time(datetime_delta(date - 2.year, month: 1, day: 1))
51
+ end
52
+
53
+ filter /去年|昨年|きょねん|さくねん/, :absolute do |text, matches, date|
54
+ truncate_time(datetime_delta(date - 1.year, month: 1, day: 1))
55
+ end
56
+
57
+ filter /今年|本年|ことし|ほんねん/, :absolute do |text, matches, date|
58
+ end
59
+
60
+ filter /来年|明年|らいねん|みょうねん/, :absolute do |text, matches, date|
61
+ truncate_time(datetime_delta(date + 1.year, month: 1, day: 1))
62
+ end
63
+
64
+ filter /再来年|明後年|さらいねん|みょうごねん/, :absolute do |text, matches, date|
65
+ truncate_time(datetime_delta(date + 2.year, month: 1, day: 1))
66
+ end
67
+
68
+ filter /明[々明]後年|みょうみょうごねん/, :absolute do |text, matches, date|
69
+ truncate_time(datetime_delta(date + 3.year, month: 1, day: 1))
70
+ end
71
+
72
+ # 月 (絶対)
73
+ filter /(\d+)月/, :absolute do |text, matches, date|
74
+ truncate_time(datetime_delta(date, month: matches[1].to_i, day: 1))
75
+ end
76
+
77
+ filter /[先前][々先前]{3}月|せんせんせんげつ/, :absolute do |text, matches, date|
78
+ truncate_time(datetime_delta(date - 3.month, day: 1))
79
+ end
80
+
81
+ filter /[先前][々先前]月|せんせんげつ/, :absolute do |text, matches, date|
82
+ truncate_time(datetime_delta(date - 2.month, day: 1))
83
+ end
84
+
85
+ filter /先月|前月|せんげつ/, :absolute do |text, matches, date|
86
+ truncate_time(datetime_delta(date - 1.month, day: 1))
87
+ end
88
+
89
+ filter /今月|こんげつ/, :absolute do |text, matches, date|
90
+ end
91
+
92
+ filter /来月|らいげつ/, :absolute do |text, matches, date|
93
+ truncate_time(datetime_delta(date + 1.month, day: 1))
94
+ end
95
+
96
+ filter /再来月|来[々来]月|さらいげつ|らいらいげつ/, :absolute do |text, matches, date|
97
+ truncate_time(datetime_delta(date + 2.month, day: 1))
98
+ end
99
+
100
+ filter /再[々再]来月|ささらいげつ/, :absolute do |text, matches, date|
101
+ truncate_time(datetime_delta(date + 3.month, day: 1))
102
+ end
103
+
104
+ # 日 (絶対)
105
+ filter /(\d+)日/, :absolute do |text, matches, date|
106
+ truncate_time(datetime_delta(date, day: matches[1]))
107
+ end
108
+
109
+ filter /一昨[々昨]日|さきおと[とつ]い|いっさくさくじつ/, :absolute do |text, matches, date|
110
+ truncate_time(date - 3.day)
111
+ end
112
+
113
+ filter /一昨日|おと[とつ]い|いっさくじつ/, :absolute do |text, matches, date|
114
+ truncate_time(date - 2.day)
115
+ end
116
+
117
+ filter /昨日|きのう|さくじつ/, :absolute do |text, matches, date|
118
+ truncate_time(date - 1.day)
119
+ end
120
+
121
+ filter /明日|あした|みょうじつ/, :absolute do |text, matches, date|
122
+ truncate_time(date + 1.day)
123
+ end
124
+
125
+ filter /明後日|あさって|みょうごにち/, :absolute do |text, matches, date|
126
+ truncate_time(date + 2.day)
127
+ end
128
+
129
+ filter /明[々明]後日|しあさって|みょうみょうごにち/, :absolute do |text, matches, date|
130
+ truncate_time(date + 3.day)
131
+ end
132
+
133
+ # 月日表現 (絶対)
134
+ filter /(?<![\d\/-])(?<!\d)(\d{1,2})#{dd}(\d{1,2})(?!#{dd})/, :absolute do |text, matches, date|
135
+ truncate_time(datetime_delta(date, month: matches[1], day: matches[2]))
136
+ end
137
+
138
+ # 年月日表現 (絶対)
139
+ filter /(?<![\d\/-])(\d{1,2}|\d{4})#{dd}(\d{1,2})#{dd}(\d{1,2})(?!#{dd})/, :absolute do |text, matches, date|
140
+ year = year_normalize(matches[1].to_i)
141
+ truncate_time(datetime_delta(date, year: year, month: matches[2], day: matches[3]))
142
+ end
143
+
144
+ # 時分表現 (絶対)
145
+ filter /(?<![\d:])(\d{1,2})#{td}(\d{2})(?!#{td})/, :absolute do |text, matches, date|
146
+ datetime_delta(date, hour: matches[1], minute: matches[2], second: 0)
147
+ end
148
+
149
+ # 時分秒表現 (絶対)
150
+ filter /(?<![\d:])(\d{1,2})#{td}(\d{2})#{td}(\d{2})(?!#{td})/, :absolute do |text, matches, date|
151
+ datetime_delta(date, hour: matches[1], minute: matches[2], second: matches[3])
152
+ end
153
+
154
+ # 時間 (絶対)
155
+ filter /正午/, :absolute do |text, matches, date|
156
+ datetime_delta(date, hour: 12, minute: 0, second: 0)
157
+ end
158
+
159
+ filter /(\d+)時(半)?/, :absolute do |text, matches, date|
160
+ hour = matches[1]
161
+ minute = matches[2] ? 30 : 0
162
+ date = datetime_delta(date, hour: hour, minute: minute, second: 0)
163
+ date += 12.hour if date.hour <= 12 && text =~ AFTERNOON_PATTERN
164
+ date
165
+ end
166
+
167
+ filter /(\d+)分(半)?/, :absolute do |text, matches, date|
168
+ minute = matches[1]
169
+ second = matches[2] ? 30 : 0
170
+ datetime_delta(date, minute: minute, second: second)
171
+ end
172
+
173
+ filter /(\d+)秒/, :absolute do |text, matches, date|
174
+ datetime_delta(date, second: matches[1])
175
+ end
176
+
177
+ # 年 (相対)
178
+ filter /(\d+)年/, :relative do |text, matches, date|
179
+ date + matches[1].to_i.year
180
+ end
181
+
182
+ # 月 (相対)
183
+ filter /(\d+)月/, :relative do |text, matches, date|
184
+ date + matches[1].to_i.month
185
+ end
186
+
187
+ # 日 (相対)
188
+ filter /(\d+)日/, :relative do |text, matches, date|
189
+ date + matches[1].to_f.day
190
+ end
191
+
192
+ # 時間 (相対)
193
+ filter /(\d+)時間(半)?/, :relative do |text, matches, date|
194
+ date + matches[1].to_f.hour + (matches[2] ? 0.5 : 0.0).hour
195
+ end
196
+
197
+ filter /(\d+)分(半)?/, :relative do |text, matches, date|
198
+ date + matches[1].to_f.minute + (matches[2] ? 0.5 : 0.0).minute
199
+ end
200
+
201
+ filter /(\d+)秒/, :relative do |text, matches, date|
202
+ date + matches[1].to_f.second
203
+ end
204
+
205
+ end
206
+
207
+ def self.now
208
+ @now ||= DateTime.now
209
+ @now.dup
210
+ end
211
+
212
+ def self.filters
213
+ return @filters if @filters
214
+ register_filters
215
+ @filters
216
+ end
217
+
218
+ def self.relative?(text)
219
+ text =~ RELATIVE_PATTERN
220
+ end
221
+
222
+ def self.parse(text)
223
+ normalized = normalize(text)
224
+ contexts = (normalized + "$").split(RELATIVE_KEYWORDS_PATTERN)
225
+ date = now
226
+
227
+ filtered = false
228
+
229
+ contexts.each_with_index do |context, index|
230
+ if contexts.size >= 2 && index + 1 != contexts.size
231
+ mode = :relative
232
+ else
233
+ mode = :absolute
234
+ end
235
+
236
+ self.filters.each do |filter|
237
+ if (matches = context.match(filter[:pattern])) && filter[mode]
238
+ date = filter[mode].call(normalized, matches, date)
239
+ filtered = true unless filtered
240
+ end
241
+ end
242
+ end
243
+
244
+ @now = nil
245
+
246
+ return nil unless filtered
247
+ return date
248
+ end
249
+
250
+ def self.normalize(text)
251
+ normalized = text
252
+ normalized.tr!(NORMALIZE_FROM, NORMALIZE_TO)
253
+ digits = "一二三四五六七八九十百千"
254
+ classes = "万億兆"
255
+ normalized.gsub!(/[#{digits}][#{digits}#{classes}]*/) do |match|
256
+ JaNumber::JaNumberParser::parse(match)
257
+ end
258
+ return normalized
259
+ end
260
+
261
+ def self.year_normalize(year)
262
+ if year < 100
263
+ year_xx = (now.year / 100).to_i
264
+ if (year_xx - year).abs < 50
265
+ year += year_xx * 100
266
+ else
267
+ year += (year_xx - 1) * 100
268
+ end
269
+ end
270
+ year
271
+ end
272
+ end
data/lib/ja_number.rb ADDED
@@ -0,0 +1,56 @@
1
+ # coding: utf-8
2
+ # original: http://d.hatena.ne.jp/terazzo/20101226/1293313464
3
+
4
+ module JaNumber
5
+ module Constants
6
+ DIGITS = "一二三四五六七八九"
7
+ CLASSES = "十百千"
8
+ UNITS = "万億兆" # 増やしたければどーぞ
9
+ end
10
+
11
+ module JaNumberParser
12
+ require 'rparsec'
13
+
14
+ # <digit> ::= "一" | "二" | "三" | "四" | "五" | "六" | "七" | "八" | "九"
15
+ digits =
16
+ Constants::DIGITS.split(//).map.with_index {
17
+ |c, i| RParsec::Parsers.char(c).map {|c| 1 + i}
18
+ }.inject(RParsec::Parsers.zero) {|result, p| result.plus(p)}
19
+
20
+ # <class> ::= "千" | "百" | "十"
21
+ classes =
22
+ Constants::CLASSES.split(//).map.with_index {
23
+ |c, i| RParsec::Parsers.char(c).map {|c| 10 ** (1 + i)}
24
+ }.inject(RParsec::Parsers.zero) {|result, p| result.plus(p)}
25
+
26
+ # <unit> ::= "万" | "億" | "兆"
27
+ units =
28
+ Constants::UNITS.split(//).map.with_index {
29
+ |c, i| RParsec::Parsers.char(c).map {|c| 10000 ** (1 + i)}
30
+ }.inject(RParsec::Parsers.zero) {|result, p| result.plus(p)}
31
+
32
+ # <singlet> ::= <digit>
33
+ singlet = digits
34
+
35
+ # <quadruplet> ::= <digit>? <class> <quadruplet>? | <singlet>
36
+ quadruplet = nil
37
+ lazy_quadruplet = RParsec::Parsers.lazy{quadruplet}
38
+ quadruplet =
39
+ RParsec::Parsers.sequence(digits.optional(1), classes, lazy_quadruplet.optional(0)) {
40
+ |digit_value, class_value, next_value| digit_value * class_value + next_value
41
+ } | singlet
42
+
43
+ # <number> ::= <quadruplet> <unit> <number>? | <quadruplet>
44
+ number = nil
45
+ lazy_number = RParsec::Parsers.lazy{number}
46
+ number =
47
+ RParsec::Parsers.sequence(quadruplet, units, lazy_number.optional(0)) {
48
+ |quadruplet_value, unit_value, next_value| quadruplet_value * unit_value + next_value
49
+ } | quadruplet
50
+
51
+ define_method :parse do |ja_number|
52
+ number.parse ja_number
53
+ end
54
+ module_function :parse
55
+ end
56
+ end
@@ -0,0 +1,222 @@
1
+ # coding: utf-8
2
+
3
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
4
+
5
+ def now (year = nil, month = nil, day = nil, hour = nil, minute = nil, second = nil)
6
+ @now ||= Horai::now
7
+
8
+ # puts "{" + [year || @now.year,
9
+ # month || @now.month,
10
+ # day || @now.day,
11
+ # hour || @now.hour,
12
+ # minute || @now.minute,
13
+ # second || @now.second,
14
+ # Rational(9, 24)].join(',') + "}"
15
+
16
+ DateTime.new(year || @now.year,
17
+ month || @now.month,
18
+ day || @now.day,
19
+ hour || @now.hour,
20
+ minute || @now.minute,
21
+ second || @now.second,
22
+ Rational(9, 24))
23
+ end
24
+
25
+ describe Horai do
26
+ context 'normalize' do
27
+ it "number" do
28
+ Horai.normalize('01234').should === '01234'
29
+ end
30
+ it "alphabet" do
31
+ Horai.normalize('abcde').should === 'abcde'
32
+ Horai.normalize('ABCDE').should === 'ABCDE'
33
+ end
34
+ it "numeric kanji" do
35
+ Horai.normalize('十五').should === '15'
36
+ Horai.normalize('十四万二千三百四十五').should === '142345'
37
+ Horai.normalize('百五十時間後').should === '150時間後'
38
+
39
+ pending "漢数字パースライブラリがだめ"
40
+ Horai.normalize('1万秒').should === '10000秒'
41
+ end
42
+ end
43
+
44
+ context 'invalid cases' do
45
+ it 'not even one filter passed' do
46
+ Horai.parse("なにもなし").should be_nil
47
+ end
48
+ end
49
+
50
+ context 'parse absolute' do
51
+ before :each do
52
+ @sample_date = DateTime.new(2012, 4, 11, 12, 45, 30, Rational(9, 24))
53
+ @sample_text = @sample_date.strftime('%Y年%m月%d日の%H時%M分%S秒')
54
+ end
55
+ it "year, month, day, hour, minute, second" do
56
+ time = Horai.parse(@sample_text)
57
+ time.to_s.should === @sample_date.to_s
58
+ end
59
+ it "year" do
60
+ time = Horai.parse("1999年")
61
+ time.to_s.should === now(1999, 1, 1, 0, 0, 0).to_s
62
+ end
63
+ it "month" do
64
+ time = Horai.parse("1月")
65
+ time.to_s.should === now(nil, 1, 1, 0, 0, 0).to_s
66
+ end
67
+ it "half time" do
68
+ time = Horai.parse("1時半")
69
+ time.to_s.should === now(nil, nil, nil, 1, 30, 0).to_s
70
+ end
71
+ it "half minute" do
72
+ time = Horai.parse("1分半後")
73
+ time.to_s.should === (now + 1.5.minute).to_s
74
+ end
75
+ it "half hour and minute" do
76
+ time = Horai.parse("5時間半と1分半後")
77
+ time.to_s.should === (now + 5.5.hour + 1.5.minute).to_s
78
+ end
79
+ it "at night" do
80
+ time = Horai.parse("夜の8時に")
81
+ time.to_s.should === (now(nil, nil, nil, 20, 0, 0)).to_s
82
+ end
83
+ it "at afternoon" do
84
+ time = Horai.parse("午後一時に")
85
+ time.to_s.should === (now(nil, nil, nil, 13, 0, 0)).to_s
86
+ end
87
+ it "at afternoon" do
88
+ time = Horai.parse("PM1時に")
89
+ time.to_s.should === (now(nil, nil, nil, 13, 0, 0)).to_s
90
+ end
91
+ it "at AM and contains *pm*" do
92
+ time = Horai.parse("rpm 6時に")
93
+ time.to_s.should === (now(nil, nil, nil, 6, 0, 0)).to_s
94
+ end
95
+ it "at noon" do
96
+ time = Horai.parse("10日の正午に")
97
+ time.to_s.should === (now(nil, nil, 10, 12, 0, 0)).to_s
98
+ end
99
+ it "at hh:mm" do
100
+ time = Horai.parse("10:20")
101
+ time.to_s.should === (now(nil, nil, nil, 10, 20, 0)).to_s
102
+ end
103
+ it "at hh:mm:ss" do
104
+ time = Horai.parse("10:20:30")
105
+ time.to_s.should === (now(nil, nil, nil, 10, 20, 30)).to_s
106
+ end
107
+ it "at MM/DD" do
108
+ time = Horai.parse("10/20")
109
+ time.to_s.should === now(nil, 10, 20, 0, 0, 0).to_s
110
+ end
111
+ it "at YYYY/MM/DD" do
112
+ time = Horai.parse("2000/10/20")
113
+ time.to_s.should === now(2000, 10, 20, 0, 0, 0).to_s
114
+ end
115
+ it "at YY/MM/DD" do
116
+ time = Horai.parse("10/10/20")
117
+ time.to_s.should === now(2010, 10, 20, 0, 0, 0).to_s
118
+ end
119
+ it "at YYYY/MM/DD hh:mm:ss" do
120
+ time = Horai.parse("2000/10/20 12:30:40")
121
+ time.to_s.should === now(2000, 10, 20, 12, 30, 40).to_s
122
+ end
123
+ it "at YY (near current year)" do
124
+ time = Horai.parse("10年")
125
+ time.to_s.should === now(2010, 1, 1, 0, 0, 0).to_s
126
+ end
127
+ it "at YY (near feature)" do
128
+ time = Horai.parse("30年")
129
+ time.to_s.should === now(2030, 1, 1, 0, 0, 0).to_s
130
+ end
131
+ it "at YY (not near feature)" do
132
+ time = Horai.parse("90年")
133
+ time.to_s.should === now(1990, 1, 1, 0, 0, 0).to_s
134
+ end
135
+ end
136
+
137
+ context 'parse relative' do
138
+ it "check" do
139
+ Horai.relative?("10分後").should be_true
140
+ Horai.relative?("10分経ったら").should be_true
141
+ Horai.relative?("10分したら").should be_true
142
+ Horai.relative?("10分").should be_false
143
+ Horai.relative?("10時10分").should be_false
144
+ end
145
+ it "next year" do
146
+ time = Horai.parse("来年")
147
+ time.to_s.should === (now(nil, 1, 1, 0, 0, 0) + 1.year).to_s
148
+ end
149
+ it "next month" do
150
+ time = Horai.parse("来月")
151
+ time.to_s.should === (now(nil, nil, 1, 0, 0, 0) + 1.month).to_s
152
+ end
153
+ it "tomorrow" do
154
+ time = Horai.parse("明日")
155
+ time.to_s.should === (now(nil, nil, nil, 0, 0, 0) + 1.day).to_s
156
+ end
157
+ it "day after tomorrow" do
158
+ time = Horai.parse("明後日")
159
+ time.to_s.should === (now(nil, nil, nil, 0, 0, 0) + 2.day).to_s
160
+ end
161
+ it "yesterday" do
162
+ time = Horai.parse("昨日")
163
+ time.to_s.should === (now(nil, nil, nil, 0, 0, 0) - 1.day).to_s
164
+ end
165
+ it "numeric year after and absolute month" do
166
+ time = Horai.parse("10年後の8月")
167
+ time.to_s.should === (now(nil, 8, 1, 0, 0, 0) + 10.year).to_s
168
+ end
169
+ it "numeric minute after" do
170
+ time = Horai.parse("10分後")
171
+ time.to_s.should === (now + 10.minute).to_s
172
+ end
173
+ it "numeric day after" do
174
+ time = Horai.parse("10日後")
175
+ time.to_s.should === (now + 10.day).to_s
176
+ end
177
+ it "tomorrow and absolute time" do
178
+ time = Horai.parse("明日の10時")
179
+ time.to_s.should === (now(nil, nil, nil, 10, 0, 0) + 1.day).to_s
180
+ end
181
+ it "tomorrow and absolute hh:mm:ss" do
182
+ time = Horai.parse("明日の10:20:30")
183
+ time.to_s.should === (now(nil, nil, nil, 10, 20, 30) + 1.day).to_s
184
+ end
185
+ it "tomorrow and afternoon" do
186
+ time = Horai.parse("明日の午後5時")
187
+ time.to_s.should === (now(nil, nil, nil, 17, 0, 0) + 1.day).to_s
188
+ end
189
+ it "tomorrow and noon" do
190
+ time = Horai.parse("明日の正午")
191
+ time.to_s.should === (now(nil, nil, nil, 12, 0, 0) + 1.day).to_s
192
+ end
193
+ it "numeric day after and absolute time" do
194
+ time = Horai.parse("3日後の12時")
195
+ time.to_s.should === (now(nil, nil, nil, 12, 0, 0) + 3.day).to_s
196
+ end
197
+ it "numeric day after and absolute time" do
198
+ time = Horai.parse("3日後の12時45分")
199
+ time.to_s.should === (now(nil, nil, nil, 12, 45, 0) + 3.day).to_s
200
+ end
201
+ it "numeric day after and absolute hh:mm:ss" do
202
+ time = Horai.parse("3日後の12:45:55")
203
+ time.to_s.should === (now(nil, nil, nil, 12, 45, 55) + 3.day).to_s
204
+ end
205
+ it "numeric day after and relative time" do
206
+ time = Horai.parse("3日12時間45分後")
207
+ time.to_s.should === (now + 3.day + 12.hour + 45.minute).to_s
208
+ end
209
+ it "half time after" do
210
+ time = Horai.parse("1時間半後")
211
+ time.to_s.should === (now + 1.5.hour).to_s
212
+ end
213
+ it "half minute after" do
214
+ time = Horai.parse("1分半後")
215
+ time.to_s.should === (now + 1.5.minute).to_s
216
+ end
217
+ it "half hour and half minute after" do
218
+ time = Horai.parse("5時間半と1分半後")
219
+ time.to_s.should === (now + 5.5.hour + 1.5.minute).to_s
220
+ end
221
+ end
222
+ end
@@ -0,0 +1,12 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rspec'
4
+ require 'horai'
5
+
6
+ # Requires supporting files with custom matchers and macros, etc,
7
+ # in ./support/ and its subdirectories.
8
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
9
+
10
+ RSpec.configure do |config|
11
+
12
+ end
metadata ADDED
@@ -0,0 +1,162 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: horai
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.7.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - AOKI Yuuto
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-04-25 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: i18n
16
+ requirement: &2157979040 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 0.6.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *2157979040
25
+ - !ruby/object:Gem::Dependency
26
+ name: activesupport
27
+ requirement: &2157978480 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: 3.0.0
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *2157978480
36
+ - !ruby/object:Gem::Dependency
37
+ name: rparsec-ruby19
38
+ requirement: &2157977980 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '1.0'
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *2157977980
47
+ - !ruby/object:Gem::Dependency
48
+ name: rspec
49
+ requirement: &2157977440 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 2.8.0
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *2157977440
58
+ - !ruby/object:Gem::Dependency
59
+ name: rdoc
60
+ requirement: &2157976920 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ~>
64
+ - !ruby/object:Gem::Version
65
+ version: '3.12'
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *2157976920
69
+ - !ruby/object:Gem::Dependency
70
+ name: jeweler
71
+ requirement: &2157976440 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ~>
75
+ - !ruby/object:Gem::Version
76
+ version: 1.8.3
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: *2157976440
80
+ - !ruby/object:Gem::Dependency
81
+ name: guard-shell
82
+ requirement: &2157975860 !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: *2157975860
91
+ - !ruby/object:Gem::Dependency
92
+ name: guard-rspec
93
+ requirement: &2157975360 !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ! '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ type: :development
100
+ prerelease: false
101
+ version_requirements: *2157975360
102
+ - !ruby/object:Gem::Dependency
103
+ name: pry
104
+ requirement: &2157974880 !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: *2157974880
113
+ description: Horai は日本語の時刻表現をパースし、DateTime型に変換することを目標としている Gem です。
114
+ email: aoki@u-ne.co
115
+ executables: []
116
+ extensions: []
117
+ extra_rdoc_files:
118
+ - LICENSE.txt
119
+ - README.md
120
+ files:
121
+ - .document
122
+ - .rspec
123
+ - Gemfile
124
+ - Gemfile.lock
125
+ - LICENSE.txt
126
+ - README.md
127
+ - Rakefile
128
+ - VERSION
129
+ - horai.gemspec
130
+ - lib/horai.rb
131
+ - lib/ja_number.rb
132
+ - spec/horai_spec.rb
133
+ - spec/spec_helper.rb
134
+ homepage: http://github.com/wneko/horai
135
+ licenses:
136
+ - MIT
137
+ post_install_message:
138
+ rdoc_options: []
139
+ require_paths:
140
+ - lib
141
+ required_ruby_version: !ruby/object:Gem::Requirement
142
+ none: false
143
+ requirements:
144
+ - - ! '>='
145
+ - !ruby/object:Gem::Version
146
+ version: '0'
147
+ segments:
148
+ - 0
149
+ hash: -3122584729073349609
150
+ required_rubygems_version: !ruby/object:Gem::Requirement
151
+ none: false
152
+ requirements:
153
+ - - ! '>='
154
+ - !ruby/object:Gem::Version
155
+ version: '0'
156
+ requirements: []
157
+ rubyforge_project:
158
+ rubygems_version: 1.8.11
159
+ signing_key:
160
+ specification_version: 3
161
+ summary: 日本語の時刻表現をパースし、DateTime型に変換する Gem
162
+ test_files: []