reactive_graph_std_date_time/implementation/
time_graph_impl.rs1use std::sync::Arc;
2use std::time::Instant;
3
4use async_trait::async_trait;
5use chrono::Datelike;
6use log::info;
7use log::trace;
8use serde_json::json;
9
10use crate::api::TimeGraph;
11use reactive_graph_graph::prelude::*;
12use reactive_graph_plugin_api::Component;
13use reactive_graph_plugin_api::EntityInstanceManager;
14use reactive_graph_plugin_api::RelationInstanceManager;
15use reactive_graph_plugin_api::component_alias;
16
17use reactive_graph_reactive_model_impl::ReactiveEntity;
18use reactive_graph_reactive_model_impl::ReactiveProperties;
19use reactive_graph_reactive_model_impl::ReactiveRelation;
20use reactive_graph_std_date_time_model::DayProperties::DAY_OF_MONTH;
21use reactive_graph_std_date_time_model::DayProperties::ISO8601;
22use reactive_graph_std_date_time_model::ENTITY_TYPE_DAY;
23use reactive_graph_std_date_time_model::ENTITY_TYPE_MONTH;
24use reactive_graph_std_date_time_model::ENTITY_TYPE_YEAR;
25use reactive_graph_std_date_time_model::MonthProperties::MONTH_AND_YEAR;
26use reactive_graph_std_date_time_model::MonthProperties::MONTH_OF_YEAR;
27use reactive_graph_std_date_time_model::RELATION_TYPE_DAY_OF_MONTH;
28use reactive_graph_std_date_time_model::RELATION_TYPE_FIRST_DAY;
29use reactive_graph_std_date_time_model::RELATION_TYPE_FIRST_MONTH;
30use reactive_graph_std_date_time_model::RELATION_TYPE_LAST_DAY;
31use reactive_graph_std_date_time_model::RELATION_TYPE_LAST_MONTH;
32use reactive_graph_std_date_time_model::RELATION_TYPE_MONTH_OF_YEAR;
33use reactive_graph_std_date_time_model::RELATION_TYPE_NEXT_DAY;
34use reactive_graph_std_date_time_model::RELATION_TYPE_NEXT_MONTH;
35use reactive_graph_std_date_time_model::RELATION_TYPE_NEXT_YEAR;
36use reactive_graph_std_date_time_model::YearProperties::LEAP;
37use reactive_graph_std_date_time_model::YearProperties::YEAR;
38use uuid::Uuid;
39
40#[derive(Component)]
41pub struct TimeGraphImpl {
42 #[component(default = "crate::plugin::entity_instance_manager")]
43 entity_instance_manager: Arc<dyn EntityInstanceManager + Send + Sync>,
44
45 #[component(default = "crate::plugin::relation_instance_manager")]
46 relation_instance_manager: Arc<dyn RelationInstanceManager + Send + Sync>,
47}
48
49impl TimeGraphImpl {
50 async fn create_time_graph(&self) {
51 self.create_years().await;
52 }
53
54 async fn create_years(&self) {
55 let mut previous_year = None;
56 let mut previous_year_last_month: Option<ReactiveEntity> = None;
57 let mut previous_year_last_day: Option<ReactiveEntity> = None;
58 let current_year = chrono::Utc::now().year() as i64;
59 for year in current_year - 1..current_year + 1 {
60 previous_year = match self.create_year(year).await {
61 Some(current_year) => {
62 self.create_next_year(&previous_year, ¤t_year).await;
63 let (current_year_last_month, current_year_last_day) =
64 self.create_months(¤t_year, previous_year_last_month, previous_year_last_day).await;
65 previous_year_last_month = current_year_last_month;
66 previous_year_last_day = current_year_last_day;
67 Some(current_year)
68 }
69 None => None,
70 }
71 }
72 }
73
74 async fn create_year(&self, year: i64) -> Option<ReactiveEntity> {
75 trace!("Create year {}", year);
76 let id = Uuid::new_v4();
77 let properties = PropertyInstances::new().property(YEAR, json!(year)).property(LEAP, json!(is_leap_year(year)));
78 let reactive_entity = ReactiveEntity::builder()
79 .ty(ENTITY_TYPE_YEAR.clone())
80 .id(id)
81 .properties(ReactiveProperties::new_with_id_from_properties(id, properties))
82 .build();
83 self.entity_instance_manager.register(reactive_entity).ok()
84 }
85
86 async fn create_next_year(&self, previous_year: &Option<ReactiveEntity>, next_year: &ReactiveEntity) {
87 let Some(previous_year) = previous_year else {
88 return;
89 };
90 let instance_id = format!("{}__{}", previous_year.as_i64(YEAR).unwrap_or(0), next_year.as_i64(YEAR).unwrap_or(0));
91 let ty = RelationInstanceTypeId::new_unique_for_instance_id(RELATION_TYPE_NEXT_YEAR.clone(), instance_id);
92 let reactive_relation = ReactiveRelation::builder_with_entities(previous_year.clone(), &ty, next_year.clone()).build();
93 let _ = self.relation_instance_manager.register(reactive_relation);
94 }
95
96 async fn create_months(
97 &self,
98 current_year: &ReactiveEntity,
99 previous_year_last_month: Option<ReactiveEntity>,
100 previous_year_last_day: Option<ReactiveEntity>,
101 ) -> (Option<ReactiveEntity>, Option<ReactiveEntity>) {
102 let Some(year) = current_year.as_i64(YEAR) else {
103 return (None, None);
104 };
105 let mut previous_month = previous_year_last_month;
106 let mut previous_month_last_day = previous_year_last_day;
107 for month in 1..=12 {
109 let current_month = match self.create_month(year, month).await {
110 Some(current_month) => {
111 self.create_next_month(&previous_month, ¤t_month).await;
112 self.create_month_of_year(current_year, ¤t_month).await;
113 previous_month_last_day = self.create_days(current_year, ¤t_month, previous_month_last_day).await;
114 Some(current_month)
115 }
116 None => None,
117 };
118 if month == 1 {
119 previous_month = match current_month {
120 Some(current_month) => {
121 self.create_first_month(current_year, ¤t_month).await;
122 Some(current_month)
124 }
125 None => None,
126 };
127 } else if month == 12 {
128 previous_month = match current_month {
129 Some(current_month) => {
130 self.create_last_month(current_year, ¤t_month).await;
131 Some(current_month)
132 }
133 None => None,
134 };
135 } else {
137 previous_month = current_month;
138 }
139 }
140 (previous_month, previous_month_last_day)
141 }
142
143 async fn create_month(&self, year: i64, month: u64) -> Option<ReactiveEntity> {
144 let id = Uuid::new_v4();
145 let properties = PropertyInstances::new()
146 .property(MONTH_OF_YEAR, json!(month))
147 .property(MONTH_AND_YEAR, json!(format!("{:04}-{:02}", year, month)));
148 let reactive_entity = ReactiveEntity::builder()
149 .ty(ENTITY_TYPE_MONTH.clone())
150 .id(id)
151 .properties(ReactiveProperties::new_with_id_from_properties(id, properties))
152 .build();
153 self.entity_instance_manager.register(reactive_entity).ok()
154 }
155
156 async fn create_month_of_year(&self, current_year: &ReactiveEntity, month: &ReactiveEntity) {
157 let Some(year) = current_year.as_i64(YEAR) else {
158 return;
159 };
160 let Some(month_of_year) = month.as_u64(MONTH_OF_YEAR) else {
161 return;
162 };
163 let instance_id = format!("{:04}__{:02}", year, month_of_year);
164 let ty = RelationInstanceTypeId::new_unique_for_instance_id(RELATION_TYPE_MONTH_OF_YEAR.clone(), instance_id);
165 let reactive_relation = ReactiveRelation::builder_with_entities(current_year.clone(), &ty, month.clone()).build();
166 let _ = self.relation_instance_manager.register(reactive_relation);
167 }
168
169 async fn create_first_month(&self, current_year: &ReactiveEntity, first_month: &ReactiveEntity) {
170 let instance_id = format!("{:04}__{:02}", current_year.as_i64(YEAR).unwrap_or(0), first_month.as_u64(MONTH_OF_YEAR).unwrap_or(0));
171 let ty = RelationInstanceTypeId::new_unique_for_instance_id(RELATION_TYPE_FIRST_MONTH.clone(), instance_id);
172 let reactive_relation = ReactiveRelation::builder_with_entities(current_year.clone(), &ty, first_month.clone()).build();
173 let _ = self.relation_instance_manager.register(reactive_relation);
174 }
175
176 async fn create_last_month(&self, current_year: &ReactiveEntity, last_month: &ReactiveEntity) {
177 let instance_id = format!("{:04}__{:02}", current_year.as_i64(YEAR).unwrap_or(0), last_month.as_u64(MONTH_OF_YEAR).unwrap_or(0));
178 let ty = RelationInstanceTypeId::new_unique_for_instance_id(RELATION_TYPE_LAST_MONTH.clone(), instance_id);
179 let reactive_relation = ReactiveRelation::builder_with_entities(current_year.clone(), &ty, last_month.clone()).build();
180 let _ = self.relation_instance_manager.register(reactive_relation);
181 }
182
183 async fn create_next_month(&self, previous_month: &Option<ReactiveEntity>, next_month: &ReactiveEntity) {
184 let Some(previous_month) = previous_month else {
185 return;
186 };
187 let instance_id = format!(
188 "{}__{}",
189 previous_month.as_string(MONTH_AND_YEAR).unwrap_or_default(),
190 next_month.as_string(MONTH_AND_YEAR).unwrap_or_default()
191 );
192 let ty = RelationInstanceTypeId::new_unique_for_instance_id(RELATION_TYPE_NEXT_MONTH.clone(), instance_id);
193 let reactive_relation = ReactiveRelation::builder_with_entities(previous_month.clone(), &ty, next_month.clone()).build();
194 let _ = self.relation_instance_manager.register(reactive_relation);
195 }
196
197 async fn create_days(
198 &self,
199 current_year: &ReactiveEntity,
200 current_month: &ReactiveEntity,
201 previous_month_last_day: Option<ReactiveEntity>,
202 ) -> Option<ReactiveEntity> {
203 let year = current_year.as_i64(YEAR)?;
204 let month_of_year = current_month.as_u64(MONTH_OF_YEAR)?;
205 let last_day_of_month = last_day_of_month(year, month_of_year)?;
206 let mut previous_day = previous_month_last_day;
207 for day_of_month in 1..=last_day_of_month {
208 let current_day = match self.create_day(year, month_of_year, day_of_month).await {
209 Some(current_day) => {
210 self.create_next_day(&previous_day, ¤t_day).await;
211 self.create_day_of_month(current_month, ¤t_day).await;
212 Some(current_day)
213 }
214 None => None,
215 };
216 if day_of_month == 1 {
217 previous_day = match current_day {
218 Some(current_day) => {
219 self.create_first_day(current_month, ¤t_day).await;
220 Some(current_day)
222 }
223 None => None,
224 };
225 } else if day_of_month == last_day_of_month {
226 previous_day = match current_day {
227 Some(current_day) => {
228 self.create_last_day(current_month, ¤t_day).await;
229 Some(current_day)
230 }
231 None => None,
232 };
233 } else {
234 previous_day = current_day;
235 }
236 }
237 previous_day
238 }
239
240 async fn create_day(&self, year: i64, month: u64, day: u64) -> Option<ReactiveEntity> {
241 let iso8601 = format!("{:04}-{:02}-{:02}", year, month, day);
242 let id = Uuid::new_v4();
243 let properties = PropertyInstances::new().property(DAY_OF_MONTH, json!(day)).property(ISO8601, json!(iso8601));
244 let reactive_entity = ReactiveEntity::builder()
245 .ty(ENTITY_TYPE_DAY.clone())
246 .id(id)
247 .properties(ReactiveProperties::new_with_id_from_properties(id, properties))
248 .build();
249 self.entity_instance_manager.register(reactive_entity).ok()
250 }
251
252 async fn create_day_of_month(&self, current_month: &ReactiveEntity, current_day: &ReactiveEntity) {
253 let Some(month_and_year) = current_month.as_string(MONTH_AND_YEAR) else {
254 return;
255 };
256 let Some(day_of_month) = current_day.as_u64(DAY_OF_MONTH) else {
257 return;
258 };
259 let instance_id = format!("{:04}__{:02}", month_and_year, day_of_month);
260 let ty = RelationInstanceTypeId::new_unique_for_instance_id(RELATION_TYPE_DAY_OF_MONTH.clone(), instance_id);
261 let reactive_relation = ReactiveRelation::builder_with_entities(current_month.clone(), &ty, current_day.clone()).build();
262 let _ = self.relation_instance_manager.register(reactive_relation);
263 }
264
265 async fn create_first_day(&self, current_month: &ReactiveEntity, first_day: &ReactiveEntity) {
266 let Some(month_and_year) = current_month.as_string(MONTH_AND_YEAR) else {
267 return;
268 };
269 let Some(day_of_month) = first_day.as_u64(DAY_OF_MONTH) else {
270 return;
271 };
272 let instance_id = format!("{:04}__{:02}", month_and_year, day_of_month);
273 let ty = RelationInstanceTypeId::new_unique_for_instance_id(RELATION_TYPE_FIRST_DAY.clone(), instance_id);
274 let reactive_relation = ReactiveRelation::builder_with_entities(current_month.clone(), &ty, first_day.clone()).build();
275 let _ = self.relation_instance_manager.register(reactive_relation);
276 }
277
278 async fn create_last_day(&self, current_month: &ReactiveEntity, last_day: &ReactiveEntity) {
279 let Some(month_and_year) = current_month.as_string(MONTH_AND_YEAR) else {
280 return;
281 };
282 let Some(day_of_month) = last_day.as_u64(DAY_OF_MONTH) else {
283 return;
284 };
285 let instance_id = format!("{:04}__{:02}", month_and_year, day_of_month);
286 let ty = RelationInstanceTypeId::new_unique_for_instance_id(RELATION_TYPE_LAST_DAY.clone(), instance_id);
287 let reactive_relation = ReactiveRelation::builder_with_entities(current_month.clone(), &ty, last_day.clone()).build();
288 let _ = self.relation_instance_manager.register(reactive_relation);
289 }
290
291 async fn create_next_day(&self, previous_day: &Option<ReactiveEntity>, next_day: &ReactiveEntity) {
292 let Some(previous_day) = previous_day else {
293 return;
294 };
295 let instance_id = format!("{}__{}", previous_day.as_string(ISO8601).unwrap_or_default(), next_day.as_string(ISO8601).unwrap_or_default());
296 let ty = RelationInstanceTypeId::new_unique_for_instance_id(RELATION_TYPE_NEXT_DAY.clone(), instance_id);
297 let reactive_relation = ReactiveRelation::builder_with_entities(previous_day.clone(), &ty, next_day.clone()).build();
298 let _ = self.relation_instance_manager.register(reactive_relation);
299 }
300}
301
302#[async_trait]
303#[component_alias]
304impl TimeGraph for TimeGraphImpl {
305 async fn init(&self) {
306 info!("Start generating time graph");
307 let start = Instant::now();
308 self.create_time_graph().await;
309 let duration = start.elapsed();
310 info!("Successfully generated time graph in {:?}", duration);
311 }
312
313 async fn shutdown(&self) {}
314}
315
316fn is_leap_year(year: i64) -> bool {
317 year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
318}
319
320fn last_day_of_month(year: i64, month: u64) -> Option<u64> {
321 match month {
322 1 | 3 | 5 | 7 | 8 | 10 | 12 => Some(31),
323 4 | 6 | 9 | 11 => Some(30),
324 2 => {
325 if is_leap_year(year) {
326 Some(29)
327 } else {
328 Some(28)
329 }
330 }
331 _ => None,
332 }
333}