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}