summaryrefslogtreecommitdiffstats
path: root/rust/src/python/helpers.rs
diff options
context:
space:
mode:
Diffstat (limited to 'rust/src/python/helpers.rs')
-rw-r--r--rust/src/python/helpers.rs388
1 files changed, 388 insertions, 0 deletions
diff --git a/rust/src/python/helpers.rs b/rust/src/python/helpers.rs
new file mode 100644
index 0000000..4a53e59
--- /dev/null
+++ b/rust/src/python/helpers.rs
@@ -0,0 +1,388 @@
+use std::cmp::Ordering;
+
+use pyo3::{
+ intern,
+ prelude::*,
+ types::{PyDate, PyDateAccess, PyDateTime, PyDelta, PyDeltaAccess, PyString, PyTimeAccess},
+ PyTypeInfo,
+};
+
+use crate::{
+ constants::{DAYS_PER_MONTHS, SECS_PER_DAY, SECS_PER_HOUR, SECS_PER_MIN},
+ helpers,
+};
+
+use crate::python::types::PreciseDiff;
+
+struct DateTimeInfo<'py> {
+ pub year: i32,
+ pub month: i32,
+ pub day: i32,
+ pub hour: i32,
+ pub minute: i32,
+ pub second: i32,
+ pub microsecond: i32,
+ pub total_seconds: i32,
+ pub offset: i32,
+ pub tz: &'py str,
+ pub is_datetime: bool,
+}
+
+impl PartialEq for DateTimeInfo<'_> {
+ fn eq(&self, other: &Self) -> bool {
+ (
+ self.year,
+ self.month,
+ self.day,
+ self.hour,
+ self.minute,
+ self.second,
+ self.microsecond,
+ )
+ .eq(&(
+ other.year,
+ other.month,
+ other.day,
+ other.hour,
+ other.minute,
+ other.second,
+ other.microsecond,
+ ))
+ }
+}
+
+impl PartialOrd for DateTimeInfo<'_> {
+ fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+ (
+ self.year,
+ self.month,
+ self.day,
+ self.hour,
+ self.minute,
+ self.second,
+ self.microsecond,
+ )
+ .partial_cmp(&(
+ other.year,
+ other.month,
+ other.day,
+ other.hour,
+ other.minute,
+ other.second,
+ other.microsecond,
+ ))
+ }
+}
+
+pub fn get_tz_name<'py>(py: Python, dt: &'py PyAny) -> PyResult<&'py str> {
+ let tz: &str = "";
+
+ if !PyDateTime::is_type_of(dt) {
+ return Ok(tz);
+ }
+
+ let tzinfo = dt.getattr("tzinfo");
+
+ match tzinfo {
+ Err(_) => Ok(tz),
+ Ok(tzinfo) => {
+ if tzinfo.is_none() {
+ return Ok(tz);
+ }
+ if tzinfo.hasattr(intern!(py, "key")).unwrap_or(false) {
+ // zoneinfo timezone
+ let tzname: &PyString = tzinfo
+ .getattr(intern!(py, "key"))
+ .unwrap()
+ .downcast()
+ .unwrap();
+
+ return tzname.to_str();
+ } else if tzinfo.hasattr(intern!(py, "name")).unwrap_or(false) {
+ // Pendulum timezone
+ let tzname: &PyString = tzinfo
+ .getattr(intern!(py, "name"))
+ .unwrap()
+ .downcast()
+ .unwrap();
+
+ return tzname.to_str();
+ } else if tzinfo.hasattr(intern!(py, "zone")).unwrap_or(false) {
+ // pytz timezone
+ let tzname: &PyString = tzinfo
+ .getattr(intern!(py, "zone"))
+ .unwrap()
+ .downcast()
+ .unwrap();
+
+ return tzname.to_str();
+ }
+
+ Ok(tz)
+ }
+ }
+}
+
+pub fn get_offset(dt: &PyAny) -> PyResult<i32> {
+ if !PyDateTime::is_type_of(dt) {
+ return Ok(0);
+ }
+
+ let tzinfo = dt.getattr("tzinfo")?;
+
+ if tzinfo.is_none() {
+ return Ok(0);
+ }
+
+ let offset: &PyDelta = tzinfo.call_method1("utcoffset", (dt,))?.downcast()?;
+
+ Ok(offset.get_days() * SECS_PER_DAY as i32 + offset.get_seconds())
+}
+
+#[pyfunction]
+pub fn is_leap(year: i32) -> PyResult<bool> {
+ Ok(helpers::is_leap(year))
+}
+
+#[pyfunction]
+pub fn is_long_year(year: i32) -> PyResult<bool> {
+ Ok(helpers::is_long_year(year))
+}
+
+#[pyfunction]
+pub fn week_day(year: i32, month: u32, day: u32) -> PyResult<u32> {
+ Ok(helpers::week_day(year, month, day))
+}
+
+#[pyfunction]
+pub fn days_in_year(year: i32) -> PyResult<u32> {
+ Ok(helpers::days_in_year(year))
+}
+
+#[pyfunction]
+pub fn local_time(
+ unix_time: f64,
+ utc_offset: isize,
+ microsecond: usize,
+) -> PyResult<(usize, usize, usize, usize, usize, usize, usize)> {
+ Ok(helpers::local_time(unix_time, utc_offset, microsecond))
+}
+
+#[pyfunction]
+pub fn precise_diff<'py>(py: Python, dt1: &'py PyAny, dt2: &'py PyAny) -> PyResult<PreciseDiff> {
+ let mut sign = 1;
+ let mut dtinfo1 = DateTimeInfo {
+ year: dt1.downcast::<PyDate>()?.get_year(),
+ month: i32::from(dt1.downcast::<PyDate>()?.get_month()),
+ day: i32::from(dt1.downcast::<PyDate>()?.get_day()),
+ hour: 0,
+ minute: 0,
+ second: 0,
+ microsecond: 0,
+ total_seconds: 0,
+ tz: get_tz_name(py, dt1)?,
+ offset: get_offset(dt1)?,
+ is_datetime: PyDateTime::is_type_of(dt1),
+ };
+ let mut dtinfo2 = DateTimeInfo {
+ year: dt2.downcast::<PyDate>()?.get_year(),
+ month: i32::from(dt2.downcast::<PyDate>()?.get_month()),
+ day: i32::from(dt2.downcast::<PyDate>()?.get_day()),
+ hour: 0,
+ minute: 0,
+ second: 0,
+ microsecond: 0,
+ total_seconds: 0,
+ tz: get_tz_name(py, dt2)?,
+ offset: get_offset(dt2)?,
+ is_datetime: PyDateTime::is_type_of(dt2),
+ };
+ let in_same_tz: bool = dtinfo1.tz == dtinfo2.tz && !dtinfo1.tz.is_empty();
+ let mut total_days = helpers::day_number(dtinfo2.year, dtinfo2.month as u8, dtinfo2.day as u8)
+ - helpers::day_number(dtinfo1.year, dtinfo1.month as u8, dtinfo1.day as u8);
+
+ if dtinfo1.is_datetime {
+ let dt1dt: &PyDateTime = dt1.downcast()?;
+
+ dtinfo1.hour = i32::from(dt1dt.get_hour());
+ dtinfo1.minute = i32::from(dt1dt.get_minute());
+ dtinfo1.second = i32::from(dt1dt.get_second());
+ dtinfo1.microsecond = dt1dt.get_microsecond() as i32;
+
+ if !in_same_tz && dtinfo1.offset != 0 || total_days == 0 {
+ dtinfo1.hour -= dtinfo1.offset / SECS_PER_HOUR as i32;
+ dtinfo1.offset %= SECS_PER_HOUR as i32;
+ dtinfo1.minute -= dtinfo1.offset / SECS_PER_MIN as i32;
+ dtinfo1.offset %= SECS_PER_MIN as i32;
+ dtinfo1.second -= dtinfo1.offset;
+
+ if dtinfo1.second < 0 {
+ dtinfo1.second += 60;
+ dtinfo1.minute -= 1;
+ } else if dtinfo1.second > 60 {
+ dtinfo1.second -= 60;
+ dtinfo1.minute += 1;
+ }
+
+ if dtinfo1.minute < 0 {
+ dtinfo1.minute += 60;
+ dtinfo1.hour -= 1;
+ } else if dtinfo1.minute > 60 {
+ dtinfo1.minute -= 60;
+ dtinfo1.hour += 1;
+ }
+
+ if dtinfo1.hour < 0 {
+ dtinfo1.hour += 24;
+ dtinfo1.day -= 1;
+ } else if dtinfo1.hour > 24 {
+ dtinfo1.hour -= 24;
+ dtinfo1.day += 1;
+ }
+ }
+
+ dtinfo1.total_seconds = dtinfo1.hour * SECS_PER_HOUR as i32
+ + dtinfo1.minute * SECS_PER_MIN as i32
+ + dtinfo1.second;
+ }
+
+ if dtinfo2.is_datetime {
+ let dt2dt: &PyDateTime = dt2.downcast()?;
+
+ dtinfo2.hour = i32::from(dt2dt.get_hour());
+ dtinfo2.minute = i32::from(dt2dt.get_minute());
+ dtinfo2.second = i32::from(dt2dt.get_second());
+ dtinfo2.microsecond = dt2dt.get_microsecond() as i32;
+
+ if !in_same_tz && dtinfo2.offset != 0 || total_days == 0 {
+ dtinfo2.hour -= dtinfo2.offset / SECS_PER_HOUR as i32;
+ dtinfo2.offset %= SECS_PER_HOUR as i32;
+ dtinfo2.minute -= dtinfo2.offset / SECS_PER_MIN as i32;
+ dtinfo2.offset %= SECS_PER_MIN as i32;
+ dtinfo2.second -= dtinfo2.offset;
+
+ if dtinfo2.second < 0 {
+ dtinfo2.second += 60;
+ dtinfo2.minute -= 1;
+ } else if dtinfo2.second > 60 {
+ dtinfo2.second -= 60;
+ dtinfo2.minute += 1;
+ }
+
+ if dtinfo2.minute < 0 {
+ dtinfo2.minute += 60;
+ dtinfo2.hour -= 1;
+ } else if dtinfo2.minute > 60 {
+ dtinfo2.minute -= 60;
+ dtinfo2.hour += 1;
+ }
+
+ if dtinfo2.hour < 0 {
+ dtinfo2.hour += 24;
+ dtinfo2.day -= 1;
+ } else if dtinfo2.hour > 24 {
+ dtinfo2.hour -= 24;
+ dtinfo2.day += 1;
+ }
+ }
+
+ dtinfo2.total_seconds = dtinfo2.hour * SECS_PER_HOUR as i32
+ + dtinfo2.minute * SECS_PER_MIN as i32
+ + dtinfo2.second;
+ }
+
+ if dtinfo1 > dtinfo2 {
+ sign = -1;
+ (dtinfo1, dtinfo2) = (dtinfo2, dtinfo1);
+
+ total_days = -total_days;
+ }
+
+ let mut year_diff = dtinfo2.year - dtinfo1.year;
+ let mut month_diff = dtinfo2.month - dtinfo1.month;
+ let mut day_diff = dtinfo2.day - dtinfo1.day;
+ let mut hour_diff = dtinfo2.hour - dtinfo1.hour;
+ let mut minute_diff = dtinfo2.minute - dtinfo1.minute;
+ let mut second_diff = dtinfo2.second - dtinfo1.second;
+ let mut microsecond_diff = dtinfo2.microsecond - dtinfo1.microsecond;
+
+ if microsecond_diff < 0 {
+ microsecond_diff += 1_000_000;
+ second_diff -= 1;
+ }
+
+ if second_diff < 0 {
+ second_diff += 60;
+ minute_diff -= 1;
+ }
+
+ if minute_diff < 0 {
+ minute_diff += 60;
+ hour_diff -= 1;
+ }
+
+ if hour_diff < 0 {
+ hour_diff += 24;
+ day_diff -= 1;
+ }
+
+ if day_diff < 0 {
+ // If we have a difference in days,
+ // we have to check if they represent months
+ let mut year = dtinfo2.year;
+ let mut month = dtinfo2.month;
+
+ if month == 1 {
+ month = 12;
+ year -= 1;
+ } else {
+ month -= 1;
+ }
+
+ let leap = helpers::is_leap(year);
+
+ let days_in_last_month = DAYS_PER_MONTHS[usize::from(leap)][month as usize];
+ let days_in_month =
+ DAYS_PER_MONTHS[usize::from(helpers::is_leap(dtinfo2.year))][dtinfo2.month as usize];
+
+ match day_diff.cmp(&(days_in_month - days_in_last_month)) {
+ Ordering::Less => {
+ // We don't have a full month, we calculate days
+ if days_in_last_month < dtinfo1.day {
+ day_diff += dtinfo1.day;
+ } else {
+ day_diff += days_in_last_month;
+ }
+ }
+ Ordering::Equal => {
+ // We have exactly a full month
+ // We remove the days difference
+ // and add one to the months difference
+ day_diff = 0;
+ month_diff += 1;
+ }
+ Ordering::Greater => {
+ // We have a full month
+ day_diff += days_in_last_month;
+ }
+ }
+
+ month_diff -= 1;
+ }
+
+ if month_diff < 0 {
+ month_diff += 12;
+ year_diff -= 1;
+ }
+
+ Ok(PreciseDiff {
+ years: year_diff * sign,
+ months: month_diff * sign,
+ days: day_diff * sign,
+ hours: hour_diff * sign,
+ minutes: minute_diff * sign,
+ seconds: second_diff * sign,
+ microseconds: microsecond_diff * sign,
+ total_days: total_days * sign,
+ })
+}