when_exe 0.2.100 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
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