Skip to main content

bitcoin_units/
time.rs

1// SPDX-License-Identifier: CC0-1.0
2
3//! A UNIX timestamp used as the Bitcoin block time.
4//!
5//! Also known as Epoch Time - January 1, 1970.
6//!
7//! This differs from other UNIX timestamps in that we only use non-negative values. The Epoch
8//! pre-dates Bitcoin so timestamps before this are not useful for block timestamps.
9
10use core::fmt;
11
12#[cfg(feature = "arbitrary")]
13use arbitrary::{Arbitrary, Unstructured};
14#[cfg(feature = "serde")]
15use serde::{Deserialize, Deserializer, Serialize, Serializer};
16
17use crate::parse_int::{self, PrefixedHexError, UnprefixedHexError};
18
19#[rustfmt::skip]                // Keep public re-exports separate.
20#[cfg(feature = "encoding")]
21#[doc(no_inline)]
22pub use self::error::BlockTimeDecoderError;
23
24mod encapsulate {
25    /// A Bitcoin block timestamp.
26    ///
27    /// > Each block contains a Unix time timestamp. In addition to serving as a source of variation for
28    /// > the block hash, they also make it more difficult for an adversary to manipulate the block chain.
29    /// >
30    /// > A timestamp is accepted as valid if it is greater than the median timestamp of previous 11
31    /// > blocks, and less than the network-adjusted time + 2 hours. "Network-adjusted time" is the
32    /// > median of the timestamps returned by all nodes connected to you. As a result block timestamps
33    /// > are not exactly accurate, and they do not need to be. Block times are accurate only to within
34    /// > an hour or two.
35    ///
36    /// ref: <https://en.bitcoin.it/wiki/Block_timestamp>
37    #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
38    pub struct BlockTime(u32);
39
40    impl BlockTime {
41        /// Constructs a new [`BlockTime`] from an unsigned 32 bit integer value.
42        #[inline]
43        pub const fn from_u32(t: u32) -> Self { Self(t) }
44
45        /// Returns the inner `u32` value.
46        #[inline]
47        pub const fn to_u32(self) -> u32 { self.0 }
48    }
49}
50#[doc(inline)]
51pub use encapsulate::BlockTime;
52
53impl BlockTime {
54    /// Constructs a new `BlockTime` from a prefixed hex string.
55    ///
56    /// # Errors
57    ///
58    /// If the input string is not a valid hex representation of a blocktime or it does not include
59    /// the `0x` prefix.
60    #[inline]
61    pub fn from_hex(s: &str) -> Result<Self, PrefixedHexError> {
62        let block_time = parse_int::hex_u32_prefixed(s)?;
63        Ok(Self::from_u32(block_time))
64    }
65
66    /// Constructs a new `BlockTime` from an unprefixed hex string.
67    ///
68    /// # Errors
69    ///
70    /// If the input string is not a valid hex representation of a blocktime or if it includes the
71    /// `0x` prefix.
72    #[inline]
73    pub fn from_unprefixed_hex(s: &str) -> Result<Self, UnprefixedHexError> {
74        let block_time = parse_int::hex_u32_unprefixed(s)?;
75        Ok(Self::from_u32(block_time))
76    }
77}
78
79crate::internal_macros::impl_fmt_traits_for_u32_wrapper!(BlockTime, to_u32);
80
81impl fmt::Display for BlockTime {
82    #[inline]
83    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.to_u32(), f) }
84}
85
86impl From<u32> for BlockTime {
87    #[inline]
88    fn from(t: u32) -> Self { Self::from_u32(t) }
89}
90
91impl From<BlockTime> for u32 {
92    #[inline]
93    fn from(t: BlockTime) -> Self { t.to_u32() }
94}
95
96parse_int::impl_parse_str_from_int_infallible!(BlockTime, u32, from_u32);
97
98#[cfg(feature = "serde")]
99impl Serialize for BlockTime {
100    #[inline]
101    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
102    where
103        S: Serializer,
104    {
105        u32::serialize(&self.to_u32(), s)
106    }
107}
108
109#[cfg(feature = "serde")]
110impl<'de> Deserialize<'de> for BlockTime {
111    #[inline]
112    fn deserialize<D>(d: D) -> Result<Self, D::Error>
113    where
114        D: Deserializer<'de>,
115    {
116        Ok(Self::from_u32(u32::deserialize(d)?))
117    }
118}
119
120#[cfg(feature = "encoding")]
121impl encoding::Encode for BlockTime {
122    type Encoder<'e> = BlockTimeEncoder<'e>;
123    #[inline]
124    fn encoder(&self) -> Self::Encoder<'_> {
125        BlockTimeEncoder::new(encoding::ArrayEncoder::without_length_prefix(
126            self.to_u32().to_le_bytes(),
127        ))
128    }
129}
130
131#[cfg(feature = "encoding")]
132impl encoding::Decode for BlockTime {
133    type Decoder = BlockTimeDecoder;
134}
135
136#[cfg(feature = "encoding")]
137encoding::encoder_newtype_exact! {
138    /// The encoder for the [`BlockTime`] type.
139    #[derive(Debug, Clone)]
140    pub struct BlockTimeEncoder<'e>(encoding::ArrayEncoder<4>);
141}
142
143#[cfg(feature = "encoding")]
144crate::decoder_newtype! {
145    /// The decoder for the [`BlockTime`] type.
146    #[derive(Debug, Clone)]
147    pub struct BlockTimeDecoder(encoding::ArrayDecoder<4>);
148
149    /// Constructs a new [`BlockTime`] decoder.
150    pub const fn new() -> Self { Self(encoding::ArrayDecoder::new()) }
151
152    fn end(result: Result<[u8; 4], encoding::UnexpectedEofError>) -> Result<BlockTime, BlockTimeDecoderError> {
153        let value = result.map_err(BlockTimeDecoderError)?;
154        let n = u32::from_le_bytes(value);
155        Ok(BlockTime::from_u32(n))
156    }
157}
158
159/// Error types for block times.
160pub mod error {
161    #[cfg(feature = "encoding")]
162    use core::convert::Infallible;
163    #[cfg(feature = "encoding")]
164    use core::fmt;
165
166    #[cfg(feature = "encoding")]
167    use internals::write_err;
168
169    /// An error consensus decoding an `BlockTime`.
170    #[cfg(feature = "encoding")]
171    #[derive(Debug, Clone, PartialEq, Eq)]
172    pub struct BlockTimeDecoderError(pub(super) encoding::UnexpectedEofError);
173
174    #[cfg(feature = "encoding")]
175    impl From<Infallible> for BlockTimeDecoderError {
176        fn from(never: Infallible) -> Self { match never {} }
177    }
178
179    #[cfg(feature = "encoding")]
180    impl fmt::Display for BlockTimeDecoderError {
181        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
182            write_err!(f, "block time decoder error"; self.0)
183        }
184    }
185
186    #[cfg(feature = "encoding")]
187    #[cfg(feature = "std")]
188    impl std::error::Error for BlockTimeDecoderError {
189        #[inline]
190        fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { Some(&self.0) }
191    }
192}
193
194#[cfg(feature = "arbitrary")]
195impl<'a> Arbitrary<'a> for BlockTime {
196    #[inline]
197    fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
198        let t: u32 = u.arbitrary()?;
199        Ok(Self::from(t))
200    }
201}
202
203#[cfg(test)]
204mod tests {
205    #[cfg(feature = "alloc")]
206    use alloc::string::ToString;
207    #[cfg(feature = "encoding")]
208    #[cfg(feature = "std")]
209    use std::error::Error;
210
211    #[cfg(feature = "encoding")]
212    use encoding::Decoder as _;
213    #[cfg(feature = "alloc")]
214    #[cfg(feature = "encoding")]
215    use encoding::UnexpectedEofError;
216
217    use super::*;
218
219    #[test]
220    fn block_time_round_trip() {
221        let t = BlockTime::from(1_742_979_600); // 26 Mar 2025 9:00 UTC
222        assert_eq!(u32::from(t), 1_742_979_600);
223    }
224
225    #[test]
226    #[cfg(feature = "serde")]
227    fn block_time_serde_round_trip() {
228        let t = BlockTime::from(1_765_364_400); // 10 Dec 2025 11:00 UTC
229
230        let json = serde_json::to_string(&t).unwrap();
231        assert_eq!(json, "1765364400"); // ASCII number representation
232
233        let roundtrip = serde_json::from_str::<BlockTime>(&json).unwrap();
234        assert_eq!(t, roundtrip);
235    }
236
237    #[test]
238    #[cfg(feature = "alloc")]
239    #[cfg(feature = "encoding")]
240    fn block_time_decoding_error() {
241        let bytes = [0xb0, 0x52, 0x39]; // 3 bytes is an EOF error
242
243        let mut decoder = BlockTimeDecoder::default();
244        assert!(decoder.push_bytes(&mut bytes.as_slice()).unwrap().needs_more());
245
246        let error = decoder.end().unwrap_err();
247        assert!(matches!(error, BlockTimeDecoderError(UnexpectedEofError { .. })));
248    }
249
250    #[test]
251    #[cfg(feature = "alloc")]
252    fn time_module_display() {
253        assert_eq!(BlockTime::from(1_765_364_400).to_string(), "1765364400");
254    }
255
256    #[test]
257    #[cfg(feature = "alloc")]
258    #[cfg(feature = "encoding")]
259    fn time_module_error_display() {
260        // BlockTimeDecoderError
261        let bytes = [0xb0, 0x52, 0x39]; // 3 bytes is an EOF error
262
263        let mut decoder = BlockTimeDecoder::default();
264        let _ = decoder.push_bytes(&mut bytes.as_slice());
265
266        let e = decoder.end().unwrap_err();
267        assert!(!e.to_string().is_empty());
268        #[cfg(feature = "std")]
269        assert!(e.source().is_some());
270    }
271}