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 a [`BlockTime`].
170    ///
171    /// [`BlockTime`]: super::BlockTime
172    #[cfg(feature = "encoding")]
173    #[derive(Debug, Clone, PartialEq, Eq)]
174    pub struct BlockTimeDecoderError(pub(super) encoding::UnexpectedEofError);
175
176    #[cfg(feature = "encoding")]
177    impl From<Infallible> for BlockTimeDecoderError {
178        #[inline]
179        fn from(never: Infallible) -> Self { match never {} }
180    }
181
182    #[cfg(feature = "encoding")]
183    impl fmt::Display for BlockTimeDecoderError {
184        #[inline]
185        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
186            write_err!(f, "block time decoder error"; self.0)
187        }
188    }
189
190    #[cfg(feature = "encoding")]
191    #[cfg(feature = "std")]
192    impl std::error::Error for BlockTimeDecoderError {
193        #[inline]
194        fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { Some(&self.0) }
195    }
196}
197
198#[cfg(feature = "arbitrary")]
199impl<'a> Arbitrary<'a> for BlockTime {
200    #[inline]
201    fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
202        let t: u32 = u.arbitrary()?;
203        Ok(Self::from(t))
204    }
205}
206
207#[cfg(test)]
208mod tests {
209    #[cfg(feature = "alloc")]
210    use alloc::string::ToString;
211    #[cfg(feature = "encoding")]
212    #[cfg(feature = "std")]
213    use std::error::Error;
214
215    #[cfg(feature = "encoding")]
216    use encoding::Decoder as _;
217    #[cfg(feature = "alloc")]
218    #[cfg(feature = "encoding")]
219    use encoding::UnexpectedEofError;
220
221    use super::*;
222
223    #[test]
224    fn block_time_round_trip() {
225        let t = BlockTime::from(1_742_979_600); // 26 Mar 2025 9:00 UTC
226        assert_eq!(u32::from(t), 1_742_979_600);
227    }
228
229    #[test]
230    #[cfg(feature = "serde")]
231    fn block_time_serde_round_trip() {
232        let t = BlockTime::from(1_765_364_400); // 10 Dec 2025 11:00 UTC
233
234        let json = serde_json::to_string(&t).unwrap();
235        assert_eq!(json, "1765364400"); // ASCII number representation
236
237        let roundtrip = serde_json::from_str::<BlockTime>(&json).unwrap();
238        assert_eq!(t, roundtrip);
239    }
240
241    #[test]
242    #[cfg(feature = "alloc")]
243    #[cfg(feature = "encoding")]
244    fn block_time_decoding_error() {
245        let bytes = [0xb0, 0x52, 0x39]; // 3 bytes is an EOF error
246
247        let mut decoder = BlockTimeDecoder::default();
248        assert!(decoder.push_bytes(&mut bytes.as_slice()).unwrap().needs_more());
249
250        let error = decoder.end().unwrap_err();
251        assert!(matches!(error, BlockTimeDecoderError(UnexpectedEofError { .. })));
252    }
253
254    #[test]
255    #[cfg(feature = "alloc")]
256    fn time_module_display() {
257        assert_eq!(BlockTime::from(1_765_364_400).to_string(), "1765364400");
258    }
259
260    #[test]
261    #[cfg(feature = "alloc")]
262    #[cfg(feature = "encoding")]
263    fn time_module_error_display() {
264        // BlockTimeDecoderError
265        let bytes = [0xb0, 0x52, 0x39]; // 3 bytes is an EOF error
266
267        let mut decoder = BlockTimeDecoder::default();
268        let _ = decoder.push_bytes(&mut bytes.as_slice());
269
270        let e = decoder.end().unwrap_err();
271        assert!(!e.to_string().is_empty());
272        #[cfg(feature = "std")]
273        assert!(e.source().is_some());
274    }
275}