<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/).