Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,20 @@ jobs:
rust:
- stable
# Define the feature sets that will be built here (for caching you define a separate name)
style: [bashisms, default, sqlite, basqlite, external_printer]
style: [bashisms, default, sqlite, basqlite, external_printer, jiff-datetime]
include:
- style: bashisms
flags: "--features bashisms"
- style: external_printer
flags: "--features external_printer"
- style: default
flags: ""
flags: "" # Uses chrono by default
- style: sqlite
flags: "--features sqlite"
- style: basqlite
flags: "--features bashisms,sqlite"
- style: jiff-datetime
flags: "--no-default-features --features jiff-datetime"

runs-on: ${{ matrix.platform }}

Expand Down
161 changes: 161 additions & 0 deletions CHECKLIST.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
# chrono → jiff-datetime Migration Checklist

This checklist tracks the implementation of the feature-gated migration from `chrono` to `jiff-datetime`.

## Phase 1: Project Configuration ✅

### 1.1 Update Cargo.toml
- [x] Bump MSRV from 1.63.0 to 1.70.0
- [x] Make chrono optional dependency (was required)
- [x] Add jiff as optional dependency
- [x] Add jiff-datetime feature (mutually exclusive with chrono)
- [x] Keep chrono as default feature for backward compatibility
- [x] Add compile-time checks for mutual exclusivity
- [x] Update docs.rs features

## Phase 2: Create Abstraction Layer ✅

### 2.1 Create src/datetime.rs
- [x] Create new file
- [x] Add mutual exclusivity compile_error macros
- [x] Implement chrono backend (DateTime wrapper)
- [x] Implement jiff-datetime backend (DateTime wrapper)
- [x] Ensure same API: now(), from_millis(), as_millis(), format()

### 2.2 Add Tests for datetime module
- [x] test_datetime_now() - Creates datetime successfully
- [x] test_datetime_from_millis_roundtrip() - Conversion roundtrip works
- [x] test_datetime_from_millis_invalid() - Handles invalid timestamps
- [x] test_datetime_format() - Format produces expected output
- [x] test_datetime_display() - Display trait works
- [x] test_datetime_ordering() - Comparison operators work
- [x] test_datetime_serde_roundtrip() - Serde serialization works (with serde_json feature)

## Phase 3: Update Source Files ✅

### 3.1 Update src/lib.rs
- [x] Add `mod datetime;`
- [x] Add `pub use datetime::DateTime;` (public export)

### 3.2 Update src/history/item.rs
- [x] Remove `use chrono::Utc;`
- [x] Add `use crate::DateTime;`
- [x] Change `start_timestamp: Option<chrono::DateTime<Utc>>` to `Option<DateTime>`

### 3.3 Update src/history/base.rs
- [x] Remove `use chrono::Utc;`
- [x] Add `use crate::DateTime;`
- [x] Change `start_time: Option<chrono::DateTime<Utc>>` to `Option<DateTime>`
- [x] Change `end_time: Option<chrono::DateTime<Utc>>` to `Option<DateTime>`

### 3.4 Update src/history/sqlite_backed.rs
- [x] Remove `use chrono::{TimeZone, Utc};`
- [x] Add `use crate::DateTime;`
- [x] Change `session_timestamp: Option<chrono::DateTime<Utc>>` to `Option<DateTime>`
- [x] Update deserialize_history_item: Use DateTime::from_millis()
- [x] Update save: Use DateTime::as_millis()
- [x] Update construct_query: Use DateTime::as_millis()

### 3.5 Update src/prompt/default.rs
- [x] Remove `use chrono::Local;`
- [x] Add `use crate::DateTime;`
- [x] Update get_now() to use DateTime::now() and DateTime::format()
- [x] Verify format string "%m/%d/%Y %I:%M:%S %p" compatibility

### 3.6 Update examples/demo.rs
- [x] Add conditional `use reedline::DateTime;` (only for sqlite feature)
- [x] Replace `chrono::Utc::now()` with `DateTime::now()`
- [x] Fix unused import warning with conditional compilation

### 3.7 Verify all examples support both backends ✅
- [x] demo.rs - ✅ Compiles with both chrono and jiff-datetime
- [x] cwd_aware_hinter.rs - ✅ Compiles with both (uses None for timestamp)
- [x] basic.rs - ✅ Compiles with both
- [x] highlighter.rs - ✅ Compiles with both
- [x] hinter.rs - ✅ Compiles with both
- [x] history.rs - ✅ Compiles with both
- [x] validator.rs - ✅ Compiles with both
- [x] event_listener.rs - ✅ Compiles with both
- [x] event_listener_kitty_proto.rs - ✅ Compiles with both
- [x] list_bindings.rs - ✅ Compiles with both
- [x] completions.rs - ✅ Compiles with both
- [x] ide_completions.rs - ✅ Compiles with both
- [x] custom_prompt.rs - ✅ Compiles with both
- [x] transient_prompt.rs - ✅ Compiles with both

## Phase 4: CI/CD Updates ✅

### 4.1 Update .github/workflows/ci.yml
- [x] Add jiff-datetime to matrix style
- [x] Add jiff-datetime flags to include section
- [x] Keep existing chrono tests

## Phase 5: Testing & Verification ✅

