From 56b3c7280a68ce57fbd92f932d6fbac6fdac1c34 Mon Sep 17 00:00:00 2001 From: unexge Date: Sun, 31 Dec 2023 22:24:22 +0000 Subject: [PATCH] Move `diff_between_datetimes` into its own file and include appropriate license --- src/builtins/time.rs | 48 ++------------------------- src/builtins/time/diff.rs | 69 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 45 deletions(-) create mode 100644 src/builtins/time/diff.rs diff --git a/src/builtins/time.rs b/src/builtins/time.rs index 1e8710f5..fcbc0d93 100644 --- a/src/builtins/time.rs +++ b/src/builtins/time.rs @@ -16,6 +16,8 @@ use chrono::{ }; use chrono_tz::Tz; +mod diff; + pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) { m.insert("time.add_date", (add_date, 4)); m.insert("time.clock", (clock, 1)); @@ -92,8 +94,6 @@ fn date(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Res .into()) } -// Adapted from the official Go implementation: -// https://github.com/open-policy-agent/opa/blob/eb17a716b97720a27c6569395ba7c4b7409aae87/topdown/time.go#L179-L243 fn diff(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "time.diff"; ensure_args_count(span, name, params, args, 2)?; @@ -101,49 +101,7 @@ fn diff(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Res let (datetime1, _) = parse_epoch(name, ¶ms[0], &args[0])?; let (datetime2, _) = parse_epoch(name, ¶ms[1], &args[1])?; - // Make sure both datetimes in the same timezone - let datetime2 = datetime2.with_timezone(&datetime1.timezone()); - - // Make sure `datetime1` is always the smallest one - let (datetime1, datetime2) = if datetime1 > datetime2 { - (datetime2, datetime1) - } else { - (datetime1, datetime2) - }; - - let mut year = datetime2.year() - datetime1.year(); - let mut month = datetime2.month() as i32 - datetime1.month() as i32; - let mut day = datetime2.day() as i32 - datetime1.day() as i32; - let mut hour = datetime2.hour() as i32 - datetime1.hour() as i32; - let mut min = datetime2.minute() as i32 - datetime1.minute() as i32; - let mut sec = datetime2.second() as i32 - datetime1.second() as i32; - - // Normalize negative values - if sec < 0 { - sec += 60; - min -= 1; - } - if min < 0 { - min += 60; - hour -= 1; - } - if hour < 0 { - hour += 24; - day -= 1; - } - if day < 0 { - // Days in month: - let t = Utc - .with_ymd_and_hms(datetime1.year(), datetime1.month(), 32, 0, 0, 0) - .single() - .ok_or(anyhow!("Could not convert `ns1` to datetime"))?; - day += 32 - t.day() as i32; - month -= 1; - } - if month < 0 { - month += 12; - year -= 1; - } + let (year, month, day, hour, min, sec) = diff::diff_between_datetimes(datetime1, datetime2)?; Ok(Vec::from([ (year as i64).into(), diff --git a/src/builtins/time/diff.rs b/src/builtins/time/diff.rs new file mode 100644 index 00000000..f75f1555 --- /dev/null +++ b/src/builtins/time/diff.rs @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT and Apache 2.0 License. + +use anyhow::{anyhow, Result}; +use chrono::{DateTime, Datelike, FixedOffset, TimeZone, Timelike, Utc}; + +// Adapted from the official Go implementation: +// https://github.com/open-policy-agent/opa/blob/eb17a716b97720a27c6569395ba7c4b7409aae87/topdown/time.go#L179-L243 +pub fn diff_between_datetimes( + datetime1: DateTime, + datetime2: DateTime, +) -> Result<(i32, i32, i32, i32, i32, i32)> { + // The following implementation of this function is taken + // from https://github.com/icza/gox licensed under Apache 2.0. + // The only modification made is to variable names. + // + // For details, see https://stackoverflow.com/a/36531443/1705598 + // + // Copyright 2021 icza + // BEGIN REDISTRIBUTION FROM APACHE 2.0 LICENSED PROJECT + + // Make sure both datetimes in the same timezone + let datetime2 = datetime2.with_timezone(&datetime1.timezone()); + + // Make sure `datetime1` is always the smallest one + let (datetime1, datetime2) = if datetime1 > datetime2 { + (datetime2, datetime1) + } else { + (datetime1, datetime2) + }; + + let mut year = datetime2.year() - datetime1.year(); + let mut month = datetime2.month() as i32 - datetime1.month() as i32; + let mut day = datetime2.day() as i32 - datetime1.day() as i32; + let mut hour = datetime2.hour() as i32 - datetime1.hour() as i32; + let mut min = datetime2.minute() as i32 - datetime1.minute() as i32; + let mut sec = datetime2.second() as i32 - datetime1.second() as i32; + + // Normalize negative values + if sec < 0 { + sec += 60; + min -= 1; + } + if min < 0 { + min += 60; + hour -= 1; + } + if hour < 0 { + hour += 24; + day -= 1; + } + if day < 0 { + // Days in month: + let t = Utc + .with_ymd_and_hms(datetime1.year(), datetime1.month(), 32, 0, 0, 0) + .single() + .ok_or(anyhow!("Could not convert `ns1` to datetime"))?; + day += 32 - t.day() as i32; + month -= 1; + } + if month < 0 { + month += 12; + year -= 1; + } + + // END REDISTRIBUTION FROM APACHE 2.0 LICENSED PROJECT + + Ok((year, month, day, hour, min, sec)) +}