shift_algorithm/cycle.rs
1//! Default constants and the standard 42-day shift cycle.
2//!
3//! These constants are shared with the Android (Kotlin) and Flutter (Dart)
4//! reference implementations. Changing them here changes all platforms.
5
6use crate::types::ShiftType;
7use chrono::NaiveDate;
8
9/// Default cycle length: 42 days.
10///
11/// This is the most common cycle length for Chinese 6-team rotating shifts.
12pub const DEFAULT_CYCLE_LENGTH: u32 = 42;
13
14/// Total number of teams sharing the cycle.
15pub const DEFAULT_TOTAL_TEAMS: u32 = 6;
16
17/// Returns the reference date: **2025-12-15**.
18///
19/// This is day 1 of the default cycle (早班/Morning shift).
20/// Shared across all platforms — Android `ShiftCycleConfig.REFERENCE_DATE`
21/// and Flutter `ShiftCycleConfig.referenceDate` use the same value.
22///
23/// ```rust
24/// use shift_algorithm::cycle::default_reference_date;
25/// use chrono::Datelike;
26///
27/// let d = default_reference_date();
28/// assert_eq!(d.year(), 2025);
29/// assert_eq!(d.month(), 12);
30/// assert_eq!(d.day(), 15);
31/// ```
32pub fn default_reference_date() -> NaiveDate {
33 NaiveDate::from_ymd_opt(2025, 12, 15).unwrap()
34}
35
36/// Returns the default 42-day shift cycle.
37///
38/// The sequence (in Chinese notation):
39/// ```text
40/// 早早中中休夜夜休休早早中中休夜休休休早早中休夜夜休休休早中中休夜夜休休学学学学学休休
41/// ```
42///
43/// Must match Android `ShiftCycleConfig.SHIFT_CYCLE` exactly.
44///
45/// ```rust
46/// use shift_algorithm::cycle::default_shift_cycle;
47/// use shift_algorithm::ShiftType;
48///
49/// let cycle = default_shift_cycle();
50/// assert_eq!(cycle.len(), 42);
51/// assert_eq!(cycle[0], ShiftType::Morning);
52/// assert_eq!(cycle[41], ShiftType::Rest);
53/// ```
54pub fn default_shift_cycle() -> Vec<ShiftType> {
55 use ShiftType::*;
56 vec![
57 Morning, Morning, Afternoon, Afternoon, Rest, Night, Night,
58 Rest, Rest, Morning, Morning, Afternoon, Afternoon, Rest,
59 Night, Rest, Rest, Rest, Morning, Morning, Afternoon, Rest,
60 Night, Night, Rest, Rest, Rest, Morning, Afternoon, Afternoon,
61 Rest, Night, Night, Rest, Rest, Study, Study, Study, Study,
62 Study, Rest, Rest,
63 ]
64}
65
66/// Creates a [`ShiftCycleConfig`](crate::ShiftCycleConfig) with all default values.
67///
68/// This is the starting point for most use cases:
69///
70/// ```rust
71/// use shift_algorithm::cycle::default_config;
72/// use shift_algorithm::get_shift_info;
73///
74/// let config = default_config();
75/// let info = get_shift_info(config.reference_date, &config, 0);
76/// assert_eq!(info.day_of_cycle, 1);
77/// ```
78pub fn default_config() -> crate::types::ShiftCycleConfig {
79 crate::types::ShiftCycleConfig {
80 cycle: default_shift_cycle(),
81 cycle_length: DEFAULT_CYCLE_LENGTH,
82 reference_date: default_reference_date(),
83 total_teams: DEFAULT_TOTAL_TEAMS,
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90 #[cfg(test)]
91 use chrono::Datelike;
92
93 #[test]
94 fn cycle_length_is_42() {
95 assert_eq!(default_shift_cycle().len(), 42);
96 }
97
98 #[test]
99 fn cycle_starts_with_morning() {
100 assert_eq!(default_shift_cycle()[0], ShiftType::Morning);
101 }
102
103 #[test]
104 fn cycle_ends_with_rest() {
105 assert_eq!(default_shift_cycle()[41], ShiftType::Rest);
106 }
107
108 #[test]
109 fn reference_date_equals_android() {
110 let d = default_reference_date();
111 assert_eq!(d.year(), 2025);
112 assert_eq!(d.month(), 12);
113 assert_eq!(d.day(), 15);
114 }
115
116 #[test]
117 fn team_phase_offset_formula() {
118 let config = default_config();
119 assert_eq!(config.team_phase_offset(1), 0);
120 assert_eq!(config.team_phase_offset(2), 7);
121 assert_eq!(config.team_phase_offset(6), 35);
122 }
123}