1use std::convert::TryFrom;
38
39use crate::keyword::{KW_TRANSP_OPAQUE, KW_TRANSP_TRANSPARENT};
40use crate::parameter::{FreeBusyType, Parameter, ValueType};
41use crate::property::PropertyKind;
42use crate::property::common::{take_single_text, take_single_value};
43use crate::string_storage::{Segments, StringStorage};
44use crate::syntax::RawParameter;
45use crate::typed::{ParsedProperty, TypedError};
46use crate::value::{Value, ValueDate, ValueDuration, ValuePeriod, ValueTime};
47
48#[derive(Debug, Clone)]
50pub enum DateTime<S: StringStorage> {
51 Floating {
53 date: ValueDate,
55 time: Time,
57 x_parameters: Vec<RawParameter<S>>,
59 retained_parameters: Vec<Parameter<S>>,
61 },
62
63 Zoned {
65 date: ValueDate,
67 time: Time,
69 tz_id: S,
71 #[cfg(feature = "jiff")]
73 tz_jiff: jiff::tz::TimeZone,
74 x_parameters: Vec<RawParameter<S>>,
76 retained_parameters: Vec<Parameter<S>>,
78 },
79
80 Utc {
82 date: ValueDate,
84 time: Time,
86 x_parameters: Vec<RawParameter<S>>,
88 retained_parameters: Vec<Parameter<S>>,
90 },
91
92 Date {
94 date: ValueDate,
96 x_parameters: Vec<RawParameter<S>>,
98 retained_parameters: Vec<Parameter<S>>,
100 },
101}
102
103impl<S: StringStorage> DateTime<S> {
104 #[must_use]
106 pub fn date(&self) -> ValueDate {
107 match self {
108 DateTime::Floating { date, .. }
109 | DateTime::Zoned { date, .. }
110 | DateTime::Utc { date, .. }
111 | DateTime::Date { date, .. } => *date,
112 }
113 }
114
115 #[must_use]
117 pub fn time(&self) -> Option<Time> {
118 match self {
119 DateTime::Floating { time, .. }
120 | DateTime::Zoned { time, .. }
121 | DateTime::Utc { time, .. } => Some(*time),
122 DateTime::Date { .. } => None,
123 }
124 }
125
126 #[must_use]
128 pub fn tz_id(&self) -> Option<&S> {
129 match self {
130 DateTime::Zoned { tz_id, .. } => Some(tz_id),
131 _ => None,
132 }
133 }
134
135 #[cfg(feature = "jiff")]
137 #[must_use]
138 pub fn timezone(&self) -> Option<&jiff::tz::TimeZone> {
139 match self {
140 DateTime::Zoned { tz_jiff, .. } => Some(tz_jiff),
141 _ => None,
142 }
143 }
144
145 #[must_use]
147 pub fn is_date_only(&self) -> bool {
148 matches!(self, DateTime::Date { .. })
149 }
150
151 #[must_use]
153 pub fn is_utc(&self) -> bool {
154 matches!(self, DateTime::Utc { .. })
155 }
156
157 #[must_use]
159 pub fn is_floating(&self) -> bool {
160 matches!(self, DateTime::Floating { .. })
161 }
162
163 #[cfg(feature = "jiff")]
167 #[must_use]
168 pub fn civil_date_time(&self) -> Option<jiff::civil::DateTime> {
169 match self {
170 DateTime::Floating { date, time, .. }
171 | DateTime::Zoned { date, time, .. }
172 | DateTime::Utc { date, time, .. } => Some(jiff::civil::DateTime::from_parts(
173 date.civil_date(),
174 time.civil_time(),
175 )),
176 DateTime::Date { .. } => None,
177 }
178 }
179}
180
181impl<'src> TryFrom<ParsedProperty<'src>> for DateTime<Segments<'src>> {
182 type Error = Vec<TypedError<'src>>;
183
184 #[expect(clippy::too_many_lines)]
185 fn try_from(prop: ParsedProperty<'src>) -> Result<Self, Self::Error> {
186 let mut errors: Vec<TypedError<'src>> = Vec::new();
187
188 let value = match take_single_value(&prop.kind, prop.value) {
189 Ok(v) => v,
190 Err(mut e) => {
191 errors.append(&mut e);
192 return Err(errors);
193 }
194 };
195
196 let mut tz_id = None;
198 #[cfg(feature = "jiff")]
199 let mut tz_jiff = None;
200 let mut x_parameters = Vec::new();
201 let mut retained_parameters = Vec::new();
202
203 for param in prop.parameters {
204 match param {
205 p @ Parameter::TimeZoneIdentifier { .. } if tz_id.is_some() => {
206 errors.push(TypedError::ParameterDuplicated {
207 span: p.span(),
208 parameter: p.kind().into(),
209 });
210 }
211 Parameter::TimeZoneIdentifier {
212 value,
213 #[cfg(feature = "jiff")]
214 tz,
215 ..
216 } => {
217 tz_id = Some(value);
218 #[cfg(feature = "jiff")]
219 {
220 tz_jiff = Some(tz);
221 }
222 }
223 Parameter::XName(raw) => x_parameters.push(raw),
224 p @ Parameter::Unrecognized { .. } => retained_parameters.push(p),
225 p => {
226 retained_parameters.push(p);
228 }
229 }
230 }
231
232 if !errors.is_empty() {
234 return Err(errors);
235 }
236
237 if let Some(tz_id_value) = tz_id {
239 match value {
240 Value::DateTime { mut values, .. } if values.len() == 1 => {
241 let dt = values.pop().unwrap();
242 if dt.time.utc {
243 Ok(DateTime::Utc {
244 date: dt.date,
245 time: dt.time.into(),
246 x_parameters,
247 retained_parameters,
248 })
249 } else {
250 Ok(DateTime::Zoned {
251 date: dt.date,
252 time: dt.time.into(),
253 tz_id: tz_id_value,
254 #[cfg(feature = "jiff")]
255 tz_jiff: tz_jiff.unwrap(), x_parameters,
257 retained_parameters,
258 })
259 }
260 }
261
262 _ => Err(vec![TypedError::PropertyInvalidValue {
263 property: prop.kind,
264 value: "Expected date-time value".to_string(),
265 span: value.span(),
266 }]),
267 }
268 } else {
269 match value {
270 Value::Date { mut values, .. } if values.len() == 1 => {
271 let date = values.pop().unwrap();
272 Ok(DateTime::Date {
273 date,
274 x_parameters,
275 retained_parameters,
276 })
277 }
278 Value::DateTime { mut values, .. } if values.len() == 1 => {
279 let dt = values.pop().unwrap();
280 if dt.time.utc {
281 Ok(DateTime::Utc {
282 date: dt.date,
283 time: dt.time.into(),
284 x_parameters,
285 retained_parameters,
286 })
287 } else {
288 Ok(DateTime::Floating {
289 date: dt.date,
290 time: dt.time.into(),
291 x_parameters,
292 retained_parameters,
293 })
294 }
295 }
296 _ => {
297 const EXPECTED: &[ValueType<String>] = &[ValueType::Date, ValueType::DateTime];
298 Err(vec![TypedError::PropertyUnexpectedValue {
299 property: PropertyKind::DtStart, expected: EXPECTED,
301 found: value.kind().into(),
302 span: value.span(),
303 }])
304 }
305 }
306 }
307 }
308}
309
310impl DateTime<Segments<'_>> {
311 #[must_use]
313 pub fn to_owned(&self) -> DateTime<String> {
314 match self {
315 DateTime::Floating {
316 date,
317 time,
318 x_parameters,
319 retained_parameters,
320 } => DateTime::Floating {
321 date: *date,
322 time: *time,
323 x_parameters: x_parameters.iter().map(RawParameter::to_owned).collect(),
324 retained_parameters: retained_parameters
325 .iter()
326 .map(Parameter::to_owned)
327 .collect(),
328 },
329 DateTime::Zoned {
330 date,
331 time,
332 tz_id,
333 #[cfg(feature = "jiff")]
334 tz_jiff,
335 x_parameters,
336 retained_parameters,
337 } => DateTime::Zoned {
338 date: *date,
339 time: *time,
340 tz_id: tz_id.to_owned(),
341 #[cfg(feature = "jiff")]
342 tz_jiff: tz_jiff.clone(),
343 x_parameters: x_parameters.iter().map(RawParameter::to_owned).collect(),
344 retained_parameters: retained_parameters
345 .iter()
346 .map(Parameter::to_owned)
347 .collect(),
348 },
349 DateTime::Utc {
350 date,
351 time,
352 x_parameters,
353 retained_parameters,
354 } => DateTime::Utc {
355 date: *date,
356 time: *time,
357 x_parameters: x_parameters.iter().map(RawParameter::to_owned).collect(),
358 retained_parameters: retained_parameters
359 .iter()
360 .map(Parameter::to_owned)
361 .collect(),
362 },
363 DateTime::Date {
364 date,
365 x_parameters,
366 retained_parameters,
367 } => DateTime::Date {
368 date: *date,
369 x_parameters: x_parameters.iter().map(RawParameter::to_owned).collect(),
370 retained_parameters: retained_parameters
371 .iter()
372 .map(Parameter::to_owned)
373 .collect(),
374 },
375 }
376 }
377}
378
379#[derive(Debug, Clone, Copy, PartialEq, Eq)]
381pub struct Time {
382 pub hour: u8,
384
385 pub minute: u8,
387
388 pub second: u8,
390
391 #[cfg(feature = "jiff")]
393 pub(crate) jiff: jiff::civil::Time,
394}
395
396impl Time {
397 #[expect(clippy::cast_possible_wrap)]
402 pub fn new(hour: u8, minute: u8, second: u8) -> Result<Self, String> {
403 Ok(Time {
404 hour,
405 minute,
406 second,
407 #[cfg(feature = "jiff")]
408 jiff: jiff::civil::Time::new(
409 hour as i8,
410 minute as i8,
411 second.min(59) as i8, 0,
413 )
414 .map_err(|e| e.to_string())?,
415 })
416 }
417
418 #[cfg(feature = "jiff")]
420 #[must_use]
421 pub const fn civil_time(&self) -> jiff::civil::Time {
422 self.jiff
423 }
424}
425
426impl From<ValueTime> for Time {
427 fn from(value: ValueTime) -> Self {
428 Time {
429 hour: value.hour,
430 minute: value.minute,
431 second: value.second,
432 #[cfg(feature = "jiff")]
433 jiff: value.jiff,
434 }
435 }
436}
437
438#[derive(Debug, Clone)]
447pub enum Period<S: StringStorage> {
448 ExplicitUtc {
450 start_date: ValueDate,
452 start_time: Time,
454 end_date: ValueDate,
456 end_time: Time,
458 },
459
460 ExplicitFloating {
462 start_date: ValueDate,
464 start_time: Time,
466 end_date: ValueDate,
468 end_time: Time,
470 },
471
472 ExplicitZoned {
474 start_date: ValueDate,
476 start_time: Time,
478 end_date: ValueDate,
480 end_time: Time,
482 tz_id: S,
484 #[cfg(feature = "jiff")]
486 tz_jiff: jiff::tz::TimeZone,
487 },
488
489 DurationUtc {
491 start_date: ValueDate,
493 start_time: Time,
495 duration: ValueDuration,
497 },
498
499 DurationFloating {
501 start_date: ValueDate,
503 start_time: Time,
505 duration: ValueDuration,
507 },
508
509 DurationZoned {
511 start_date: ValueDate,
513 start_time: Time,
515 duration: ValueDuration,
517 tz_id: S,
519 #[cfg(feature = "jiff")]
521 tz_jiff: jiff::tz::TimeZone,
522 },
523}
524
525impl<S: StringStorage> Period<S> {
526 #[must_use]
528 pub fn tz_id(&self) -> Option<&S> {
529 match self {
530 Period::ExplicitZoned { tz_id, .. } | Period::DurationZoned { tz_id, .. } => {
531 Some(tz_id)
532 }
533 _ => None,
534 }
535 }
536
537 #[cfg(feature = "jiff")]
539 #[must_use]
540 pub fn jiff_timezone(&self) -> Option<&jiff::tz::TimeZone> {
541 match self {
542 Period::ExplicitZoned { tz_jiff: tz, .. }
543 | Period::DurationZoned { tz_jiff: tz, .. } => Some(tz),
544 _ => None,
545 }
546 }
547
548 #[must_use]
550 pub fn start(&self) -> DateTime<S> {
551 match self {
552 Period::ExplicitUtc {
553 start_date,
554 start_time,
555 ..
556 }
557 | Period::DurationUtc {
558 start_date,
559 start_time,
560 ..
561 } => DateTime::Utc {
562 date: *start_date,
563 time: *start_time,
564 x_parameters: Vec::new(),
565 retained_parameters: Vec::new(),
566 },
567 Period::ExplicitFloating {
568 start_date,
569 start_time,
570 ..
571 }
572 | Period::DurationFloating {
573 start_date,
574 start_time,
575 ..
576 } => DateTime::Floating {
577 date: *start_date,
578 time: *start_time,
579 x_parameters: Vec::new(),
580 retained_parameters: Vec::new(),
581 },
582 Period::ExplicitZoned {
583 start_date,
584 start_time,
585 tz_id,
586 #[cfg(feature = "jiff")]
587 tz_jiff,
588 ..
589 }
590 | Period::DurationZoned {
591 start_date,
592 start_time,
593 tz_id,
594 #[cfg(feature = "jiff")]
595 tz_jiff,
596 ..
597 } => DateTime::Zoned {
598 date: *start_date,
599 time: *start_time,
600 tz_id: tz_id.clone(),
601 #[cfg(feature = "jiff")]
602 tz_jiff: tz_jiff.clone(),
603 x_parameters: Vec::new(),
604 retained_parameters: Vec::new(),
605 },
606 }
607 }
608
609 #[cfg(feature = "jiff")]
613 #[expect(clippy::missing_panics_doc, clippy::too_many_lines)]
614 #[must_use]
615 pub fn end(&self) -> DateTime<S> {
616 match self {
617 Period::ExplicitUtc {
618 end_date, end_time, ..
619 } => DateTime::Utc {
620 date: *end_date,
621 time: *end_time,
622 x_parameters: Vec::new(),
623 retained_parameters: Vec::new(),
624 },
625 Period::ExplicitFloating {
626 end_date, end_time, ..
627 } => DateTime::Floating {
628 date: *end_date,
629 time: *end_time,
630 x_parameters: Vec::new(),
631 retained_parameters: Vec::new(),
632 },
633 Period::ExplicitZoned {
634 end_date,
635 end_time,
636 tz_id,
637 tz_jiff,
638 ..
639 } => DateTime::Zoned {
640 date: *end_date,
641 time: *end_time,
642 tz_id: tz_id.clone(),
643 tz_jiff: tz_jiff.clone(),
644 x_parameters: Vec::new(),
645 retained_parameters: Vec::new(),
646 },
647 Period::DurationUtc {
648 start_date,
649 start_time,
650 duration,
651 ..
652 } => {
653 let start = jiff::civil::DateTime::from_parts(
654 start_date.civil_date(),
655 start_time.civil_time(),
656 );
657 let end = add_duration(start, duration);
658 DateTime::Utc {
659 date: ValueDate {
660 year: end.year(),
661 month: end.month(),
662 day: end.day(),
663 },
664 #[expect(clippy::cast_sign_loss)]
665 time: Time::new(end.hour() as u8, end.minute() as u8, end.second() as u8)
666 .map_err(|e| format!("invalid time: {e}"))
667 .unwrap(), x_parameters: Vec::new(),
669 retained_parameters: Vec::new(),
670 }
671 }
672 Period::DurationFloating {
673 start_date,
674 start_time,
675 duration,
676 ..
677 } => {
678 let start = jiff::civil::DateTime::from_parts(
679 start_date.civil_date(),
680 start_time.civil_time(),
681 );
682 let end = add_duration(start, duration);
683 DateTime::Floating {
684 date: ValueDate {
685 year: end.year(),
686 month: end.month(),
687 day: end.day(),
688 },
689 #[expect(clippy::cast_sign_loss)]
690 time: Time::new(end.hour() as u8, end.minute() as u8, end.second() as u8)
691 .map_err(|e| format!("invalid time: {e}"))
692 .unwrap(), x_parameters: Vec::new(),
694 retained_parameters: Vec::new(),
695 }
696 }
697 Period::DurationZoned {
698 start_date,
699 start_time,
700 tz_id,
701 tz_jiff,
702 duration,
703 } => {
704 let start = jiff::civil::DateTime::from_parts(
705 start_date.civil_date(),
706 start_time.civil_time(),
707 );
708 let end = add_duration(start, duration);
709 DateTime::Zoned {
710 date: ValueDate {
711 year: end.year(),
712 month: end.month(),
713 day: end.day(),
714 },
715 #[expect(clippy::cast_sign_loss)]
716 time: Time::new(end.hour() as u8, end.minute() as u8, end.second() as u8)
717 .expect("invalid time"),
718 tz_id: tz_id.clone(),
719 tz_jiff: tz_jiff.clone(),
720 x_parameters: Vec::new(),
721 retained_parameters: Vec::new(),
722 }
723 }
724 }
725 }
726
727 #[cfg(feature = "jiff")]
729 #[must_use]
730 #[rustfmt::skip]
731 pub fn start_civil(&self) -> jiff::civil::DateTime {
732 match self {
733 Period::ExplicitUtc { start_date, start_time, .. }
734 | Period::ExplicitFloating { start_date, start_time, .. }
735 | Period::ExplicitZoned { start_date, start_time, .. }
736 | Period::DurationUtc { start_date, start_time, .. }
737 | Period::DurationFloating { start_date, start_time, .. }
738 | Period::DurationZoned { start_date, start_time, .. } => {
739 jiff::civil::DateTime::from_parts(start_date.civil_date(), start_time.civil_time())
740 }
741 }
742 }
743
744 #[cfg(feature = "jiff")]
748 #[must_use]
749 pub fn end_civil(&self) -> jiff::civil::DateTime {
750 self.start().civil_date_time().unwrap_or_default() }
752}
753
754impl<'src> TryFrom<Value<Segments<'src>>> for Period<Segments<'src>> {
755 type Error = Vec<TypedError<'src>>;
756
757 fn try_from(value: Value<Segments<'src>>) -> Result<Self, Self::Error> {
758 let span = value.span();
759 match value {
760 Value::Period { mut values, .. } if values.len() == 1 => {
761 let value_period = values.pop().unwrap();
762 match value_period {
763 ValuePeriod::Explicit { start, end } if start.time.utc => {
765 Ok(Period::ExplicitUtc {
766 start_date: start.date,
767 start_time: start.time.into(),
768 end_date: end.date,
769 end_time: end.time.into(),
770 })
771 }
772 ValuePeriod::Explicit { start, end } => Ok(Period::ExplicitFloating {
773 start_date: start.date,
774 start_time: start.time.into(),
775 end_date: end.date,
776 end_time: end.time.into(),
777 }),
778 ValuePeriod::Duration { start, duration } => {
779 if !matches!(duration, ValueDuration::DateTime { positive: true, .. })
781 && !matches!(duration, ValueDuration::Week { positive: true, .. })
782 {
783 return Err(vec![TypedError::PropertyInvalidValue {
784 property: PropertyKind::FreeBusy,
785 value: "Duration must be positive for periods".to_string(),
786 span,
787 }]);
788 }
789
790 if start.time.utc {
791 Ok(Period::DurationUtc {
792 start_date: start.date,
793 start_time: start.time.into(),
794 duration,
795 })
796 } else {
797 Ok(Period::DurationFloating {
798 start_date: start.date,
799 start_time: start.time.into(),
800 duration,
801 })
802 }
803 }
804 }
805 }
806 _ => Err(vec![TypedError::ValueTypeDisallowed {
807 property: PropertyKind::FreeBusy,
808 value_type: value.kind().into(),
809 expected_types: &[ValueType::Period],
810 span,
811 }]),
812 }
813 }
814}
815
816impl Period<Segments<'_>> {
817 #[must_use]
819 pub fn to_owned(&self) -> Period<String> {
820 match self {
821 Period::ExplicitUtc {
822 start_date,
823 start_time,
824 end_date,
825 end_time,
826 } => Period::ExplicitUtc {
827 start_date: *start_date,
828 start_time: *start_time,
829 end_date: *end_date,
830 end_time: *end_time,
831 },
832 Period::ExplicitFloating {
833 start_date,
834 start_time,
835 end_date,
836 end_time,
837 } => Period::ExplicitFloating {
838 start_date: *start_date,
839 start_time: *start_time,
840 end_date: *end_date,
841 end_time: *end_time,
842 },
843 Period::ExplicitZoned {
844 start_date,
845 start_time,
846 end_date,
847 end_time,
848 tz_id,
849 #[cfg(feature = "jiff")]
850 tz_jiff,
851 } => Period::ExplicitZoned {
852 start_date: *start_date,
853 start_time: *start_time,
854 end_date: *end_date,
855 end_time: *end_time,
856 tz_id: tz_id.to_owned(),
857 #[cfg(feature = "jiff")]
858 tz_jiff: tz_jiff.clone(),
859 },
860 Period::DurationUtc {
861 start_date,
862 start_time,
863 duration,
864 } => Period::DurationUtc {
865 start_date: *start_date,
866 start_time: *start_time,
867 duration: *duration,
868 },
869 Period::DurationFloating {
870 start_date,
871 start_time,
872 duration,
873 } => Period::DurationFloating {
874 start_date: *start_date,
875 start_time: *start_time,
876 duration: *duration,
877 },
878 Period::DurationZoned {
879 start_date,
880 start_time,
881 duration,
882 tz_id,
883 #[cfg(feature = "jiff")]
884 tz_jiff,
885 } => Period::DurationZoned {
886 start_date: *start_date,
887 start_time: *start_time,
888 duration: *duration,
889 tz_id: tz_id.to_owned(),
890 #[cfg(feature = "jiff")]
891 tz_jiff: tz_jiff.clone(),
892 },
893 }
894 }
895}
896
897#[cfg(feature = "jiff")]
898fn add_duration(start: jiff::civil::DateTime, duration: &ValueDuration) -> jiff::civil::DateTime {
899 match duration {
900 ValueDuration::DateTime {
901 positive,
902 day,
903 hour,
904 minute,
905 second,
906 } => {
907 let span = jiff::Span::new()
908 .try_days(i64::from(*day))
909 .unwrap()
910 .try_hours(i64::from(*hour))
911 .unwrap()
912 .try_minutes(i64::from(*minute))
913 .unwrap()
914 .try_seconds(i64::from(*second))
915 .unwrap();
916
917 if *positive {
918 start.checked_add(span).unwrap()
919 } else {
920 start.checked_sub(span).unwrap()
921 }
922 }
923 ValueDuration::Week { positive, week } => {
924 let span = jiff::Span::new().try_weeks(i64::from(*week)).unwrap();
925 if *positive {
926 start.checked_add(span).unwrap()
927 } else {
928 start.checked_sub(span).unwrap()
929 }
930 }
931 }
932}
933
934simple_property_wrapper!(
937 pub Completed<S> => DateTime
939);
940
941impl Completed<String> {
942 #[must_use]
944 pub fn new(value: DateTime<String>) -> Self {
945 Self {
946 inner: value,
947 span: (),
948 }
949 }
950}
951
952simple_property_wrapper!(
953 pub DtEnd<S> => DateTime
955);
956
957impl DtEnd<String> {
958 #[must_use]
960 pub fn new(value: DateTime<String>) -> Self {
961 Self {
962 inner: value,
963 span: (),
964 }
965 }
966}
967
968simple_property_wrapper!(
969 pub Due<S> => DateTime
971);
972
973impl Due<String> {
974 #[must_use]
976 pub fn new(value: DateTime<String>) -> Self {
977 Self {
978 inner: value,
979 span: (),
980 }
981 }
982}
983
984simple_property_wrapper!(
985 pub DtStart<S> => DateTime
987);
988
989impl DtStart<String> {
990 #[must_use]
992 pub fn new(value: DateTime<String>) -> Self {
993 Self {
994 inner: value,
995 span: (),
996 }
997 }
998}
999
1000#[derive(Debug, Clone)]
1004pub struct Duration<S: StringStorage> {
1005 pub value: ValueDuration,
1007 pub x_parameters: Vec<RawParameter<S>>,
1009 pub retained_parameters: Vec<Parameter<S>>,
1011 pub span: S::Span,
1013}
1014
1015impl<'src> TryFrom<ParsedProperty<'src>> for Duration<Segments<'src>> {
1016 type Error = Vec<TypedError<'src>>;
1017
1018 fn try_from(prop: ParsedProperty<'src>) -> Result<Self, Self::Error> {
1019 if !matches!(prop.kind, PropertyKind::Duration) {
1020 return Err(vec![TypedError::PropertyUnexpectedKind {
1021 expected: PropertyKind::Duration,
1022 found: prop.kind,
1023 span: prop.span,
1024 }]);
1025 }
1026
1027 let mut x_parameters = Vec::new();
1028 let mut retained_parameters = Vec::new();
1029
1030 for param in prop.parameters {
1031 match param {
1032 Parameter::XName(raw) => x_parameters.push(raw),
1033 p @ Parameter::Unrecognized { .. } => retained_parameters.push(p),
1034 p => {
1035 retained_parameters.push(p);
1037 }
1038 }
1039 }
1040
1041 match take_single_value(&PropertyKind::Duration, prop.value) {
1042 Ok(Value::Duration { values, .. }) if values.is_empty() => {
1043 Err(vec![TypedError::PropertyMissingValue {
1044 property: prop.kind,
1045 span: prop.span,
1046 }])
1047 }
1048 Ok(Value::Duration { values, .. }) if values.len() != 1 => {
1049 Err(vec![TypedError::PropertyInvalidValueCount {
1050 property: prop.kind,
1051 expected: 1,
1052 found: values.len(),
1053 span: prop.span,
1054 }])
1055 }
1056 Ok(Value::Duration { mut values, .. }) => Ok(Self {
1057 value: values.pop().unwrap(), x_parameters,
1059 retained_parameters,
1060 span: prop.span,
1061 }),
1062 Ok(v) => {
1063 const EXPECTED: &[ValueType<String>] = &[ValueType::Duration];
1064 let span = v.span();
1065 Err(vec![TypedError::PropertyUnexpectedValue {
1066 property: prop.kind,
1067 expected: EXPECTED,
1068 found: v.kind().into(),
1069 span,
1070 }])
1071 }
1072 Err(e) => Err(e),
1073 }
1074 }
1075}
1076
1077impl Duration<Segments<'_>> {
1078 #[must_use]
1080 pub fn to_owned(&self) -> Duration<String> {
1081 Duration {
1082 value: self.value,
1083 x_parameters: self
1084 .x_parameters
1085 .iter()
1086 .map(RawParameter::to_owned)
1087 .collect(),
1088 retained_parameters: self
1089 .retained_parameters
1090 .iter()
1091 .map(Parameter::to_owned)
1092 .collect(),
1093 span: (),
1094 }
1095 }
1096}
1097
1098#[derive(Debug, Clone)]
1102pub struct FreeBusy<S: StringStorage> {
1103 pub fb_type: FreeBusyType<S>,
1105 pub values: Vec<Period<S>>,
1107 pub x_parameters: Vec<RawParameter<S>>,
1109 pub retained_parameters: Vec<Parameter<S>>,
1111 pub span: S::Span,
1113}
1114
1115impl<'src> TryFrom<ParsedProperty<'src>> for FreeBusy<Segments<'src>> {
1116 type Error = Vec<TypedError<'src>>;
1117
1118 fn try_from(prop: ParsedProperty<'src>) -> Result<Self, Self::Error> {
1119 if !matches!(prop.kind, PropertyKind::FreeBusy) {
1120 return Err(vec![TypedError::PropertyUnexpectedKind {
1121 expected: PropertyKind::FreeBusy,
1122 found: prop.kind,
1123 span: prop.span,
1124 }]);
1125 }
1126
1127 let mut fb_type: FreeBusyType<Segments<'src>> = FreeBusyType::Busy;
1129 let mut x_parameters = Vec::new();
1130 let mut retained_parameters = Vec::new();
1131
1132 for param in prop.parameters {
1133 match param {
1134 Parameter::FreeBusyType { value, .. } => {
1135 fb_type = value.clone();
1136 }
1137 Parameter::XName(raw) => x_parameters.push(raw),
1138 p @ Parameter::Unrecognized { .. } => retained_parameters.push(p),
1139 p => {
1140 retained_parameters.push(p);
1142 }
1143 }
1144 }
1145
1146 let (periods, value_span) = match prop.value {
1147 Value::Period { values, span: _ } if values.is_empty() => {
1148 return Err(vec![TypedError::PropertyMissingValue {
1149 property: prop.kind,
1150 span: prop.span,
1151 }]);
1152 }
1153 Value::Period { values, span } => (values, span),
1154 v => {
1155 const EXPECTED: &[ValueType<String>] = &[ValueType::Period];
1156 let span = v.span();
1157 return Err(vec![TypedError::PropertyUnexpectedValue {
1158 property: prop.kind,
1159 expected: EXPECTED,
1160 found: v.kind().into(),
1161 span,
1162 }]);
1163 }
1164 };
1165
1166 let mut values = Vec::with_capacity(periods.len());
1167 let mut errors: Vec<TypedError<'src>> = Vec::new();
1168 for value_period in periods {
1169 let period = match value_period {
1170 ValuePeriod::Explicit { start, end } if start.time.utc => Period::ExplicitUtc {
1172 start_date: start.date,
1173 start_time: start.time.into(),
1174 end_date: end.date,
1175 end_time: end.time.into(),
1176 },
1177 ValuePeriod::Explicit { start, end } => Period::ExplicitFloating {
1178 start_date: start.date,
1179 start_time: start.time.into(),
1180 end_date: end.date,
1181 end_time: end.time.into(),
1182 },
1183 ValuePeriod::Duration { start, duration } => {
1184 if !matches!(duration, ValueDuration::DateTime { positive: true, .. })
1186 && !matches!(duration, ValueDuration::Week { positive: true, .. })
1187 {
1188 errors.push(TypedError::PropertyInvalidValue {
1190 property: prop.kind.clone(),
1191 value: "Duration must be positive for periods".to_string(),
1192 span: value_span,
1193 });
1194 continue;
1195 }
1196
1197 if start.time.utc {
1198 Period::DurationUtc {
1199 start_date: start.date,
1200 start_time: start.time.into(),
1201 duration,
1202 }
1203 } else {
1204 Period::DurationFloating {
1205 start_date: start.date,
1206 start_time: start.time.into(),
1207 duration,
1208 }
1209 }
1210 }
1211 };
1212
1213 values.push(period);
1214 }
1215
1216 if !errors.is_empty() {
1217 return Err(errors);
1218 }
1219
1220 Ok(FreeBusy {
1221 fb_type,
1222 values,
1223 x_parameters,
1224 retained_parameters,
1225 span: prop.span,
1226 })
1227 }
1228}
1229
1230impl FreeBusy<Segments<'_>> {
1231 #[must_use]
1233 pub fn to_owned(&self) -> FreeBusy<String> {
1234 FreeBusy {
1235 fb_type: self.fb_type.to_owned(),
1236 values: self.values.iter().map(Period::to_owned).collect(),
1237 x_parameters: self
1238 .x_parameters
1239 .iter()
1240 .map(RawParameter::to_owned)
1241 .collect(),
1242 retained_parameters: self
1243 .retained_parameters
1244 .iter()
1245 .map(Parameter::to_owned)
1246 .collect(),
1247 span: (),
1248 }
1249 }
1250}
1251
1252define_prop_value_enum! {
1253 #[derive(Default)]
1255 pub enum TimeTransparencyValue {
1256 #[default]
1258 Opaque => KW_TRANSP_OPAQUE,
1259 Transparent => KW_TRANSP_TRANSPARENT,
1261 }
1262}
1263
1264#[derive(Debug, Clone, Default)]
1266pub struct TimeTransparency<S: StringStorage> {
1267 pub value: TimeTransparencyValue,
1269 pub x_parameters: Vec<RawParameter<S>>,
1271 pub retained_parameters: Vec<Parameter<S>>,
1273 pub span: S::Span,
1275}
1276
1277impl<'src> TryFrom<ParsedProperty<'src>> for TimeTransparency<Segments<'src>> {
1278 type Error = Vec<TypedError<'src>>;
1279
1280 fn try_from(prop: ParsedProperty<'src>) -> Result<Self, Self::Error> {
1281 if !matches!(prop.kind, PropertyKind::Transp) {
1282 return Err(vec![TypedError::PropertyUnexpectedKind {
1283 expected: PropertyKind::Transp,
1284 found: prop.kind,
1285 span: prop.span,
1286 }]);
1287 }
1288
1289 let mut x_parameters = Vec::new();
1290 let mut retained_parameters = Vec::new();
1291
1292 for param in prop.parameters {
1293 match param {
1294 Parameter::XName(raw) => x_parameters.push(raw),
1295 p @ Parameter::Unrecognized { .. } => retained_parameters.push(p),
1296 p => {
1297 retained_parameters.push(p);
1299 }
1300 }
1301 }
1302
1303 let value_span = prop.value.span();
1304 let text = take_single_text(&PropertyKind::Transp, prop.value)?;
1305 let value = text.try_into().map_err(|value| {
1306 vec![TypedError::PropertyInvalidValue {
1307 property: PropertyKind::Transp,
1308 value: format!("Invalid time transparency value: {value}"),
1309 span: value_span,
1310 }]
1311 })?;
1312
1313 Ok(TimeTransparency {
1314 value,
1315 x_parameters,
1316 retained_parameters,
1317 span: prop.span,
1318 })
1319 }
1320}
1321
1322impl TimeTransparency<Segments<'_>> {
1323 #[must_use]
1325 pub fn to_owned(&self) -> TimeTransparency<String> {
1326 TimeTransparency {
1327 value: self.value,
1328 x_parameters: self
1329 .x_parameters
1330 .iter()
1331 .map(RawParameter::to_owned)
1332 .collect(),
1333 retained_parameters: self
1334 .retained_parameters
1335 .iter()
1336 .map(Parameter::to_owned)
1337 .collect(),
1338 span: (),
1339 }
1340 }
1341}