Skip to content

Commit

Permalink
Move diff_between_datetimes into its own file and include appropria…
Browse files Browse the repository at this point in the history
…te license
  • Loading branch information
unexge committed Dec 31, 2023
1 parent 81e5f5a commit 56b3c72
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 45 deletions.
48 changes: 3 additions & 45 deletions src/builtins/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down Expand Up @@ -92,58 +94,14 @@ fn date(span: &Span, params: &[Ref<Expr>], 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<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "time.diff";
ensure_args_count(span, name, params, args, 2)?;

let (datetime1, _) = parse_epoch(name, &params[0], &args[0])?;
let (datetime2, _) = parse_epoch(name, &params[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(),
Expand Down
69 changes: 69 additions & 0 deletions src/builtins/time/diff.rs
Original file line number Diff line number Diff line change
@@ -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<FixedOffset>,
datetime2: DateTime<FixedOffset>,
) -> 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))
}

0 comments on commit 56b3c72

Please sign in to comment.