2 # vim:et:sts=4:sw=4:si:fdm=marker:tw=0
4 # speedcalc : calculate time, distance, speed
5 # author : Michael Adam <obnox@samba.org>
8 # v0.1 2005-04-25 initial version
9 # v0.2 2015-11-03 support yards and miles, support pace
17 # configuration {{{ --------------------------------------------
20 my $Version_date = "2015-11-03";
21 my $Email = "obnox\@samba.org";
22 my $Real_name = "Michael Adam";
24 # - error constants/messages {{{ -------------------------------
25 use constant ERR_OPTIONS_INPUT_THREE => 1;
26 use constant ERR_OPTIONS_INPUT_MISS => 2;
27 use constant ERR_SYNTAX_TIME => 3;
28 use constant ERR_SYNTAX_SPEED => 4;
29 use constant ERR_SYNTAX_DISTANCE => 5;
30 use constant ERR_OUTPUT_FORMAT => 6;
31 use constant ERR_OUTPUT_FORMAT_SPEED => 7;
32 use constant ERR_OUTPUT_FORMAT_DISTANCE => 8;
33 use constant ERR_OUTPUT_FORMAT_PRESENT => 9;
34 use constant ERR_SYNTAX_PRECISION => 10;
35 use constant ERR_NO_TARGET => 11;
38 $errors[ERR_OPTIONS_INPUT_THREE] = "It makes no sense to give all three input options.";
39 $errors[ERR_OPTIONS_INPUT_MISS] = "Need at least one input option.";
40 $errors[ERR_SYNTAX_TIME] = "Wrong time format.";
41 $errors[ERR_SYNTAX_SPEED] = "Wrong speed syntax.";
42 $errors[ERR_SYNTAX_DISTANCE] = "Wrong distance syntax.";
43 $errors[ERR_SYNTAX_PRECISION] = "Wrong precision syntax.";
44 $errors[ERR_OUTPUT_FORMAT] = "Invalid output format.";
45 $errors[ERR_OUTPUT_FORMAT_SPEED] = "Invalid output format.";
46 $errors[ERR_OUTPUT_FORMAT_DISTANCE] = "Invalid output format.";
47 $errors[ERR_OUTPUT_FORMAT_PRESENT] = "Option -o only allowed with target SPEED or DISTANCE.";
48 $errors[ERR_NO_TARGET] = "No target given.";
49 # - error contstants/messages }}}
51 # input/target entities
52 use constant TIME => 1;
53 use constant DISTANCE => 2;
54 use constant SPEED => 3;
58 $Entity_text[TIME] = "time";
59 $Entity_text[DISTANCE] = "distance";
60 $Entity_text[SPEED] = "speed";
62 # option letter for command line:
64 $Entity_option[TIME] = "t";
65 $Entity_option[SPEED] = "s";
66 $Entity_option[DISTANCE] = "d";
68 # regexes for parsing cmd line:
70 $Entity_pattern[TIME] = '^(\d+(?:\.\d+)?)([smhdw]?)$';
71 $Entity_pattern[SPEED] = '^(\d+(?:\.\d+)?)(m\/s|km\/h|yd\/s|mi\/h|s\/m|s\/yd|s\/km|s\/mi)?$';
72 $Entity_pattern[DISTANCE] = '^(\d+(?:\.\d+)?)(yd|m|km|mi)?$';
74 # helper regex to separate pace format for speed (reciprocal):
75 my $Entity_pattern_speed_reciprocal = '^(.*)\/(m|yd|km|mi)$';
77 my @Entity_syntax_error;
78 $Entity_syntax_error[TIME] = ERR_SYNTAX_TIME;
79 $Entity_syntax_error[SPEED] = ERR_SYNTAX_SPEED;
80 $Entity_syntax_error[DISTANCE] = ERR_SYNTAX_DISTANCE;
88 # default unit for in-/output:
90 $Default_unit[TIME] = "s";
91 $Default_unit[DISTANCE] = "m";
92 $Default_unit[SPEED] = "km/h";
94 # all units are converted to this base unit first:
96 $Base_unit[TIME] = "s";
97 $Base_unit[DISTANCE] = "m";
98 $Base_unit[SPEED] = "m/s";
100 # factor to get base-unit from given unit:
102 $Unit_factor[TIME] = {
109 $Unit_factor[DISTANCE] = {
115 $Unit_factor[SPEED] = {
116 "m/s" => $Unit_factor[DISTANCE]->{"m"} / $Unit_factor[TIME]->{"s"},
117 "km/h" => $Unit_factor[DISTANCE]->{"km"} / $Unit_factor[TIME]->{"h"},
118 "yd/s" => $Unit_factor[DISTANCE]->{"yd"} / $Unit_factor[TIME]->{"s"},
119 "mi/h" => $Unit_factor[DISTANCE]->{"mi"} / $Unit_factor[TIME]->{"h"},
120 # pace options (reciprocal)
121 "s/m" => $Unit_factor[DISTANCE]->{"m"} / $Unit_factor[TIME]->{"s"},
122 "s/yd" => $Unit_factor[DISTANCE]->{"yd"} / $Unit_factor[TIME]->{"s"},
123 "s/km" => $Unit_factor[DISTANCE]->{"km"} / $Unit_factor[TIME]->{"s"},
124 "s/mi" => $Unit_factor[DISTANCE]->{"mi"} / $Unit_factor[TIME]->{"s"},
126 "/m" => $Unit_factor[DISTANCE]->{"m"} / $Unit_factor[TIME]->{"s"},
127 "/yd" => $Unit_factor[DISTANCE]->{"yd"} / $Unit_factor[TIME]->{"s"},
128 "/km" => $Unit_factor[DISTANCE]->{"km"} / $Unit_factor[TIME]->{"s"},
129 "/mi" => $Unit_factor[DISTANCE]->{"mi"} / $Unit_factor[TIME]->{"s"},
132 my $Speed_reciprocal = {
147 # text output strings for units:
156 $Unit_text[SPEED] = {
166 # pace, for formatting time with format_time
173 $Unit_text[DISTANCE] = {
176 "km" => "kilometers",
181 my @Output_unit_pattern;
182 $Output_unit_pattern[SPEED] = '^(km\/h|m\/s|yd\/s|mi\/h|s\/m|s\/yd|s\/km|s\/mi|\/m|\/yd|\/km|\/mi)$';
183 $Output_unit_pattern[DISTANCE] = '^(yd|m|km|mi)$';
186 # analyse options {{{ ------------------------------------------
198 getopts("t:d:s:p:o:vh", \%options);
200 if (keys %options == 0 or $options{h}) {
204 # - determine debug {{{ ----------------------------------------
208 mydebug("debug: on.\n");
211 # - determine debug }}}
212 # - determine target {{{ ---------------------------------------
214 if (exists($options{t}) and exists($options{s}) and exists($options{d})) {
215 error(ERR_OPTIONS_INPUT_THREE);
218 elsif (exists($options{t}) and exists($options{d})) {
221 elsif (exists($options{t}) and exists($options{s})) {
224 elsif (exists($options{d}) and exists($options{s})) {
230 if (exists($options{s})) {
233 elsif (exists($options{d})) {
236 elsif (exists($options{t})) {
240 error(ERR_OPTIONS_INPUT_MISS);
243 mydebug("target: $Target\n");
245 # - determine target }}}
246 # - get values {{{ ---------------------------------------------
253 # - output options {{{ -----------------------------------------
256 if ($Target == SPEED or $Target == DISTANCE ) {
257 if ($options{o} =~ /$Output_unit_pattern[$Target]/ ) {
261 error(ERR_OUTPUT_FORMAT);
265 error(ERR_OUTPUT_FORMAT_PRESENT);
269 $Output_unit = $Default_unit[$Target];
271 mydebug("output unit: $Output_unit\n");
273 # - output options }}}
274 # - determine precision {{{ ------------------------------------
277 if ($options{p} =~ /^\d+$/) {
278 $Precision = $options{p};
281 error(ERR_SYNTAX_PRECISION);
284 mydebug("precision: $Precision\n");
286 # - determine precision }}}
288 # analyse options }}}
289 # action {{{ ---------------------------------------------------
292 mydebug("Target: $Values[$Target] $Base_unit[$Target]\n");
294 if ($Target == TIME) {
295 print format_time($Values[TIME]) . "\n";
298 my $Output_value = $Values[$Target] / $Unit_factor[$Target]->{$Output_unit};
299 if (($Target == SPEED) and ($Speed_reciprocal->{$Output_unit})) {
300 $Output_value = 1 / $Output_value;
301 if ($Output_unit =~ /^\//) {
302 print format_time($Output_value) . "$Output_unit\n";
305 printf "%." . $Precision . "f "
306 . $Unit_text[SPEED]->{$Output_unit} . "\n", $Output_value;
310 printf "%." . $Precision . "f "
311 . $Unit_text[$Target]->{$Output_unit} . "\n", $Output_value;
316 # functions {{{ ------------------------------------------------
321 unless ($Recalculate) {
322 if ($target == DISTANCE) {
323 $value = $Values[SPEED] * $Values[TIME];
325 elsif ($target == SPEED) {
326 $value = ( $Values[DISTANCE] / $Values[TIME]);
328 elsif ($target == TIME) {
329 $value = $Values[DISTANCE] / $Values[SPEED];
332 error(ERR_NO_TARGET);
334 $Values[$target] = $value;
340 if ($options{$Entity_option[$entity]}) {
341 if ($entity == TIME) {
342 my $time_str = $options{$Entity_option[TIME]};
343 $Values[TIME] = parse_time($time_str);
345 elsif (($entity == SPEED) and
346 ($options{$Entity_option[SPEED]} =~ /$Entity_pattern_speed_reciprocal/))
350 my $time = parse_time($time_str);
351 $Values[SPEED] = $Unit_factor[DISTANCE]->{$dist_unit} / $time;
353 mydebug("parsing reciprocal speed:\n");
354 mydebug(" - time_str: $time_str\n");
355 mydebug(" - time: $time sec\n");
356 mydebug(" - dist_unit: $dist_unit\n");
358 elsif ($options{$Entity_option[$entity]} =~ /$Entity_pattern[$entity]/) {
360 my $unit = $2 || $Default_unit[$entity];
361 $Values[$entity] = $value * $Unit_factor[$entity]->{$unit};
364 error($Entity_syntax_error[$entity]);
366 mydebug("$Entity_text[$entity]: $Values[$entity] " .
367 $Unit_text[$entity]->{$Base_unit[$entity]} . "\n");
379 print "speedcalc - time/distance/speed calculator\n";
381 print "v$Version $Version_date $Real_name <$Email>\n";
389 my $err_code = shift;
391 print "ERROR: $errors[$err_code]\n";
400 USAGE: speedcalc [-t <time>] [-d <dist>] [-s <speed>] [-p <num>] [-o <unit>] [-v] [-h]
407 $time = floor($time + 0.5);
409 my $sec = $time % 60;
410 $time = ( $time - $sec ) / 60;
412 $format = sprintf("%02ds", $sec);
413 my $min = $time % 60;
414 $time = ($time - $min ) / 60;
416 $format = sprintf("%02dm$format", $min);
417 my $hour = $time % 24;
418 $time = ($time - $hour) / 24;
420 $format = sprintf("%02dh$format", $hour);
422 $time = ($time - $day) / 7;
424 $format = sprintf("%02dd$format", $day);
425 $format = $time . "w$format";
428 $format = $day . "d$format";
432 $format = $hour . "h$format";
436 $format = $min . "m$format";
440 $format = $sec . "s";
445 # parse time in format [Xw][Xd][Xh][Xm][X[.Y][s]] and return seconds
449 foreach my $unit (qw(w d h m)) {
450 if ($timestr =~ s/^(\d+)$unit(.*)$/$2/) {
451 $seconds += $1 * $Unit_factor[TIME]->{$unit};
454 if ($timestr =~ /^(\d+(?:\.\d+)?)s?$/ ) {
458 error(ERR_SYNTAX_TIME);