eventual 0.4.9 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +22 -0
- data/LICENSE +20 -0
- data/README.rdoc +86 -47
- data/Rakefile +45 -18
- data/VERSION +1 -0
- data/lib/eventual/es/event_parser.treetop +83 -0
- data/lib/eventual/es.rb +1 -0
- data/lib/eventual/syntax_nodes.rb +217 -0
- data/lib/eventual.rb +4 -190
- data/spec/es_eventual_spec.rb +373 -160
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +13 -11
- metadata +43 -29
- data/History.txt +0 -6
- data/Manifest.txt +0 -11
- data/eventual.gemspec +0 -33
- data/lib/eventual/date.rb +0 -1
- data/lib/eventual/date_time.rb +0 -1
- data/spec/date_time_spec.rb +0 -15
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Macario Ortega
|
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.rdoc
CHANGED
@@ -9,75 +9,114 @@ Reconocimiento de fechas y periodos en lenguaje natural. Útil para crear interf
|
|
9
9
|
== SINOPSIS:
|
10
10
|
|
11
11
|
El método event_parse del modulo Eventual reconoce y convierte una fecha o periodo expresado en lenguaje natural en objetos _Date_ o _DateTime_
|
12
|
-
Ejemplos:
|
13
12
|
|
13
|
+
Ejemplos:
|
14
14
|
require 'rubygems'
|
15
|
-
require 'eventual'
|
15
|
+
require 'eventual'
|
16
16
|
|
17
|
-
|
17
|
+
EsDatesParser.new.parse( 'del 5 al 7 de junio del 2009' ).map
|
18
18
|
=> [#<DateTime: 4909975/2,0,2299161>, #<DateTime: 4909977/2,0,2299161>, #<DateTime: 4909979/2,0,2299161>]
|
19
19
|
|
20
|
-
|
21
|
-
|
20
|
+
# Si no se especifica el año se usará el año actual
|
21
|
+
EsDatesParser.new.parse( 'del 5 al 7 de junio' ).map{ |d| d.to_s } # Quizá mas tarde se localize a otros idiomas
|
22
|
+
=> ["2010-06-05", "2010-06-06", "2010-06-07"]
|
23
|
+
|
24
|
+
# Se puede especificar un año por omisión
|
25
|
+
dates = EsDatesParser.new.parse( 'del 5 al 7 de junio' )
|
26
|
+
dates.year = 2007
|
27
|
+
dates.map{ |d| d.to_s }
|
28
|
+
=> ["2007-06-05", "2007-06-06", "2007-06-07"]
|
22
29
|
|
23
|
-
|
30
|
+
# Si se especifica la hora el resultado será un Array de DateTime
|
31
|
+
EsDatesParser.new.parse( 'del 5 al 7 de junio del 2009 a las 16:00 y 18:00 horas' ).map
|
32
|
+
=> [#<DateTime: 14729929/6,0,2299161>, #<DateTime: 9819953/4,0,2299161>, #<DateTime: 14729935/6,0,2299161>, #<DateTime: 9819957/4,0,2299161>, #<DateTime: 14729941/6,0,2299161>, #<DateTime: 9819961/4,0,2299161>]
|
33
|
+
|
34
|
+
EsDatesParser.new.parse( 'del 5 al 7 de junio del 2009 a las 16:00 y 18:00 horas' ).map{ |d| d.to_s }
|
24
35
|
=> ["2009-06-05T16:00:00+00:00", "2009-06-05T18:00:00+00:00", "2009-06-06T16:00:00+00:00", "2009-06-06T18:00:00+00:00", "2009-06-07T16:00:00+00:00", "2009-06-07T18:00:00+00:00"]
|
25
36
|
|
26
|
-
|
27
|
-
|
37
|
+
# Se pueden restringir los resultados a ciertos dias
|
38
|
+
EsDatesParser.new.parse('lunes y martes de diciembre del 2001 a las 15:00').map{ |d| d.to_s }
|
39
|
+
=> ["2010-12-06T15:00:00+00:00", "2010-12-07T15:00:00+00:00", "2010-12-13T15:00:00+00:00", "2010-12-14T15:00:00+00:00", "2010-12-20T15:00:00+00:00", "2010-12-21T15:00:00+00:00", "2010-12-27T15:00:00+00:00", "2010-12-28T15:00:00+00:00"]
|
28
40
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
* 21, 22 y 23 de enero a las 20:00 horas
|
34
|
-
* 21, 22 y 23 de enero a las 20:00 y 22:00 horas
|
35
|
-
* viernes 1, sábado 2, domingo 3 y lunes 4 de enero del 2010
|
36
|
-
* martes y miércoles del 1 al 20 de junio del 2009 a las 16:00 y 18:00 horas
|
37
|
-
* sábados y domingos del 1 de enero al 31 de diciembre del 2008 a las 16:00
|
38
|
-
* sábado y domingo del 1 de enero al 31 de diciembre
|
39
|
-
* lunes a viernes del 1 de enero al 31 de diciembre del 2008 a las 16:00
|
40
|
-
* todos los sábados de diciembre del 2009
|
41
|
-
* lunes a viernes de enero a abril
|
42
|
-
* del viernes 1 al domingo 3 de enero del 2010
|
41
|
+
# Se puede checar si las fechas reconocidas incluyen cierta fecha, la comparación es "perezosa" es decir no instancia todos los objetos Date o DateTime
|
42
|
+
# como hace map y por lo tanto es mas eficiente
|
43
|
+
EsDatesParser.new.parse( 'del 5 al 7 de junio del 2007' ).include? Date.civil(2007, 6, 6)
|
44
|
+
=> true
|
43
45
|
|
44
|
-
|
46
|
+
EsDatesParser.new.parse( 'del 5 al 7 de junio del 2007' ).include? Date.civil(2006, 6, 6)
|
47
|
+
=> false
|
45
48
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
require 'eventual/date'
|
50
|
-
|
51
|
-
DateTime.event_parse( 'del 5 al 7 de junio' )
|
52
|
-
=> [#<DateTime: 4909975/2,0,2299161>, #<DateTime: 4909977/2,0,2299161>, #<DateTime: 4909979/2,0,2299161>]
|
49
|
+
# Se toma en cuenta la hora
|
50
|
+
EsDatesParser.new.parse( 'del 5 al 7 de junio del 2007 a las 16:00' ).include? DateTime.civil(2007, 6, 6, 16, 0)
|
51
|
+
=> true
|
53
52
|
|
54
|
-
|
55
|
-
=>
|
56
|
-
|
57
|
-
|
58
|
-
|
53
|
+
EsDatesParser.new.parse( 'del 5 al 7 de junio del 2007 a las 16:00' ).include? DateTime.civil(2007, 6, 6, 15, 0)
|
54
|
+
=> false
|
55
|
+
|
56
|
+
# Si se pasa un Date que corresponda al periodo la comparación es positiva
|
57
|
+
EsDatesParser.new.parse( 'del 5 al 7 de junio del 2007 a las 16:00' ).include? Date.civil(2007, 6, 6)
|
58
|
+
=> true
|
59
|
+
|
60
|
+
# El evento tiene una duración por omisión de 60 minutos
|
61
|
+
EsDatesParser.new.parse( 'del 5 al 7 de junio del 2007 a las 16:00' ).include? DateTime.civil(2007, 6, 6, 16, 59)
|
62
|
+
=> true
|
63
|
+
|
64
|
+
EsDatesParser.new.parse( 'del 5 al 7 de junio del 2007 a las 16:00' ).include? DateTime.civil(2007, 6, 6, 17, 00)
|
65
|
+
=> false
|
66
|
+
|
67
|
+
# Pero se puede cambiar
|
68
|
+
dates = EsDatesParser.new.parse( 'del 5 al 7 de junio del 2007 a las 16:00' )
|
69
|
+
dates.time_span = 120
|
70
|
+
dates.include? DateTime.civil(2007, 6, 6, 17, 00)
|
71
|
+
=> true
|
72
|
+
|
73
|
+
Ejemplos de formatos reconocidos:
|
59
74
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
75
|
+
* marzo
|
76
|
+
* marzo de 2009
|
77
|
+
* marzo del 2009
|
78
|
+
* todo marzo 2009
|
79
|
+
* marzo, 2009
|
80
|
+
* marzo '09
|
81
|
+
* lunes y martes marzo del 2010
|
82
|
+
* todos los lunes y martes de marzo del 2010
|
83
|
+
* lunes y martes durante marzo del 2010
|
84
|
+
* lunes y martes durante todo marzo del 2010
|
85
|
+
* lunes y martes, marzo del 2010
|
86
|
+
* 21 de marzo
|
87
|
+
* 21 marzo
|
88
|
+
* domingo 21 de marzo
|
89
|
+
* 1, 2 y 3 de marzo
|
90
|
+
* 1, 2 y 3 marzo
|
91
|
+
* lunes 1, martes 2 y miercoles 3 de marzo
|
92
|
+
* 1 al 3 de marzo
|
93
|
+
* 1 al 3, marzo
|
94
|
+
* del 1 al 3 de marzo
|
95
|
+
* del 1 al 3, marzo
|
96
|
+
* 24 de febrero al 3 de marzo del 2010
|
97
|
+
* 24 de diciembre del 2009 al 3 de enero del 2010
|
98
|
+
* lunes y martes del 1 al 22 de marzo del '10
|
99
|
+
* fines de semana del 1 al 22 de marzo del '10
|
100
|
+
* entre semana del 1 al 22 de marzo del '10
|
101
|
+
* lunes y martes del 1 al 22 de marzo del '10
|
102
|
+
* todos los lunes y martes del 1 al 22 de marzo del '10
|
103
|
+
* los lunes y martes del 1 al 22 de marzo del '10
|
104
|
+
* los lunes y los martes del 1 al 22 de marzo del '10
|
105
|
+
* lunes y martes de diciembre a las 15
|
106
|
+
* lunes y martes de diciembre a las 15:30 hrs.
|
107
|
+
* lunes y martes de diciembre a las 15:00 y 16:00 horas
|
108
|
+
* lunes y martes de diciembre a las 3 am
|
109
|
+
* lunes y martes de diciembre a las 3:15 p.m.
|
65
110
|
|
66
111
|
== TODO:
|
67
112
|
|
68
|
-
* No estoy seguro de que Iconv funcione en windows, lo arreglaré pronto
|
69
|
-
|
70
113
|
Formatos a reconocer
|
71
114
|
|
72
|
-
* todos los lunes de junio
|
73
|
-
* todo junio
|
74
|
-
* domingos de septiembre
|
75
|
-
* martes y miércoles de agosto
|
76
115
|
* todo el año
|
77
116
|
|
78
117
|
== INSTALACIÓN:
|
79
118
|
|
80
|
-
sudo gem install
|
119
|
+
[sudo] gem install eventual -s http://gemcutter.org
|
81
120
|
|
82
121
|
== LICENCIA:
|
83
122
|
|
data/Rakefile
CHANGED
@@ -1,18 +1,45 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "eventual"
|
8
|
+
gem.summary = %Q{ Reconocimiento de eventos y periodos de tiempo en lengua natural. Natural date event parsing in spanish so far. }
|
9
|
+
gem.description = %Q{ Reconocimiento de eventos y periodos de tiempo en lengua natural. Natural date event parsing in spanish so far. }
|
10
|
+
gem.email = "macarui@gmail.com"
|
11
|
+
gem.homepage = "http://github.com/maca/eventual"
|
12
|
+
gem.authors = ["Macario Ortega"]
|
13
|
+
gem.post_install_message = %{ \n\n***********************************\nPor favor tenga en cuenta que el API ha cambiado, consulte la página del proyecto: http://github.com/maca/eventual. English implementation is due.\n***********************************\n\n }
|
14
|
+
gem.add_development_dependency "rspec", ">= 1.2.9"
|
15
|
+
end
|
16
|
+
Jeweler::GemcutterTasks.new
|
17
|
+
rescue LoadError
|
18
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'spec/rake/spectask'
|
22
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
23
|
+
spec.libs << 'lib' << 'spec'
|
24
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
25
|
+
end
|
26
|
+
|
27
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
28
|
+
spec.libs << 'lib' << 'spec'
|
29
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
30
|
+
spec.rcov = true
|
31
|
+
end
|
32
|
+
|
33
|
+
task :spec => :check_dependencies
|
34
|
+
|
35
|
+
task :default => :spec
|
36
|
+
|
37
|
+
require 'rake/rdoctask'
|
38
|
+
Rake::RDocTask.new do |rdoc|
|
39
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
40
|
+
|
41
|
+
rdoc.rdoc_dir = 'rdoc'
|
42
|
+
rdoc.title = "eventual #{version}"
|
43
|
+
rdoc.rdoc_files.include('README*')
|
44
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
45
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.5.0
|
@@ -0,0 +1,83 @@
|
|
1
|
+
grammar EsDates
|
2
|
+
rule root
|
3
|
+
(date_list / dates) year? times? <Eventual::Node>
|
4
|
+
end
|
5
|
+
|
6
|
+
rule dates
|
7
|
+
(period / date)
|
8
|
+
end
|
9
|
+
|
10
|
+
rule period
|
11
|
+
weekdays_constrain? (range / month)
|
12
|
+
end
|
13
|
+
|
14
|
+
rule date
|
15
|
+
weekday_constrain? day_number month_name?
|
16
|
+
end
|
17
|
+
|
18
|
+
rule date_list
|
19
|
+
dates (',' space dates)* (space 'y' space dates) / dates (space? "\n" space? dates)*
|
20
|
+
end
|
21
|
+
|
22
|
+
rule range
|
23
|
+
(('del' / 'de' / ',') space)? (month / date) year? space ('al' / 'a') space (month / date) <Eventual::DatePeriod>
|
24
|
+
end
|
25
|
+
|
26
|
+
rule month
|
27
|
+
month_name '' <Eventual::MonthPeriod>
|
28
|
+
end
|
29
|
+
|
30
|
+
rule day_number
|
31
|
+
([0-2] [0-9] / '3' [0-1] / [1-9]) '' <Eventual::Day>
|
32
|
+
end
|
33
|
+
|
34
|
+
##########
|
35
|
+
rule times
|
36
|
+
space 'a' space ('las' / 'la') (time_12 / time_24) ((space 'y' / ',') (time_12 / time_24))* <Eventual::Times>
|
37
|
+
end
|
38
|
+
|
39
|
+
rule time_24
|
40
|
+
space ([0-1] [0-9] / '2' [0-4] / [0-9]) (':' [0-5] [0-9])? (space? ('hrs.' / 'hrs' / 'horas'))? <Eventual::Time>
|
41
|
+
end
|
42
|
+
|
43
|
+
rule time_12
|
44
|
+
space ('0' [0-9] / '1' [0-2] / [0-9]) (':' [0-5] [0-9])? space? period:(('a'/'p') '.'? space? 'm' '.'? space?) <Eventual::Time12>
|
45
|
+
end
|
46
|
+
|
47
|
+
##########
|
48
|
+
rule month_name
|
49
|
+
(((space 'de') / ',')? space)? ('enero' / 'febrero' / 'marzo' / 'abril' / 'mayo' / 'junio' / 'julio' / 'agosto' / 'septiembre' / 'octubre' / 'noviembre' / 'diciembre') <Eventual::MonthName>
|
50
|
+
end
|
51
|
+
|
52
|
+
rule year
|
53
|
+
((space 'de' 'l'?) / ',')? space ([1-9] [0-9] [0-9] [0-9] / "'" [0-9] [0-9]) <Eventual::Year>
|
54
|
+
end
|
55
|
+
|
56
|
+
rule weekdays_constrain
|
57
|
+
wdays_node:(weekday_list / weekdays / weekday) (',' / (space ('del' / 'de' / 'durante todo' / 'durante')))? space <Eventual::WeekdayConstrain>
|
58
|
+
end
|
59
|
+
|
60
|
+
rule weekday_constrain
|
61
|
+
wdays_node:weekday space <Eventual::WeekdayConstrain>
|
62
|
+
end
|
63
|
+
|
64
|
+
rule weekday_list
|
65
|
+
(weekday_constrain_sugar? weekday (',' space weekday_constrain_sugar? weekday)* (space 'y' space weekday_constrain_sugar? weekday)?)
|
66
|
+
end
|
67
|
+
|
68
|
+
rule weekdays
|
69
|
+
weekday_constrain_sugar? ((('dias' space)? 'entre' space 'semana') / 'fines' space 'de' space 'semana')
|
70
|
+
end
|
71
|
+
|
72
|
+
rule weekday_constrain_sugar
|
73
|
+
(('todos' space)? 'los' space)
|
74
|
+
end
|
75
|
+
|
76
|
+
rule weekday
|
77
|
+
(('lun' 'es'? ) / ('mar' 'tes'? ) / ('mi' 'ercoles'? ) / ('jue' 'ves'? ) / ('vie' 'rnes'? ) / ('sab' 'ado'? 's'? ) / ('dom' 'ingo'? 's'? ))
|
78
|
+
end
|
79
|
+
|
80
|
+
rule space
|
81
|
+
' '+
|
82
|
+
end
|
83
|
+
end
|
data/lib/eventual/es.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Treetop.load "#{ File.dirname __FILE__ }/es/event_parser"
|
@@ -0,0 +1,217 @@
|
|
1
|
+
module Eventual
|
2
|
+
Weekdays = %w(domingo lunes martes miércoles jueves viernes sábado).freeze
|
3
|
+
MonthNames = %w(enero febrero marzo abril mayo junio julio agosto septiembre noviembre).unshift(nil).freeze
|
4
|
+
ShortMonthNames = %w(ene feb mar abr may jun jul ago sept oct nov dic).freeze
|
5
|
+
WdaysR = [/d/, /l/, /ma/, /mi/, /j/, /v/, /s/].freeze
|
6
|
+
WdayListR = /\b(?:#{ WdaysR.join('|') })/.freeze
|
7
|
+
|
8
|
+
class WdayMatchError < StandardError
|
9
|
+
def initialize value, wday_index
|
10
|
+
@value, @wday_index = value, wday_index
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_s
|
14
|
+
"El #{@value.day} de #{MonthNames[@value.month]} del #{@value.year} cae en #{Weekdays[@value.wday]} no #{Weekdays[@wday_index]}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class Year < Treetop::Runtime::SyntaxNode
|
19
|
+
def value
|
20
|
+
match = text_value.match(/(')?(\d{2,4})/)
|
21
|
+
value = match[2].to_i
|
22
|
+
value += 2000 if match[1]
|
23
|
+
value
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class WeekdayConstrain < Treetop::Runtime::SyntaxNode
|
28
|
+
def value
|
29
|
+
text = wdays_node.text_value.sub('semana', '')
|
30
|
+
days = text.scan(WdayListR).map{ |d| WdaysR.index /#{d}/ }
|
31
|
+
days += (1..5).map if text.include?('entre')
|
32
|
+
days += [6,0] if text.include?('fines')
|
33
|
+
days.uniq
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class MonthName < Treetop::Runtime::SyntaxNode
|
38
|
+
def value
|
39
|
+
ShortMonthNames.index(text_value.downcase.match(/#{ ShortMonthNames.join('|') }/).to_s) + 1
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class Node < Treetop::Runtime::SyntaxNode
|
44
|
+
attr_accessor :year
|
45
|
+
attr_accessor :time_span
|
46
|
+
|
47
|
+
attr_accessor :month
|
48
|
+
attr_accessor :weekdays
|
49
|
+
attr_accessor :times
|
50
|
+
|
51
|
+
def last
|
52
|
+
to_a.last
|
53
|
+
end
|
54
|
+
|
55
|
+
def first
|
56
|
+
to_a.first
|
57
|
+
end
|
58
|
+
|
59
|
+
def to_a
|
60
|
+
map
|
61
|
+
end
|
62
|
+
|
63
|
+
def date_within_weekdays? date
|
64
|
+
return true unless weekdays
|
65
|
+
weekdays.include?(date.wday)
|
66
|
+
end
|
67
|
+
|
68
|
+
def map &block
|
69
|
+
walk { |elements| elements.map &block }
|
70
|
+
end
|
71
|
+
|
72
|
+
def include? date
|
73
|
+
result = false
|
74
|
+
walk { |elements| break result = true if elements.include? date }
|
75
|
+
|
76
|
+
unless date.class == Date or times.nil? or times.empty?
|
77
|
+
@time_span ||= 60
|
78
|
+
within_time = times.inject(nil) { |memo, time|
|
79
|
+
first = ::Time.local date.year, date.month, date.day, time.hour, time.minute
|
80
|
+
time = ::Time.local date.year, date.month, date.day, date.hour, date.min
|
81
|
+
break true if time >= first and time < first + 60 * @time_span
|
82
|
+
}
|
83
|
+
return false unless within_time
|
84
|
+
end
|
85
|
+
result
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
def walk &block
|
90
|
+
year = self.year || Date.today.year
|
91
|
+
month = nil
|
92
|
+
|
93
|
+
walk = lambda do |elements|
|
94
|
+
break unless elements
|
95
|
+
weekdays = elements.first.value if elements.first.class == WeekdayConstrain
|
96
|
+
|
97
|
+
elements.reverse.map do |element|
|
98
|
+
case element
|
99
|
+
when Day, Period
|
100
|
+
element.weekdays = weekdays
|
101
|
+
element.year = year
|
102
|
+
element.month = month
|
103
|
+
element.times = @times
|
104
|
+
|
105
|
+
yield element
|
106
|
+
when Year
|
107
|
+
year = element.value
|
108
|
+
next nil
|
109
|
+
when MonthName
|
110
|
+
month = element.value
|
111
|
+
next nil
|
112
|
+
when WeekdayConstrain
|
113
|
+
next nil
|
114
|
+
when Times
|
115
|
+
@times = element.map
|
116
|
+
next nil
|
117
|
+
else
|
118
|
+
walk.call element.elements
|
119
|
+
end
|
120
|
+
end.reverse
|
121
|
+
end
|
122
|
+
walk.call(elements).flatten.compact
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
class Day < Node
|
127
|
+
def map &block
|
128
|
+
dates = times ? times.map{ |time| DateTime.civil year, month, text_value.to_i, time.hour, time.minute } : [Date.civil(year, month, text_value.to_i)]
|
129
|
+
raise WdayMatchError.new(dates.first, weekdays.first) unless date_within_weekdays? dates.first
|
130
|
+
dates.map(&block)
|
131
|
+
end
|
132
|
+
|
133
|
+
def include? date
|
134
|
+
to_a.include? date
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
class Period < Node
|
139
|
+
def range
|
140
|
+
(first..last)
|
141
|
+
end
|
142
|
+
|
143
|
+
def include? date
|
144
|
+
return false unless date_within_weekdays? date
|
145
|
+
range.include? date
|
146
|
+
end
|
147
|
+
|
148
|
+
alias node_map map
|
149
|
+
private :node_map
|
150
|
+
|
151
|
+
def map
|
152
|
+
array = []
|
153
|
+
range.each do |date|
|
154
|
+
next unless date_within_weekdays? date
|
155
|
+
next array.push(block_given? ? yield(date) : date) unless times
|
156
|
+
|
157
|
+
times.each do |time|
|
158
|
+
new_date = DateTime.civil date.year, date.month, date.day, time.hour, time.minute
|
159
|
+
array.push block_given? ? yield(new_date) : new_date
|
160
|
+
end
|
161
|
+
end
|
162
|
+
array
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
class MonthPeriod < Period
|
167
|
+
def first
|
168
|
+
return Date.civil(year, month_name.value) unless times and !times.empty?
|
169
|
+
time = times.first
|
170
|
+
return DateTime.civil(year, month_name.value, 1, time.hour, time.minute)
|
171
|
+
end
|
172
|
+
|
173
|
+
def last
|
174
|
+
date = (first >> 1) - 1
|
175
|
+
return date unless times and !times.empty?
|
176
|
+
time = times.last
|
177
|
+
DateTime.civil(date.year, date.month, date.day, time.hour, time.minute)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
class DatePeriod < Period
|
182
|
+
def first
|
183
|
+
node_map.first
|
184
|
+
end
|
185
|
+
|
186
|
+
def last
|
187
|
+
node_map.last
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
class Times < Treetop::Runtime::SyntaxNode
|
192
|
+
def map
|
193
|
+
walk_times = lambda do |elements|
|
194
|
+
break unless elements
|
195
|
+
elements.map { |e| Time === e ? e.value : walk_times.call(e.elements) }
|
196
|
+
end
|
197
|
+
walk_times.call(elements).flatten.compact.sort_by{ |t| '%02d%02d' % [t.hour, t.minute] }
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
class Time < Treetop::Runtime::SyntaxNode
|
202
|
+
attr_accessor :hour, :minute
|
203
|
+
def value
|
204
|
+
@hour, @minute = text_value.scan(/\d+/).map(&:to_i)
|
205
|
+
@minute ||= 0
|
206
|
+
self
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
class Time12 < Time
|
211
|
+
def value
|
212
|
+
super
|
213
|
+
@hour += 12 if period.text_value.gsub(/[^a-z]/, '') == 'pm'
|
214
|
+
self
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|