opsview/config/
timeperiod.rs

1use super::{HostRef, ServiceCheckRef, TimeZone};
2use crate::{prelude::*, util::*};
3use serde::{Deserialize, Serialize};
4use std::sync::Arc;
5
6/// Represents a [Time Period](https://docs.itrsgroup.com/docs/opsview/current/configuration/time-periods/time-periods/index.html) in Opsview.
7///
8/// Time periods are used within Opsview Monitor for one of two purposes; determining when a
9/// [`super::Host`] or [`super::ServiceCheck`] is being actively monitored, and determining when
10/// notifications should be sent.
11///
12/// For example, if a Host only needs to be monitored during office hours, then an administrator can
13/// create a Time Period called ‘Working Hours’ that is 9am-5pm, Monday to Friday, and then ‘apply’
14/// this `TimePeriod` to the Host via a field called the ‘Check Period’, i.e. ‘What *period *of time
15/// should I actively *check *this Host’. Service Checks can be configured to have a fixed Time
16/// Period or to inherit the check period from the Host. This will apply to all Service Checks
17/// whether the Service Checks are applied individually via the ‘Service Checks’ tab or in bulk via
18/// the addition of a Host template.
19///
20/// There are seven days within the `TimePeriod`, Sunday through to Saturday. In each day, the hours
21/// can be defined in an ‘HH:MM’ format, and comma-separated for multiple ranges. For example,
22/// ‘00:00-24:00’ means ‘all day’, ‘09:00-17:00’ means ‘9am to 5pm’, ‘00:00-09:00,17:00-23:59’ means
23/// ’not 9-5pm’, and so forth.
24///
25/// An important point to note is the hours defined do not go over the midnight boundary, for
26/// example “22:00-02:00” is not valid - instead use ‘22:00-23:59’ on the first day, and
27/// ‘00:00-02:00’ on the following day.
28///
29/// This struct represents the structure of a time period entity as used in the [Opsview
30/// API](https://docs.itrsgroup.com/docs/opsview/current/rest-api/config/api-config-time-period/index.html).
31///
32/// # Example
33/// ```rust
34/// use opsview::config::TimePeriod;
35/// use opsview::prelude::*;
36///
37/// let time_period = TimePeriod::builder()
38///    .name("MyTimePeriod")
39///    .alias("My Time Period Alias")
40///    .monday("00:00-09:00,17:00-24:00")
41///    .tuesday("00:00-09:00,17:00-24:00")
42///    .wednesday("00:00-09:00,17:00-24:00")
43///    .thursday("00:00-09:00,17:00-24:00")
44///    .friday("00:00-09:00,17:00-24:00")
45///    .saturday("00:00-09:00,17:00-24:00")
46///    .sunday("00:00-09:00,17:00-24:00")
47///    .build()
48///    .unwrap();
49///
50/// assert_eq!(time_period.name, "MyTimePeriod".to_string());
51/// assert_eq!(time_period.alias, Some("My Time Period Alias".to_string()));
52/// assert_eq!(time_period.monday, Some("00:00-09:00,17:00-24:00".to_string()));
53/// ```
54#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
55pub struct TimePeriod {
56    // Required fields ---------------------------------------------------------------------------//
57    /// The name of the `TimePeriod`.
58    pub name: String,
59
60    // Optional fields ---------------------------------------------------------------------------//
61    /// Optional alias for the `TimePeriod`.
62    #[serde(skip_serializing_if = "Option::is_none")]
63    pub alias: Option<String>,
64
65    /// Optional string representing the `TimePeriod` for Friday.
66    ///
67    /// Example: `"00:00-09:00,17:00-24:00"`
68    #[serde(skip_serializing_if = "Option::is_none")]
69    pub friday: Option<String>,
70
71    /// Optional string representing the `TimePeriod` for Monday.
72    ///
73    /// Example: `"00:00-09:00,17:00-24:00"`
74    #[serde(skip_serializing_if = "Option::is_none")]
75    pub monday: Option<String>,
76
77    /// Optional string representing the `TimePeriod` for Saturday.
78    ///
79    /// Example: `"00:00-09:00,17:00-24:00"`
80    #[serde(skip_serializing_if = "Option::is_none")]
81    pub saturday: Option<String>,
82
83    /// Optional string representing the `TimePeriod` for Sunday.
84    ///
85    /// Example: `"00:00-09:00,17:00-24:00"`
86    #[serde(skip_serializing_if = "Option::is_none")]
87    pub sunday: Option<String>,
88
89    /// Optional string representing the `TimePeriod` for Thursday.
90    ///
91    /// Example: `"00:00-09:00,17:00-24:00"`
92    #[serde(skip_serializing_if = "Option::is_none")]
93    pub thursday: Option<String>,
94
95    /// Optional string representing the `TimePeriod` for Tuesday.
96    ///
97    /// Example: `"00:00-09:00,17:00-24:00"`
98    #[serde(skip_serializing_if = "Option::is_none")]
99    pub tuesday: Option<String>,
100
101    /// Optional string representing the `TimePeriod` for Wednesday.
102    ///
103    /// Example: `"00:00-09:00,17:00-24:00"`
104    #[serde(skip_serializing_if = "Option::is_none")]
105    pub wednesday: Option<String>,
106
107    /// Optional [`TimeZone`] in which the `TimePeriod` is defined.
108    ///
109    /// Default: `Some(TimeZone{ name: "SYSTEM".to_string(), ref_: "/rest/config/timezone/1".to_string() })`.
110    #[serde(skip_serializing_if = "Option::is_none")]
111    pub zone: Option<TimeZone>,
112
113    // Read-only fields --------------------------------------------------------------------------//
114    /// [`ConfigRefMap`] of [`HostRef`] objects associated with this `TimePeriod` for their checks.
115    #[serde(skip_serializing_if = "Option::is_none")]
116    pub host_check_periods: Option<ConfigRefMap<HostRef>>,
117
118    /// [`ConfigRefMap`] of [`HostRef`] objects associated with this `TimePeriod` for their notifications.
119    #[serde(skip_serializing_if = "Option::is_none")]
120    pub host_notification_periods: Option<ConfigRefMap<HostRef>>,
121
122    /// The unique identifier of the `TimePeriod`.
123    #[serde(
124        skip_serializing_if = "Option::is_none",
125        deserialize_with = "deserialize_string_or_number_to_u64",
126        default
127    )]
128    pub id: Option<u64>,
129
130    // TODO: This is not in the API documentation, but is returned in the JSON response.
131    //       It is not clear what this field is used for.
132    /// Optional boolean indicating whether the `TimePeriod` is locked.
133    #[serde(
134        skip_serializing_if = "Option::is_none",
135        deserialize_with = "deserialize_string_or_number_to_option_bool",
136        serialize_with = "serialize_option_bool_as_string",
137        default
138    )]
139    pub object_locked: Option<bool>,
140
141    /// A reference string unique to this `TimePeriod`.
142    #[serde(
143        rename = "ref",
144        skip_serializing_if = "Option::is_none",
145        deserialize_with = "deserialize_readonly",
146        default
147    )]
148    pub ref_: Option<String>,
149
150    /// [`ConfigRefMap`] of [`ServiceCheckRef`] objects associated with this `TimePeriod` for their checks.
151    #[serde(skip_serializing_if = "Option::is_none")]
152    pub servicecheck_check_periods: Option<ConfigRefMap<ServiceCheckRef>>,
153
154    /// [`ConfigRefMap`] of [`ServiceCheckRef`] objects associated with this `TimePeriod` for their notifications.
155    #[serde(skip_serializing_if = "Option::is_none")]
156    pub servicecheck_notification_periods: Option<ConfigRefMap<ServiceCheckRef>>,
157
158    /// A boolean indicating whether the `TimePeriod` is uncommitted.
159    #[serde(
160        skip_serializing_if = "Option::is_none",
161        deserialize_with = "deserialize_string_or_number_to_option_bool",
162        serialize_with = "serialize_option_bool_as_string",
163        default
164    )]
165    pub uncommitted: Option<bool>,
166}
167
168impl Default for TimePeriod {
169    fn default() -> Self {
170        let tz = TimeZone::builder()
171            .name("SYSTEM")
172            .ref_("/rest/config/timezone/1")
173            .build()
174            .unwrap();
175
176        TimePeriod {
177            name: String::new(),
178            alias: None,
179            friday: None,
180            host_check_periods: None,
181            host_notification_periods: None,
182            id: None,
183            monday: None,
184            object_locked: None,
185            ref_: None,
186            saturday: None,
187            servicecheck_check_periods: None,
188            servicecheck_notification_periods: None,
189            sunday: None,
190            thursday: None,
191            tuesday: None,
192            uncommitted: None,
193            wednesday: None,
194            zone: Some(tz),
195        }
196    }
197}
198
199/// Enables the creation of a [`TimePeriod`] instance from a JSON representation.
200/// Typically used when parsing JSON data from the Opsview API.
201impl CreateFromJson for TimePeriod {}
202
203impl ConfigObject for TimePeriod {
204    type Builder = TimePeriodBuilder;
205
206    /// Returns a builder for constructing a [`TimePeriod`] object.
207    ///
208    /// # Returns
209    /// A [`TimePeriodBuilder`] object.
210    fn builder() -> Self::Builder {
211        TimePeriodBuilder::new()
212    }
213
214    /// Provides the configuration path for a [`TimePeriod`] object within the Opsview system.
215    ///
216    /// # Returns
217    /// A string representing the API path where time periods are configured.
218    fn config_path() -> Option<String> {
219        Some("/config/timeperiod".to_string())
220    }
221
222    /// Returns the unique name of the [`TimePeriod`] object.
223    ///
224    /// This name is used to identify the `TimePeriod` when building the `HashMap` for an
225    /// `ConfigObjectMap`.
226    ///
227    /// # Returns
228    /// A string representing the unique name of the `TimePeriod`.
229    fn unique_name(&self) -> String {
230        self.name.clone()
231    }
232
233    fn minimal(name: &str) -> Result<Self, OpsviewConfigError> {
234        Ok(Self {
235            name: validate_and_trim_timeperiod_name(name)?,
236            ..Default::default()
237        })
238    }
239}
240
241impl Persistent for TimePeriod {
242    /// Returns the unique identifier.
243    fn id(&self) -> Option<u64> {
244        self.id
245    }
246
247    /// Returns the reference string if it's not empty.
248    fn ref_(&self) -> Option<String> {
249        if self.ref_.as_ref().is_some_and(|x| !x.is_empty()) {
250            self.ref_.clone()
251        } else {
252            None
253        }
254    }
255    /// Returns the name if it's not empty.
256    fn name(&self) -> Option<String> {
257        if self.name.is_empty() {
258            None
259        } else {
260            Some(self.name.clone())
261        }
262    }
263
264    fn name_regex(&self) -> Option<String> {
265        Some(TIMEPERIOD_NAME_REGEX_STR.to_string())
266    }
267
268    fn validated_name(&self, name: &str) -> Result<String, OpsviewConfigError> {
269        validate_and_trim_timeperiod_name(name)
270    }
271
272    fn set_name(&mut self, new_name: &str) -> Result<String, OpsviewConfigError> {
273        self.name = self.validated_name(new_name)?;
274        Ok(self.name.clone())
275    }
276
277    fn clear_readonly(&mut self) {
278        self.host_check_periods = None;
279        self.host_notification_periods = None;
280        self.id = None;
281        self.object_locked = None;
282        self.ref_ = None;
283        self.servicecheck_check_periods = None;
284        self.servicecheck_notification_periods = None;
285        self.uncommitted = None;
286    }
287}
288
289impl PersistentMap for ConfigObjectMap<TimePeriod> {
290    fn config_path() -> Option<String> {
291        Some("/config/timeperiod".to_string())
292    }
293}
294
295/// Builder for [`TimePeriod`] objects.
296///
297/// Provides a fluent interface for constructing a `TimePeriod` object with optional fields.
298#[derive(Clone, Debug, Default)]
299pub struct TimePeriodBuilder {
300    alias: Option<String>,
301    friday: Option<String>,
302    monday: Option<String>,
303    name: Option<String>,
304    saturday: Option<String>,
305    sunday: Option<String>,
306    thursday: Option<String>,
307    tuesday: Option<String>,
308    wednesday: Option<String>,
309    zone: Option<TimeZone>,
310}
311
312impl Builder for TimePeriodBuilder {
313    type ConfigObject = TimePeriod;
314
315    /// Creates a new [`TimePeriodBuilder`] instance used to construct a [`TimePeriod`] object.
316    ///
317    /// # Returns
318    /// A `TimePeriodBuilder` instance.
319    fn new() -> Self {
320        Self::default()
321    }
322
323    /// Sets the name field for the `TimePeriod`.
324    ///
325    /// # Arguments
326    /// * `name` - The name for the `TimePeriod`.
327    fn name(mut self, name: &str) -> Self {
328        self.name = Some(name.to_string());
329        self
330    }
331
332    /// Consumes the builder and returns a [`TimePeriod`] object.
333    ///
334    /// # Returns
335    /// A `TimePeriod` object with the configured settings.
336    ///
337    /// # Errors
338    /// Returns a `OpsviewConfigError` if the name field is not set.
339    fn build(self) -> Result<Self::ConfigObject, OpsviewConfigError> {
340        let name = require_field(&self.name, "name")?;
341        let validated_alias = validate_opt_string(self.alias, validate_and_trim_timeperiod_alias)?;
342        let validated_monday =
343            validate_opt_string(self.monday, validate_and_trim_timeperiod_weekday)?;
344        let validated_tuesday =
345            validate_opt_string(self.tuesday, validate_and_trim_timeperiod_weekday)?;
346        let validated_wednesday =
347            validate_opt_string(self.wednesday, validate_and_trim_timeperiod_weekday)?;
348        let validated_thursday =
349            validate_opt_string(self.thursday, validate_and_trim_timeperiod_weekday)?;
350        let validated_friday =
351            validate_opt_string(self.friday, validate_and_trim_timeperiod_weekday)?;
352        let validated_saturday =
353            validate_opt_string(self.saturday, validate_and_trim_timeperiod_weekday)?;
354        let validated_sunday =
355            validate_opt_string(self.sunday, validate_and_trim_timeperiod_weekday)?;
356
357        Ok(TimePeriod {
358            name: validate_and_trim_timeperiod_name(&name)?,
359            alias: validated_alias,
360            friday: validated_friday,
361            monday: validated_monday,
362            saturday: validated_saturday,
363            sunday: validated_sunday,
364            thursday: validated_thursday,
365            tuesday: validated_tuesday,
366            wednesday: validated_wednesday,
367            zone: self.zone,
368            host_check_periods: None,
369            host_notification_periods: None,
370            id: None,
371            object_locked: None,
372            ref_: None,
373            servicecheck_check_periods: None,
374            servicecheck_notification_periods: None,
375            uncommitted: None,
376        })
377    }
378}
379
380impl TimePeriodBuilder {
381    /// Sets the alias field for the `TimePeriod`.
382    ///
383    /// # Arguments
384    /// * `alias` - The alias for the `TimePeriod`.
385    pub fn alias(mut self, alias: &str) -> Self {
386        self.alias = Some(alias.to_string());
387        self
388    }
389
390    /// Clears the alias field for the `TimePeriod`.
391    pub fn clear_alias(mut self) -> Self {
392        self.alias = None;
393        self
394    }
395
396    /// Clears the friday field for the `TimePeriod`.
397    pub fn clear_friday(mut self) -> Self {
398        self.friday = None;
399        self
400    }
401
402    /// Clears the monday field for the `TimePeriod`.
403    pub fn clear_monday(mut self) -> Self {
404        self.monday = None;
405        self
406    }
407
408    /// Clears the name field for the `TimePeriod`.
409    pub fn clear_name(mut self) -> Self {
410        self.name = None;
411        self
412    }
413
414    /// Clears the saturday field for the `TimePeriod`.
415    pub fn clear_saturday(mut self) -> Self {
416        self.saturday = None;
417        self
418    }
419
420    /// Clears the sunday field for the `TimePeriod`.
421    pub fn clear_sunday(mut self) -> Self {
422        self.sunday = None;
423        self
424    }
425
426    /// Clears the thursday field for the `TimePeriod`.
427    pub fn clear_thursday(mut self) -> Self {
428        self.thursday = None;
429        self
430    }
431
432    /// Clears the tuesday field for the `TimePeriod`.
433    pub fn clear_tuesday(mut self) -> Self {
434        self.tuesday = None;
435        self
436    }
437
438    /// Clears the wednesday field for the `TimePeriod`.
439    pub fn clear_wednesday(mut self) -> Self {
440        self.wednesday = None;
441        self
442    }
443
444    /// Clears the zone field for the `TimePeriod`.
445    pub fn clear_zone(mut self) -> Self {
446        self.zone = None;
447        self
448    }
449
450    /// Sets the friday field for the `TimePeriod`.
451    ///
452    /// # Arguments
453    /// * `friday` - The friday field for the `TimePeriod`.
454    ///
455    /// Example: `"00:00-09:00,17:00-24:00"`
456    pub fn friday(mut self, friday: &str) -> Self {
457        self.friday = Some(friday.to_string());
458        self
459    }
460
461    /// Sets the monday field for the `TimePeriod`.
462    ///
463    /// # Arguments
464    /// * `monday` - The monday field for the `TimePeriod`.
465    ///
466    /// Example: `"00:00-09:00,17:00-24:00"`
467    pub fn monday(mut self, monday: &str) -> Self {
468        self.monday = Some(monday.to_string());
469        self
470    }
471
472    /// Sets the saturday field for the `TimePeriod`.
473    ///
474    /// # Arguments
475    /// * `saturday` - The saturday field for the `TimePeriod`.
476    ///
477    /// Example: `"00:00-09:00,17:00-24:00"`
478    pub fn saturday(mut self, saturday: &str) -> Self {
479        self.saturday = Some(saturday.to_string());
480        self
481    }
482
483    /// Sets the sunday field for the `TimePeriod`.
484    ///
485    /// # Arguments
486    /// * `sunday` - The sunday field for the `TimePeriod`.
487    ///
488    /// Example: `"00:00-09:00,17:00-24:00"`
489    pub fn sunday(mut self, sunday: &str) -> Self {
490        self.sunday = Some(sunday.to_string());
491        self
492    }
493
494    /// Sets the thursday field for the `TimePeriod`.
495    ///
496    /// # Arguments
497    /// * `thursday` - The thursday field for the `TimePeriod`.
498    ///
499    /// Example: `"00:00-09:00,17:00-24:00"`
500    pub fn thursday(mut self, thursday: &str) -> Self {
501        self.thursday = Some(thursday.to_string());
502        self
503    }
504
505    /// Sets the tuesday field for the `TimePeriod`.
506    ///
507    /// # Arguments
508    /// * `tuesday` - The tuesday field for the `TimePeriod`.
509    ///
510    /// Example: `"00:00-09:00,17:00-24:00"`
511    pub fn tuesday(mut self, tuesday: &str) -> Self {
512        self.tuesday = Some(tuesday.to_string());
513        self
514    }
515
516    /// Sets the wednesday field for the `TimePeriod`.
517    ///
518    /// # Arguments
519    /// * `wednesday` - The wednesday field for the `TimePeriod`.
520    ///
521    /// Example: `"00:00-09:00,17:00-24:00"`
522    pub fn wednesday(mut self, wednesday: &str) -> Self {
523        self.wednesday = Some(wednesday.to_string());
524        self
525    }
526
527    /// Sets the zone field for the `TimePeriod`.
528    ///
529    /// # Arguments
530    /// * `zone` - The [`TimeZone`] in which the `TimePeriod` is defined.
531    pub fn zone(mut self, zone: TimeZone) -> Self {
532        self.zone = Some(zone);
533        self
534    }
535}
536
537/// A reference version of [`TimePeriod`] that is used when passing or retrieving a
538/// [`TimePeriod`] object as part of another object.
539#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq)]
540pub struct TimePeriodRef {
541    name: String,
542    #[serde(
543        rename = "ref",
544        skip_serializing_if = "Option::is_none",
545        deserialize_with = "deserialize_readonly",
546        default
547    )]
548    ref_: Option<String>,
549}
550
551/// Enables the creation of a [`TimePeriodRef`] instance from a JSON representation.
552/// Typically used when parsing JSON data from the Opsview API.
553impl CreateFromJson for TimePeriodRef {}
554
555impl ConfigRef for TimePeriodRef {
556    type FullObject = TimePeriod;
557
558    /// Returns the reference string of the [`TimePeriodRef`] object.
559    fn ref_(&self) -> Option<String> {
560        self.ref_.clone()
561    }
562
563    /// Returns the name of the [`TimePeriodRef`] object.
564    fn name(&self) -> String {
565        self.name.clone()
566    }
567
568    /// Returns the unique name of the [`TimePeriodRef`] object.
569    ///
570    /// This name is used to identify the `TimePeriodRef` when building the `HashMap` for a
571    /// [`ConfigRefMap`].
572    fn unique_name(&self) -> String {
573        self.name.clone()
574    }
575}
576
577impl From<TimePeriod> for TimePeriodRef {
578    /// Creates a [`TimePeriodRef`] object from a [`TimePeriod`] object.
579    ///
580    /// # Arguments
581    /// * `timeperiod` - The [`TimePeriod`] object from which to create the [`TimePeriodRef`] object.
582    ///
583    /// # Returns
584    /// A [`TimePeriodRef`] object.
585    fn from(timeperiod: TimePeriod) -> Self {
586        TimePeriodRef {
587            name: timeperiod.name.clone(),
588            ref_: timeperiod.ref_.clone(),
589        }
590    }
591}
592
593impl From<Arc<TimePeriod>> for TimePeriodRef {
594    /// Creates a [`TimePeriodRef`] object from an [`Arc`] wrapped [`TimePeriod`] object.
595    ///
596    /// # Arguments
597    /// * `timeperiod` - The [`Arc`] wrapped [`TimePeriod`] object from which to create the [`TimePeriodRef`] object.
598    ///
599    /// # Returns
600    /// A [`TimePeriodRef`] object.
601    fn from(timeperiod: Arc<TimePeriod>) -> Self {
602        TimePeriodRef {
603            name: timeperiod.name.clone(),
604            ref_: timeperiod.ref_.clone(),
605        }
606    }
607}
608
609impl From<&ConfigObjectMap<TimePeriod>> for ConfigRefMap<TimePeriodRef> {
610    fn from(timeperiods: &ConfigObjectMap<TimePeriod>) -> Self {
611        ref_map_from(timeperiods)
612    }
613}
614
615#[cfg(test)]
616mod tests {
617    use super::*;
618
619    #[test]
620    fn test_timeperiod_default() {
621        let tz = TimeZone::builder()
622            .name("SYSTEM")
623            .ref_("/rest/config/timezone/1")
624            .build()
625            .unwrap();
626
627        let timeperiod = TimePeriod::default();
628
629        assert_eq!(timeperiod.name, String::new());
630        assert_eq!(timeperiod.alias, None);
631        assert_eq!(timeperiod.friday, None);
632        assert_eq!(timeperiod.host_check_periods, None);
633        assert_eq!(timeperiod.host_notification_periods, None);
634        assert_eq!(timeperiod.id, None);
635        assert_eq!(timeperiod.monday, None);
636        assert_eq!(timeperiod.object_locked, None);
637        assert_eq!(timeperiod.ref_, None);
638        assert_eq!(timeperiod.saturday, None);
639        assert_eq!(timeperiod.servicecheck_check_periods, None);
640        assert_eq!(timeperiod.servicecheck_notification_periods, None);
641        assert_eq!(timeperiod.sunday, None);
642        assert_eq!(timeperiod.thursday, None);
643        assert_eq!(timeperiod.tuesday, None);
644        assert_eq!(timeperiod.uncommitted, None);
645        assert_eq!(timeperiod.wednesday, None);
646        assert_eq!(timeperiod.zone, Some(tz));
647    }
648
649    #[test]
650    fn test_timeperiod_minimal() {
651        let timeperiod = TimePeriod::minimal("MyTimePeriod");
652
653        assert_eq!(timeperiod.unwrap().name, "MyTimePeriod".to_string());
654    }
655
656    #[test]
657    fn test_is_valid_timeperiod_name() {
658        // Valid names
659        assert!(validate_and_trim_timeperiod_name("24x7").is_ok());
660
661        // Invalid names
662        assert!(validate_and_trim_timeperiod_name("My Time Period").is_err());
663    }
664
665    #[test]
666    fn test_valid_timeperiods() {
667        let valid_strings = [
668            "00:00-09:00,17:00-24:00",
669            "00:00-24:00",
670            "00:00-09:00,10:00-11:00,12:00-24:00",
671        ];
672
673        for s in valid_strings {
674            println!("Testing timeperiod string '{}'", s);
675            let tp = TimePeriod::builder()
676                .name("foo")
677                .monday(s)
678                .tuesday(s)
679                .wednesday(s)
680                .thursday(s)
681                .friday(s)
682                .saturday(s)
683                .sunday(s)
684                .build();
685
686            assert!(tp.is_ok());
687        }
688    }
689
690    #[test]
691    fn test_invalid_timeperiods() {
692        let invalid_strings = ["foo", "00:00-22:00 23:00-24:00", "10:00-11:00Z", ""];
693
694        for s in invalid_strings {
695            println!("Testing timeperiod string '{}'", s);
696            let tp = TimePeriod::builder()
697                .name("foo")
698                .monday(s)
699                .tuesday(s)
700                .wednesday(s)
701                .thursday(s)
702                .friday(s)
703                .saturday(s)
704                .sunday(s)
705                .build();
706
707            assert!(tp.is_err());
708        }
709    }
710}