use std::time::{Duration, SystemTime, UNIX_EPOCH};
use automerge::{ChangeHash, ObjId, ObjType, Prop, ReadDoc, Value, transaction::Transactable};
use crate::{Automorph, ChangeReport, Error, PrimitiveChanged, Result, ScalarCursor};
impl Automorph for Duration {
type Changes = PrimitiveChanged;
type Cursor = ScalarCursor;
fn save<D: Transactable + ReadDoc>(
&self,
doc: &mut D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
) -> Result<()> {
let prop: Prop = prop.into();
let obj = obj.as_ref();
let map_id = match doc.get(obj, prop.clone())? {
Some((Value::Object(ObjType::Map), id)) => id,
_ => doc.put_object(obj, prop, ObjType::Map)?,
};
self.as_secs().save(doc, &map_id, "secs")?;
self.subsec_nanos().save(doc, &map_id, "nanos")?;
Ok(())
}
fn load<D: ReadDoc>(doc: &D, obj: impl AsRef<ObjId>, prop: impl Into<Prop>) -> Result<Self> {
let prop: Prop = prop.into();
let obj = obj.as_ref();
match doc.get(obj, prop)? {
Some((Value::Object(ObjType::Map), map_id)) => {
let secs = u64::load(doc, &map_id, "secs")?;
let nanos = u32::load(doc, &map_id, "nanos")?;
Ok(Duration::new(secs, nanos))
}
Some((v, _)) => Err(Error::type_mismatch(
"Duration (Map)",
Some(format!("{:?}", v)),
)),
None => Err(Error::missing_value()),
}
}
fn load_at<D: ReadDoc>(
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
heads: &[ChangeHash],
) -> Result<Self> {
let prop: Prop = prop.into();
let obj = obj.as_ref();
match doc.get_at(obj, prop, heads)? {
Some((Value::Object(ObjType::Map), map_id)) => {
let secs = u64::load_at(doc, &map_id, "secs", heads)?;
let nanos = u32::load_at(doc, &map_id, "nanos", heads)?;
Ok(Duration::new(secs, nanos))
}
Some((v, _)) => Err(Error::type_mismatch(
"Duration (Map)",
Some(format!("{:?}", v)),
)),
None => Err(Error::missing_value()),
}
}
fn diff<D: ReadDoc>(
&self,
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
) -> Result<Self::Changes> {
let prop: Prop = prop.into();
let obj = obj.as_ref();
match doc.get(obj, prop)? {
Some((Value::Object(ObjType::Map), map_id)) => {
let secs_changes = self.as_secs().diff(doc, &map_id, "secs")?;
if secs_changes.any() {
return Ok(PrimitiveChanged::new(true));
}
let nanos_changes = self.subsec_nanos().diff(doc, &map_id, "nanos")?;
Ok(PrimitiveChanged::new(nanos_changes.any()))
}
Some((v, _)) => Err(Error::type_mismatch(
"Duration (Map)",
Some(format!("{:?}", v)),
)),
None => Err(Error::missing_value()),
}
}
fn diff_at<D: ReadDoc>(
&self,
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
heads: &[ChangeHash],
) -> Result<Self::Changes> {
let prop: Prop = prop.into();
let obj = obj.as_ref();
match doc.get_at(obj, prop, heads)? {
Some((Value::Object(ObjType::Map), map_id)) => {
let secs_changes = self.as_secs().diff_at(doc, &map_id, "secs", heads)?;
if secs_changes.any() {
return Ok(PrimitiveChanged::new(true));
}
let nanos_changes = self.subsec_nanos().diff_at(doc, &map_id, "nanos", heads)?;
Ok(PrimitiveChanged::new(nanos_changes.any()))
}
Some((v, _)) => Err(Error::type_mismatch(
"Duration (Map)",
Some(format!("{:?}", v)),
)),
None => Err(Error::missing_value()),
}
}
fn update<D: ReadDoc>(
&mut self,
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
) -> Result<Self::Changes> {
let prop: Prop = prop.into();
let obj = obj.as_ref();
match doc.get(obj, prop)? {
Some((Value::Object(ObjType::Map), map_id)) => {
let secs = u64::load(doc, &map_id, "secs")?;
let nanos = u32::load(doc, &map_id, "nanos")?;
let new_value = Duration::new(secs, nanos);
let changed = *self != new_value;
if changed {
*self = new_value;
}
Ok(PrimitiveChanged::new(changed))
}
Some((v, _)) => Err(Error::type_mismatch(
"Duration (Map)",
Some(format!("{:?}", v)),
)),
None => Err(Error::missing_value()),
}
}
fn update_at<D: ReadDoc>(
&mut self,
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
heads: &[ChangeHash],
) -> Result<Self::Changes> {
let prop: Prop = prop.into();
let obj = obj.as_ref();
match doc.get_at(obj, prop, heads)? {
Some((Value::Object(ObjType::Map), map_id)) => {
let secs = u64::load_at(doc, &map_id, "secs", heads)?;
let nanos = u32::load_at(doc, &map_id, "nanos", heads)?;
let new_value = Duration::new(secs, nanos);
let changed = *self != new_value;
if changed {
*self = new_value;
}
Ok(PrimitiveChanged::new(changed))
}
Some((v, _)) => Err(Error::type_mismatch(
"Duration (Map)",
Some(format!("{:?}", v)),
)),
None => Err(Error::missing_value()),
}
}
}
impl Automorph for SystemTime {
type Changes = PrimitiveChanged;
type Cursor = ScalarCursor;
fn save<D: Transactable + ReadDoc>(
&self,
doc: &mut D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
) -> Result<()> {
let duration = self
.duration_since(UNIX_EPOCH)
.map_err(|e| Error::invalid_value(format!("SystemTime before UNIX_EPOCH: {}", e)))?;
duration.save(doc, obj, prop)
}
fn load<D: ReadDoc>(doc: &D, obj: impl AsRef<ObjId>, prop: impl Into<Prop>) -> Result<Self> {
let duration = Duration::load(doc, obj, prop)?;
Ok(UNIX_EPOCH + duration)
}
fn load_at<D: ReadDoc>(
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
heads: &[ChangeHash],
) -> Result<Self> {
let duration = Duration::load_at(doc, obj, prop, heads)?;
Ok(UNIX_EPOCH + duration)
}
fn diff<D: ReadDoc>(
&self,
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
) -> Result<Self::Changes> {
let duration = self
.duration_since(UNIX_EPOCH)
.map_err(|e| Error::invalid_value(format!("SystemTime before UNIX_EPOCH: {}", e)))?;
duration.diff(doc, obj, prop)
}
fn diff_at<D: ReadDoc>(
&self,
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
heads: &[ChangeHash],
) -> Result<Self::Changes> {
let duration = self
.duration_since(UNIX_EPOCH)
.map_err(|e| Error::invalid_value(format!("SystemTime before UNIX_EPOCH: {}", e)))?;
duration.diff_at(doc, obj, prop, heads)
}
fn update<D: ReadDoc>(
&mut self,
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
) -> Result<Self::Changes> {
let duration = Duration::load(doc, obj, prop)?;
let new_value = UNIX_EPOCH + duration;
let changed = *self != new_value;
if changed {
*self = new_value;
}
Ok(PrimitiveChanged::new(changed))
}
fn update_at<D: ReadDoc>(
&mut self,
doc: &D,
obj: impl AsRef<ObjId>,
prop: impl Into<Prop>,
heads: &[ChangeHash],
) -> Result<Self::Changes> {
let duration = Duration::load_at(doc, obj, prop, heads)?;
let new_value = UNIX_EPOCH + duration;
let changed = *self != new_value;
if changed {
*self = new_value;
}
Ok(PrimitiveChanged::new(changed))
}
}
#[cfg(test)]
mod tests {
use super::*;
use automerge::{AutoCommit, ROOT};
#[test]
fn test_duration() {
let mut doc = AutoCommit::new();
let dur = Duration::new(123, 456789);
dur.save(&mut doc, &ROOT, "dur").unwrap();
let restored = Duration::load(&doc, &ROOT, "dur").unwrap();
assert_eq!(restored, dur);
}
#[test]
fn test_system_time() {
let mut doc = AutoCommit::new();
let time = SystemTime::now();
time.save(&mut doc, &ROOT, "time").unwrap();
let restored = SystemTime::load(&doc, &ROOT, "time").unwrap();
let diff = time.duration_since(UNIX_EPOCH).unwrap().as_nanos() as i128
- restored.duration_since(UNIX_EPOCH).unwrap().as_nanos() as i128;
assert!(diff.abs() < 1000); }
}