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::Glean;
16use crate::{CommonMetricData, TestGetValue};
17
18use chrono::{DateTime, Datelike, FixedOffset, NaiveTime, TimeZone, Timelike};
19use malloc_size_of_derive::MallocSizeOf;
20
21pub type ChronoDatetime = DateTime<FixedOffset>;
25
26#[derive(Clone, PartialEq, Eq, MallocSizeOf)]
28pub struct Datetime {
29 pub year: i32,
31 pub month: u32,
33 pub day: u32,
35 pub hour: u32,
37 pub minute: u32,
39 pub second: u32,
41 pub nanosecond: u32,
43 pub offset_seconds: i32,
46}
47
48impl fmt::Debug for Datetime {
49 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50 write!(
51 f,
52 "Datetime({:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:03}{}{:02}{:02})",
53 self.year,
54 self.month,
55 self.day,
56 self.hour,
57 self.minute,
58 self.second,
59 self.nanosecond,
60 if self.offset_seconds < 0 { "-" } else { "+" },
61 self.offset_seconds / 3600, (self.offset_seconds % 3600) / 60, )
64 }
65}
66
67impl Default for Datetime {
68 fn default() -> Self {
69 Datetime {
70 year: 1970,
71 month: 1,
72 day: 1,
73 hour: 0,
74 minute: 0,
75 second: 0,
76 nanosecond: 0,
77 offset_seconds: 0,
78 }
79 }
80}
81
82#[derive(Clone, Debug)]
87pub struct DatetimeMetric {
88 meta: Arc<CommonMetricDataInternal>,
89 time_unit: TimeUnit,
90}
91
92impl MetricType for DatetimeMetric {
93 fn meta(&self) -> &CommonMetricDataInternal {
94 &self.meta
95 }
96}
97
98impl From<ChronoDatetime> for Datetime {
99 fn from(dt: ChronoDatetime) -> Self {
100 let date = dt.date_naive();
101 let time = dt.time();
102 let tz = dt.timezone();
103 Self {
104 year: date.year(),
105 month: date.month(),
106 day: date.day(),
107 hour: time.hour(),
108 minute: time.minute(),
109 second: time.second(),
110 nanosecond: time.nanosecond(),
111 offset_seconds: tz.local_minus_utc(),
112 }
113 }
114}
115
116impl DatetimeMetric {
121 pub fn new(meta: CommonMetricData, time_unit: TimeUnit) -> Self {
123 Self {
124 meta: Arc::new(meta.into()),
125 time_unit,
126 }
127 }
128
129 pub fn set(&self, dt: Option<Datetime>) {
135 let metric = self.clone();
136 crate::launch_with_glean(move |glean| {
137 metric.set_sync(glean, dt);
138 })
139 }
140
141 #[doc(hidden)]
145 pub fn set_sync(&self, glean: &Glean, value: Option<Datetime>) {
146 if !self.should_record(glean) {
147 return;
148 }
149
150 let value = match value {
151 None => local_now_with_offset(),
152 Some(dt) => {
153 let timezone_offset = FixedOffset::east_opt(dt.offset_seconds);
154 if timezone_offset.is_none() {
155 let msg = format!(
156 "Invalid timezone offset {}. Not recording.",
157 dt.offset_seconds
158 );
159 record_error(glean, &self.meta, ErrorType::InvalidValue, msg, None);
160 return;
161 };
162
163 let datetime_obj = FixedOffset::east_opt(dt.offset_seconds)
164 .unwrap()
165 .with_ymd_and_hms(dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second)
166 .unwrap()
167 .with_nanosecond(dt.nanosecond);
168
169 if let Some(dt) = datetime_obj {
170 dt
171 } else {
172 record_error(
173 glean,
174 &self.meta,
175 ErrorType::InvalidValue,
176 "Invalid input data. Not recording.",
177 None,
178 );
179 return;
180 }
181 }
182 };
183
184 self.set_sync_chrono(glean, value);
185 }
186
187 pub(crate) fn set_sync_chrono(&self, glean: &Glean, value: ChronoDatetime) {
188 let value = Metric::Datetime(value, self.time_unit);
189 glean.storage().record(glean, &self.meta, &value)
190 }
191
192 #[doc(hidden)]
194 pub fn get_value<'a, S: Into<Option<&'a str>>>(
195 &self,
196 glean: &Glean,
197 ping_name: S,
198 ) -> Option<ChronoDatetime> {
199 let (d, tu) = self.get_value_inner(glean, ping_name.into())?;
200
201 let time = d.time();
202 let time = match tu {
203 TimeUnit::Nanosecond => time,
204 TimeUnit::Microsecond => time.with_nanosecond(time.nanosecond() / 1000)?,
205 TimeUnit::Millisecond => time.with_nanosecond(time.nanosecond() / 1000000)?,
206 TimeUnit::Second => time.with_nanosecond(0)?,
207 TimeUnit::Minute => NaiveTime::from_hms_opt(time.hour(), time.minute(), 0)?,
208 TimeUnit::Hour => NaiveTime::from_hms_opt(time.hour(), 0, 0)?,
209 TimeUnit::Day => NaiveTime::MIN,
210 };
211 d.with_time(time).single()
212 }
213
214 fn get_value_inner(
215 &self,
216 glean: &Glean,
217 ping_name: Option<&str>,
218 ) -> Option<(ChronoDatetime, TimeUnit)> {
219 let queried_ping_name = ping_name.unwrap_or_else(|| &self.meta().inner.send_in_pings[0]);
220
221 match StorageManager.snapshot_metric(
222 glean.storage(),
223 queried_ping_name,
224 &self.meta.identifier(glean),
225 self.meta.inner.lifetime,
226 ) {
227 Some(Metric::Datetime(d, tu)) => Some((d, tu)),
228 _ => None,
229 }
230 }
231
232 pub fn test_get_value_as_string(&self, ping_name: Option<String>) -> Option<String> {
247 crate::block_on_dispatcher();
248 crate::core::with_glean(|glean| self.get_value_as_string(glean, ping_name))
249 }
250
251 #[doc(hidden)]
255 pub fn get_value_as_string(&self, glean: &Glean, ping_name: Option<String>) -> Option<String> {
256 let value = self.get_value_inner(glean, ping_name.as_deref());
257 value.map(|(dt, tu)| get_iso_time_string(dt, tu))
258 }
259
260 pub fn test_get_num_recorded_errors(&self, error: ErrorType) -> i32 {
272 crate::block_on_dispatcher();
273
274 crate::core::with_glean(|glean| {
275 test_get_num_recorded_errors(glean, self.meta(), error).unwrap_or(0)
276 })
277 }
278}
279
280impl TestGetValue<Datetime> for DatetimeMetric {
281 fn test_get_value(&self, ping_name: Option<String>) -> Option<Datetime> {
296 crate::block_on_dispatcher();
297 crate::core::with_glean(|glean| {
298 let dt = self.get_value(glean, ping_name.as_deref());
299 dt.map(Datetime::from)
300 })
301 }
302}