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};
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();
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(dt.offset_seconds)
164 .ymd_opt(dt.year, dt.month, dt.day)
165 .and_hms_nano_opt(dt.hour, dt.minute, dt.second, dt.nanosecond);
166
167 if let Some(dt) = datetime_obj.single() {
168 dt
169 } else {
170 record_error(
171 glean,
172 &self.meta,
173 ErrorType::InvalidValue,
174 "Invalid input data. Not recording.",
175 None,
176 );
177 return;
178 }
179 }
180 };
181
182 self.set_sync_chrono(glean, value);
183 }
184
185 pub(crate) fn set_sync_chrono(&self, glean: &Glean, value: ChronoDatetime) {
186 let value = Metric::Datetime(value, self.time_unit);
187 glean.storage().record(glean, &self.meta, &value)
188 }
189
190 #[doc(hidden)]
192 pub fn get_value<'a, S: Into<Option<&'a str>>>(
193 &self,
194 glean: &Glean,
195 ping_name: S,
196 ) -> Option<ChronoDatetime> {
197 let (d, tu) = self.get_value_inner(glean, ping_name.into())?;
198
199 let time = d.time();
204 match tu {
205 TimeUnit::Nanosecond => d.date().and_hms_nano_opt(
206 time.hour(),
207 time.minute(),
208 time.second(),
209 time.nanosecond(),
210 ),
211 TimeUnit::Microsecond => d.date().and_hms_nano_opt(
212 time.hour(),
213 time.minute(),
214 time.second(),
215 time.nanosecond() / 1000,
216 ),
217 TimeUnit::Millisecond => d.date().and_hms_nano_opt(
218 time.hour(),
219 time.minute(),
220 time.second(),
221 time.nanosecond() / 1000000,
222 ),
223 TimeUnit::Second => {
224 d.date()
225 .and_hms_nano_opt(time.hour(), time.minute(), time.second(), 0)
226 }
227 TimeUnit::Minute => d.date().and_hms_nano_opt(time.hour(), time.minute(), 0, 0),
228 TimeUnit::Hour => d.date().and_hms_nano_opt(time.hour(), 0, 0, 0),
229 TimeUnit::Day => d.date().and_hms_nano_opt(0, 0, 0, 0),
230 }
231 }
232
233 fn get_value_inner(
234 &self,
235 glean: &Glean,
236 ping_name: Option<&str>,
237 ) -> Option<(ChronoDatetime, TimeUnit)> {
238 let queried_ping_name = ping_name.unwrap_or_else(|| &self.meta().inner.send_in_pings[0]);
239
240 match StorageManager.snapshot_metric(
241 glean.storage(),
242 queried_ping_name,
243 &self.meta.identifier(glean),
244 self.meta.inner.lifetime,
245 ) {
246 Some(Metric::Datetime(d, tu)) => Some((d, tu)),
247 _ => None,
248 }
249 }
250
251 pub fn test_get_value(&self, ping_name: Option<String>) -> Option<Datetime> {
266 crate::block_on_dispatcher();
267 crate::core::with_glean(|glean| {
268 let dt = self.get_value(glean, ping_name.as_deref());
269 dt.map(Datetime::from)
270 })
271 }
272
273 pub fn test_get_value_as_string(&self, ping_name: Option<String>) -> Option<String> {
288 crate::block_on_dispatcher();
289 crate::core::with_glean(|glean| self.get_value_as_string(glean, ping_name))
290 }
291
292 #[doc(hidden)]
296 pub fn get_value_as_string(&self, glean: &Glean, ping_name: Option<String>) -> Option<String> {
297 let value = self.get_value_inner(glean, ping_name.as_deref());
298 value.map(|(dt, tu)| get_iso_time_string(dt, tu))
299 }
300
301 pub fn test_get_num_recorded_errors(&self, error: ErrorType) -> i32 {
313 crate::block_on_dispatcher();
314
315 crate::core::with_glean(|glean| {
316 test_get_num_recorded_errors(glean, self.meta(), error).unwrap_or(0)
317 })
318 }
319}