when_exe 0.2.100 → 0.3.1

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.
Files changed (193) hide show
  1. data/LICENSE.ja.txt +25 -25
  2. data/LICENSE.txt +31 -31
  3. data/bin/irb.rc +5 -0
  4. data/bin/locales.rb +2 -2
  5. data/bin/when.rb +16 -0
  6. data/bin/when.rb.config +7 -0
  7. data/lib/when_exe.rb +616 -14
  8. data/lib/when_exe/basictypes.rb +615 -0
  9. data/lib/when_exe/calendartypes.rb +1700 -0
  10. data/lib/when_exe/coordinates.rb +1936 -0
  11. data/lib/when_exe/core/compatibility.rb +54 -0
  12. data/lib/when_exe/core/duration.rb +72 -72
  13. data/lib/when_exe/core/extension.rb +382 -0
  14. data/lib/when_exe/ephemeris.rb +1845 -0
  15. data/lib/when_exe/googlecalendar.rb +140 -0
  16. data/lib/when_exe/icalendar.rb +1587 -0
  17. data/lib/when_exe/inspect.rb +1237 -0
  18. data/lib/when_exe/locales/af.rb +90 -0
  19. data/lib/when_exe/locales/ar.rb +145 -0
  20. data/lib/when_exe/locales/az.rb +90 -0
  21. data/lib/when_exe/locales/bg.rb +90 -0
  22. data/lib/when_exe/locales/bn.rb +94 -0
  23. data/lib/when_exe/locales/bs.rb +121 -0
  24. data/lib/when_exe/locales/ca.rb +92 -0
  25. data/lib/when_exe/locales/cs.rb +107 -0
  26. data/lib/when_exe/locales/cy.rb +150 -0
  27. data/lib/when_exe/locales/da.rb +84 -0
  28. data/lib/when_exe/locales/de.rb +92 -0
  29. data/lib/when_exe/locales/de_AT.rb +92 -0
  30. data/lib/when_exe/locales/de_CH.rb +92 -0
  31. data/lib/when_exe/locales/el.rb +93 -0
  32. data/lib/when_exe/locales/en.rb +88 -0
  33. data/lib/when_exe/locales/en_AU.rb +88 -0
  34. data/lib/when_exe/locales/en_CA.rb +88 -0
  35. data/lib/when_exe/locales/en_GB.rb +88 -0
  36. data/lib/when_exe/locales/en_IN.rb +88 -0
  37. data/lib/when_exe/locales/en_NZ.rb +88 -0
  38. data/lib/when_exe/locales/eo.rb +89 -0
  39. data/lib/when_exe/locales/es.rb +84 -0
  40. data/lib/when_exe/locales/es_419.rb +84 -0
  41. data/lib/when_exe/locales/es_AR.rb +84 -0
  42. data/lib/when_exe/locales/es_CL.rb +84 -0
  43. data/lib/when_exe/locales/es_CO.rb +84 -0
  44. data/lib/when_exe/locales/es_MX.rb +84 -0
  45. data/lib/when_exe/locales/es_PE.rb +85 -0
  46. data/lib/when_exe/locales/es_VE.rb +84 -0
  47. data/lib/when_exe/locales/et.rb +94 -0
  48. data/lib/when_exe/locales/eu.rb +95 -0
  49. data/lib/when_exe/locales/fa.rb +80 -0
  50. data/lib/when_exe/locales/fi.rb +89 -0
  51. data/lib/when_exe/locales/fr.rb +88 -0
  52. data/lib/when_exe/locales/fr_CA.rb +88 -0
  53. data/lib/when_exe/locales/fr_CH.rb +88 -0
  54. data/lib/when_exe/locales/gl.rb +81 -0
  55. data/lib/when_exe/locales/he.rb +84 -0
  56. data/lib/when_exe/locales/hi.rb +80 -0
  57. data/lib/when_exe/locales/hi_IN.rb +84 -0
  58. data/lib/when_exe/locales/hr.rb +128 -0
  59. data/lib/when_exe/locales/hu.rb +84 -0
  60. data/lib/when_exe/locales/id.rb +89 -0
  61. data/lib/when_exe/locales/is.rb +89 -0
  62. data/lib/when_exe/locales/it.rb +87 -0
  63. data/lib/when_exe/locales/it_CH.rb +87 -0
  64. data/lib/when_exe/locales/ja.rb +78 -0
  65. data/lib/when_exe/locales/kn.rb +86 -0
  66. data/lib/when_exe/locales/ko.rb +78 -0
  67. data/lib/when_exe/locales/links.rb +2342 -0
  68. data/lib/when_exe/locales/lo.rb +123 -0
  69. data/lib/when_exe/locales/locales.rb +91 -0
  70. data/lib/when_exe/locales/lt.rb +111 -0
  71. data/lib/when_exe/locales/lv.rb +118 -0
  72. data/lib/when_exe/locales/mk.rb +93 -0
  73. data/lib/when_exe/locales/mn.rb +80 -0
  74. data/lib/when_exe/locales/nb.rb +81 -0
  75. data/lib/when_exe/locales/ne.rb +81 -0
  76. data/lib/when_exe/locales/nl.rb +92 -0
  77. data/lib/when_exe/locales/nn.rb +73 -0
  78. data/lib/when_exe/locales/or.rb +84 -0
  79. data/lib/when_exe/locales/pl.rb +128 -0
  80. data/lib/when_exe/locales/pt.rb +88 -0
  81. data/lib/when_exe/locales/pt_BR.rb +88 -0
  82. data/lib/when_exe/locales/rm.rb +143 -0
  83. data/lib/when_exe/locales/ro.rb +105 -0
  84. data/lib/when_exe/locales/ru.rb +128 -0
  85. data/lib/when_exe/locales/sk.rb +109 -0
  86. data/lib/when_exe/locales/sl.rb +122 -0
  87. data/lib/when_exe/locales/sr.rb +122 -0
  88. data/lib/when_exe/locales/sv.rb +83 -0
  89. data/lib/when_exe/locales/sw.rb +89 -0
  90. data/lib/when_exe/locales/th.rb +78 -0
  91. data/lib/when_exe/locales/tl.rb +99 -0
  92. data/lib/when_exe/locales/tr.rb +96 -0
  93. data/lib/when_exe/locales/uk.rb +128 -0
  94. data/lib/when_exe/locales/uz.rb +128 -0
  95. data/lib/when_exe/locales/vi.rb +94 -0
  96. data/lib/when_exe/locales/wo.rb +82 -0
  97. data/lib/when_exe/locales/zh_CN.rb +77 -0
  98. data/lib/when_exe/locales/zh_HK.rb +77 -0
  99. data/lib/when_exe/locales/zh_TW.rb +77 -0
  100. data/lib/when_exe/mini_application.rb +252 -0
  101. data/lib/when_exe/parts/enumerator.rb +472 -0
  102. data/lib/when_exe/parts/geometric_complex.rb +379 -0
  103. data/lib/when_exe/parts/locale.rb +513 -0
  104. data/lib/when_exe/parts/method_cash.rb +207 -0
  105. data/lib/when_exe/parts/resource.rb +806 -0
  106. data/lib/when_exe/parts/timezone.rb +182 -0
  107. data/lib/when_exe/region/bahai.rb +145 -0
  108. data/lib/when_exe/region/balinese.rb +627 -0
  109. data/lib/when_exe/region/chinese.rb +896 -0
  110. data/lib/when_exe/region/chinese_calendar.rb +919 -0
  111. data/lib/when_exe/region/chinese_epoch.rb +1245 -0
  112. data/lib/when_exe/region/christian.rb +644 -0
  113. data/lib/when_exe/region/far_east.rb +192 -0
  114. data/lib/when_exe/region/french.rb +66 -0
  115. data/lib/when_exe/region/geologicalage.rb +639 -0
  116. data/lib/when_exe/region/indian.rb +1066 -0
  117. data/lib/when_exe/region/iranian.rb +66 -0
  118. data/lib/when_exe/region/islamic.rb +105 -0
  119. data/lib/when_exe/region/japanese.rb +851 -0
  120. data/lib/when_exe/region/japanese_notes.rb +964 -0
  121. data/lib/when_exe/region/japanese_residues.rb +1149 -0
  122. data/lib/when_exe/region/javanese.rb +228 -0
  123. data/lib/when_exe/region/jewish.rb +127 -0
  124. data/lib/when_exe/region/korean.rb +267 -0
  125. data/lib/when_exe/region/m17n.rb +115 -0
  126. data/lib/when_exe/region/martian.rb +215 -0
  127. data/lib/when_exe/region/mayan.rb +122 -0
  128. data/lib/when_exe/region/moon.rb +333 -0
  129. data/lib/when_exe/region/nihon_shoki.rb +73 -0
  130. data/lib/when_exe/region/planets.rb +585 -0
  131. data/lib/when_exe/region/pope.rb +298 -0
  132. data/lib/when_exe/region/residue.rb +229 -0
  133. data/lib/when_exe/region/roman.rb +325 -0
  134. data/lib/when_exe/region/ryukyu.rb +98 -0
  135. data/lib/when_exe/region/shire.rb +254 -0
  136. data/lib/when_exe/region/sun.rb +210 -0
  137. data/lib/when_exe/region/thai.rb +227 -0
  138. data/lib/when_exe/region/tibetan.rb +233 -0
  139. data/lib/when_exe/region/v50.rb +111 -0
  140. data/lib/when_exe/region/vietnamese.rb +173 -0
  141. data/lib/when_exe/region/world.rb +197 -0
  142. data/lib/when_exe/timestandard.rb +547 -0
  143. data/lib/when_exe/tmduration.rb +330 -330
  144. data/lib/when_exe/tmobjects.rb +1295 -0
  145. data/lib/when_exe/tmposition.rb +1955 -0
  146. data/lib/when_exe/tmreference.rb +1547 -0
  147. data/lib/when_exe/version.rb +10 -3
  148. data/link_to_online_documents +4 -0
  149. data/test/examples/JapanHolidays.ics +456 -0
  150. data/test/examples/Millennium.ics +17 -0
  151. data/test/examples/NewYork.ics +61 -0
  152. data/test/examples/Residue.m17n +135 -0
  153. data/test/examples/Spatial.m17n +179 -0
  154. data/test/examples/Terms.m17n +39 -0
  155. data/test/examples/Test.ics +53 -0
  156. data/test/examples/USA-DST.ics +61 -0
  157. data/test/examples/geometric_complex.rb +41 -0
  158. data/test/examples/sample.xml +14 -0
  159. data/test/examples/today.rb +61 -0
  160. data/test/test.rb +54 -19
  161. data/test/test.rb.config +1 -0
  162. data/test/test/basictypes.rb +368 -0
  163. data/test/test/calendartypes.rb +57 -0
  164. data/test/test/coordinates.rb +380 -0
  165. data/test/test/ephemeris.rb +127 -0
  166. data/test/test/googlecalendar.rb +167 -0
  167. data/test/test/icalendar.rb +848 -0
  168. data/test/test/inspect.rb +115 -0
  169. data/test/test/parts.rb +480 -0
  170. data/test/test/region/chinese.rb +161 -0
  171. data/test/test/region/french.rb +33 -0
  172. data/test/test/region/geologicalage.rb +14 -0
  173. data/test/test/region/indian.rb +55 -0
  174. data/test/test/region/iran.rb +54 -0
  175. data/test/test/region/islamic.rb +18 -0
  176. data/test/test/region/japanese.rb +62 -0
  177. data/test/test/region/jewish.rb +61 -0
  178. data/test/test/region/m17n.rb +181 -0
  179. data/test/test/region/mayan.rb +78 -0
  180. data/test/test/region/moon.rb +14 -0
  181. data/test/test/region/planets.rb +14 -0
  182. data/test/test/region/residue.rb +123 -0
  183. data/test/test/region/sun.rb +14 -0
  184. data/test/test/region/thai.rb +94 -0
  185. data/test/test/region/tibetan.rb +30 -0
  186. data/test/test/tmobjects.rb +356 -57
  187. data/test/test/tmposition.rb +237 -0
  188. data/test/test/tmreference.rb +95 -0
  189. data/when_exe.gemspec +2 -2
  190. metadata +187 -7
  191. data/doc/COPYING +0 -31
  192. data/doc/COPYING.ja +0 -25
  193. data/doc/document_url +0 -1
