diff --git a/time-macros/src/format_description/ast.rs b/time-macros/src/format_description/ast.rs
index 5ac201ac1..497c8965d 100644
--- a/time-macros/src/format_description/ast.rs
+++ b/time-macros/src/format_description/ast.rs
@@ -81,7 +81,7 @@ fn parse_inner<
 
         Some(match next {
             lexer::Token::Literal(Spanned { value: _, span: _ }) if NESTED => {
-                unreachable!("internal error: literal should not be present in nested description")
+                bug!("literal should not be present in nested description")
             }
             lexer::Token::Literal(value) => Ok(Item::Literal(value)),
             lexer::Token::Bracket {
@@ -105,23 +105,18 @@ fn parse_inner<
                 kind: lexer::BracketKind::Closing,
                 location: _,
             } if NESTED => {
-                unreachable!(
-                    "internal error: closing bracket should be caught by the `if` statement"
-                )
+                bug!("closing bracket should be caught by the `if` statement")
             }
             lexer::Token::Bracket {
                 kind: lexer::BracketKind::Closing,
                 location: _,
             } => {
-                unreachable!(
-                    "internal error: closing bracket should have been consumed by \
-                     `parse_component`"
-                )
+                bug!("closing bracket should have been consumed by `parse_component`")
             }
             lexer::Token::ComponentPart { kind: _, value } if NESTED => Ok(Item::Literal(value)),
-            lexer::Token::ComponentPart { kind: _, value: _ } => unreachable!(
-                "internal error: component part should have been consumed by `parse_component`"
-            ),
+            lexer::Token::ComponentPart { kind: _, value: _ } => {
+                bug!("component part should have been consumed by `parse_component`")
+            }
         })
     })
 }
diff --git a/time-macros/src/format_description/format_item.rs b/time-macros/src/format_description/format_item.rs
index c94a04431..46c277d2f 100644
--- a/time-macros/src/format_description/format_item.rs
+++ b/time-macros/src/format_description/format_item.rs
@@ -107,7 +107,7 @@ impl<'a> From<Box<[Item<'a>]>> for crate::format_description::public::OwnedForma
             if let Ok([item]) = <[_; 1]>::try_from(items) {
                 item.into()
             } else {
-                unreachable!("the length was just checked to be 1")
+                bug!("the length was just checked to be 1")
             }
         } else {
             Self::Compound(items.into_iter().map(Self::from).collect())
@@ -181,9 +181,7 @@ macro_rules! component_definition {
                                     then {
                                         match $field {
                                             Some(value) => value.into(),
-                                            None => unreachable!(
-                                                "internal error: required modifier was not set"
-                                            ),
+                                            None => bug!("required modifier was not set"),
                                         }
                                     } else {
                                         $field.unwrap_or_default().into()
diff --git a/time-macros/src/helpers/string.rs b/time-macros/src/helpers/string.rs
index fa3780f5e..6b478f60d 100644
--- a/time-macros/src/helpers/string.rs
+++ b/time-macros/src/helpers/string.rs
@@ -57,7 +57,7 @@ fn parse_lit_str_cooked(mut s: &str) -> Vec<u8> {
                             continue 'outer;
                         }
                     },
-                    _ => unreachable!("invalid escape"),
+                    _ => bug!("invalid escape"),
                 }
             }
             b'\r' => {
@@ -120,7 +120,7 @@ fn parse_lit_byte_str_cooked(mut v: &[u8]) -> Vec<u8> {
                             continue 'outer;
                         }
                     },
-                    _ => unreachable!("invalid escape"),
+                    _ => bug!("invalid escape"),
                 }
             }
             b'\r' => {
@@ -151,7 +151,7 @@ where
         b'0'..=b'9' => b1 - b'0',
         b'a'..=b'f' => 10 + (b1 - b'a'),
         b'A'..=b'F' => 10 + (b1 - b'A'),
-        _ => unreachable!("invalid hex escape"),
+        _ => bug!("invalid hex escape"),
     };
     (ch, &s[2..])
 }
@@ -172,7 +172,7 @@ fn backslash_u(mut s: &str) -> (char, &str) {
                 continue;
             }
             b'}' if digits != 0 => break,
-            _ => unreachable!("invalid unicode escape"),
+            _ => bug!("invalid unicode escape"),
         };
         ch *= 0x10;
         ch += u32::from(digit);
diff --git a/time-macros/src/lib.rs b/time-macros/src/lib.rs
index 9d7ccb889..84ad25113 100644
--- a/time-macros/src/lib.rs
+++ b/time-macros/src/lib.rs
@@ -33,6 +33,13 @@
     clippy::option_if_let_else, // suggests terrible code
 )]
 
