1use std::fmt;
6use std::sync::Arc;
7
8use crate::common_metric_data::CommonMetricDataInternal;
9use crate::error_recording::{record_error, test_get_num_recorded_errors, ErrorType};
10use crate::metrics::time_unit::TimeUnit;
11use crate::metrics::Metric;
12use crate::metrics::MetricType;
13use crate::storage::StorageManager;
14use crate::util::{get_iso_time_string, local_now_with_offset};
15use crate::CommonMetricData;
16use crate::Glean;
17
18use chrono::{DateTime, Datelike, FixedOffset, TimeZone, Timelike};
19
20pub type ChronoDatetime = DateTime<FixedOffset>;
24
25#[derive(Clone, PartialEq, Eq)]
27pub struct Datetime {
28    pub year: i32,
30    pub month: u32,
32    pub day: u32,
34    pub hour: u32,
36    pub minute: u32,
38    pub second: u32,
40    pub nanosecond: u32,
42    pub offset_seconds: i32,
45}
46
47impl fmt::Debug for Datetime {
48    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49        write!(
50            f,
51            "Datetime({:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:03}{}{:02}{:02})",
52            self.year,
53            self.month,
54            self.day,
55            self.hour,
56            self.minute,
57            self.second,
58            self.nanosecond,
59            if self.offset_seconds < 0 { "-" } else { "+" },
60            self.offset_seconds / 3600,        (self.offset_seconds % 3600) / 60, )
63    }
64}
65
66impl Default for Datetime {
67    fn default() -> Self {
68        Datetime {
69            year: 1970,
70            month: 1,
71            day: 1,
72            hour: 0,
73            minute: 0,
74            second: 0,
75            nanosecond: 0,
76            offset_seconds: 0,
77        }
78    }
79}
80
81#[derive(Clone, Debug)]
86pub struct DatetimeMetric {
87    meta: Arc<CommonMetricDataInternal>,
88    time_unit: TimeUnit,
89}
90
91impl MetricType for DatetimeMetric {
92    fn meta(&self) -> &CommonMetricDataInternal {
93        &self.meta
94    }
95}
96
97impl From<ChronoDatetime> for Datetime {
98    fn from(dt: ChronoDatetime) -> Self {
99        let date = dt.date();
100        let time = dt.time();
101        let tz = dt.timezone();
102        Self {
103            year: date.year(),
104            month: date.month(),
105            day: date.day(),
106            hour: time.hour(),
107            minute: time.minute(),
108            second: time.second(),
109            nanosecond: time.nanosecond(),
110            offset_seconds: tz.local_minus_utc(),
111        }
112    }
113}
114
115impl DatetimeMetric {
120    pub fn new(meta: CommonMetricData, time_unit: TimeUnit) -> Self {
122        Self {
123            meta: Arc::new(meta.into()),
124            time_unit,
125        }
126    }
127
128    pub fn set(&self, dt: Option<Datetime>) {
134        let metric = self.clone();
135        crate::launch_with_glean(move |glean| {
136            metric.set_sync(glean, dt);
137        })
138    }
139
140    #[doc(hidden)]
144    pub fn set_sync(&self, glean: &Glean, value: Option<Datetime>) {
145        if !self.should_record(glean) {
146            return;
147        }
148
149        let value = match value {
150            None => local_now_with_offset(),
151            Some(dt) => {
152                let timezone_offset = FixedOffset::east_opt(dt.offset_seconds);
153                if timezone_offset.is_none() {
154                    let msg = format!(
155                        "Invalid timezone offset {}. Not recording.",
156                        dt.offset_seconds
157                    );
158                    record_error(glean, &self.meta, ErrorType::InvalidValue, msg, None);
159                    return;
160                };
161
162                let datetime_obj = FixedOffset::east(dt.offset_seconds)
163                    .ymd_opt(dt.year, dt.month, dt.day)
164                    .and_hms_nano_opt(dt.hour, dt.minute, dt.second, dt.nanosecond);
165
166                if let Some(dt) = datetime_obj.single() {
167                    dt
168                } else {
169                    record_error(
170                        glean,
171                        &self.meta,
172                        ErrorType::InvalidValue,
173                        "Invalid input data. Not recording.",
174                        None,
175                    );
176                    return;
177                }
178            }
179        };
180
181        self.set_sync_chrono(glean, value);
182    }
183
184    pub(crate) fn set_sync_chrono(&self, glean: &Glean, value: ChronoDatetime) {
185        let value = Metric::Datetime(value, self.time_unit);
186        glean.storage().record(glean, &self.meta, &value)
187    }
188
189    #[doc(hidden)]
191    pub fn get_value<'a, S: Into<Option<&'a str>>>(
192        &self,
193        glean: &Glean,
194        ping_name: S,
195    ) -> Option<ChronoDatetime> {
196        let (d, tu) = self.get_value_inner(glean, ping_name.into())?;
197
198        let time = d.time();
203        match tu {
204            TimeUnit::Nanosecond => d.date().and_hms_nano_opt(
205                time.hour(),
206                time.minute(),
207                time.second(),
208                time.nanosecond(),
209            ),
210            TimeUnit::Microsecond => {
211                eprintln!(
212                    "microseconds. nanoseconds={}, nanoseconds/1000={}",
213                    time.nanosecond(),
214                    time.nanosecond() / 1000
215                );
216                d.date().and_hms_nano_opt(
217                    time.hour(),
218                    time.minute(),
219                    time.second(),
220                    time.nanosecond() / 1000,
221                )
222            }
223            TimeUnit::Millisecond => d.date().and_hms_nano_opt(
224                time.hour(),
225                time.minute(),
226                time.second(),
227                time.nanosecond() / 1000000,
228            ),
229            TimeUnit::Second => {
230                d.date()
231                    .and_hms_nano_opt(time.hour(), time.minute(), time.second(), 0)
232            }
233            TimeUnit::Minute => d.date().and_hms_nano_opt(time.hour(), time.minute(), 0, 0),
234            TimeUnit::Hour => d.date().and_hms_nano_opt(time.hour(), 0, 0, 0),
235            TimeUnit::Day => d.date().and_hms_nano_opt(0, 0, 0, 0),
236        }
237    }
238
239    fn get_value_inner(
240        &self,
241        glean: &Glean,
242        ping_name: Option<&str>,
243    ) -> Option<(ChronoDatetime, TimeUnit)> {
244        let queried_ping_name = ping_name.unwrap_or_else(|| &self.meta().inner.send_in_pings[0]);
245
246        match StorageManager.snapshot_metric(
247            glean.storage(),
248            queried_ping_name,
249            &self.meta.identifier(glean),
250            self.meta.inner.lifetime,
251        ) {
252            Some(Metric::Datetime(d, tu)) => Some((d, tu)),
253            _ => None,
254        }
255    }
256
257    pub fn test_get_value(&self, ping_name: Option<String>) -> Option<Datetime> {
272        crate::block_on_dispatcher();
273        crate::core::with_glean(|glean| {
274            let dt = self.get_value(glean, ping_name.as_deref());
275            dt.map(Datetime::from)
276        })
277    }
278
279    pub fn test_get_value_as_string(&self, ping_name: Option<String>) -> Option<String> {
294        crate::block_on_dispatcher();
295        crate::core::with_glean(|glean| self.get_value_as_string(glean, ping_name))
296    }
297
298    #[doc(hidden)]
302    pub fn get_value_as_string(&self, glean: &Glean, ping_name: Option<String>) -> Option<String> {
303        let value = self.get_value_inner(glean, ping_name.as_deref());
304        value.map(|(dt, tu)| get_iso_time_string(dt, tu))
305    }
306
307    pub fn test_get_num_recorded_errors(&self, error: ErrorType) -> i32 {
319        crate::block_on_dispatcher();
320
321        crate::core::with_glean(|glean| {
322            test_get_num_recorded_errors(glean, self.meta(), error).unwrap_or(0)
323        })
324    }
325}