diff --git a/lib/src/annotation.rs b/lib/src/annotation.rs index 8c403327b..8dfeb1b73 100644 --- a/lib/src/annotation.rs +++ b/lib/src/annotation.rs @@ -1,14 +1,13 @@ use crate::traits::*; use crate::types::*; -use chrono::prelude::*; use taskchampion::Annotation; /// TCAnnotation contains the details of an annotation. #[repr(C)] pub struct TCAnnotation { - /// Time the annotation was made, as a UNIX epoch timestamp - pub entry: i64, - /// Content of the annotation + /// Time the annotation was made. Must be nonzero. + pub entry: libc::time_t, + /// Content of the annotation. Must not be NULL. pub description: *mut TCString<'static>, } @@ -16,13 +15,16 @@ impl PassByValue for TCAnnotation { type RustType = Annotation; unsafe fn from_ctype(self) -> Annotation { - let entry = Utc.timestamp(self.entry, 0); + // SAFETY: + // - any time_t value is valid + // - time_t is not zero, so unwrap is safe (see type docstring) + let entry = unsafe { self.entry.from_ctype() }.unwrap(); // SAFETY: // - self is owned, so we can take ownership of this TCString - // - self.description was created from a String so has valid UTF-8 - // - caller did not change it (promised by caller) + // - self.description is a valid, non-null TCString (see type docstring) let description = unsafe { TCString::take_from_arg(self.description) } .into_string() + // TODO: might not be valid utf-8 .unwrap(); Annotation { entry, description } } @@ -30,7 +32,7 @@ impl PassByValue for TCAnnotation { fn as_ctype(arg: Annotation) -> Self { let description: TCString = arg.description.into(); TCAnnotation { - entry: arg.entry.timestamp(), + entry: libc::time_t::as_ctype(Some(arg.entry)), // SAFETY: caller will later free this value via tc_annotation_free description: unsafe { description.return_val() }, } @@ -40,7 +42,7 @@ impl PassByValue for TCAnnotation { impl Default for TCAnnotation { fn default() -> Self { TCAnnotation { - entry: 0, + entry: 0 as libc::time_t, description: std::ptr::null_mut(), } } diff --git a/lib/src/atomic.rs b/lib/src/atomic.rs index db8315922..8a0d9e3d1 100644 --- a/lib/src/atomic.rs +++ b/lib/src/atomic.rs @@ -1,6 +1,7 @@ //! Trait implementations for a few atomic types use crate::traits::*; +use chrono::prelude::*; impl PassByValue for usize { type RustType = usize; @@ -13,3 +14,21 @@ impl PassByValue for usize { arg } } + +/// Convert an Option> to a libc::time_t, or zero if not set. +impl PassByValue for libc::time_t { + type RustType = Option>; + + unsafe fn from_ctype(self) -> Option> { + if self == 0 { + None + } else { + Some(Utc.timestamp(self as i64, 0)) + } + } + + fn as_ctype(arg: Option>) -> libc::time_t { + arg.map(|ts| ts.timestamp() as libc::time_t) + .unwrap_or(0 as libc::time_t) + } +} diff --git a/lib/src/task.rs b/lib/src/task.rs index b56a71498..717f5de78 100644 --- a/lib/src/task.rs +++ b/lib/src/task.rs @@ -1,7 +1,7 @@ use crate::traits::*; use crate::types::*; use crate::util::err_to_tcstring; -use chrono::{DateTime, TimeZone, Utc}; +use chrono::{TimeZone, Utc}; use std::convert::TryFrom; use std::ops::Deref; use std::ptr::NonNull; @@ -155,22 +155,6 @@ impl TryFrom> for Tag { } } -/// Convert a DateTime to a libc::time_t, or zero if not set. -fn to_time_t(timestamp: Option>) -> libc::time_t { - timestamp - .map(|ts| ts.timestamp() as libc::time_t) - .unwrap_or(0 as libc::time_t) -} - -/// Convert a libc::time_t to Option>, treating time zero as None -fn to_datetime(time: libc::time_t) -> Option> { - if time == 0 { - None - } else { - Some(Utc.timestamp(time as i64, 0)) - } -} - /// TCTaskList represents a list of tasks. /// /// The content of this struct must be treated as read-only. @@ -275,19 +259,19 @@ pub unsafe extern "C" fn tc_task_get_description<'a>(task: *mut TCTask) -> *mut /// Get the entry timestamp for a task (when it was created), or 0 if not set. #[no_mangle] pub unsafe extern "C" fn tc_task_get_entry<'a>(task: *mut TCTask) -> libc::time_t { - wrap(task, |task| to_time_t(task.get_entry())) + wrap(task, |task| libc::time_t::as_ctype(task.get_entry())) } /// Get the wait timestamp for a task, or 0 if not set. #[no_mangle] pub unsafe extern "C" fn tc_task_get_wait<'a>(task: *mut TCTask) -> libc::time_t { - wrap(task, |task| to_time_t(task.get_wait())) + wrap(task, |task| libc::time_t::as_ctype(task.get_wait())) } /// Get the modified timestamp for a task, or 0 if not set. #[no_mangle] pub unsafe extern "C" fn tc_task_get_modified<'a>(task: *mut TCTask) -> libc::time_t { - wrap(task, |task| to_time_t(task.get_modified())) + wrap(task, |task| libc::time_t::as_ctype(task.get_modified())) } /// Check if a task is waiting. @@ -396,7 +380,8 @@ pub unsafe extern "C" fn tc_task_set_entry(task: *mut TCTask, entry: libc::time_ wrap_mut( task, |task| { - task.set_entry(to_datetime(entry))?; + // SAFETY: any time_t value is a valid timestamp + task.set_entry(unsafe { entry.from_ctype() })?; Ok(TCResult::Ok) }, TCResult::Error, @@ -409,7 +394,8 @@ pub unsafe extern "C" fn tc_task_set_wait(task: *mut TCTask, wait: libc::time_t) wrap_mut( task, |task| { - task.set_wait(to_datetime(wait))?; + // SAFETY: any time_t value is a valid timestamp + task.set_wait(unsafe { wait.from_ctype() })?; Ok(TCResult::Ok) }, TCResult::Error, @@ -426,7 +412,9 @@ pub unsafe extern "C" fn tc_task_set_modified( task, |task| { task.set_modified( - to_datetime(modified).ok_or_else(|| anyhow::anyhow!("modified cannot be zero"))?, + // SAFETY: any time_t value is a valid timestamp + unsafe { modified.from_ctype() } + .ok_or_else(|| anyhow::anyhow!("modified cannot be zero"))?, )?; Ok(TCResult::Ok) }, diff --git a/lib/taskchampion.h b/lib/taskchampion.h index a6ffc7644..4b993d8ff 100644 --- a/lib/taskchampion.h +++ b/lib/taskchampion.h @@ -122,11 +122,11 @@ typedef struct TCTask TCTask; */ typedef struct TCAnnotation { /** - * Time the annotation was made, as a UNIX epoch timestamp + * Time the annotation was made. Must be nonzero. */ - int64_t entry; + time_t entry; /** - * Content of the annotation + * Content of the annotation. Must not be NULL. */ struct TCString *description; } TCAnnotation;