@@ -0,0 +1,140 @@
1
+ # -*- coding: utf-8 -*-
2
+ =begin
3
+ Copyright (C) 2012 Takashi SUGA
4
+
5
+ You may use and/or modify this file according to the license described in the LICENSE.txt file included in this archive.
6
+ =end
7
+
8
+ require 'gcalapi'
9
+
10
+ #
11
+ # gcalapi の GoogleCalendar モジュールへの機能追加
12
+ #
13
+ module GoogleCalendar
14
+
15
+ #
16
+ # gcalapi の GoogleCalendar::Event クラスへの機能追加
17
+ #
18
+ class Event
19
+ # 繰り返しイベントの定義
20
+ # @return [Array<String>]
21
+ attr_accessor :recurrence
22
+
23
+ Event::ATTRIBUTES_MAP["recurrence"] = { "element" => "gd:recurrence"}
24
+
25
+ # @private
26
+ alias :_instance_to_xml :instance_to_xml
27
+
28
+ # @private
29
+ def instance_to_xml
30
+ _instance_to_xml
31
+ @xml.root.elements[recurrence ? "gd:when" : "gd:recurrence"].remove
32
+ end
33
+
34
+ # When::V::Event オブジェクトへの変換
35
+ #
36
+ # @return [When::V::Event]
37
+ #
38
+ def to_vevent
39
+ options = {'summary'=>@title, 'description'=>@desc, 'location'=>@where}
40
+ if @recurrence
41
+ @recurrence.each_line do |line|
42
+ options[$1.downcase] ||= $2 if line =~ /^(DTSTART|DTEND|RRULE).(.+)$/i
43
+ end
44
+ else
45
+ options.update({'dtstart'=>When.when?(@st), 'dtend'=>When.when?(@en)})
46
+ end
47
+ When::V::Event.new(options)
48
+ end
49
+ alias :vevent :to_vevent
50
+
51
+ # イベントのステータス
52
+ #
53
+ # @return [String]
54
+ # [ 'confirmed' - 有効 ]
55
+ # [ 'canceled' - 無効 ]
56
+ #
57
+ def event_status
58
+ @xml.root.elements['gd:eventStatus'].attributes['value'][/[^.]+$/]
59
+ end
60
+ end
61
+
62
+ #
63
+ # gcalapi の GoogleCalendar::Calendar クラスへの機能追加
64
+ #
65
+ class Calendar
66
+ # イベントを順に取り出す enumerator
67
+ #
68
+ # @param [Hash] conditions
69
+ # @option conditions [String, When::TM::TemporalPosition] 'start-min' 最初のイベント開始日時(省略時は現在時刻)
70
+ # @option conditions [String, When::TM::TemporalPosition] 'start-max' 最後のイベント開始日時(省略時は制限なし)
71
+ # @option conditions [String] 'sortorder'
72
+ # [ 'a' - asend(省略時) ]
73
+ # [ 'd' - desend ]
74
+ #
75
+ # @return [Enumerator]
76
+ #
77
+ def enum_for(conditions={})
78
+ conditions['start-min'] ||= When.now
79
+ conditions['sortorder'] ||= 'a'
80
+ conditions['orderby'] = 'starttime'
81
+ direction = (conditions['sortorder'] == 'd') ? :reverse : :forward
82
+ first = When.when?(conditions['start-min'])
83
+ conditions['start-min'] = (When.Calendar('Gregorian') ^ first).to_s
84
+ if conditions['start-max']
85
+ last = When.when?(conditions['start-max'])
86
+ inner_args = [first...last]
87
+ conditions['start-max'] = (When.Calendar('Gregorian') ^ last).to_s
88
+ else
89
+ inner_args = [first, direction]
90
+ end
91
+
92
+ confirmed = []
93
+ canceled = []
94
+ self.events(conditions).each do |event|
95
+ case event.event_status
96
+ when 'canceled' ; canceled << event
97
+ else ; confirmed << event
98
+ end
99
+ end
100
+ outer_args = [first, direction]
101
+ outer_args << {:exevent => canceled.map {|event| event.to_vevent}} if canceled.length > 0
102
+
103
+ When::Parts::Enumerator::Integrated.new(self, confirmed.map {|event|
104
+ event.to_vevent.enum_for(*inner_args)
105
+ }, *outer_args)
106
+ end
107
+ alias :to_enum :enum_for
108
+ end
109
+ end
110
+
111
+ module When::V
112
+ class Event
113
+
114
+ # GoogleCalendar::Event オブジェクトへの変換
115
+ #
116
+ # @param [GoogleCalendar::Calendar] cal
117
+ #
118
+ # @return [GoogleCalendar::Event]
119
+ #
120
+ def to_gcalevent(cal)
121
+ event = cal.create_event
122
+ event.title = summary if respond_to?(:summary)
123
+ event.desc = description if respond_to?(:description)
124
+ event.where = location if respond_to?(:location)
125
+ if rrule.size == 0
126
+ event.st = dtstart.to_time
127
+ event.en = dtend.to_time
128
+ else
129
+ event.recurrence =
130
+ (['RRULE:' + property['rrule'][0].object] +
131
+ ['dtstart', 'dtend'].map {|key|
132
+ value = property[key].attribute['.']
133
+ key.upcase + (value =~ /=/ ? ';' : ':') + value
134
+ }).join("\n") + "\n"
135
+ end
136
+ event
137
+ end
138
+ alias :gcalevent :to_gcalevent
139
+ end
140
+ end
@@ -0,0 +1,1587 @@
1
+ # -*- coding: utf-8 -*-
2
+ =begin
3
+ Copyright (C) 2011-2013 Takashi SUGA
4
+
5
+ You may use and/or modify this file according to the license described in the LICENSE.txt file included in this archive.
6
+ =end
7
+
8
+ #
9
+ # An implementation of RFC 5545 - iCalendar ( http://tools.ietf.org/html/rfc5545 )
10
+ #
11
+ # == Extensions of RFC 5545
12
+ # === Multi Calendar
13
+ # * CALSCALE Property can specify calendars other than GREGORIAN
14
+ # The value of CALSCALE Property is capitalized.
15
+ # * Date and Time Representation of DTSTART Property
16
+ # When.exe Standard Representation is available except ','. (',' cannot be used as a decimal mark)
17
+ # Calendar can be specified by a notation '^^calnedar' or '^calnedar'.
18
+ # (same as epoch of When::TM::CalendarEra)
19
+ # === The extension of RRULE property
20
+ # * FREQ=duration
21
+ # When.exe Standard Representation により duration を指定できる。
22
+ # ただし、この指定は BYxxx とは共存できない。
23
+ # * BYHOUR=h(,..)
24
+ # h に When.exe Standard Representation を使用できる
25
+ # example: 'BYHOUR=2,3'
26
+ # 夏時間から標準時間への切り替え時に 夏時間の2時,標準時間の3時 を生成
27
+ # example: 'BYHOUR=2,2=,3'
28
+ # 夏時間から標準時間への切り替え時に 夏時間の2時,標準時間の2時,標準時間の3時 を生成
29
+ # 他のソフトウェアとの互換性を損なう可能性があるが、本ライブラリは両方の動作の違いを
30
+ # 記述できる必要があると判断。
31
+ # * BYDAY/c=n*e±s(,..)
32
+ # c で指定したWhen::CalendarTypes::CalendarNoteオブジェクトのイベントeのうち、
33
+ # n番目のものについて、その±s日目
34
+ # デフォルトは n=all, s=0, cとeは省略不可(eはcのメソッドとして定義されている必要がある)
35
+ # example: 'BYDAY/Christian=easter-2' は、キリスト教の聖金曜日
36
+ # example: 'BYDAY/SolarTerms=term180' は、秋分日(太陽黄経が180度になる日)
37
+ # * BYDAY/d=n*m±s(,..);DAYST=b
38
+ # ユリウス日をdで除した余りがmになる日のうち、n番目のものについて、その±s日目
39
+ # m は b を基点として計算する
40
+ # デフォルトは n=all, s=0, b=0, dとmは省略不可
41
+ # /d がない場合、次項のBYWEEKDAYとして扱い、RFC 5545に対する互換性を確保する
42
+ # * BYWEEKDAY=n*m±s(,..);WKST=b
43
+ # ユリウス日を7で除した余りがmになる日のうち、n番目のものについて、その±s日目
44
+ # m は b を基点として計算する(mとbはともに文字列'MO','TU','WE','TH','FR','SA','SU'で指定)
45
+ # デフォルトは n=all, s=0, b=MO, mは省略不可
46
+ # * BYYEAR/d=n*m±s(,..);YEARST=b
47
+ # 通年をdで除した余りがmになる年のうち、n番目のものについて、その±s年目
48
+ # m は b を基点として計算する
49
+ # デフォルトは n=all, s=0, b=4, dとmは省略不可
50
+ # 通年の意味は暦法に依存する
51
+ # === icd 形式の多言語対応
52
+ # * NAMESPACE Property を追加
53
+ # * LOCALE Property を追加
54
+ # * SUMMARY Property で[...]表現を多言語対応文字列として解釈する
55
+ # === Content Lines
56
+ # RFC 5545 3.1 Content Lines では、記述が75文字を超える場合、改行してインデントする。
57
+ # 本ライブラリでは文字数によらず、BEGIN:の次の行を基準にして、より深いインデントがある場合は、
58
+ # 論理的1行を物理的に分割したものとみなして解釈している。実質的には上位互換と判断している
59
+ #
60
+ # == Limitations from RFC 5545
61
+ # * VALARM
62
+ # VENENT や VTODO に包含されている VALARM は無視される。
63
+ # 包含元のイベントからの差分時刻での計算に対応していないため。
64
+ # * RRULE
65
+ # BYYEARDAY, BYMONTHDAY, BYDAY が存在する場合 BYWEEKDAY の n と s は指定できない。
66
+ # BYYEARDAY, BYMONTHDAY, BYWEEKDAY が存在する場合 BYDAY の n と s は指定できない。
67
+ module When::V
68
+
69
+ # iCalendar を構成するクラス群の共通抽象クラス
70
+ #
71
+ # RFC 5545 のクラスは、有無のチェックを除いてProperty の扱いが共通なので、
72
+ # Property の扱いを、本クラスにまとめて記述している。
73
+ #
74
+ class Root < When::BasicTypes::Object
75
+
76
+ Properties = [[],[],[],[],[]]
77
+ Classes = nil
78
+ DefaultUnique = ['calscale', 'namespace', 'locale']
79
+ DefaultOptional = ['x_prop', 'iana_prop']
80
+ AwareProperties = DefaultUnique +
81
+ ['tzoffsetfrom', 'tzoffsetto', 'tzname',
82
+ 'dtstart', 'dtend', 'due',
83
+ 'repeat', 'duration',
84
+ 'rrule', 'rdate', 'exdate', 'exevent',
85
+ 'summary', 'freebusy']
86
+
87
+ #
88
+ # iCalendar クラス群の属性
89
+ # @return [Hash] { String => When::Parts::Resource::Element }
90
+ #
91
+ attr_reader :property
92
+
93
+ #
94
+ # デフォルトのWhen::TM::Calendar
95
+ # @return [When::TM::Calendar]
96
+ #
97
+ # @note
98
+ # RFC 5545 では、'GREGORIAN' のみ指定可能としている。
99
+ # CALSCALE Property 文字列を capitalize したものに、
100
+ # prefix _c:(=http://hosi.org/When/CalendarTypes/)を補い
101
+ # When::TM::Calendar オブジェクトの定義を取得する。
102
+ #
103
+ attr_reader :calscale
104
+
105
+ # 指定の日時を含むか?
106
+ #
107
+ # @param [When::TM::TemporalPosition] date
108
+ #
109
+ # @return [Boolean]
110
+ # [ true - 含む ]
111
+ # [ false - 含まない ]
112
+ #
113
+ def include?(date)
114
+ first = enum_for(date).next
115
+ return first.include?(date) if first.kind_of?(When::Parts::GeometricComplex)
116
+ return first == date if first.precision <= date.precision
117
+ return false
118
+ end
119
+
120
+ # イテレータの生成
121
+ # @private
122
+ def _enumerator(*args)
123
+ options = When::Parts::Enumerator._options(args)
124
+ args << options
125
+ exdate = options[:exdate]
126
+
127
+ enumerators = _enumerator_list(args)
128
+ raise ArgumentError, "No enumerator exists" if (enumerators.length==0)
129
+
130
+ # Enumerator の生成
131
+ return enumerators[0] if (enumerators.length==1 && exdate.node.size==0)
132
+ options[:exdate] = exdate
133
+ return When::Parts::Enumerator::Integrated.new(self, enumerators, *args)
134
+ end
135
+ alias :to_enum :_enumerator
136
+ alias :enum_for :_enumerator
137
+
138
+ private
139
+
140
+ # オブジェクトの生成
141
+ def initialize(*args)
142
+ # option の取得
143
+ options = args[-1].kind_of?(Hash) ? args.pop.dup : {}
144
+
145
+ # 包含関係
146
+ @_pool = {}
147
+ @_pool['..'] = options['..']
148
+
149
+ # parsed 部の属性化
150
+ _parsed(options)
151
+
152
+ # 属性の存在チェック & 設定
153
+ _initialize_attributes(_attribute_appearance(self.class::Properties)) # .const_get(:Properties)))
154
+
155
+ # 包含オブジェクトの生成
156
+ _child(options, self.class::Classes) #.const_get(:Classes))
157
+ end
158
+
159
+ # parsed 部の属性化
160
+ def _parsed(options)
161
+ @property = {}
162
+ if options['.']
163
+ options['.'].each do |v|
164
+ v = When::Parts::Resource._parse(v)
165
+ next unless (v.kind_of?(Element))
166
+ key = v.predicate
167
+ case @property[key]
168
+ when nil ; @property[key] = v
169
+ when Array ; @property[key] << v
170
+ else ; @property[key] = [@property[key], v]
171
+ end
172
+ end
173
+ else
174
+ options.each_pair do |key, value|
175
+ @property[key] = Element.new(key, value) if key.kind_of?(String)
176
+ end
177
+ @property['dtstamp'] ||= Element.new('dtstamp',
178
+ When.now.to_s.gsub(/[-:]/,'')) if self.class::Properties[0].index('dtstamp')
179
+ @property['uid'] ||= Element.new('uid',
180
+ @property['dtstamp'].object + '-auto') if self.class::Properties[0].index('uid')
181
+ end
182
+ @property.each_key do |key|
183
+ next if respond_to?(key)
184
+ instance_eval %Q{
185
+ def #{key}
186
+ @property['#{key}'].object
187
+ end
188
+ }
189
+ end
190
+ end
191
+
192
+ # 包含オブジェクトの生成
193
+ def _child(options, classes)
194
+ @child = []
195
+ opt = options.dup
196
+ opt['..'] = self
197
+ if options['.']
198
+ options['.'].each do |v|
199
+ next unless (v.kind_of?(Array) && v[0].kind_of?(Class))
200
+ raise ArgumentError, "The #{self.class} cannot include #{v[0]}" if (classes && !classes.index(v[0]))
201
+ opt['.'] = v
202
+ obj = v[0].new(opt)
203
+ @child << obj
204
+ @_pool[obj.label.to_s] = obj
205
+ end
206
+ else
207
+ options.each_pair do |key, value|
208
+ next unless key.kind_of?(Class)
209
+ obj = key.new(opt.merge(value))
210
+ @child << obj
211
+ @_pool[obj.label.to_s] = obj
212
+ end
213
+ end
214
+ end
215
+
216
+ # attribute の出現数のチェック
217
+ def _attribute_appearance(attributes)
218
+
219
+ require_unique, require, unique, almost_unique, optional = attributes
220
+
221
+ # REQUIRED but MUST NOT occur more than once
222
+ require_unique.each do |key|
223
+ unless @property[key].kind_of?(When::Parts::Resource::Element)
224
+ raise ArgumentError, "The #{key.upcase.gsub(/_/,'-')} is REQUIRED but MUST NOT occur more than once"
225
+ end
226
+ end
227
+
228
+ # REQUIRED and MAY occur more than once
229
+ require.each do |key|
230
+ unless @property[key]
231
+ raise ArgumentError, "The #{key.upcase.gsub(/_/,'-')} is REQUIRED and MAY occur more than once"
232
+ end
233
+ @property[key] = [@property[key]] if (@property[key].kind_of?(When::Parts::Resource::Element))
234
+ end
235
+
236
+ # OPTIONAL but MUST NOT occur more than once
237
+ (unique + DefaultUnique).each do |key|
238
+ if (@property[key].kind_of?(Array))
239
+ raise ArgumentError, "The #{key.upcase.gsub(/_/,'-')} is OPTIONAL but MUST NOT occur more than once"
240
+ end
241
+ end
242
+
243
+ # OPTIONAL but SHOULD NOT occur more than once
244
+ almost_unique.each do |key|
245
+ if (@property[key].kind_of?(Array))
246
+ raise ArgumentError, "The #{key.upcase.gsub(/_/,'-')} is OPTIONAL but SHOULD NOT occur more than once"
247
+ end
248
+ end
249
+
250
+ # OPTIONAL and MAY occur more than once
251
+ (optional + DefaultOptional).each do |key|
252
+ @property[key] = [@property[key]] if (@property[key].kind_of?(When::Parts::Resource::Element))
253
+ end
254
+
255
+ # Other Properties
256
+ allowed_attributes = require_unique + require + unique + almost_unique + optional + DefaultUnique + DefaultOptional
257
+ #@property.each_key do |key|
258
+ # raise ArgumentError, "The #{key.upcase.gsub(/_/,'-')} is not allowed" unless allowed_attributes.index(key)
259
+ #end
260
+
261
+ return (AwareProperties & allowed_attributes)
262
+ end
263
+
264
+ # attribute の設定
265
+ def _initialize_attributes(aware)
266
+
267
+ # calscale の登録
268
+ @calscale = @_pool['..'].calscale if @_pool['..'].respond_to?(:calscale)
269
+ @calscale = @property['calscale'].object if @property['calscale']
270
+ @calscale = When.Resource((@calscale||'GREGORIAN').capitalize, '_c:') unless (@calscale.kind_of?(When::TM::Calendar))
271
+
272
+ # namespace の登録
273
+ @namespace = @_pool['..'].respond_to?(:namespace) ? @_pool['..'].namespace : {}
274
+ @namespace.update(When::Parts::Locale._namespace(@property['namespace'].object)) if @property['namespace']
275
+
276
+ # locale の登録
277
+ @locale = @_pool['..'].respond_to?(:locale) ? @_pool['..'].locale : []
278
+ @locale = When::Parts::Locale._locale(@property['locale'].object) if @property['locale']
279
+
280
+ # tzoffsetfrom の登録
281
+ if @property['tzoffsetfrom']
282
+ object = @property['tzoffsetfrom'].object
283
+ new_zone = object.to_s # See RFC5545 page.66
284
+ @tzoffsetfrom = When.Clock(object).dup
285
+ @tzoffsetfrom.tz_prop = self
286
+ else
287
+ new_zone = ''
288
+ end
289
+
290
+ # tzoffsetto, tzname の登録
291
+ if @property['tzoffsetto']
292
+ clock = When.Clock(@property['tzoffsetto'].object).dup
293
+ clock.tz_prop = self
294
+ @tzname = []
295
+ (@property['tzname']||[]).each do |tzname|
296
+ tz = tzname.object # ↓このチェックは不十分?
297
+ When::TM::Clock.synchronize do
298
+ raise ArgumentError, "Conflict tzname: #{tz}" if When::TM::Clock[tz] &&
299
+ When::TM::Clock[tz].universal_time != clock.universal_time
300
+ When::TM::Clock[tz] = clock
301
+ end
302
+ @tzname << tz
303
+ end
304
+ @tzoffsetto = clock
305
+ @tzname = @tzname[0] if (@tzname.length <= 1)
306
+ end
307
+
308
+ # due の登録
309
+ date_options = {:frame=>@calscale}
310
+ if @property['due']
311
+ @due = When.when?(@property['due'].attribute['.'] ? @property['due'].attribute['.'] + new_zone :
312
+ @property['due'].object, date_options)
313
+ end
314
+
315
+ # dtstart の登録
316
+ if @property['dtstart']
317
+ @dtstart = When.when?(@property['dtstart'].attribute['.'] ? @property['dtstart'].attribute['.'] + new_zone :
318
+ @property['dtstart'].object, date_options)
319
+ @dtstart.clk_time.frame = @tzoffsetfrom unless (new_zone == '') # See RFC5545 page.66
320
+
321
+ @first_occurrence = "Include"
322
+ else
323
+ @dtstart = When.now(@due||{})
324
+ @first_occurrence = "Don't care"
325
+ end
326
+
327
+ # dtend の登録
328
+ if @property['dtend']
329
+ @dtend = When.when?(@property['dtend'].attribute['.'] ? @property['dtend'].attribute['.'] + new_zone :
330
+ @property['dtend'].object, date_options)
331
+ end
332
+
333
+ # repeat の登録
334
+ if aware.index('repeat')
335
+ raise ArgumentError, "The DURATION MUST occur" if ( @property['repeat'] && !@property['duration'])
336
+ raise ArgumentError, "The REPEAT MUST occur" if (!@property['repeat'] && @property['duration'])
337
+ if @property['repeat']
338
+ @repeat = @property['repeat'].object.to_i
339
+ end
340
+ end
341
+
342
+ # duration の登録
343
+ if aware.index('duration')
344
+ if @property['duration']
345
+ raise ArgumentError, "The DURATION should appear with DTSTART" unless (@dtstart||@repeat)
346
+ raise ArgumentError, "The DTEND and DURATION are exclusive" if (@dtend)
347
+ raise ArgumentError, "The DUE and DURATION are exclusive" if (@due)
348
+ duration = @property['duration'].object
349
+ duration = When.Duration(duration) unless duration.kind_of?(Numeric)
350
+ elsif @dtend
351
+ duration = When::TM::IntervalLength.difference(@dtend, @dtstart)
352
+ end
353
+ duration_precision = When::Coordinates::PERIOD[duration.to_s]
354
+ @duration = duration unless (duration_precision && @dtstart.precision <= duration_precision)
355
+ end
356
+
357
+ # rrule の登録
358
+ if aware.index('rrule')
359
+ @rrule = []
360
+ @rrule = @property['rrule'].map {|v| Event::Enumerator._decode_rule(v.object, @dtstart, new_zone)} if @property['rrule']
361
+ end
362
+
363
+ # rdate の登録
364
+ if aware.index('rdate')
365
+ @rdate = []
366
+ @exevent = []
367
+ @exdate = When::Parts::GeometricComplex.new()
368
+ if (@property['rdate'])
369
+ @rdate = @property['rdate'].inject([]) do |sum, v|
370
+ if When::Parts::Resource::Element === v
371
+ if new_zone == ''
372
+ sum += When.when?(v.attribute['.'].split(/,/), date_options)
373
+ else
374
+ sum += When.when?((v.attribute['.'].split(/,/).map {|d| d + new_zone}), date_options)
375
+ end
376
+ else
377
+ sum << v
378
+ end
379
+ end
380
+ end
381
+ unless new_zone == ''
382
+ @rdate.each do |date|
383
+ date.clk_time.frame = @tzoffsetto
384
+ end
385
+ end
386
+ end
387
+
388
+ # exdate の登録
389
+ if @property['exdate']
390
+ dates = @property['exdate'].inject([]) do |sum, v|
391
+ if When::Parts::Resource::Element === v
392
+ sum += When.when?(v.attribute['.'].split(/,/), date_options)
393
+ else
394
+ sum << v
395
+ end
396
+ end
397
+ dates.each do |date|
398
+ @exdate |= date
399
+ end
400
+ end
401
+
402
+ # exevent の登録
403
+ if @property['exevent']
404
+ @exevent += @property['exevent'].object.split(/,/)
405
+ end
406
+
407
+ # summary の登録
408
+ term_options = {'namespace'=>@namespace, 'locale'=>@locale}
409
+ if @property['summary']
410
+ text = @property['summary'].object
411
+ @summary = (text =~ /^\[/) ? m17n(text, nil, nil, term_options) : text
412
+ end
413
+
414
+ # freebusy の登録
415
+ if aware.index('freebusy')
416
+ @freebusy = []
417
+ if (@property['freebusy'])
418
+ @freebusy = @property['freebusy'].inject([]) do |sum, v|
419
+ if When::Parts::Resource::Element === v
420
+ sum += When.when?((v.attribute['.']||v.object).split(/,/), date_options)
421
+ else
422
+ sum << v
423
+ end
424
+ end
425
+ end
426
+ @rdate = @freebusy
427
+ @rrule = []
428
+ end
429
+ end
430
+ end
431
+
432
+ # ひとつの ics 形式ファイルをまとめて保持する
433
+ #
434
+ # BEGIN:VCALENADR...END:VCALENDAR のブロックに対応
435
+ #
436
+ class Calendar < Root
437
+
438
+ Properties= [['prodid', 'version'], [],
439
+ ['method'], [], []]
440
+ Classes = nil
441
+
442
+ attr_accessor :child
443
+ protected :child=
444
+
445
+ # When::V::Event の検索
446
+ #
447
+ # @param [Hash] keys { key => value }
448
+ #
449
+ # @return [When::V::Calendar] key で指定する Property の値が value に一致する When::V::Event のみを持つ self の複製を作る
450
+ # (一致の判断は演算子 === による)
451
+ #
452
+ def intersection(keys={})
453
+ copy = self.dup
454
+ copy.child = @child.select {|ev|
455
+ if ev.kind_of?(Event)
456
+ keys.each_pair do |key, value|
457
+ value = /#{value}/ if value.kind_of?(String)
458
+ break unless (value === ev.property[key].object)
459
+ end
460
+ else
461
+ true
462
+ end
463
+ }
464
+ return copy
465
+ end
466
+
467
+ # @private
468
+ def _enumerator_list(args)
469
+ (@child.reject {|el| !el.kind_of?(Event)}).inject([]) { |sum, ev|
470
+ sum += ev._enumerator_list(args)
471
+ }
472
+ end
473
+ end
474
+
475
+ # Eventを定義する
476
+ #
477
+ # BEGIN:VEVENT...END:VEVENT のブロックに対応
478
+ #
479
+ class Event < Root
480
+
481
+ Properties= [['dtstamp', 'uid', 'dtstart'], [],
482
+ ['class', 'created', 'description', 'geo',
483
+ 'last_modified', 'location', 'organizer', 'priority',
484
+ 'seq', 'status', 'summary', 'transp',
485
+ 'url', 'recurid', 'dtend', 'duration'], [],
486
+ ['rrule',
487
+ 'attach', 'attendee', 'categories', 'comment', 'contact',
488
+ 'exdate', 'exevent', 'rstatus', 'related',
489
+ 'resources', 'rdate']]
490
+ # Classes = [V::Root, V::Alarm]
491
+
492
+ class << self
493
+ include When::Parts::Resource::Pool
494
+
495
+ # 最大打ち切り時間
496
+ # @return [When::TM::IntervalLength]
497
+ attr_reader :default_until
498
+
499
+ # When::V::Event Class のグローバルな設定を行う
500
+ #
501
+ # @param [When::TM::IntervalLength] default_until
502
+ #
503
+ # @note
504
+ # RRULE の条件が成立しない場合に無限ループにおちいることを避けるため
505
+ # 他に指定がなくとも、計算を打ち切るようにしている。その打ち切り時間
506
+ # (When.now + default_until)を本メソッドで指定している。
507
+ # default_until の指定がない場合、default_until は 1000年と解釈する。
508
+ #
509
+ def _setup_(default_until=nil)
510
+ @_lock_ = Mutex.new if When.multi_thread
511
+ @_lock_.lock if @_lock_
512
+ @_pool = {}
513
+ @default_until = default_until
514
+ @_lock_.unlock if @_lock_
515
+ end
516
+ end
517
+
518
+ # RRULE Property
519
+ #
520
+ # @return [Hash]
521
+ #
522
+ # @note
523
+ # iCalendar の RRULE を Hash に展開したものを保持している。
524
+ # RRULE は、年のサイクルや7日以外の日のサイクルおよび夏時間の切り替えを
525
+ # 扱えるように RFC 5545 から拡張されている。
526
+ #
527
+ attr_reader :rrule
528
+
529
+ # DTSTART Property
530
+ #
531
+ # @return [When::TM::TemporalPosition, When::Parts::GeometricComplex]
532
+ #
533
+ attr_reader :dtstart
534
+
535
+ # DTEND Property
536
+ #
537
+ # @return [When::TM::TemporalPosition, When::Parts::GeometricComplex]
538
+ #
539
+ attr_reader :dtend
540
+
541
+ # DURATION Property
542
+ #
543
+ # @return [When::TM::Duration]
544
+ #
545
+ # @note
546
+ # DTSTART Property が保持する When::TM::TemporalPosition の分解能で識別できない
547
+ # 時間差はイベント継続中とみなすので、例えば分解能が DAY の場合、DURATION Porperty
548
+ # に When.Duration('P1D')と指定する必要はない。
549
+ # DTEND Property が指定された場合、DURATION Property に変換して保持する。
550
+ #
551
+ attr_reader :duration
552
+
553
+ # EXDATE Property
554
+ #
555
+ # @return [When::Parts::GeometricComplex]
556
+ #
557
+ attr_reader :exdate
558
+
559
+ # RDATE Property
560
+ #
561
+ # @return [Array<When::TM::TemporalPosition, When::Parts::GeometricComplex>]
562
+ #
563
+ # @note
564
+ # RRULE の COUNT が指定されている場合、後で途中の系列を抜き出すような指定をされても
565
+ # よいように、Enumerator 生成時にCOUNT分の計算をして RDATE Property に登録する。
566
+ # このため、COUNTに大きな値を指定すると、Enumerator 生成に予想外の時間がかかることが
567
+ # ある。
568
+ #
569
+ attr_reader :rdate
570
+
571
+ # DTSTART Property を first occurrence とするか
572
+ #
573
+ # @return [String]
574
+ # [ 'Include' - first occurrence とする ]
575
+ # [ 'Exclude' - first occurrence しない ]
576
+ # [ それ以外 - RRULE により該当する場合に first occurrence とする ]
577
+ #
578
+ # @note
579
+ # RFC 5545 では 'Include' となっているが、それ以外の振る舞いが可能なように拡張。
580
+ #
581
+ attr_reader :first_occurrence # See RFC 5545 [Page 41] 3rd paragraph
582
+
583
+ # ユニーク識別名 - UID Property をユニーク識別名とする。
584
+ #
585
+ # @return [String]
586
+ #
587
+ def label
588
+ @property['uid'].object
589
+ end
590
+
591
+ # 最後のイベント
592
+ #
593
+ # @return [When::TM::TemporalPosition, When::Parts::GeometricComplex]
594
+ #
595
+ # @note
596
+ # 無限に続く可能性がある場合、When::TimeValue::Max(+Infinity)
597
+ #
598
+ def dtstop
599
+ return @dtstop if (@dtstop)
600
+
601
+ @dtstop = @dtstart
602
+ @rdate.each do |date|
603
+ @dtstop = date if (date > @dtstop)
604
+ end
605
+ @rrule.each do |rrule|
606
+ unless (rrule['UNTIL'])
607
+ @dtstop = When::TimeValue::Max
608
+ break
609
+ end
610
+ @dtstop = rrule['UNTIL'] if (rrule['UNTIL'] > @dtstop)
611
+ end
612
+ return @dtstop
613
+ end
614
+
615
+ # 順次実行
616
+ #
617
+ # @overload initialize()
618
+ #
619
+ # @overload initialize(range, count_limit=nil)
620
+ # @param [Range, When::Parts::GeometricComplex] range 始点-range.first, 終点-range.last
621
+ # @param [Integer] count_limit 繰り返し回数(デフォルトは指定なし)
622
+ #
623
+ # @overload initialize(first, direction, count_limit)
624
+ # @param [When::TM::TemporalPosition] first 始点
625
+ # @param [Symbol] direction :forward - 昇順, :reverse - 降順
626
+ # @param [Integer] count_limit 繰り返し回数(デフォルトは指定なし)
627
+ #
628
+ # @return [Enumerator]
629
+ #
630
+ # @note block が与えられている場合、yield する。
631
+ #
632
+ def each(*args, &block)
633
+ if args.length > 0
634
+ super
635
+ else
636
+ super(@dtstart, &block)
637
+ end
638
+ end
639
+
640
+ # @private
641
+ def _enumerator_list(args)
642
+ options = args[-1]
643
+ rdate = @rdate.dup
644
+ options[:exdate] = @exdate.dup if @exdate
645
+ options[:exevent] = @exevent.map {|v| self[v]} if @exevent
646
+ case (options['1st'] || @first_occurrence).capitalize
647
+ when 'Include' ; rdate.unshift(@duration ? When::Parts::GeometricComplex.new(@dtstart, @duration) : @dtstart)
648
+ when 'Exclude' ; options[:exdate] |= @dtstart
649
+ end
650
+
651
+ # 配下の Enumerator の初期化
652
+ enumerators = []
653
+ @rrule.each do |rrule|
654
+ if @due && !(rrule['UNTIL'] && rrule['UNTIL'] <= @due)
655
+ rrule = rrule.dup
656
+ rrule['UNTIL'] = @due
657
+ end
658
+ if rrule['COUNT']
659
+ Event::Enumerator.new(self, rrule, @dtstart, @duration, @dtstart, options).each {|date| rdate << date }
660
+ else
661
+ enumerators << Event::Enumerator.new(self, rrule, @dtstart, @duration, *args)
662
+ end
663
+ end
664
+
665
+ enumerators.unshift(When::Parts::Enumerator::Array.new(self, rdate, *args[1..-1])) if (rdate.length > 0)
666
+
667
+ return enumerators
668
+ end
669
+ end
670
+
671
+ # Alarm を定義する
672
+ #
673
+ # BEGIN:VALARM...END:VALARM のブロックに対応
674
+ #
675
+ class Alarm < Event
676
+
677
+ # ユニーク識別名 - ACTION Property をユニーク識別名とする。
678
+ # @return [String]
679
+ def label
680
+ @property['action'].object # TODO
681
+ end
682
+
683
+ # @private
684
+ def initialize(options)
685
+ # 包含関係
686
+ @_pool = {}
687
+ @_pool['..'] = options['..']
688
+
689
+ # parsed 部の属性化
690
+ _parsed(options)
691
+
692
+ # 属性の存在チェック
693
+ case (@property['action'].kind_of?(When::Parts::Resource::Element) && @property['action'].object)
694
+ when 'AUDIO'
695
+ aware = _attribute_appearance([
696
+ ['action', 'trigger'], [],
697
+ ['duration', 'repeat', 'attach'], [], []])
698
+ when 'DISPLAY'
699
+ aware = _attribute_appearance([
700
+ ['action', 'description', 'trigger'], [],
701
+ ['duration', 'repeat'], [], []])
702
+ when 'EMAIL'
703
+ aware = _attribute_appearance([
704
+ ['action', 'description', 'trigger', 'summary'], ['attendee'],
705
+ ['duration', 'repeat'], [], ['attach']])
706
+ else
707
+ raise ArgumentError, "The ACTION is invalid"
708
+ end
709
+
710
+ # 属性の設定
711
+ _initialize_attributes(aware)
712
+
713
+ # 包含オブジェクトがないことの確認
714
+ _child(options, [])
715
+ end
716
+ end
717
+
718
+ # Todo を定義する
719
+ #
720
+ # BEGIN:VTODO...END:VTODO のブロックに対応
721
+ #
722
+ class Todo < Event
723
+ Properties= [['dtstamp', 'uid'], [],
724
+ ['class', 'completed', 'created', 'description',
725
+ 'dtstart', 'geo', 'last_modified', 'location', 'organizer',
726
+ 'percent', 'priority', 'recurid', 'seq', 'status',
727
+ 'summary', 'url'], ['due', 'duration'],
728
+ ['rrule',
729
+ 'attach', 'attendee', 'categories', 'comment', 'contact',
730
+ 'exdate', 'exevent', 'rstatus', 'related', 'resources',
731
+ 'rdate']]
732
+ Classes = [Alarm]
733
+ end
734
+
735
+ # Journal を定義する
736
+ #
737
+ # BEGIN:VJOURNAL...END:VJOURNAL のブロックに対応
738
+ #
739
+ class Journal < Event
740
+
741
+ Properties= [['dtstamp', 'uid'], [],
742
+ ['class', 'created', 'dtstart',
743
+ 'last_modified', 'organizer', 'recurid', 'seq',
744
+ 'status', 'summary', 'url'], [],
745
+ ['rrule',
746
+ 'attach', 'attendee', 'categories', 'comment', 'contact',
747
+ 'description', 'exdate', 'exevent', 'related', 'rdate',
748
+ 'rstatus']]
749
+ Classes = []
750
+ end
751
+
752
+ # Freebusy を定義する
753
+ #
754
+ # BEGIN:VFREEBUSY...END:VFREEBUSY のブロックに対応
755
+ #
756
+ class Freebusy < Event
757
+
758
+ Properties= [['dtstamp', 'uid'], [],
759
+ ['contact', 'dtstart', 'dtend',
760
+ 'organizer', 'url'], [],
761
+ ['attendee', 'comment', 'freebusy', 'rstatus']]
762
+ Classes = []
763
+
764
+ attr_reader :freebusy
765
+ end
766
+
767
+ #
768
+ # Timezone Property の共通抽象クラス
769
+ #
770
+ class TimezoneProperty < Event
771
+
772
+ Properties= [['dtstart', 'tzoffsetto', 'tzoffsetfrom'], [],
773
+ [], [], ['rrule',
774
+ 'comment', 'rdate', 'tzname']]
775
+ Classes = []
776
+
777
+ # TZNAME Property
778
+ #
779
+ # @return [Array<String(サブクラスであるWhen::BasicTypes::M17nでもよい)>]
780
+ #
781
+ # @note
782
+ # RFC 5545 は複数個の指定を許しているため、Array としている。
783
+ #
784
+ attr_reader :tzname
785
+
786
+ # TZOFFSETFROM Property
787
+ #
788
+ # @return [When::TM::Clock]
789
+ #
790
+ # @note
791
+ # イベント時刻前の時間帯
792
+ # 保持する When::TM::Clock の tz_prop は、本オブジェクトを参照している。
793
+ #
794
+ attr_reader :tzoffsetfrom
795
+
796
+ # TZOFFSETTO Property
797
+ #
798
+ # @return [When::TM::Clock]
799
+ #
800
+ # @note
801
+ # イベント時刻後の時間帯
802
+ # 保持する When::TM::Clock の tz_prop は、本オブジェクトを参照している。
803
+ #
804
+ attr_reader :tzoffsetto
805
+
806
+ # ユニーク識別名 - DTSTART Property をユニーク識別名とする
807
+ # @return [String]
808
+ def label
809
+ @property['dtstart'].attribute['.']
810
+ end
811
+ end
812
+
813
+ # 標準時間帯を定義する
814
+ #
815
+ # BEGIN:STANDARD...END:STANDARD のブロックに対応
816
+ #
817
+ class Standard < TimezoneProperty
818
+ end
819
+
820
+ # 夏時間帯を定義する
821
+ #
822
+ # BEGIN:DAYLIGHT...END:DAYLIGHT のブロックに対応
823
+ #
824
+ class Daylight < TimezoneProperty
825
+ end
826
+
827
+ # Timezone を定義する
828
+ #
829
+ # BEGIN:VTIMEZONE...END:VTIMEZONE のブロックに対応
830
+ #
831
+ class Timezone < Root
832
+
833
+ Properties= [['tzid'], [],
834
+ ['last_modified', 'tzurl', 'x_lic_location'], [], []]
835
+ Classes = [Standard, Daylight]
836
+
837
+ class << self; include When::Parts::Resource::Pool; end
838
+
839
+ # 標準時間帯の時計
840
+ # @return [When::TM::Clock]
841
+ attr_reader :standard
842
+
843
+ # 夏時間帯の時計
844
+ # @return [When::TM::Clock]
845
+ attr_reader :daylight
846
+
847
+ # 夏時間帯と標準時間帯の時間差
848
+ # @return [When::TM:IntervalLength]
849
+ attr_reader :difference
850
+
851
+ # ユニーク識別名 - TZID Property をユニーク識別名とする
852
+ # @return [String]
853
+ def label
854
+ @property['tzid'].object
855
+ end
856
+
857
+ # 同一の時間帯を用いた期間
858
+ #
859
+ # @param [When::TM::TemporalPositionに変換可能な型] current_date 期間に含まれる日時
860
+ #
861
+ # @return [Range]
862
+ # [ 期間が過去または未来に対して無限に続く場合は、GeometricComplex ]
863
+ # [ 期間が有界の場合は、start...end という形式の Range ]
864
+ #
865
+ def current_period(current_date=Time.now)
866
+ current_date = When.when?(current_date) unless current_date.kind_of?(When::TM::TemporalPosition)
867
+ period = _tz_period(current_date)
868
+ range = period[1]
869
+ return range if range.kind_of?(Range)
870
+ GeometricComplex.new([period], !range)
871
+ end
872
+
873
+ # @private
874
+ def _daylight(zdate=nil)
875
+ raise ArgumentError, "Needless daylight saving time evaluation" unless _need_validate
876
+ zdate = yield(@standard.dup.tap{|clock| clock.tz_prop = nil}) if block_given?
877
+ ndate = _neighbor_event_date(zdate)
878
+ nprop = ndate.clock.tz_prop
879
+ if block_given? && !@standard.equal?(nprop.tzoffsetfrom)
880
+ zdate = yield(nprop.tzoffsetfrom.dup.tap{|clock| clock.tz_prop = nil})
881
+ end
882
+ deltad = zdate.universal_time - ndate.universal_time
883
+ if nprop.instance_of?(Standard)
884
+ return nprop.tzoffsetto if (deltad >= @difference)
885
+ return nprop.tzoffsetfrom
886
+ else
887
+ return nprop.tzoffsetto if (deltad >= 0)
888
+ return nprop.tzoffsetfrom
889
+ end
890
+ end
891
+
892
+ # @private
893
+ def _need_validate
894
+ (@difference != 0)
895
+ end
896
+
897
+ private
898
+
899
+ # オブジェクトの生成
900
+ def initialize(options)
901
+ super
902
+
903
+ @child.each do |prop|
904
+ @dtstart = prop.dtstart if (@dtstart==nil || prop.dtstart < @dtstart)
905
+ @dtstop = prop.dtstop unless (prop.dtstop.kind_of?(When::TimeValue) &&
906
+ @dtstop.kind_of?(When::TimeValue) &&
907
+ prop.dtstop <= @dtstop)
908
+ [prop.tzoffsetfrom, prop.tzoffsetto].each do |tz|
909
+ @daylight = tz if (@daylight==nil || tz.universal_time < @daylight.universal_time)
910
+ @standard = tz if (@standard==nil || tz.universal_time > @standard.universal_time)
911
+ end
912
+ end
913
+ @difference = @standard.universal_time - @daylight.universal_time
914
+
915
+ name = @property['tzid'].object
916
+ case self.class[name]
917
+ when nil ; self.class[name] = self
918
+ when Array ; self.class[name] << self
919
+ else ; self.class[name] = [self.class[name], self]
920
+ end
921
+
922
+ @_lock_ = Mutex.new if When.multi_thread
923
+ @range = []
924
+ end
925
+
926
+ # 指定の日時に最も近い、時間帯変更イベントの日時
927
+ #
928
+ # @param [When::TM::TemporalPosition] current_date 捜索の基点の日時
929
+ #
930
+ # @return [When::TM::TemporalPosition] 捜索の基点の日時に最も近い、時間帯変更イベントの日時
931
+ #
932
+ def _neighbor_event_date(current_date)
933
+ _tz_period(current_date)[0]
934
+ end
935
+
936
+ def _tz_period(current_date)
937
+ universal_time = current_date.universal_time
938
+ return [@dtstart, false] if (universal_time <= @dtstart.universal_time)
939
+ return [@dtstop, true ] if (@dtstop.kind_of?(When::TimeValue) &&
940
+ universal_time >= @dtstop.universal_time)
941
+
942
+ # Thread 要注意 - @range は生成後に更新
943
+ synchronize do
944
+ @range.each do |range|
945
+ from = universal_time - range.first.universal_time
946
+ to = range.last.universal_time - universal_time
947
+ return [(from < to) ? range.first : range.last, range] if (from >= 0 && to > 0)
948
+ end
949
+ early = _neighbor(current_date, :reverse)
950
+ late = _neighbor(current_date, :forward)
951
+ from = universal_time - early.universal_time
952
+ to = late.universal_time - universal_time
953
+ @range << (early...late)
954
+ return [(from < to) ? early : late, @range[-1]]
955
+ end
956
+ end
957
+
958
+ def _neighbor(current_date, direction)
959
+ event = nil
960
+ minimum = nil
961
+ @child.each do |prop|
962
+ date = prop.enum_for(current_date, direction, 1).succ
963
+ if (date)
964
+ diff = (date - current_date).duration.abs
965
+ if (minimum == nil || minimum > diff)
966
+ event = date
967
+ minimum = diff
968
+ end
969
+ end
970
+ end
971
+ return event
972
+ end
973
+ end
974
+
975
+ class Event < Root
976
+
977
+ Classes = [Root, Alarm]
978
+
979
+ #
980
+ # When::V::Event が使用する Enumerator
981
+ #
982
+ class Enumerator < When::Parts::Enumerator
983
+
984
+ include When
985
+ include Coordinates
986
+
987
+ FreqIndex = {'YEARLY' =>YEAR, 'MONTHLY' =>MONTH,
988
+ 'WEEKLY' =>WEEK, 'DAILY' =>DAY,
989
+ 'HOURLY' =>HOUR, 'MINUTELY' =>MINUTE, 'SECONDLY' =>SECOND}
990
+ PostFreqIndex = {'BYYEAR' =>YEAR, 'BYMONTH' =>MONTH, 'BYWEEKNO' =>WEEK,
991
+ 'BYYEARDAY'=>DAY, 'BYMONTHDAY'=>DAY,
992
+ 'BYWEEKDAY'=>DAY, 'BYDAY' =>DAY,
993
+ 'BYHOUR' =>HOUR, 'BYMINUTE' =>MINUTE, 'BYSECOND' =>SECOND}
994
+
995
+ # RRULE Property - iCalendar の RRULE を Hash に展開したものを保持している
996
+ # @return [Hash]
997
+ attr_reader :rule
998
+
999
+ # 繰り返しの始点
1000
+ # @return [When::TM::TemporalPosition, When::Parts::GeometricComplex]
1001
+ attr_reader :dtstart
1002
+
1003
+ # 多重繰り返しロジック
1004
+ # @return [Array<When::V::Event::Enumerator::Logic>]
1005
+ attr_reader :logics
1006
+
1007
+ # 多重繰り返しの現在状態
1008
+ # @return [Array<When::V::Event::Enumerator::Step>]
1009
+ attr_accessor :steps
1010
+ protected :steps=
1011
+
1012
+ #
1013
+ # 巻き戻す
1014
+ #
1015
+ # @return [rewind された self]
1016
+ #
1017
+ def _rewind
1018
+ @steps = [Step.new(_first_seed(@first, @dtstart))]
1019
+ super
1020
+ end
1021
+
1022
+ private
1023
+
1024
+ # オブジェクトの生成
1025
+ def initialize(*args)
1026
+ @options = When::Parts::Enumerator._options(args)
1027
+ @exdate = @options.delete(:exdate)
1028
+ @exevent = @options.delete(:exevent)
1029
+ @parent, @rule, @dtstart, @duration, *args = args
1030
+ @dtstart = When.when?(@dtstart)
1031
+ @rule = self.class._decode_rule(@rule, @dtstart) if (@rule.kind_of?(String))
1032
+ @logics = @rule[:logics]
1033
+ @tz_prop = nil
1034
+ if (@dtstart.kind_of?(When::TM::DateAndTime))
1035
+ clock = @dtstart.clock
1036
+ if (clock.kind_of?(When::TM::Clock) &&
1037
+ clock.tz_prop.kind_of?(TimezoneProperty) &&
1038
+ (clock.tz_prop.dtstart == @dtstart))
1039
+ @tz_prop = @dtstart.clock.tz_prop
1040
+ @dtstart = @dtstart.dup._copy({:tz_prop=>nil, :validate=>:done})
1041
+ end
1042
+ end
1043
+ _range(args)
1044
+ _rewind
1045
+ end
1046
+
1047
+ def _copy
1048
+ copy = super
1049
+ copy.steps = @steps.dup
1050
+ return copy
1051
+ end
1052
+
1053
+ def _first_seed(target, dtstart)
1054
+ if (@rule['FREQ'].kind_of?(When::TM::Duration))
1055
+ @interval = @rule['FREQ']
1056
+ @interval *= @rule['INTERVAL'] unless (@rule['INTERVAL'] == 1)
1057
+ else
1058
+ @interval = When::TM::PeriodDuration.new(@rule['INTERVAL'], FreqIndex[@rule['FREQ']])
1059
+ end
1060
+ return dtstart if (target == dtstart)
1061
+ interval_time = (dtstart + @interval) - dtstart
1062
+ return dtstart if (interval_time == 0)
1063
+ div, mod = (target - dtstart).duration.divmod(interval_time.duration)
1064
+ seed = dtstart + (@interval * div)
1065
+ case @direction
1066
+ when :reverse ; seed += @interval while (seed <= target)
1067
+ else ; seed -= @interval while (seed >= target)
1068
+ end
1069
+ return seed
1070
+ end
1071
+
1072
+ def _next_seed(seed)
1073
+ case @direction
1074
+ when :reverse ; return seed - @interval
1075
+ else ; return seed + @interval
1076
+ end
1077
+ end
1078
+
1079
+ def _succ(depth=0)
1080
+ return nil if (@rule['COUNT'] && (@count >= @rule['COUNT']))
1081
+ step = @steps[depth]
1082
+ unless (step)
1083
+ step = Step.new(_candidates(depth-1))
1084
+ @steps[depth..-1] = [step]
1085
+ end
1086
+
1087
+ case depth
1088
+ when 0
1089
+ if (@logics.length == 0)
1090
+ result = @steps[0]._current_date
1091
+ @steps[0..-1] = [Step.new(_next_seed(result))]
1092
+ else
1093
+ loop do
1094
+ result = _succ(depth+1)
1095
+ break if (result)
1096
+ @steps[0..-1] = [Step.new(_next_seed(@steps[0]._current_date))]
1097
+ end
1098
+ end
1099
+ return (@direction==:reverse) ? nil : :next if (@dtstart > result)
1100
+ return (@direction==:reverse) ? :next : nil if (@rule['UNTIL'] && (result > @rule['UNTIL']))
1101
+ if (@tz_prop)
1102
+ result = result.dup._copy({:tz_prop=>nil, :validate=>:done})
1103
+ result.clock.tz_prop = @tz_prop
1104
+ end
1105
+ return result
1106
+
1107
+ when logics.length
1108
+ step._inc
1109
+ return step._previuos_date
1110
+
1111
+ else
1112
+ while (step._current_date) do
1113
+ result = _succ(depth+1)
1114
+ return result if (result)
1115
+ @steps[depth..-1] = [step._inc]
1116
+ end
1117
+ return nil
1118
+ end
1119
+ end
1120
+
1121
+ def _candidates(depth)
1122
+ # logics と seed
1123
+ logics = @logics[depth]
1124
+ seed = @steps[depth]._current_date
1125
+ logics.cash ||= {}
1126
+ return logics.cash[seed.universal_time] if logics.cash[seed.universal_time]
1127
+
1128
+ # 上下限の決定
1129
+ lower_bound, higher_bound = logics._bound(seed, @rule['WKST'])
1130
+
1131
+ # 候補
1132
+ list = logics._candidates(lower_bound, higher_bound).reject { |date|
1133
+ # See RFC 5545 [Page 43] invalid date (e.g., February 30)
1134
+ !(lower_bound.universal_time <= date.universal_time && date.universal_time < higher_bound.universal_time)
1135
+ }
1136
+ list = Array._sort(list, @direction)
1137
+ list = Array._sort((@rule['BYSETPOS'].map {|pos| list[pos]}).compact, @direction) if @rule['BYSETPOS']
1138
+
1139
+ # 結果をCash
1140
+ logics.cash[seed.universal_time] = list
1141
+ end
1142
+
1143
+ # @private
1144
+ def self._decode_rule(description, dtstart, new_zone='')
1145
+ # 指定の読み込み
1146
+ if description.kind_of?(Hash)
1147
+ rule = description
1148
+ else
1149
+ rule = {}
1150
+ description.split(/;/).each do |pair|
1151
+ raise ArgumentError, "Invalid RRULE format" unless pair =~ /^([^\/=]+)(\/(.+))?=(.*)$/
1152
+ k, f, c, v = $~[1..4]
1153
+ case k
1154
+ when 'BYYEAR', 'BYDAY', 'YEARPOS', 'DAYPOS', 'YEARST', 'DAYST'
1155
+ rule[k] ||= {}
1156
+ c ||= ""
1157
+ raise ArgumentError, "The #{k}#{f} part MUST NOT occur more than once" if (rule[k][c])
1158
+ rule[k][c] = v
1159
+ else
1160
+ raise ArgumentError, "Invalid RRULE format" if (f)
1161
+ raise ArgumentError, "The #{k} part MUST NOT occur more than once" if (rule[k])
1162
+ rule[k] = v
1163
+ end
1164
+ end
1165
+ end
1166
+
1167
+ # 整合性確認と省略された指定の設定
1168
+ raise ArgumentError, "The UNTIL and COUNT are exclusive" if (rule['UNTIL'] && rule['COUNT'])
1169
+ rule['FREQ'] = When.Duration(rule['FREQ']) unless rule['FREQ'].kind_of?(When::TM::Duration) ||
1170
+ FreqIndex.key?(rule['FREQ'])
1171
+ if (rule['UNTIL'].kind_of?(String))
1172
+ rule['UNTIL'] += new_zone unless rule['UNTIL'] =~ /Z$/
1173
+ rule['UNTIL'] = When.when?(rule['UNTIL'])
1174
+ end
1175
+ unless rule['UNTIL']
1176
+ rule['UNTIL'] = When.now + (Event.default_until || 1000*When::TM::Duration::YEAR)
1177
+ end
1178
+ rule['COUNT'] = rule['COUNT'].to_i if (rule['COUNT'])
1179
+ rule['INTERVAL'] = (rule['INTERVAL'] || 1).to_i
1180
+ rule['WKST'] = Residue.day_of_week(rule['WKST'] || 'MO')
1181
+
1182
+ if (rule['BYSETPOS'])
1183
+ rule['BYSETPOS'] = rule['BYSETPOS'].split(/,/).map {|v|
1184
+ pos = v.to_i
1185
+ pos -= 1 if (pos>0)
1186
+ pos
1187
+ }
1188
+ end
1189
+ ['YEARPOS', 'DAYPOS', 'YEARST', 'DAYST'].each do |by_part|
1190
+ if (rule[by_part])
1191
+ rule[by_part].each_pair do |c,v|
1192
+ rule[by_part][c] = v.to_i
1193
+ end
1194
+ else
1195
+ rule[by_part] = {}
1196
+ end
1197
+ end
1198
+
1199
+ if (rule['BYDAY'])
1200
+ v = rule['BYDAY'].delete("")
1201
+ if (v)
1202
+ raise ArgumentError, "The BYWEEKDAY and BYDAY are exclusive" if (rule['BYWEEKDAY'])
1203
+ rule['BYWEEKDAY'] = v
1204
+ end
1205
+ rule.delete('BYDAY') if rule['BYDAY'].size == 0
1206
+ end
1207
+
1208
+ count = 0
1209
+ if (rule['BYWEEKNO'])
1210
+ raise ArgumentError, "The 'BYWEEKNO' is not allowed" unless (rule['FREQ'] == 'YEARLY')
1211
+ count += 1
1212
+ end
1213
+ count += 1 if (rule['BYYEARDAY'])
1214
+ raise ArgumentError, "The 'BYMONTHDAY' is not allowed" if (rule['BYMONTHDAY'] && (count>0))
1215
+ count += 1 if (rule['BYMONTH'])
1216
+ raise ArgumentError, "The BYMONTH, BYWEEKNO and BYYEARDAY are exclusive" if (count > 1)
1217
+
1218
+ # ロジックオブジェクトの生成
1219
+ if (rule['BYWEEKDAY'])
1220
+ rule['BYWEEKDAY'] = Logic::Weekday.new('BYWEEKDAY', rule['BYWEEKDAY'])
1221
+ end
1222
+
1223
+ ['YEAR', 'DAY'].each do |part|
1224
+ by_part = 'BY' + part
1225
+ if (rule[by_part])
1226
+ rule[by_part].each_pair do |ref,list|
1227
+ position = rule[(by_part == 'BYYEAR') ? 'YEARPOS' : 'DAYPOS'][ref]
1228
+ start = rule[(by_part == 'BYYEAR') ? 'YEARST' : 'DAYST' ][ref]
1229
+ rule[by_part][ref] = ((ref.to_i==0) ? Logic::Enumerator :
1230
+ Logic::Residue).new(by_part, list, position, ref, start)
1231
+ end
1232
+ end
1233
+ end
1234
+
1235
+ ['MONTH', 'WEEKNO', 'YEARDAY', 'MONTHDAY'].each do |part|
1236
+ by_part = 'BY' + part
1237
+ rule[by_part] = Logic.const_get(part.capitalize).new(by_part, rule[by_part]) if rule[by_part]
1238
+ end
1239
+
1240
+ ['HOUR', 'MINUTE', 'SECOND'].each do |part|
1241
+ by_part = 'BY' + part
1242
+ clock = dtstart.clock
1243
+ leap = dtstart.time_standard.has_leap?
1244
+ if rule[by_part]
1245
+ if clock
1246
+ index = When::Coordinates::PRECISION[part]
1247
+ lower = clock.base[index]
1248
+ upper = clock.unit[index] + lower
1249
+ end
1250
+ rule[by_part] = Logic.const_get(part.capitalize).new(by_part, rule[by_part], lower, upper, leap)
1251
+ end
1252
+ end
1253
+
1254
+ freq_index = FreqIndex[rule['FREQ']]
1255
+ if (dtstart && dtstart.has_time?)
1256
+ ['HOUR', 'MINUTE', 'SECOND'].each do |part|
1257
+ by_part = 'BY' + part
1258
+ if (rule[by_part]==nil && freq_index && freq_index < PostFreqIndex[by_part])
1259
+ base = dtstart[PostFreqIndex[by_part]]
1260
+ rule[by_part] = Logic.const_get(part.capitalize).new(by_part, base) unless (base == 0)
1261
+ end
1262
+ end
1263
+ end
1264
+
1265
+ # 配列に登録
1266
+ logics = []
1267
+ ['BYYEAR', 'BYMONTH', 'BYWEEKNO', 'BYYEARDAY', 'BYMONTHDAY', 'BYDAY', 'BYWEEKDAY',
1268
+ 'BYHOUR', 'BYMINUTE', 'BYSECOND'].each do |by_part|
1269
+ if (rule[by_part])
1270
+ if (rule[by_part].kind_of?(Hash))
1271
+ rule[by_part].each_pair do |ref,logic|
1272
+ raise ArgumentError, "The #{by_part} must have class specification" if (ref == "")
1273
+ logic.freq_index = freq_index
1274
+ logics << logic
1275
+ end
1276
+ else
1277
+ rule[by_part].freq_index = freq_index
1278
+ logics << rule[by_part]
1279
+ end
1280
+ rule.delete(by_part)
1281
+ freq_index = [PostFreqIndex[by_part], freq_index].max if freq_index
1282
+ end
1283
+ end
1284
+ raise ArgumentError, "BY part must not be specified" if rule['FREQ'].kind_of?(When::TM::Duration) &&
1285
+ logics.length > 0
1286
+ rule[:logics] = logics
1287
+ rule[:parsed] = description
1288
+ return rule
1289
+ end
1290
+
1291
+ #
1292
+ # 多重繰り返しの現在状態
1293
+ #
1294
+ class Step
1295
+
1296
+ attr_reader :index
1297
+ attr_reader :date
1298
+
1299
+ # @private
1300
+ def _current_date
1301
+ return @date[@index]
1302
+ end
1303
+
1304
+ # @private
1305
+ def _previuos_date
1306
+ return @date[@index-1]
1307
+ end
1308
+
1309
+ # @private
1310
+ def _inc
1311
+ @index += 1
1312
+ return self
1313
+ end
1314
+
1315
+ # @private
1316
+ def initialize(date)
1317
+ @index = 0
1318
+ @date = date.kind_of?(Array) ? date : [date]
1319
+ end
1320
+ end
1321
+
1322
+ #
1323
+ # 多重繰り返しロジック
1324
+ #
1325
+ class Logic
1326
+ attr_accessor :freq_index
1327
+ attr_reader :by_part
1328
+ attr_reader :list
1329
+ attr_accessor :cash
1330
+ #protected :cash=
1331
+
1332
+ # @private
1333
+ def _bound(seed, week_start)
1334
+ if (@freq_index == When::WEEK)
1335
+ # 週の初め
1336
+ bound = seed & week_start
1337
+ if (bound == seed)
1338
+ lower_bound = bound.floor(When::DAY, nil)
1339
+ higher_bound = lower_bound + week_start.duration
1340
+ else
1341
+ higher_bound = bound.floor(When::DAY, nil)
1342
+ lower_bound = higher_bound - week_start.duration
1343
+ end
1344
+ else
1345
+ # 指定桁で切り捨て/切り上げ
1346
+ lower_bound = seed.floor(@freq_index, nil)
1347
+ higher_bound = seed.ceil(@freq_index, nil)
1348
+ end
1349
+ return [lower_bound, higher_bound]
1350
+ end
1351
+
1352
+ # 候補日時の生成
1353
+ # @private
1354
+ def _candidates(lower_bound, higher_bound)
1355
+ # TODO unitBaseの扱い
1356
+ return @list.map { |ord|
1357
+ if (ord >= 0)
1358
+ period = ord - 1
1359
+ bound = lower_bound
1360
+ else
1361
+ period = ord
1362
+ bound = higher_bound
1363
+ end
1364
+ index = PostFreqIndex[@by_part]
1365
+ period = ord-bound[index] if (index>0)
1366
+ result = bound + When::TM::PeriodDuration.new(period, index)
1367
+ if period > 0 && result.universal_time < lower_bound.universal_time
1368
+ result = result._copy({:date=>result.cal_date, :validate=>:done, :events=>nil,
1369
+ :time=>result.clk_time._copy({:clock=>result.clock.tz_prop.tzoffsetto})})
1370
+ end
1371
+ result
1372
+ }
1373
+ end
1374
+
1375
+ # @private
1376
+ def initialize(by_part, list)
1377
+ @by_part = by_part
1378
+ @list =
1379
+ case list
1380
+ when String ; When::Coordinates::Pair::_en_pair_array(list)
1381
+ when Array ;
1382
+ else ; [list]
1383
+ end
1384
+ end
1385
+
1386
+ #
1387
+ # 剰余類を用いた繰り返し
1388
+ # 曜日の指定などに用いる
1389
+ #
1390
+ class Residue < Logic
1391
+ # @private
1392
+ def _candidates(lower_bound, higher_bound)
1393
+ candidate = []
1394
+ @list.each do |ord|
1395
+ nth, residue, period = ord
1396
+ shift = (period[PostFreqIndex[@by_part]] != 0)
1397
+ raise ArgumentError, "n*m+/-s format not permitted" if (nth || shift) && @freq_index >= When::DAY
1398
+ if (nth)
1399
+ date = ((nth >= 0) ? lower_bound & residue :
1400
+ higher_bound & residue)
1401
+ date += period if shift
1402
+ candidate << date
1403
+ else
1404
+ (lower_bound ^ residue).each do |date|
1405
+ date += period if shift
1406
+ break unless date < higher_bound
1407
+ candidate << date
1408
+ end
1409
+ end
1410
+ end
1411
+ return candidate
1412
+ end
1413
+
1414
+ # @private
1415
+ def initialize(by_part, list, position, ref, start)
1416
+ @by_part = by_part
1417
+ divisor = When::Coordinates::Pair._en_number(ref)
1418
+ @list = list.split(/,/).map {|w|
1419
+ raise ArgumentError, "The #{by_part} rule format error" unless w =~ /^(([-+]?\d+)\*)?(.+?)([-+]\d+)?$/
1420
+ nth, spec, period = $~[2..4]
1421
+ nth = (nth) ? nth.to_i : position
1422
+ period = When::TM::PeriodDuration.new((period||0).to_i, PostFreqIndex[@by_part])
1423
+ args = [spec.to_i, divisor, (nth) ? ((nth>0) ? nth-1 : nth) : 0]
1424
+ case by_part
1425
+ when 'BYYEAR'
1426
+ start ||= 4
1427
+ start = start % divisor
1428
+ args << {'year'=>start}
1429
+ when 'BYDAY'
1430
+ start ||= 11
1431
+ start = start % divisor
1432
+ args << {'day'=>start} unless (start == 0)
1433
+ end
1434
+ residue = When::Coordinates::Residue.new(*args)
1435
+ [nth, residue, period]
1436
+ }
1437
+ end
1438
+ end
1439
+
1440
+ #
1441
+ # 外部 Enumerator 用いた繰り返し
1442
+ # 春分日、秋分日、復活祭などの指定に用いる
1443
+ #
1444
+ class Enumerator < Logic
1445
+ attr_reader :ref
1446
+ attr_reader :start
1447
+
1448
+ # @private
1449
+ def _candidates(lower_bound, higher_bound)
1450
+ candidate = []
1451
+ @list.each do |ord|
1452
+ nth, spec, period = ord
1453
+ shift = (period[PostFreqIndex[@by_part]] != 0)
1454
+ raise ArgumentError, "n*e+/-s format not permitted" if (nth || shift) && @freq_index >= When::DAY
1455
+ if (nth==nil || nth>0)
1456
+ enum = @ref.enum_for(lower_bound, :forward, spec)
1457
+ else
1458
+ enum = @ref.enum_for(higher_bound, :reverse, spec)
1459
+ end
1460
+ if (nth)
1461
+ date = nil
1462
+ nth.abs.times {date = enum.succ}
1463
+ date += period if shift
1464
+ candidate << date
1465
+ else
1466
+ enum.each do |date|
1467
+ date += period if shift
1468
+ break unless date < higher_bound
1469
+ candidate << date
1470
+ end
1471
+ end
1472
+ end
1473
+ return candidate
1474
+ end
1475
+
1476
+ # @private
1477
+ def initialize(by_part, list, position, ref, start)
1478
+ @by_part = by_part
1479
+ @ref = When.Resource(ref, '_n:')
1480
+ @start = start
1481
+ @list = list.split(/,/).map {|w|
1482
+ raise ArgumentError, "The #{by_part} rule format error" unless w =~ /^(([-+]?\d+)\*)?(.+?)([-+]\d+)?$/
1483
+ nth, spec, period = $~[2..4]
1484
+ nth = (nth) ? nth.to_i : position
1485
+ period = When::TM::PeriodDuration.new((period||0).to_i, PostFreqIndex[@by_part])
1486
+ [nth, spec, period]
1487
+ }
1488
+ end
1489
+ end
1490
+
1491
+ #
1492
+ # BYMONTHを実装
1493
+ # @private
1494
+ class Month < Logic
1495
+ end
1496
+
1497
+ #
1498
+ # BYWEEKNOを実装
1499
+ # @private
1500
+ class Weekno < Logic
1501
+ # @private
1502
+ def _bound(seed, week_start)
1503
+ duration = week_start.duration
1504
+ center = duration / 2
1505
+ # 1月4日を含む週
1506
+ return [seed.floor(When::YEAR, nil), seed.ceil(When::YEAR, nil)].map { |s|
1507
+ s += center
1508
+ bound = s & week_start
1509
+ bound -= duration if (bound > s)
1510
+ bound
1511
+ }
1512
+ end
1513
+ end
1514
+
1515
+ #
1516
+ # BYYEARDAYを実装
1517
+ # @private
1518
+ class Yearday < Logic
1519
+ end
1520
+
1521
+ #
1522
+ # BYMONTHDAYを実装
1523
+ # @private
1524
+ class Monthday < Logic
1525
+ end
1526
+
1527
+ #
1528
+ # BYWEEKDAY(=BYDAY)を実装
1529
+ # @private
1530
+ class Weekday < Residue
1531
+ # @private
1532
+ def initialize(by_part, list)
1533
+ @by_part = by_part
1534
+ @list = list.split(/,/).map {|w|
1535
+ raise ArgumentError, "The BYDAY rule format error" unless w =~ /^([-+]?\d+)?(MO|TU|WE|TH|FR|SA|SU)([-+]\d+)?$/
1536
+ nth, spec, period = $~[1..3]
1537
+ if nth
1538
+ nth = nth.to_i
1539
+ nth = (nth>0) ? nth-1 : nth
1540
+ end
1541
+ residue = When::Coordinates::Residue.day_of_week(spec) >> (nth||0)
1542
+ [nth, residue, When::TM::PeriodDuration.new([0,0,(period||0).to_i])]
1543
+ }
1544
+ end
1545
+ end
1546
+
1547
+ #
1548
+ # BYHOURを実装
1549
+ # @private
1550
+ class Hour < Logic
1551
+ # @private
1552
+ def initialize(by_part, list, lower=nil, upper=nil, leap=false)
1553
+ super(by_part, list)
1554
+ if lower
1555
+ @list.each do |v|
1556
+ raise ArgumentError, "#{by_part} out of range: #{v}" unless lower <= v && v < upper
1557
+ end
1558
+ end
1559
+ end
1560
+ end
1561
+
1562
+ #
1563
+ # BYMINUTEを実装
1564
+ # @private
1565
+ class Minute < Hour
1566
+ end
1567
+
1568
+ #
1569
+ # BYSECONDを実装
1570
+ # @private
1571
+ class Second < Logic
1572
+ # @private
1573
+ def initialize(by_part, list, lower=nil, upper=nil, leap=false)
1574
+ super(by_part, list)
1575
+ if lower
1576
+ @list = @list.map {|v|
1577
+ v -= 1 if leap && upper <= v && v < upper+1
1578
+ raise ArgumentError, "#{by_part} out of range: #{v}" unless lower <= v && v < upper
1579
+ v
1580
+ }
1581
+ end
1582
+ end
1583
+ end
1584
+ end
1585
+ end
1586
+ end
1587
+ end