eventual 0.4.9 → 0.5.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/.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
|