@@ -3871,7 +3871,7 @@ def wfdbdesc(record_name, pn_dir=None):
38713871 if (pn_dir is not None ) and ('.' not in pn_dir ):
38723872 dir_list = pn_dir .split ('/' )
38733873 pn_dir = posixpath .join (dir_list [0 ], get_version (dir_list [0 ]),
3874- * dir_list [1 :])
3874+ * dir_list [1 :])
38753875
38763876 record = rdheader (record_name , pn_dir = pn_dir )
38773877 if type (record ) is MultiRecord :
@@ -3922,6 +3922,193 @@ def wfdbdesc(record_name, pn_dir=None):
39223922 print (f' Checksum: { record .checksum [i ]} ' )
39233923
39243924
3925+ def wfdbtime (record_name , input_times , pn_dir = None ):
3926+ """
3927+ Use the specified record as a reference for determining the length of a
3928+ sample interval and the absolute time represented by sample number 0. This
3929+ program accepts one or more time arguments (in WFDB standard time format)
3930+ and produces one line on the standard output for each such argument. In
3931+ each output line, the corresponding time is written as a sample number (in
3932+ the form sNNN), as an elapsed time interval in hours, minutes, and seconds
3933+ from the beginning of the record (in the form hh:mm:ss.sss), and as an
3934+ absolute time and date (in the form [hh:mm:ss.sss DD/MM/YYYY]). If the
3935+ base time for the record is undefined, the absolute time cannot be
3936+ calculated, and in this case the elapsed time appears twice instead.
3937+
3938+ Parameters
3939+ ----------
3940+ record_name : str
3941+ The name of the WFDB record to be read, without any file
3942+ extensions. If the argument contains any path delimiter
3943+ characters, the argument will be interpreted as PATH/BASE_RECORD.
3944+ Both relative and absolute paths are accepted. If the `pn_dir`
3945+ parameter is set, this parameter should contain just the base
3946+ record name, and the files fill be searched for remotely.
3947+ Otherwise, the data files will be searched for in the local path.
3948+ input_times : str, list
3949+ The desired times (or samples) to be converted and displayed for the
3950+ chosen record. This can either be a single string, or a list of
3951+ strings depending on how many results the user would like. The string
3952+ must either be an integer (in which case '1' will be interpreted as 1
3953+ second), a datetime format (hh:mm:ss.sss DD/MM/YYYY), or prefixed with
3954+ the letter 's' if the time at a selected sample is desired (i.e. 's10'
3955+ for sample time). For the datetime format, 0's do not have be filled
3956+ for all values (i.e. '5::' will be interpreted as '05:00:00.000',
3957+ ':5:' will be '00:05:00.000', etc.). The string 'e' can be used to
3958+ represent the end of the record.
3959+ pn_dir : str, optional
3960+ Option used to stream data from Physionet. The Physionet
3961+ database directory from which to find the required record files.
3962+ eg. For record '100' in 'http://physionet.org/content/mitdb'
3963+ pn_dir='mitdb'.
3964+
3965+ Returns
3966+ -------
3967+ N/A
3968+
3969+ Examples
3970+ --------
3971+ * Note, the use of a single string instead of a list.
3972+ >>> wfdb.wfdbtime('sample-data/100', 's250')
3973+ s250 00:00:00.694 00:00:00.694
3974+
3975+ * Note, the elapsed time and date fields are the same since no start time
3976+ or date was provided.
3977+ >>> wfdb.wfdbtime('sample-data/100', ['s10',':5:','2'])
3978+ s10 00:00:00.028 00:00:00.028
3979+ s108000 00:05:00.000 00:05:00.000
3980+ s720 00:00:02.000 00:00:02.000
3981+
3982+ * Note, '01/01/0001' represents a start time was provided but not a date.
3983+ >>> wfdb.wfdbtime('sample-data/3000003_0003', ['s1','0:0:05','e'])
3984+ s1 00:00:00.008 [19:46:25.765000 01/01/0001]
3985+ s625 00:00:05.000 [19:46:30.757000 01/01/0001]
3986+ s1028 00:00:08.224 [19:46:33.981000 01/01/0001]
3987+
3988+ * Note, the final argument results in the same date field as the argument
3989+ but a different value for the elapsed time.
3990+ >>> wfdb.wfdbtime('sample-data/3000003_0003', ['s1','::5','e','19:46:34.981 01/01/0001'])
3991+ s1 00:00:00.008 [19:46:25.765000 01/01/0001]
3992+ s625 00:00:05.000 [19:46:30.757000 01/01/0001]
3993+ s1028 00:00:08.224 [19:46:33.981000 01/01/0001]
3994+ s1153 00:00:09.224 [19:46:34.981000 01/01/0001]
3995+
3996+ """
3997+ if (pn_dir is not None ) and ('.' not in pn_dir ):
3998+ dir_list = pn_dir .split ('/' )
3999+ pn_dir = posixpath .join (dir_list [0 ], get_version (dir_list [0 ]),
4000+ * dir_list [1 :])
4001+
4002+ record = rdheader (record_name , pn_dir = pn_dir )
4003+ try :
4004+ start_time = datetime .datetime .combine (record .base_date , record .base_time )
4005+ except TypeError :
4006+ try :
4007+ start_time = record .base_time
4008+ except AttributeError :
4009+ start_time = None
4010+
4011+ if type (input_times ) is str :
4012+ input_times = [input_times ]
4013+
4014+ for times in input_times :
4015+ if times == 'e' :
4016+ sample_num = record .sig_len
4017+ sample_time = float (sample_num / record .fs )
4018+ seconds = float (sample_time )% 60
4019+ minutes = int (float (sample_time )// 60 )
4020+ hours = int (float (sample_time )// 60 // 60 )
4021+ else :
4022+ if times .startswith ('s' ):
4023+ sample_num = int (times [1 :])
4024+ new_times = float (sample_num / record .fs )
4025+ times_split = [f'{ new_times } ' ]
4026+ elif '/' in times :
4027+ out_date = datetime .datetime .strptime (times , '%H:%M:%S.%f %d/%m/%Y' )
4028+ try :
4029+ start_time = datetime .datetime .combine (datetime .date .min , start_time )
4030+ except TypeError :
4031+ start_time = start_time
4032+ try :
4033+ elapsed_time = out_date - start_time
4034+ except TypeError :
4035+ raise Exception ('No start date or time provided in the record.' )
4036+ total_seconds = elapsed_time .total_seconds ()
4037+ sample_num = 's' + str (int (elapsed_time .total_seconds () * record .fs ))
4038+ hours = int (total_seconds // 60 // 60 )
4039+ minutes = int (total_seconds // 60 )
4040+ out_time = f'{ hours :02} :{ minutes :02} :{ total_seconds :06.3f} '
4041+ out_date = f'[{ out_date .strftime ("%H:%M:%S.%f %d/%m/%Y" )} ]'
4042+ print (f'{ sample_num :>12} { out_time :>24} { out_date :>32} ' )
4043+ break
4044+ else :
4045+ new_times = times
4046+ times_split = [t if t != '' else '0' for t in times .split (':' )]
4047+ if len (times_split ) == 1 :
4048+ seconds = float (new_times )% 60
4049+ minutes = int (float (new_times )// 60 )
4050+ hours = int (float (new_times )// 60 // 60 )
4051+ elif len (times_split ) == 2 :
4052+ seconds = float (times_split [1 ])
4053+ minutes = int (times_split [0 ])
4054+ hours = 0
4055+ elif len (times_split ) == 3 :
4056+ seconds = float (times_split [2 ])
4057+ minutes = int (times_split [1 ])
4058+ hours = int (times_split [0 ])
4059+ if seconds >= 60 :
4060+ raise Exception ('Seconds not in correct format' )
4061+ if minutes >= 60 :
4062+ raise Exception ('Minutes not in correct format' )
4063+ out_time = f'{ hours :02} :{ minutes :02} :{ seconds :06.3f} '
4064+ out_date = _get_date_from_time (start_time , hours , minutes , seconds ,
4065+ out_time )
4066+ if not times .startswith ('s' ):
4067+ sample_num = int (sum (x * 60 ** i for i ,x in enumerate ([seconds ,minutes ,hours ])) * record .fs )
4068+ sample_num = 's' + str (sample_num )
4069+ print (f'{ sample_num :>12} { out_time :>24} { out_date :>32} ' )
4070+
4071+
4072+ def _get_date_from_time (start_time , hours , minutes , seconds , out_time ):
4073+ """
4074+ Convert a starting time to a date using the elapsed time.
4075+
4076+ Parameters
4077+ ----------
4078+ start_time : datetime object
4079+ The time the record start if available.
4080+ hours : int
4081+ The number of hours elapsed.
4082+ minutes : int
4083+ The number of minutes elapsed.
4084+ seconds : int
4085+ The number of seconds elapsed.
4086+ out_time : str
4087+ The string formatted time elapsed for a desired sample, if available.
4088+
4089+ Returns
4090+ -------
4091+ out_date : datetime object
4092+ The time the record ends after the caculcated elapsed time.
4093+
4094+ """
4095+ if start_time :
4096+ try :
4097+ start_time = datetime .datetime .combine (datetime .date .min , start_time ) - \
4098+ datetime .datetime .min
4099+ except TypeError :
4100+ start_time = start_time - datetime .datetime .min
4101+ microseconds = int (1000000 * (seconds % 1 ))
4102+ elapsed_time = datetime .timedelta (hours = hours , minutes = minutes ,
4103+ seconds = int (seconds ),
4104+ microseconds = microseconds )
4105+ out_date = start_time + elapsed_time
4106+ out_date = f'[{ (datetime .datetime .min + out_date ).strftime ("%H:%M:%S.%f %d/%m/%Y" )} ]'
4107+ else :
4108+ out_date = out_time
4109+ return out_date
4110+
4111+
39254112def _get_wanted_channels (wanted_sig_names , record_sig_names , pad = False ):
39264113 """
39274114 Given some wanted signal names, and the signal names contained in a
0 commit comments