1#![cfg_attr(
19 feature = "alloc",
20 doc = r##"
21Format a number as an ordinal, allocating a new `String`:
22
23```
24use ordinal::ToOrdinal as _;
25assert_eq!(12.to_ordinal_string(), "12th");
26```
27
28Get a number representing an ordinal you can use with comparisons and formatting.
29
30```
31use ordinal::ToOrdinal as _;
32let n = 12.to_ordinal();
33assert_eq!(*n, 12);
34assert_eq!(format!("{n}"), "12th");
35```
36"##
37)]
38#![cfg_attr(not(feature = "std"), no_std)]
39
40#[cfg(feature = "alloc")]
41extern crate alloc;
42#[cfg(feature = "std")]
43extern crate std;
44
45#[allow(unused_imports)]
46#[cfg(feature = "alloc")]
47use alloc::{
48 format,
49 string::{String, ToString as _},
50};
51use core::fmt;
52
53#[cfg(feature = "alloc")]
54mod number {
55 use super::*;
56 use core::ops::Deref;
57
58 #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
78 pub struct Ordinal<T: ToOrdinal>(pub T);
79
80 impl<T: ToOrdinal> Ordinal<T> {
81 pub fn suffix(&self) -> &'static str {
90 self.0.suffix()
91 }
92 }
93
94 impl<T: ToOrdinal> Deref for Ordinal<T> {
95 type Target = T;
96
97 fn deref(&self) -> &Self::Target {
98 &self.0
99 }
100 }
101
102 impl<T: ToOrdinal> fmt::Display for Ordinal<T> {
103 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104 write!(f, "{}{}", self.0, self.0.suffix())
105 }
106 }
107
108 impl<T: ToOrdinal> From<T> for Ordinal<T> {
109 fn from(value: T) -> Self {
110 Ordinal(value)
111 }
112 }
113
114 #[test]
115 fn test_number() {
116 const TWO: Ordinal<i32> = Ordinal(2);
117 let twelve = Ordinal::from(12);
118
119 assert!(TWO < twelve);
120 assert_eq!(*twelve, 12);
121 assert_eq!(twelve.to_string(), String::from("12th"));
122 }
123}
124
125#[cfg(feature = "alloc")]
126pub use number::Ordinal;
127
128pub trait ToOrdinal: fmt::Display + Copy {
130 #[cfg(feature = "alloc")]
141 fn to_ordinal(self) -> Ordinal<Self> {
142 Ordinal(self)
143 }
144
145 #[cfg(feature = "alloc")]
154 fn to_ordinal_string(self) -> String {
155 format!("{}{}", self, self.suffix())
156 }
157
158 fn suffix(self) -> &'static str;
167}
168
169macro_rules! impl_ordinal {
170 ($($t:ty)*) => { $(
171 impl $crate::ToOrdinal for $t {
172 fn suffix(self) -> &'static str {
173 let n = Abs::abs(self);
174 let n = (n % 20) as u8;
175 if (11..=13).contains(&n) {
176 return "th";
177 }
178
179 match (n % 10) {
180 1 => "st",
181 2 => "nd",
182 3 => "rd",
183 _ => "th",
184 }
185 }
186 }
187 )* }
188}
189
190impl_ordinal!(u8 u16 u32 u64 u128 usize);
191impl_ordinal!(i8 i16 i32 i64 i128 isize);
192
193trait Abs<T> {
194 fn abs(self) -> T;
195}
196
197macro_rules! impl_abs {
198 (signed $($t:ty)*) => { $(
199 impl $crate::Abs<$t> for $t {
200 fn abs(self) -> $t {
201 self.abs()
202 }
203 }
204 )* };
205
206 (unsigned $($t:ty)*) => { $(
207 impl $crate::Abs<$t> for $t {
208 fn abs(self) -> $t {
209 self
210 }
211 }
212 )* };
213}
214
215impl_abs!(unsigned u8 u16 u32 u64 u128 usize);
216impl_abs!(signed i8 i16 i32 i64 i128 isize);
217
218#[cfg(feature = "alloc")]
219#[test]
220fn test_fmt() {
221 assert_eq!(0u8.to_ordinal_string(), "0th");
222 assert_eq!(1u16.to_ordinal_string(), "1st");
223 assert_eq!(2u32.to_ordinal_string(), "2nd");
224 assert_eq!(3u64.to_ordinal_string(), "3rd");
225 assert_eq!(4u128.to_ordinal_string(), "4th");
226 assert_eq!(5usize.to_ordinal_string(), "5th");
227 assert_eq!(6i8.to_ordinal_string(), "6th");
228 assert_eq!(7i16.to_ordinal_string(), "7th");
229 assert_eq!(8i32.to_ordinal_string(), "8th");
230 assert_eq!(9i64.to_ordinal_string(), "9th");
231 assert_eq!(10i128.to_ordinal_string(), "10th");
232 assert_eq!(11isize.to_ordinal_string(), "11th");
233
234 assert_eq!((-0i8).to_ordinal_string(), "0th");
235 assert_eq!((-1i16).to_ordinal_string(), "-1st");
236 assert_eq!((-2i32).to_ordinal_string(), "-2nd");
237 assert_eq!((-3i64).to_ordinal_string(), "-3rd");
238 assert_eq!((-4i128).to_ordinal_string(), "-4th");
239 assert_eq!((-5isize).to_ordinal_string(), "-5th");
240 assert_eq!((-6i8).to_ordinal_string(), "-6th");
241 assert_eq!((-7i16).to_ordinal_string(), "-7th");
242 assert_eq!((-8i32).to_ordinal_string(), "-8th");
243 assert_eq!((-9i64).to_ordinal_string(), "-9th");
244 assert_eq!((-10i128).to_ordinal_string(), "-10th");
245 assert_eq!((-11isize).to_ordinal_string(), "-11th");
246
247 assert_eq!(19u8.to_ordinal_string(), "19th");
248 assert_eq!(20u8.to_ordinal_string(), "20th");
249 assert_eq!(21u8.to_ordinal_string(), "21st");
250 assert_eq!(22u8.to_ordinal_string(), "22nd");
251 assert_eq!(23u8.to_ordinal_string(), "23rd");
252 assert_eq!(24u8.to_ordinal_string(), "24th");
253
254 assert_eq!(100u8.to_ordinal_string(), "100th");
255 assert_eq!(101u8.to_ordinal_string(), "101st");
256
257 assert_eq!(111u8.to_ordinal_string(), "111th");
258 assert_eq!(112u8.to_ordinal_string(), "112th");
259
260 assert_eq!(1001u32.to_ordinal_string(), "1001st");
261 assert_eq!(1002u32.to_ordinal_string(), "1002nd");
262 assert_eq!(1003u32.to_ordinal_string(), "1003rd");
263 assert_eq!(1004u32.to_ordinal_string(), "1004th");
264
265 assert_eq!(10001001u128.to_ordinal_string(), "10001001st");
266 assert_eq!(10001002u128.to_ordinal_string(), "10001002nd");
267 assert_eq!(10001003u128.to_ordinal_string(), "10001003rd");
268 assert_eq!(10001004u128.to_ordinal_string(), "10001004th");
269
270 assert_eq!(10001111u128.to_ordinal_string(), "10001111th");
271 assert_eq!(10001111u128.to_ordinal_string(), "10001111th");
272 assert_eq!(10001111u128.to_ordinal_string(), "10001111th");
273}
274
275#[test]
276fn test_suffix() {
277 assert_eq!(0u8.suffix(), "th");
278 assert_eq!(1u16.suffix(), "st");
279 assert_eq!(2u32.suffix(), "nd");
280 assert_eq!(3u64.suffix(), "rd");
281 assert_eq!(4u128.suffix(), "th");
282 assert_eq!(5usize.suffix(), "th");
283 assert_eq!(6i8.suffix(), "th");
284 assert_eq!(7i16.suffix(), "th");
285 assert_eq!(8i32.suffix(), "th");
286 assert_eq!(9i64.suffix(), "th");
287 assert_eq!(10i128.suffix(), "th");
288 assert_eq!(11isize.suffix(), "th");
289
290 assert_eq!((-0i8).suffix(), "th");
291 assert_eq!((-1i16).suffix(), "st");
292 assert_eq!((-2i32).suffix(), "nd");
293 assert_eq!((-3i64).suffix(), "rd");
294 assert_eq!((-4i128).suffix(), "th");
295 assert_eq!((-5isize).suffix(), "th");
296 assert_eq!((-6i8).suffix(), "th");
297 assert_eq!((-7i16).suffix(), "th");
298 assert_eq!((-8i32).suffix(), "th");
299 assert_eq!((-9i64).suffix(), "th");
300 assert_eq!((-10i128).suffix(), "th");
301 assert_eq!((-11isize).suffix(), "th");
302
303 assert_eq!(19u8.suffix(), "th");
304 assert_eq!(20u8.suffix(), "th");
305 assert_eq!(21u8.suffix(), "st");
306 assert_eq!(22u8.suffix(), "nd");
307 assert_eq!(23u8.suffix(), "rd");
308 assert_eq!(24u8.suffix(), "th");
309
310 assert_eq!(100u8.suffix(), "th");
311 assert_eq!(101u8.suffix(), "st");
312
313 assert_eq!(111u8.suffix(), "th");
314 assert_eq!(112u8.suffix(), "th");
315
316 assert_eq!(1001u32.suffix(), "st");
317 assert_eq!(1002u32.suffix(), "nd");
318 assert_eq!(1003u32.suffix(), "rd");
319 assert_eq!(1004u32.suffix(), "th");
320
321 assert_eq!(10001001u128.suffix(), "st");
322 assert_eq!(10001002u128.suffix(), "nd");
323 assert_eq!(10001003u128.suffix(), "rd");
324 assert_eq!(10001004u128.suffix(), "th");
325
326 assert_eq!(10001111u128.suffix(), "th");
327 assert_eq!(10001111u128.suffix(), "th");
328 assert_eq!(10001111u128.suffix(), "th");
329}
330
331#[cfg(all(doctest, feature = "std"))]
332#[doc = include_str!("../README.md")]
333struct ReadMe;