@@ -170,9 +170,38 @@ impl Network {
170170 ///
171171 /// The Slot does not have to be a valid slot present in the blockchain.
172172 #[ must_use]
173- pub fn time_to_slot ( & self , _time : DateTime < Utc > ) -> Option < u64 > {
174- // TODO: Implement this, for now just return None.
175- None
173+ pub fn time_to_slot ( & self , time : DateTime < Utc > ) -> Option < u64 > {
174+ let genesis = self . genesis_values ( ) ;
175+
176+ let byron_start_time = i64:: try_from ( genesis. byron_known_time )
177+ . map ( |time| DateTime :: < Utc > :: from_timestamp ( time, 0 ) )
178+ . ok ( ) ??;
179+ let shelley_start_time = i64:: try_from ( genesis. shelley_known_time )
180+ . map ( |time| DateTime :: < Utc > :: from_timestamp ( time, 0 ) )
181+ . ok ( ) ??;
182+
183+ // determine if the given time is in Byron or Shelley era.
184+ if time < byron_start_time {
185+ return None ;
186+ }
187+
188+ if time < shelley_start_time {
189+ // Byron era
190+ let time_diff = time - byron_start_time;
191+ let elapsed_slots = time_diff. num_seconds ( ) / i64:: from ( genesis. byron_slot_length ) ;
192+
193+ u64:: try_from ( elapsed_slots)
194+ . map ( |elapsed_slots| Some ( genesis. byron_known_slot + elapsed_slots) )
195+ . ok ( ) ?
196+ } else {
197+ // Shelley era
198+ let time_diff = time - shelley_start_time;
199+ let elapsed_slots = time_diff. num_seconds ( ) / i64:: from ( genesis. shelley_slot_length ) ;
200+
201+ u64:: try_from ( elapsed_slots)
202+ . map ( |elapsed_slots| Some ( genesis. shelley_known_slot + elapsed_slots) )
203+ . ok ( ) ?
204+ }
176205 }
177206}
178207
@@ -191,6 +220,7 @@ mod tests {
191220 use std:: str:: FromStr ;
192221
193222 use anyhow:: Ok ;
223+ use chrono:: { TimeZone , Utc } ;
194224
195225 use super :: * ;
196226
@@ -214,4 +244,153 @@ mod tests {
214244
215245 Ok ( ( ) )
216246 }
247+
248+ #[ test]
249+ fn test_time_to_slot_before_blockchain ( ) {
250+ let network = Network :: Mainnet ;
251+ let genesis = network. genesis_values ( ) ;
252+
253+ let before_blockchain = Utc
254+ . timestamp_opt ( i64:: try_from ( genesis. byron_known_time ) . unwrap ( ) - 1 , 0 )
255+ . unwrap ( ) ;
256+
257+ assert_eq ! ( network. time_to_slot( before_blockchain) , None ) ;
258+ }
259+
260+ #[ test]
261+ fn test_time_to_slot_byron_era ( ) {
262+ let network = Network :: Mainnet ;
263+ let genesis = network. genesis_values ( ) ;
264+
265+ let byron_start_time = Utc
266+ . timestamp_opt ( i64:: try_from ( genesis. byron_known_time ) . unwrap ( ) , 0 )
267+ . unwrap ( ) ;
268+ let byron_slot_length = i64:: from ( genesis. byron_slot_length ) ;
269+
270+ // a time in the middle of the Byron era.
271+ let time = byron_start_time + chrono:: Duration :: seconds ( byron_slot_length * 100 ) ;
272+ let expected_slot = genesis. byron_known_slot + 100 ;
273+
274+ assert_eq ! ( network. time_to_slot( time) , Some ( expected_slot) ) ;
275+ }
276+
277+ #[ test]
278+ fn test_time_to_slot_transition_to_shelley ( ) {
279+ let network = Network :: Mainnet ;
280+ let genesis = network. genesis_values ( ) ;
281+
282+ let shelley_start_time = Utc
283+ . timestamp_opt ( i64:: try_from ( genesis. shelley_known_time ) . unwrap ( ) , 0 )
284+ . unwrap ( ) ;
285+ let byron_slot_length = i64:: from ( genesis. byron_slot_length ) ;
286+
287+ // a time just before Shelley era starts.
288+ let time = shelley_start_time - chrono:: Duration :: seconds ( 1 ) ;
289+ let elapsed_slots = ( time
290+ - Utc
291+ . timestamp_opt ( i64:: try_from ( genesis. byron_known_time ) . unwrap ( ) , 0 )
292+ . unwrap ( ) )
293+ . num_seconds ( )
294+ / byron_slot_length;
295+ let expected_slot = genesis. byron_known_slot + u64:: try_from ( elapsed_slots) . unwrap ( ) ;
296+
297+ assert_eq ! ( network. time_to_slot( time) , Some ( expected_slot) ) ;
298+ }
299+
300+ #[ test]
301+ fn test_time_to_slot_shelley_era ( ) {
302+ let network = Network :: Mainnet ;
303+ let genesis = network. genesis_values ( ) ;
304+
305+ let shelley_start_time = Utc
306+ . timestamp_opt ( i64:: try_from ( genesis. shelley_known_time ) . unwrap ( ) , 0 )
307+ . unwrap ( ) ;
308+ let shelley_slot_length = i64:: from ( genesis. shelley_slot_length ) ;
309+
310+ // a time in the middle of the Shelley era.
311+ let time = shelley_start_time + chrono:: Duration :: seconds ( shelley_slot_length * 200 ) ;
312+ let expected_slot = genesis. shelley_known_slot + 200 ;
313+
314+ assert_eq ! ( network. time_to_slot( time) , Some ( expected_slot) ) ;
315+ }
316+
317+ #[ test]
318+ fn test_slot_to_time_to_slot_consistency ( ) {
319+ let network = Network :: Mainnet ;
320+
321+ // a few arbitrary slots in different ranges.
322+ let slots_to_test = vec ! [ 0 , 10_000 , 1_000_000 , 50_000_000 ] ;
323+
324+ for slot in slots_to_test {
325+ let time = network. slot_to_time ( slot) ;
326+ let calculated_slot = network. time_to_slot ( time) ;
327+
328+ assert_eq ! ( calculated_slot, Some ( slot) , "Failed for slot: {slot}" ) ;
329+ }
330+ }
331+
332+ #[ test]
333+ #[ allow( clippy:: panic) ]
334+ fn test_time_to_slot_to_time_consistency ( ) {
335+ let network = Network :: Mainnet ;
336+ let genesis = network. genesis_values ( ) ;
337+
338+ // Byron, Shelley, and Conway.
339+ let times_to_test = vec ! [
340+ Utc . timestamp_opt( i64 :: try_from( genesis. byron_known_time) . unwrap( ) + 100 , 0 )
341+ . unwrap( ) ,
342+ Utc . timestamp_opt(
343+ i64 :: try_from( genesis. shelley_known_time) . unwrap( ) + 1_000 ,
344+ 0 ,
345+ )
346+ . unwrap( ) ,
347+ Utc . timestamp_opt(
348+ i64 :: try_from( genesis. shelley_known_time) . unwrap( ) + 10_000_000 ,
349+ 0 ,
350+ )
351+ . unwrap( ) ,
352+ ] ;
353+
354+ for time in times_to_test {
355+ if let Some ( slot) = network. time_to_slot ( time) {
356+ let calculated_time = network. slot_to_time ( slot) ;
357+
358+ assert_eq ! (
359+ calculated_time. timestamp( ) ,
360+ time. timestamp( ) ,
361+ "Failed for time: {time}"
362+ ) ;
363+ } else {
364+ panic ! ( "time_to_slot returned None for a valid time: {time}" ) ;
365+ }
366+ }
367+ }
368+
369+ #[ test]
370+ fn test_conway_era_time_to_slot_and_back ( ) {
371+ let network = Network :: Mainnet ;
372+ let genesis = network. genesis_values ( ) ;
373+
374+ // a very late time, far in the Conway era.
375+ let conway_time = Utc
376+ . timestamp_opt (
377+ i64:: try_from ( genesis. shelley_known_time ) . unwrap ( ) + 20_000_000 ,
378+ 0 ,
379+ )
380+ . unwrap ( ) ;
381+
382+ let slot = network. time_to_slot ( conway_time) ;
383+ assert ! (
384+ slot. is_some( ) ,
385+ "Failed to calculate slot for Conway era time"
386+ ) ;
387+
388+ let calculated_time = network. slot_to_time ( slot. unwrap ( ) ) ;
389+
390+ assert_eq ! (
391+ calculated_time. timestamp( ) ,
392+ conway_time. timestamp( ) ,
393+ "Inconsistency for Conway era time"
394+ ) ;
395+ }
217396}
0 commit comments