below_common/
cliutil.rs

1// Copyright (c) Facebook, Inc. and its affiliates.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::time::Duration;
16use std::time::SystemTime;
17use std::time::UNIX_EPOCH;
18
19use anyhow::anyhow;
20use anyhow::bail;
21use anyhow::Result;
22
23use crate::dateutil;
24use crate::util;
25
26const MISSING_SAMPLE_WARN_DURATION_S: u64 = 60;
27
28/// Convert from date to `SystemTime`
29pub fn system_time_from_date(date: &str) -> Result<SystemTime> {
30    Ok(UNIX_EPOCH
31    + Duration::from_secs(
32        dateutil::HgTime::parse(date)
33            .ok_or_else(|| {
34                anyhow!(
35                    "Unrecognized timestamp format\n\
36                Input: {}.\n\
37                Examples:\n\t\
38                Keywords: now, today, yesterday\n\t\
39                Relative: \"{{humantime}} ago\", e.g. 2 days 3 hr 15m 10sec ago\n\t\
40                Relative short: Mixed {{time_digit}}{{time_unit_char}}. E.g. 10m, 3d2H, 5h30s, 10m5h\n\t\
41                Absolute: \"Jan 01 23:59\", \"01/01/1970 11:59PM\", \"1970-01-01 23:59:59\"\n\t\
42                Unix Epoch: 1589808367",
43                    &date
44                )
45            })?
46            .unixtime,
47    ))
48}
49
50/// Convert from date and an optional days adjuster to `SystemTime`. Days
51/// adjuster is of form y[y...] to `SystemTime`. Each "y" will deduct 1 day
52/// from the resulting time.
53pub fn system_time_from_date_and_adjuster(
54    date: &str,
55    days_adjuster: Option<&str>,
56) -> Result<SystemTime> {
57    let mut time = system_time_from_date(date)?;
58    if let Some(days) = days_adjuster {
59        if days.is_empty() || days.find(|c: char| c != 'y').is_some() {
60            bail!("Unrecognized days adjuster format: {}", days);
61        }
62        let time_to_deduct = Duration::from_secs(days.chars().count() as u64 * 86400);
63        time -= time_to_deduct;
64    }
65    Ok(time)
66}
67
68/// Convert from date range and an optional days adjuster to a start and end
69/// `SystemTime`. Days adjuster is of form y[y...]. Each "y" will deduct 1 day
70/// from the resulting time.
71pub fn system_time_range_from_date_and_adjuster(
72    start_date: &str,
73    end_date: Option<&str>,
74    duration_str: Option<&str>,
75    days_adjuster: Option<&str>,
76) -> Result<(SystemTime, SystemTime)> {
77    let start = system_time_from_date_and_adjuster(start_date, days_adjuster)?;
78    let end = match (end_date, duration_str) {
79        (Some(_), Some(_)) => {
80            bail!("--end and --duration are incompatible options")
81        }
82        (Some(end_date), None) => system_time_from_date_and_adjuster(end_date, days_adjuster)?,
83        (None, Some(duration_str)) => duration_str
84            .parse::<humantime::Duration>()
85            .ok()
86            .map(|duration: humantime::Duration| start + Into::<Duration>::into(duration))
87            .unwrap(),
88        _ => SystemTime::now(),
89    };
90
91    Ok((start, end))
92}
93
94/// Check that initial sample time is within `MISSING_SAMPLE_WARN_DURATION_S`
95/// seconds of the requested start time.
96pub fn check_initial_sample_time_with_requested_time(
97    initial_sample_time: SystemTime,
98    time_begin: SystemTime,
99) {
100    if initial_sample_time > time_begin + Duration::from_secs(MISSING_SAMPLE_WARN_DURATION_S) {
101        eprintln!(
102            "Warning: Initial sample found at {} which is over {} seconds \
103            after the requested start time of {}",
104            util::systemtime_to_datetime(initial_sample_time),
105            MISSING_SAMPLE_WARN_DURATION_S,
106            util::systemtime_to_datetime(time_begin),
107        );
108    };
109}
110
111/// Check that initial sample time is within `MISSING_SAMPLE_WARN_DURATION_S`
112/// seconds of the requested start time, and is not after the requested end time.
113pub fn check_initial_sample_time_in_time_range(
114    initial_sample_time: SystemTime,
115    time_begin: SystemTime,
116    time_end: SystemTime,
117) -> Result<()> {
118    if initial_sample_time > time_end {
119        bail!(
120            "No samples found in desired time range.\n\
121            Earliest sample found after {} is at {} which is after the \
122            requested end time of {}",
123            util::systemtime_to_datetime(time_begin),
124            util::systemtime_to_datetime(initial_sample_time),
125            util::systemtime_to_datetime(time_end),
126        );
127    }
128    check_initial_sample_time_with_requested_time(initial_sample_time, time_begin);
129    Ok(())
130}
131
132/// Check that final sample time is within `MISSING_SAMPLE_WARN_DURATION_S`
133/// seconds of the requested end time.
134pub fn check_final_sample_time_with_requested_time(
135    final_sample_time: SystemTime,
136    time_end: SystemTime,
137) {
138    if final_sample_time < time_end - Duration::from_secs(MISSING_SAMPLE_WARN_DURATION_S) {
139        eprintln!(
140            "Warning: Final sample processed was for {} which is over {} \
141            seconds before the requested end time of {}",
142            util::systemtime_to_datetime(final_sample_time),
143            MISSING_SAMPLE_WARN_DURATION_S,
144            util::systemtime_to_datetime(time_end),
145        );
146    };
147}
148
149#[cfg(test)]
150mod tests {
151    use super::*;
152
153    #[test]
154    fn test_system_time_from_date_fail() {
155        if system_time_from_date("invalid").is_ok() {
156            panic!("Expected to fail but didn't")
157        }
158    }
159
160    #[test]
161    fn test_system_time_from_date_and_adjuster() {
162        assert_eq!(
163            system_time_from_date_and_adjuster("2006-02-01 13:00:30 UTC", None).unwrap(),
164            t("2006-02-01 13:00:30 UTC")
165        );
166        assert_eq!(
167            system_time_from_date_and_adjuster("2006-02-01 13:00:30 UTC", Some("y")).unwrap(),
168            t("2006-01-31 13:00:30 UTC")
169        );
170        assert_eq!(
171            system_time_from_date_and_adjuster("2006-02-01 13:00:30 UTC", Some("yy")).unwrap(),
172            t("2006-01-30 13:00:30 UTC")
173        );
174        assert_eq!(
175            system_time_from_date_and_adjuster("2006-02-01 13:00:30 UTC", Some("yyy")).unwrap(),
176            t("2006-01-29 13:00:30 UTC")
177        );
178    }
179
180    #[test]
181    fn test_system_time_from_date_and_adjuster_fail() {
182        if system_time_from_date_and_adjuster("2006-02-01 13:00:30 UTC", Some("invalid")).is_ok() {
183            panic!("Expected fo fail as adjuster is invalid")
184        }
185    }
186
187    #[test]
188    fn test_system_time_range_from_date_and_adjuster() {
189        assert_eq!(
190            system_time_range_from_date_and_adjuster(
191                "2006-02-01 13:00:30 UTC",
192                Some("2006-02-01 15:00:30 UTC"),
193                None,
194                Some("y"),
195            )
196            .unwrap(),
197            (t("2006-01-31 13:00:30 UTC"), t("2006-01-31 15:00:30 UTC"))
198        );
199        assert_eq!(
200            system_time_range_from_date_and_adjuster(
201                "2006-02-01 13:00:30 UTC",
202                None,
203                Some("10min"),
204                Some("y"),
205            )
206            .unwrap(),
207            (t("2006-01-31 13:00:30 UTC"), t("2006-01-31 13:10:30 UTC"))
208        );
209    }
210
211    /// Convert date to `SystemTime`
212    fn t(h: &str) -> SystemTime {
213        system_time_from_date(h).unwrap()
214    }
215}