### 5.1 Build & Test
- [x] Test default build (chrono): `cargo test` - ✅ PASSED (26 tests + 6 datetime tests)
- [x] Test jiff-datetime build: `cargo test --no-default-features --features jiff-datetime` - ✅ PASSED (26 tests + 6 datetime tests)
- [x] Verify mutual exclusion compile error: `cargo check --features "chrono jiff-datetime"` - ✅ ERROR SHOWN
- [x] Test SQLite compatibility between backends: `cargo test --no-default-features --features "jiff-datetime sqlite"` - ✅ PASSED
- [x] Run clippy: `cargo clippy --all-targets --all -- -D warnings` - Pre-existing warnings only
- [x] Run fmt: `cargo fmt --all -- --check` - ✅ PASSED

### 5.2 Examples Build Verification
- [x] All examples build with chrono (default)
- [x] All examples build with jiff-datetime
- [x] Examples with conditional imports work correctly

### 5.3 Documentation
- [ ] Update README.md with feature selection docs - Skipped for now
- [ ] Update CHANGELOG.md - Skipped for now
- [x] Verify all public API changes documented - DateTime is public in lib.rs
- [x] Tests added with documentation comments

## Progress Summary ✅

**Last Updated:** 2026-01-31
**Status:** COMPLETED ✅

### Test Summary
| Configuration | Tests | Status |
|--------------|-------|--------|
| chrono (default) | 26 + 6 datetime | ✅ PASSED |
| jiff-datetime | 26 + 6 datetime | ✅ PASSED |
| jiff-datetime + sqlite | 26 + 6 datetime | ✅ PASSED |
| mutual exclusivity | Compile error | ✅ VERIFIED |
| format string | Both backends | ✅ COMPATIBLE |

### Completed Items ✅
- [x] Project configuration (Cargo.toml)
- [x] Abstraction layer (datetime.rs)
- [x] Core library updates
- [x] History module updates
- [x] Prompt updates
- [x] Example updates (all 14 examples verified)
- [x] Datetime unit tests (6 comprehensive tests)
- [x] CI updates
- [x] All tests passing

### Blockers
None

### Notes
- Format string "%m/%d/%Y %I:%M:%S %p" verified compatible with both backends
- MSRV bumped to 1.70.0 for jiff compatibility
- Both chrono and jiff-datetime are equally supported (no deprecation)
- Pre-existing clippy warning in core_editor/line_buffer.rs:314 (unrelated to this migration)
- All 14 examples compile successfully with both backends

### How to Use

**Default (chrono) - No changes needed:**
```toml
[dependencies]
reedline = "0.45"
```

**With jiff-datetime:**
```toml
[dependencies]
reedline = { version = "0.45", default-features = false, features = ["jiff-datetime", "sqlite"] }
```
65 changes: 60 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 8 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ edition = "2021"
license = "MIT"
name = "reedline"
repository = "https://github.com/nushell/reedline"
rust-version = "1.63.0"
rust-version = "1.70.0"
version = "0.45.0"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
Expand All @@ -19,7 +19,8 @@ arboard = { version = "3.3.0", optional = true, default-features = false, featur
chrono = { version = "0.4.19", default-features = false, features = [
"clock",
"serde",
] }
], optional = true }
jiff = { version = "0.2", features = ["serde"], optional = true }
crossbeam = { version = "0.8.2", optional = true }
crossterm = { version = "0.29.0", features = ["serde"] }
fd-lock = "4.0.2"
Expand All @@ -43,6 +44,10 @@ rstest = { version = "0.23.0", default-features = false }
tempfile = "3.3.0"

[features]
default = ["chrono"]
# Note: chrono and jiff-datetime features are mutually exclusive
chrono = ["dep:chrono"]
jiff-datetime = ["dep:jiff"]
bashisms = []
external_printer = ["crossbeam"]
sqlite = ["rusqlite/bundled", "serde_json"]
Expand All @@ -60,4 +65,4 @@ required-features = ["external_printer"]
[package.metadata.docs.rs]
# Whether to pass `--all-features` to Cargo (default: false)
all-features = false
features = ["bashisms", "external_printer", "sqlite"]
features = ["bashisms", "external_printer", "sqlite", "jiff-datetime"]
6 changes: 4 additions & 2 deletions examples/demo.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#[cfg(any(feature = "sqlite", feature = "sqlite-dynlib"))]
use reedline::DateTime;
use std::env::temp_dir;
use std::process::Command;
use {
Expand Down Expand Up @@ -37,7 +39,7 @@ fn main() -> reedline::Result<()> {
reedline::SqliteBackedHistory::with_file(
"history.sqlite3".into(),
history_session_id,
Some(chrono::Utc::now()),
Some(DateTime::now()),
)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?,
);
Expand Down Expand Up @@ -148,7 +150,7 @@ fn main() -> reedline::Result<()> {
if !buffer.is_empty() {
line_editor
.update_last_command_context(&|mut c: reedline::HistoryItem| {
c.start_timestamp = Some(chrono::Utc::now());
c.start_timestamp = Some(DateTime::now());
c.hostname =
Some(gethostname::gethostname().to_string_lossy().to_string());
c.cwd = std::env::current_dir()
Expand Down
2 changes: 1 addition & 1 deletion src/core_editor/line_buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ impl LineBuffer {
fn at_end_of_line_with_preceding_whitespace(&self) -> bool {
!self.is_empty() // No point checking if empty
&& self.insertion_point == self.lines.len()
&& self.lines.chars().last().map_or(false, |c| c.is_whitespace())
&& self.lines.chars().last().is_some_and(|c| c.is_whitespace())
}

/// Cursor position at the end of the current whitespace block.
Expand Down
Loading