sm-2 0.2.0

Rust Crate for SM-2 Spaced Repetition
Documentation
<div align="center">
  <img src="https://raw.githubusercontent.com/open-spaced-repetition/sm-2-ts/main/osr_logo.png" height="100" alt="Open Spaced Repetition logo"/>
</div>
<div align="center">

# SM-2

</div>
<div align="center">
  <em>🧠🔄 The Classic SM-2 Spaced Repetition Algorithm in Rust 🧠🔄</em>
</div>
<br />
<div align="center" style="text-decoration: none;">
    <a href="https://crates.io/crates/sm-2"><img src="https://img.shields.io/crates/v/sm-2"></a>
    <a href="https://github.com/open-spaced-repetition/sm-2-rs/blob/main/LICENSE" style="text-decoration: none;"><img src="https://img.shields.io/badge/License-MIT-brightgreen.svg"></a>
</div>
<br />

**Rust crate for the classic <a href="https://super-memory.com/english/ol/sm2.htm">SM-2</a> algorithm for spaced repetition scheduling.**

## Table of Contents
- [Installation]#installation
- [Quickstart]#quickstart
- [Versioning]#versioning

## Installation

You can install the `sm-2` crate from crates.io [[link](https://crates.io/crates/sm-2)] using `cargo`:

```bash
cargo add sm-2
```

## Quickstart

```rust
use sm_2::Utc; // chrono
use sm_2::{Card, Scheduler};

fn main() {
    // NOTE: cards created with Card::default() are due immediately upon creation
    let card = Card::default();

    // Choose a rating and review the card with the scheduler
    // 5 - perfect response
    // 4 - correct response after a hesitation
    // 3 - correct response recalled with serious difficulty
    // 2 - incorrect response; where the correct one seemed easy to recall
    // 1 - incorrect response; the correct one remembered
    // 0 - complete blackout.
    let rating = 5;

    let (card, review_log) = Scheduler::review_card(&card, rating, None, None).unwrap();

    println!("Card rated {} at {}", rating, review_log.review_datetime);
    // > Card rated 5 at 2025-08-18 00:28:59.756451 UTC

    // how much time between when the card is due and now
    let time_delta = card.due - Utc::now();

    const SECONDS_PER_HOUR: f32 = 60.0 * 60.0;
    let time_delta_hours = (time_delta.as_seconds_f32() / SECONDS_PER_HOUR).round() as i32;

    println!("Card due in {time_delta_hours} hours");
    // > Card due in 24 hours
}
```

### Serialization

`Card` and `ReviewLog` variables are json-serializable for easy database storage and network requests

```rust
use sm_2::{Card, ReviewLog};

// ...

// serialize before storage / request
let card_json: String = card.to_json().unwrap();
let review_log_json: String = review_log.to_json().unwrap();

println!("{card_json}");
// > {"card_id":1755476939756,"n":1,"ef":2.6,"i":1,"due":"2025-08-19T00:28:59.756421Z","needs_extra_review":false}
println!("{review_log_json}");
// > {"card_id":1755476939756,"rating":5,"review_datetime":"2025-08-18T00:28:59.756451Z","review_duration":null}

// deserialize after storage / request
let new_card: Card = Card::from_json(&card_json).unwrap();
let new_review_log: ReviewLog = ReviewLog::from_json(&review_log_json).unwrap();
```

## Versioning

This package is currently unstable and adheres to the following versioning scheme:

- **Minor** version will increase when a backward-incompatible change is introduced.
- **Patch** version will increase when a bug is fixed or a new feature is added.

Once this package is considered stable, the **Major** version will be bumped to 1.0.0 and will follow [semver](https://semver.org/).