+ my $t = shift;
+ my ($sec,$min,$hour,$mday,$mon,$year) = gmtime((defined $t) ? $t : time);
+ $year += 1900;
+ my $buf = sprintf "%02d%s%04d\@%02d:%02d:%02d", $mday, $month[$mon], $year, $hour, $min, $sec;
+ return $buf;
+}
+
+# get a zulu time in cluster format (2300Z)
+sub ztime
+{
+ my $t = shift;
+ $t = defined $t ? $t : time;
+ my $dst = shift;
+ my ($sec,$min,$hour) = $dst ? localtime($t): gmtime($t);
+ my $buf = sprintf "%02d%02d%s", $hour, $min, ($dst) ? '' : 'Z';
+ return $buf;
+}
+
+# get a cluster format date (23-Jun-1998)
+sub cldate
+{
+ my $t = shift;
+ $t = defined $t ? $t : time;
+ my $dst = shift;
+ my ($sec,$min,$hour,$mday,$mon,$year) = $dst ? localtime($t) : gmtime($t);
+ $year += 1900;
+ my $buf = sprintf "%2d-%s-%04d", $mday, $month[$mon], $year;
+ return $buf;
+}
+
+# return a cluster style date time
+sub cldatetime
+{
+ my $t = shift;
+ my $dst = shift;
+ my $date = cldate($t, $dst);
+ my $time = ztime($t, $dst);
+ return "$date $time";
+}
+
+# return a unix date from a cluster date and time
+sub cltounix
+{
+ my $date = shift;
+ my $time = shift;
+ my ($thisyear) = (gmtime)[5] + 1900;
+
+ return 0 unless $date =~ /^\s*(\d+)-(\w\w\w)-([12][90]\d\d)$/;
+ return 0 if $3 > 2036;
+ return 0 unless abs($thisyear-$3) <= 1;
+ $date = "$1 $2 $3";
+ return 0 unless $time =~ /^([012]\d)([012345]\d)Z$/;
+ $time = "$1:$2 +0000";
+ my $r = str2time("$date $time");
+ return $r unless $r;
+ return $r == -1 ? undef : $r;
+}
+
+# turn a latitude in degrees into a string
+sub slat
+{
+ my $n = shift;
+ my ($deg, $min, $let);
+ $let = $n >= 0 ? 'N' : 'S';
+ $n = abs $n;
+ $deg = int $n;
+ $min = int ((($n - $deg) * 60) + 0.5);
+ return "$deg $min $let";
+}
+
+# turn a longitude in degrees into a string
+sub slong
+{
+ my $n = shift;
+ my ($deg, $min, $let);
+ $let = $n >= 0 ? 'E' : 'W';
+ $n = abs $n;
+ $deg = int $n;
+ $min = int ((($n - $deg) * 60) + 0.5);
+ return "$deg $min $let";
+}
+
+# turn a true into 'yes' and false into 'no'
+sub yesno
+{
+ my $n = shift;
+ return $n ? $main::yes : $main::no;
+}
+
+# provide a data dumpered version of the object passed
+sub dd
+{
+ my $value = shift;
+ my $dd = new Data::Dumper([$value]);
+ $dd->Indent(0);
+ $dd->Terse(1);
+ $dd->Quotekeys($] < 5.005 ? 1 : 0);
+ $value = $dd->Dumpxs;
+ $value =~ s/([\r\n\t])/sprintf("%%%02X", ord($1))/eg;
+ $value =~ s/^\s*\[//;
+ $value =~ s/\]\s*$//;
+
+ return $value;
+}
+
+# format a prompt with its current value and return it with its privilege
+sub promptf
+{
+ my ($line, $value) = @_;
+ my ($priv, $prompt, $action) = split ',', $line;
+
+ # if there is an action treat it as a subroutine and replace $value
+ if ($action) {
+ my $q = qq{\$value = $action(\$value)};
+ eval $q;
+ } elsif (ref $value) {
+ $value = dd($value);
+ }
+ $prompt = sprintf "%15s: %s", $prompt, $value;
+ return ($priv, $prompt);
+}
+
+# turn a hex field into printed hex
+sub phex
+{
+ my $val = shift;
+ return sprintf '%X', $val;
+}
+
+# take an arg as a hash of call=>time pairs and print it
+sub ptimelist
+{
+ my $ref = shift;
+ my $out;
+ for (sort keys %$ref) {
+ $out .= "$_=$ref->{$_}, ";
+ }
+ chop $out;
+ chop $out;
+ return $out;
+}
+
+# take an arg as an array list and print it
+sub parray
+{
+ my $ref = shift;
+ return ref $ref ? join(', ', @{$ref}) : $ref;
+}
+
+# take the arg as an array reference and print as a list of pairs
+sub parraypairs
+{
+ my $ref = shift;
+ my $i;
+ my $out;
+
+ for ($i = 0; $i < @$ref; $i += 2) {
+ my $r1 = @$ref[$i];
+ my $r2 = @$ref[$i+1];
+ $out .= "$r1-$r2, ";
+ }
+ chop $out; # remove last space
+ chop $out; # remove last comma
+ return $out;
+}
+
+sub _sort_fields
+{
+ my $ref = shift;
+ my @a = split /,/, $ref->field_prompt(shift);
+ my @b = split /,/, $ref->field_prompt(shift);
+ return lc $a[1] cmp lc $b[1];