+macro_rules! bug {
+    () => { compile_error!("provide an error message to help fix a possible bug") };
+    ($descr:literal $($rest:tt)?) => {
+        unreachable!(concat!("internal error: ", $descr) $($rest)?)
+    }
+}
+
 #[macro_use]
 mod quote;
 #[cfg(any(feature = "formatting", feature = "parsing"))]
@@ -165,7 +172,7 @@ pub fn format_description(input: TokenStream) -> TokenStream {
             Some(VersionOrModuleName::Version(version)) => Some(version),
             None => None,
             // This branch should never occur here, as `false` is the provided as a const parameter.
-            Some(VersionOrModuleName::ModuleName(_)) => unreachable!(),
+            Some(VersionOrModuleName::ModuleName(_)) => bug!("branch should never occur"),
         };
         let (span, string) = helpers::get_string_literal(input)?;
         let items = format_description::parse_with_version(version, &string, span)?;
diff --git a/time/src/format_description/parse/ast.rs b/time/src/format_description/parse/ast.rs
index 0af4e0bde..f7b0ab78c 100644
--- a/time/src/format_description/parse/ast.rs
+++ b/time/src/format_description/parse/ast.rs
@@ -129,7 +129,7 @@ fn parse_inner<
 
         Some(match next {
             lexer::Token::Literal(Spanned { value: _, span: _ }) if NESTED => {
-                unreachable!("internal error: literal should not be present in nested description")
+                bug!("literal should not be present in nested description")
             }
             lexer::Token::Literal(value) => Ok(Item::Literal(value)),
             lexer::Token::Bracket {
@@ -153,26 +153,21 @@ fn parse_inner<
                 kind: lexer::BracketKind::Closing,
                 location: _,
             } if NESTED => {
-                unreachable!(
-                    "internal error: closing bracket should be caught by the `if` statement"
-                )
+                bug!("closing bracket should be caught by the `if` statement")
             }
             lexer::Token::Bracket {
                 kind: lexer::BracketKind::Closing,
                 location: _,
             } => {
-                unreachable!(
-                    "internal error: closing bracket should have been consumed by \
-                     `parse_component`"
-                )
+                bug!("closing bracket should have been consumed by `parse_component`")
             }
             lexer::Token::ComponentPart {
                 kind: _, // whitespace is significant in nested components
                 value,
             } if NESTED => Ok(Item::Literal(value)),
-            lexer::Token::ComponentPart { kind: _, value: _ } => unreachable!(
-                "internal error: component part should have been consumed by `parse_component`"
-            ),
+            lexer::Token::ComponentPart { kind: _, value: _ } => {
+                bug!("component part should have been consumed by `parse_component`")
+            }
         })
     })
 }
diff --git a/time/src/format_description/parse/format_item.rs b/time/src/format_description/parse/format_item.rs
index b65e211e0..582bfbbce 100644
--- a/time/src/format_description/parse/format_item.rs
+++ b/time/src/format_description/parse/format_item.rs
@@ -152,7 +152,7 @@ impl<'a> From<Box<[Item<'a>]>> for crate::format_description::OwnedFormatItem {
             if let Ok([item]) = <[_; 1]>::try_from(items) {
                 item.into()
             } else {
-                unreachable!("the length was just checked to be 1")
+                bug!("the length was just checked to be 1")
             }
         } else {
             Self::Compound(items.into_iter().map(Self::from).collect())
@@ -241,9 +241,7 @@ macro_rules! component_definition {
                                     then {
                                         match $field {
                                             Some(value) => value.into(),
-                                            None => unreachable!(
-                                                "internal error: required modifier was not set"
-                                            ),
+                                            None => bug!("required modifier was not set"),
                                         }
                                     } else {
                                         $field.unwrap_or_default().into()
diff --git a/time/src/lib.rs b/time/src/lib.rs
index e0f439377..5ebcf83a1 100644
--- a/time/src/lib.rs
+++ b/time/src/lib.rs
@@ -300,6 +300,14 @@ macro_rules! expect_opt {
         }
     };
 }
+
+/// `unreachable!()`, but better.
+macro_rules! bug {
+    () => { compile_error!("provide an error message to help fix a possible bug") };
+    ($descr:literal $($rest:tt)?) => {
+        unreachable!(concat!("internal error: ", $descr) $($rest)?)
+    }
+}
 // endregion macros
 
 #[macro_use]
diff --git a/time/src/parsing/parsable.rs b/time/src/parsing/parsable.rs
index cd2f2723d..c519dc36f 100644
--- a/time/src/parsing/parsable.rs
+++ b/time/src/parsing/parsable.rs
@@ -750,7 +750,7 @@ impl<const CONFIG: EncodedConfig> sealed::Sealed for Iso8601<CONFIG> {
         if !date_is_present && !time_is_present && !offset_is_present {
             match first_error {
                 Some(err) => return Err(err),
-                None => unreachable!("an error should be present if no components were parsed"),
+                None => bug!("an error should be present if no components were parsed"),
             }
         }