diff --git a/helix-core/src/date.rs b/helix-core/src/increment/date.rs
similarity index 95%
rename from helix-core/src/date.rs
rename to helix-core/src/increment/date.rs
index c447ef70..05442990 100644
--- a/helix-core/src/date.rs
+++ b/helix-core/src/increment/date.rs
@@ -9,6 +9,8 @@ use crate::{Range, Tendril};
 
 use chrono::{Datelike, Duration, NaiveDate};
 
+use super::Increment;
+
 fn ndays_in_month(year: i32, month: u32) -> u32 {
     // The first day of the next month...
     let (y, m) = if month == 12 {
@@ -29,6 +31,7 @@ fn add_days(date: NaiveDate, amount: i64) -> Option<NaiveDate> {
 fn add_months(date: NaiveDate, amount: i64) -> Option<NaiveDate> {
     let month = date.month0() as i64 + amount;
     let year = date.year() + i32::try_from(month / 12).ok()?;
+    let year = if month.is_negative() { year - 1 } else { year };
 
     // Normalize month
     let month = month % 12;
@@ -45,7 +48,6 @@ fn add_months(date: NaiveDate, amount: i64) -> Option<NaiveDate> {
 
 fn add_years(date: NaiveDate, amount: i64) -> Option<NaiveDate> {
     let year = i32::try_from(date.year() as i64 + amount).ok()?;
-
     let ndays = ndays_in_month(year, date.month());
 
     if date.day() > ndays {
@@ -85,9 +87,8 @@ enum DateField {
 
 #[derive(Debug, PartialEq, Eq)]
 pub struct DateIncrementor {
-    pub date: NaiveDate,
-    pub range: Range,
-
+    date: NaiveDate,
+    range: Range,
     field: DateField,
     format: Format,
 }
@@ -150,8 +151,10 @@ impl DateIncrementor {
             })
         })
     }
+}
 
-    pub fn incremented_text(&self, amount: i64) -> Tendril {
+impl Increment for DateIncrementor {
+    fn increment(&self, amount: i64) -> (Range, Tendril) {
         let date = match self.field {
             DateField::Year => add_years(self.date, amount),
             DateField::Month => add_months(self.date, amount),
@@ -159,15 +162,18 @@ impl DateIncrementor {
         }
         .unwrap_or(self.date);
 
-        format!(
-            "{:04}{}{:02}{}{:02}",
-            date.year(),
-            self.format.separator,
-            date.month(),
-            self.format.separator,
-            date.day()
+        (
+            self.range,
+            format!(
+                "{:04}{}{:02}{}{:02}",
+                date.year(),
+                self.format.separator,
+                date.month(),
+                self.format.separator,
+                date.day()
+            )
+            .into(),
         )
-        .into()
     }
 }
 
@@ -437,6 +443,8 @@ mod test {
             ("2020-02-29", 0, 1, "2021-03-01"),
             ("2020-01-31", 5, 1, "2020-02-29"),
             ("2020-01-20", 5, 1, "2020-02-20"),
+            ("2021-01-01", 5, -1, "2020-12-01"),
+            ("2021-01-31", 5, -2, "2020-11-30"),
             ("2020-02-28", 8, 1, "2020-02-29"),
             ("2021-02-28", 8, 1, "2021-03-01"),
             ("2021-02-28", 0, -1, "2020-02-28"),
@@ -457,7 +465,8 @@ mod test {
             assert_eq!(
                 DateIncrementor::from_range(rope.slice(..), range)
                     .unwrap()
-                    .incremented_text(amount),
+                    .increment(amount)
+                    .1,
                 expected.into()
             );
         }
diff --git a/helix-core/src/increment/mod.rs b/helix-core/src/increment/mod.rs
new file mode 100644
index 00000000..71a1f183
--- /dev/null
+++ b/helix-core/src/increment/mod.rs
@@ -0,0 +1,8 @@
+pub mod date;
+pub mod number;
+
+use crate::{Range, Tendril};
+
+pub trait Increment {
+    fn increment(&self, amount: i64) -> (Range, Tendril);
+}
diff --git a/helix-core/src/numbers.rs b/helix-core/src/increment/number.rs
similarity index 96%
rename from helix-core/src/numbers.rs
rename to helix-core/src/increment/number.rs
index e9f3c898..a19b7e75 100644
--- a/helix-core/src/numbers.rs
+++ b/helix-core/src/increment/number.rs
@@ -2,6 +2,8 @@ use std::borrow::Cow;
 
 use ropey::RopeSlice;
 
+use super::Increment;
+
 use crate::{
     textobject::{textobject_word, TextObject},
     Range, Tendril,
@@ -9,9 +11,9 @@ use crate::{
 
 #[derive(Debug, PartialEq, Eq)]
 pub struct NumberIncrementor<'a> {
-    pub range: Range,
-    pub value: i64,
-    pub radix: u32,
+    value: i64,
+    radix: u32,
+    range: Range,
 
     text: RopeSlice<'a>,
 }
@@ -71,9 +73,10 @@ impl<'a> NumberIncrementor<'a> {
             text,
         })
     }
+}
 
-    /// Add `amount` to the number and return the formatted text.
-    pub fn incremented_text(&self, amount: i64) -> Tendril {
+impl<'a> Increment for NumberIncrementor<'a> {
+    fn increment(&self, amount: i64) -> (Range, Tendril) {
         let old_text: Cow<str> = self.text.slice(self.range.from()..self.range.to()).into();
         let old_length = old_text.len();
         let new_value = self.value.wrapping_add(amount);
@@ -144,7 +147,7 @@ impl<'a> NumberIncrementor<'a> {
             }
         }
 
-        new_text.into()
+        (self.range, new_text.into())
     }
 }
 
@@ -366,7 +369,8 @@ mod test {
             assert_eq!(
                 NumberIncrementor::from_range(rope.slice(..), range)
                     .unwrap()
-                    .incremented_text(amount),
+                    .increment(amount)
+                    .1,
                 expected.into()
             );
         }
@@ -392,7 +396,8 @@ mod test {
             assert_eq!(
                 NumberIncrementor::from_range(rope.slice(..), range)
                     .unwrap()
-                    .incremented_text(amount),
+                    .increment(amount)
+                    .1,
                 expected.into()
             );
         }
@@ -419,7 +424,8 @@ mod test {
             assert_eq!(
                 NumberIncrementor::from_range(rope.slice(..), range)
                     .unwrap()
-                    .incremented_text(amount),
+                    .increment(amount)
+                    .1,
                 expected.into()
             );
         }
@@ -464,7 +470,8 @@ mod test {
             assert_eq!(
                 NumberIncrementor::from_range(rope.slice(..), range)
                     .unwrap()
-                    .incremented_text(amount),
+                    .increment(amount)
+                    .1,
                 expected.into()
             );
         }
@@ -491,7 +498,8 @@ mod test {
             assert_eq!(
                 NumberIncrementor::from_range(rope.slice(..), range)
                     .unwrap()
-                    .incremented_text(amount),
+                    .increment(amount)
+                    .1,
                 expected.into()
             );
         }
diff --git a/helix-core/src/lib.rs b/helix-core/src/lib.rs
index b16a716f..4ae044cc 100644
--- a/helix-core/src/lib.rs
+++ b/helix-core/src/lib.rs
@@ -1,17 +1,16 @@
 pub mod auto_pairs;
 pub mod chars;
 pub mod comment;
-pub mod date;
 pub mod diagnostic;
 pub mod diff;
 pub mod graphemes;
 pub mod history;
+pub mod increment;
 pub mod indent;
 pub mod line_ending;
 pub mod macros;
 pub mod match_brackets;
 pub mod movement;
-pub mod numbers;
 pub mod object;
 pub mod path;
 mod position;
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index 639bbd83..6329dec7 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -1,14 +1,13 @@
 use helix_core::{
-    comment, coords_at_pos,
-    date::DateIncrementor,
-    find_first_non_whitespace_char, find_root, graphemes,
+    comment, coords_at_pos, find_first_non_whitespace_char, find_root, graphemes,
     history::UndoKind,
+    increment::date::DateIncrementor,
+    increment::{number::NumberIncrementor, Increment},
     indent,
     indent::IndentStyle,
     line_ending::{get_line_ending_of_str, line_end_char_index, str_is_line_ending},
     match_brackets,
     movement::{self, Direction},
-    numbers::NumberIncrementor,
     object, pos_at_coords,
     regex::{self, Regex, RegexBuilder},
     search, selection, surround, textobject,
@@ -5804,23 +5803,18 @@ fn increment_impl(cx: &mut Context, amount: i64) {
     let text = doc.text();
 
     let changes = selection.ranges().iter().filter_map(|range| {
-        if let Some(incrementor) = DateIncrementor::from_range(text.slice(..), *range) {
-            let new_text = incrementor.incremented_text(amount);
-            Some((
-                incrementor.range.from(),
-                incrementor.range.to(),
-                Some(new_text),
-            ))
+        let incrementor: Option<Box<dyn Increment>> = if let Some(incrementor) =
+            DateIncrementor::from_range(text.slice(..), *range)
+        {
+            Some(Box::new(incrementor))
         } else if let Some(incrementor) = NumberIncrementor::from_range(text.slice(..), *range) {
-            let new_text = incrementor.incremented_text(amount);
-            Some((
-                incrementor.range.from(),
-                incrementor.range.to(),
-                Some(new_text),
-            ))
+            Some(Box::new(incrementor))
         } else {
             None
-        }
+        };
+
+        let (range, new_text) = incrementor?.increment(amount);
+        Some((range.from(), range.to(), Some(new_text)))
     });
 
     if changes.clone().count() > 0 {