new file mode 100644
--- /dev/null
+++ b/libconv.pl
@@ -0,0 +1,566 @@
+#======================================================================
+# L I B C O N V . P L
+# doc: Sat Dec 4 13:03:49 1999
+# dlm: Tue Apr 17 10:34:41 2012
+# (c) 1999 A.M. Thurnherr
+# uE-Info: 61 76 NIL 0 0 72 2 2 4 NIL ofnI
+#======================================================================
+
+# HISTORY:
+# Dec 12, 1999: - created for the Rainbow CM data as libdate
+# Jul 07, 2000: - renamed to libconv and added lat/lon conversions
+# Aug 21, 2000: - added &s2d()
+# Aug 24, 2000: - added &DDMMXh2d() and &DDDMMXh2d()
+# Aug 28, 2000: - moved &DDMMXh2d() and &DDDMMXh2d() to [libNODC.pl]
+# Sep 04, 2000: - added &GMT2d()
+# Sep 20, 2000: - added &fmtdate(), &fmttime()
+# Oct 16, 2000: - added &dmsh2d()
+# Oct 31, 2000: - added &T90(), &T68()
+# Jan 22, 2001: - BUG: &GMT2d() wrongly +ve-ized stuff like -00:05
+# Feb 28, 2001: - added &O2mlpl2umpkg()
+# Aug 3, 2001: - made &O2mlpl2umpkg() return NaN on -ve input
+# Aug 7, 2001: - added &O2umpkg2mlpl()
+# - replaced temp_scale by ITS
+# Aug 8, 2001: - BUG: allowed for NaN O2 values
+# Aug 19, 2001: - change temp-conversions to allow nop; changed names
+# Sep 1, 2001: - BUG: allow for nan in addition to NaN
+# Dec 30, 2001: - generalized s2d()
+# - BUG: allow NaNs on temp conversions
+# Feb 4, 2002: - allow for -ve O2 values in conversions to handle offsets
+# Aug 14, 2002: - moved ITS checks in ITS_68() & ITS_90()
+# Oct 9, 2002: - added &fmttime1(),&fmtdate1()
+# Jan 23, 2003: - added more time conversion routines
+# Apr 14, 2003: - removed antsReplaceParam() call
+# May 21, 2004: - added ``-'' as a valid date separator
+# Jun 22, 2004: - added HSV2RGB()
+# Jun 27, 2004: - renamed degree-conversion routines
+# - added wraplon()
+# Dec 1, 2005: - cosmetics
+# Dec 9, 2005: - Version 3.2 [HISTORY]
+# Apr 4, 2006: - made epoch optional in &dayNo()
+# Apr 28, 2006: - added &dn2date()
+# Jul 1, 2006: - Version 3.3 [HISTORY]
+# Nov 9, 2006: - added dec_time()
+# - added dn2date_time()
+# - moved year forward in dn2date()
+# Oct 17, 2007: - dn2date -> Date
+# - dn2date_time -> Time
+# - adapted Date/Time to antsFunUsage() with default fields
+# Oct 19, 2007: - removed antsFunUsage() from non-UI funs
+# Nov 17, 2007: - added "." as another legal date separator
+# Jul 10, 2008: - added support for month names in dayNo()
+# Dec 1, 2008: - added dec_time_long()
+# Dec 3, 2008: - renamed many of the date-conversion routines
+# - added frac_day()
+# Jan 8, 2009: - BUG: &Date() returned wrong date for any time after
+# midnite on last day of a month
+# Oct 27, 2010: - added &day_secs()
+# Jan 3, 2010: - extended frac_day() to allow a single string time-spec
+# Jul 19, 2011: - made epoch aptional in mmddyy2dec_time()
+# Aug 2, 2011: - enhanced yymmdd2dec_time()
+# Apr 17, 2012: - added space as another date separator in ddmmyy2dec_time
+
+require "$ANTS/libEOS83.pl"; # &sigma()
+require "$ANTS/libPOSIX.pl"; # &floor()
+require "$ANTS/libstats.pl"; # &min(),&max()
+
+#----------------------------------------------------------------------
+# Date Conversion
+#----------------------------------------------------------------------
+
+sub leapYearP(@) # leap year?
+{
+ my($y) = @_;
+
+ $y += ($y < 50) ? 2000 : 1900 if ($y < 100); # Y2K
+
+ return 0 if ($y%4 != 0);
+ return 1 if ($y%100 != 0);
+ return 0 if ($y%400 > 0);
+ return 1;
+}
+
+sub monthLength(@) # #days in given month/year
+{
+ my($y,$m) = @_;
+
+ return 31 if ($m==1 || $m==3 || $m==5 || $m==7 ||
+ $m==8 || $m==10 || $m==12);
+ return 30 if ($m==4 || $m==6 || $m==9 || $m==11);
+ return 28 + &leapYearP($y) if ($m == 2);
+ croak("$0: &monthLength(): Illegal month\n");
+}
+
+{ my(@fc);
+
+ sub Date(@) # day number -> date
+ {
+
+ my($dnf); # find std dn field & epoch
+ if (@_ == 0) {
+ for (my($i)=0; $i<@antsLayout; $i++) {
+ next unless ($antsLayout[$i] =~ /^dn(\d\d)$/);
+ $dnf = $antsLayout[$i]; push(@_,$1);
+ last;
+ }
+ }
+
+ my($year,$day) = &antsFunUsage(2,"cf","epoch, dayNo",\@fc,undef,$dnf,@_);
+
+ $year += ($year < 50) ? 2000 : 1900 # Y2K
+ if ($year < 100);
+
+ $day = int($day); # prevent runover on last day of month
+ while ($day > 365+&leapYearP($year)) { # adjust year
+ $day -= 365 + &leapYearP($year);
+ $year++;
+ }
+
+ my($month) = 1;
+ while ($day > &monthLength($year,$month)) {
+ $day -= &monthLength($year,$month);
+ $month++;
+ }
+
+ return sprintf('%04d/%02d/%02d',$year,$month,$day);
+ }
+}
+
+{ my(@fc);
+
+ sub Time(@) # day number -> date/time
+ {
+ my($dnf); # find standard dn field
+ for (my($i)=0; $i<@antsLayout; $i++) {
+ next unless ($antsLayout[$i] =~ /^dn\d\d$/);
+ $dnf = $antsLayout[$i];
+ last;
+ }
+
+ my($fday) = &antsFunUsage(1,"f","dayNo",\@fc,$dnf,@_);
+ my($day) = int($fday);
+ $fday -= $day;
+
+ my($hour) = int(24*$fday);
+ $fday -= $hour/24;
+ my($min) = int(24*60*$fday);
+ $fday -= $min/24/60;
+ my($sec) = round(24*3600*$fday);
+ $min++,$sec=0 if ($sec == 60);
+ $hour++,$min=0 if ($min == 60);
+ $day++,$hour=0 if ($hour == 24);
+
+ return sprintf('%02d:%02d:%02d',$hour,$min,$sec);
+ }
+}
+
+sub dayNo(@) # day number, starting at 1
+{
+ my($epoch,$y,$m,$d) =
+ &antsFunUsage(-3,"c..","[epoch,] year, month, day",@_);
+ unless (defined($d)) {
+ $d = $m; $m = $y; $y = $epoch;
+ }
+
+ unless (cardinalp($m)) {
+ $m = lc($m);
+ if ($m =~ /^jan/) { $m = 1; }
+ elsif ($m =~ /^feb/) { $m = 2; }
+ elsif ($m =~ /^mar/) { $m = 3; }
+ elsif ($m =~ /^apr/) { $m = 4; }
+ elsif ($m =~ /^may/) { $m = 5; }
+ elsif ($m =~ /^jun/) { $m = 6; }
+ elsif ($m =~ /^jul/) { $m = 7; }
+ elsif ($m =~ /^aug/) { $m = 8; }
+ elsif ($m =~ /^sep/) { $m = 9; }
+ elsif ($m =~ /^oct/) { $m = 10; }
+ elsif ($m =~ /^nov/) { $m = 11; }
+ elsif ($m =~ /^dec/) { $m = 12; }
+ else { croak("$0: unknown month $m\n"); }
+ }
+
+ my($dn) = 0;
+
+ $epoch += ($epoch < 50) ? 2000 : 1900 # Y2K
+ if ($epoch < 100);
+ $y += ($y < 50) ? 2000 : 1900
+ if ($y < 100);
+
+ croak("$0: &dayNo(): Error: epoch > year\n") # only positive times
+ if ($y < $epoch);
+ while ($epoch < $y) { # entire years
+ $dn += 365 + &leapYearP($epoch);
+ $epoch++;
+ }
+
+ croak("$0: &dayNo(): Error: day > #days of month\n") # current month
+ if ($d > &monthLength($y,$m));
+ $dn += $d;
+ $m--;
+
+ while ($m > 0) { # current year
+ $dn += &monthLength($y,$m);
+ $m--;
+ }
+
+ return $dn
+}
+
+sub frac_day(@) # fractional day
+{
+ my($h,$m,$s);
+ if (@_ == 1) {
+ ($h,$m,$s) = split(':',$_[0]);
+ } else {
+ ($h,$m,$s) = &antsFunUsage(3,'ccf',"<h:m:s>|<hour> <min> <sec>",@_);
+ }
+
+ croak("$0: &frac_day_long(): illegal time spec $h:$m:$s\n")
+ unless (defined($h) && $h>=0 && $h<24 &&
+ defined($m) && $m>=0 && $m<60 &&
+ defined($s) && $s>=0 && $s<60);
+ return $h/24 + $m/24/60 + $s/24/3600;
+}
+
+sub day_secs(@) # seconds since daystart
+{
+ my($h,$m,$s) = &antsFunUsage(3,'ccf',"<hour> <min> <sec>",@_);
+
+ croak("$0: &frac_day_long(): illegal time spec $h:$m:$s\n")
+ unless (defined($h) && $h>=0 && $h<24 &&
+ defined($m) && $m>=0 && $m<60 &&
+ defined($s) && $s>=0 && $s<60);
+ return $h*3600 + $m*60 + $s;
+}
+
+sub str2dec_time(@) # decimal time
+{
+ my($ts,$ds,$epoch) =
+ &antsFunUsage(-1,'.',"'hh:mm[:ss]'[,'[YY]YY/MM/DD'[,epoch]]",@_);
+
+ my($dayNo) = 0;
+ if ($ds ne '') { # date
+ my($yy,$mm,$dd) = split('[-/\.]',$ds);
+ $dayNo = defined($epoch) ?
+ dayNo($epoch,$yy,$mm,$dd) : dayNo($yy,$mm,$dd);
+ }
+
+ my($h,$m,$s) = split(':',$ts);
+ $s = 0 unless defined($s);
+ return $dayNo + &frac_day($h,$m,$s);
+}
+
+sub dec_time(@) # decimal time
+{
+ my($epoch,$yy,$mm,$dd,$h,$m,$s) =
+ &antsFunUsage(7,'ccccccf',"<epoch> <year> <month> <day> <hour> <min> <sec>",@_);
+ return &dayNo($epoch,$yy,$mm,$dd) + &frac_day($h,$m,$s);
+}
+
+sub mmddyy2dec_time(@) # decimal time
+{
+ my($ds,$ts,$epoch) =
+ &antsFunUsage(2,"..","date-string (empty ok), time-string[, epoch]",@_);
+
+ my($time) = 0;
+ if ($ds ne "") { # date
+ my($mm,$dd,$yy) = split('[-/\. ]',$ds);
+ if (defined($epoch)) {
+ $time = dayNo($epoch,$yy,$mm,$dd);
+ } else {
+ $time = dayNo($yy,$yy,$mm,$dd);
+ }
+ }
+
+ my($h,$m,$s) = split(':',$ts); # time
+ croak("$0: &dec_time(): illegal time spec $ts\n")
+ unless (defined($h) && $h>=0 && $h<24 &&
+ defined($m) && $m>=0 && $m<60 &&
+ defined($s) && $s>=0 && $s<60);
+ $time += $h/24 + $m/24/60 + $s/24/3600;
+
+ return $time;
+}
+
+sub ddmmyy2dec_time(@) # decimal time
+{
+ my($epoch,$ds,$ts) =
+ &antsFunUsage(3,"c..","epoch, date-string (empty ok), time-string",@_);
+
+ my($time) = 0;
+ if ($ds ne "") { # date
+ my($dd,$mm,$yy) = split('[-/\.]',$ds);
+ $time = dayNo($epoch,$yy,$mm,$dd);
+ }
+
+ my($h,$m,$s) = split(':',$ts);
+ croak("$0: &dec_time(): illegal time spec $ts\n")
+ unless (defined($h) && $h>=0 && $h<24 &&
+ defined($m) && $m>=0 && $m<60 &&
+ defined($s) && $s>=0 && $s<60);
+ $time += $h/24 + $m/24/60 + $s/24/3600;
+
+ return $time;
+}
+
+sub yymmdd2dec_time(@) # decimal time
+{
+ my($epoch,$ds,$ts) =
+ &antsFunUsage(3,"c..","epoch, date-string (empty ok), time-string (empty ok)",@_);
+
+ my($time) = 0;
+ if ($ds ne "") { # date
+ my($yy,$mm,$dd);
+ if (length($ds) == 6) {
+ $yy = substr($ds,0,2);
+ $mm = substr($ds,2,2);
+ $dd = substr($ds,4,2);
+ } else {
+ ($yy,$mm,$dd) = split('[-/\.]',$ds);
+ }
+ $time = dayNo($epoch,$yy,$mm,$dd);
+ }
+
+ if ($ts ne '') {
+ my($h,$m,$s) = split(':',$ts);
+ croak("$0: &dec_time(): illegal time spec $ts\n")
+ unless (defined($h) && $h>=0 && $h<24 &&
+ defined($m) && $m>=0 && $m<60 &&
+ defined($s) && $s>=0 && $s<60);
+ $time += $h/24 + $m/24/60 + $s/24/3600;
+ }
+
+ return $time;
+}
+
+sub date2str(@)
+{
+ my($MM,$DD,$YYYY) = &antsFunUsage(3,"ccc","month, day, year",@_);
+ $YYYY += 2000 if ($YYYY < 50);
+ $YYYY += 1900 if ($YYYY < 100);
+ return sprintf("%02d",$MM) . "/" .
+ sprintf("%02d",$DD) . "/" . $YYYY;
+}
+
+sub card_date2str(@)
+{
+ my($DDMMYY) = &antsFunUsage(1,"c","ddmmyy",@_);
+ $DDMMYY = sprintf("%06d",$DDMMYY);
+ return &fmtdate(substr($DDMMYY,2,2),substr($DDMMYY,0,2),substr($DDMMYY,4,2));
+}
+
+sub time2str(@)
+{
+ my($HH,$MM) = &antsFunUsage(2,"cc","hr, min",@_);
+ return sprintf("%02d",$HH) . ":" . sprintf("%02d",$MM);
+}
+
+sub card_time2str(@)
+{
+ my($HHMM) = &antsFunUsage(1,"c","hrmin",@_);
+ return &fmttime(int($HHMM/100),$HHMM%100);
+}
+
+#----------------------------------------------------------------------
+# Lat/Lon Conversion
+#----------------------------------------------------------------------
+
+sub wraplon(@) # get sign of longitudes right
+{
+ my($deg) = &antsFunUsage(1,'f','deg',@_);
+ return ($deg > 180) ? $deg - 360 : $deg;
+}
+
+sub dmh2deg(@) # dd mm.m NSEW -> dd.d
+{
+ my($deg,$min,$hemisph) =
+ &antsFunUsage(3,"ff1","deg, min, hemisphere",@_);
+ croak("$0 dmh2d(): <deg> may not be -ve\n") if ($deg < 0);
+ croak("$0 dmh2d(): <min> may not be -ve\n") if ($min < 0);
+ $deg += $min/60;
+ $_ = $hemisph;
+ SWITCH: {
+ $deg = -$deg, last SWITCH if (/[sSwW]/);
+ last SWITCH if (/[nNeE]/);
+ croak("$0 dmh2d(): $hemisph is an invalid hemisphere id\n");
+ }
+ return $deg;
+}
+
+sub dmsh2deg(@) # dd mm ss NSEW -> dd.d
+{
+ my($deg,$min,$sec,$hemisph) =
+ &antsFunUsage(4,"fff1","deg, min, sec, hemisphere",@_);
+ croak("$0 dmsh2d(): <deg> may not be -ve\n") if ($deg < 0);
+ croak("$0 dmsh2d(): <min> may not be -ve\n") if ($min < 0);
+ croak("$0 dmsh2d(): <sec> may not be -ve\n") if ($sec < 0);
+ $deg += $min/60 + $sec/3600;
+ $_ = $hemisph;
+ SWITCH: {
+ $deg = -$deg, last SWITCH if (/[sSwW]/);
+ last SWITCH if (/[nNeE]/);
+ croak("$0 dmh2d(): $hemisph is an invalid hemisphere id\n");
+ }
+ return $deg;
+}
+
+sub str2deg(@) # string containing dd [mm.m] [NSEW] -> dd.d
+{
+ my($s) = &antsFunUsage(1,".","'deg[ :][min][ ]hemisphere'",@_);
+ my($deg,$a,$b) = ($s =~ m{^([-\d]+)[\s:]([\d\.]+)\s*([NSEW])$});
+# print(STDERR "--> $deg, $a, $b\n");
+ return ($b eq "") ? &dmh2d($deg,0,$a) : &dmh2d($deg,$a,$b);
+}
+
+sub GMT2deg(@) # GMT degree format to decimal
+{
+ my($GMT) = &antsFunUsage(1,".","GMT-degs ",@_);
+ return (substr($1,0,1) eq "-") ? $1-$2/60.0 : $1+$2/60.0
+ if ($GMT =~ /\s*([^:]+):([^:]+)/);
+ return $GMT;
+}
+
+#----------------------------------------------------------------------
+# Temp-Scale Conversion
+#----------------------------------------------------------------------
+
+{ my($ITS);
+
+sub ITS_68(@) # T90|T68 -> T68
+{
+ unless (defined($ITS)) {
+ $ITS = &antsRequireParam('ITS');
+ croak("$0 ITS_68(): ITS == $ITS???\n")
+ unless ($ITS == 68 || $ITS ==90);
+ unless ($ITS == 68) {
+ croak("$0 ITS_68(): can't change %ITS after flushing header\n")
+ if ($antsHeadersPrinted);
+ &antsAddParams('ITS',68);
+ }
+ }
+ my($temp) = &antsFunUsage(1,".","temp",@_);
+ return nan unless (numberp($temp));
+ return($temp) if ($ITS == 68);
+ return $temp * 1.00024;
+}
+
+} # static scope
+
+{ my($ITS);
+
+sub ITS_90(@) # T90|T68 -> T90
+{
+ unless (defined($ITS)) {
+ $ITS = &antsRequireParam('ITS');
+ croak("$0 ITS_90(): ITS == $ITS???\n")
+ unless ($ITS == 68 || $ITS ==90);
+ unless ($ITS == 90) {
+ croak("$0 ITS_90(): can't change %ITS after flushing header\n")
+ if ($antsHeadersPrinted);
+ &antsAddParams('ITS',90);
+ }
+ }
+ my($temp) = &antsFunUsage(1,".","temp",@_);
+ return nan unless (numberp($temp));
+ return($temp) if ($ITS == 90);
+ return $temp / 1.00024;
+}
+
+}
+
+#----------------------------------------------------------------------
+# Oxygen Unit Conversion
+#
+# - old units (e.g. sd2) are ml/l
+# - new units (e.g. WOCE) are umol/kg => independent of pressure!
+# - conversion (from [http://sea-mat.whoi.edu/robbins/ox_units.m]; Paul
+# Robbins) uses potential density ref'd to surface --- makes sense
+# because titration is presumably done at atmospheric pressure
+# - constant divisor is volume of one mole derived from gas law
+# (PV = nRT) in the right units (whatever)
+#----------------------------------------------------------------------
+
+{ my(@fc);
+ sub O2mlpl2umpkg(@)
+ {
+ return nan if isnan($_[3]);
+ my($S,$T,$P,$mlpl) =
+ &antsFunUsage(4,'ffff','[S, T, P [dbar], O2 [ml/l]]',
+ \@fc,'salin','temp','press','O2',@_);
+ return $mlpl * 1000/(1000+sigma($S,$T,$P,0)) / .022403;
+ }
+}
+
+{ my(@fc);
+ sub O2umpkg2mlpl(@)
+ {
+ return nan if isnan($_[3]);
+ my($S,$T,$P,$umpkg) =
+ &antsFunUsage(4,'ffff','[S, T, P [dbar], O2 [ml/l]]',
+ \@fc,'salin','temp','press','O2',@_);
+ return .022403 * $umpkg * (1000+sigma($S,$T,$P,0))/1000;
+ }
+}
+
+#----------------------------------------------------------------------
+# Color Conversion
+#
+# - algorithms taken from the web; source given alternatively as ACM
+# and Foley and VanDam
+# - from the available GMT default cpt files, it looks like
+# the range for hue is 0 - 359 (angles on a circle)
+# - ACM implementation uses a hue range of 0-6 with pure red being 0 or 6
+# - ACM implementation uses range of 0-1 for R,G,B
+# - in HSV, gray scales are not uniquely defined; I extended the
+# algorithms to behave like matlab in this case, i.e. return a hue of
+# pure red (0)
+#----------------------------------------------------------------------
+
+sub HSV2RGB(@)
+{
+ my($H,$S,$V) = &antsFunUsage(3,"fff","H (0-360), S (0-1), V (0-1), ",@_);
+ my($m,$n,$f,$i);
+
+ $H = 0 if ($H < 0 && $H >= -$PRACTICALLY_ZERO);
+ croak("$0 HSV2RGB(): H=$H out of range\n") if ($H < 0 || $H > 360);
+ croak("$0 HSV2RGB(): S=$S out of range\n") if ($S < 0 || $S > 1);
+ croak("$0 HSV2RGB(): V=$V out of range\n") if ($V < 0 || $V > 1);
+
+ $i = POSIX::floor($H/60); # ACM implementation uses [0-6] with red = 0 = 6
+ $f = $H/60 - $i;
+ $f = 1 - $f if (!($i & 1)); # if i is even
+ $m = $V * (1 - $S);
+ $n = $V * (1 - $S * $f);
+ return (int(255*$V+0.5),int(255*$n+0.5),int(255*$m+0.5)) if ($i==0 || $i==6);
+ return (int(255*$n+0.5),int(255*$V+0.5),int(255*$m+0.5)) if ($i == 1);
+ return (int(255*$m+0.5),int(255*$V+0.5),int(255*$n+0.5)) if ($i == 2);
+ return (int(255*$m+0.5),int(255*$n+0.5),int(255*$V+0.5)) if ($i == 3);
+ return (int(255*$n+0.5),int(255*$m+0.5),int(255*$V+0.5)) if ($i == 4);
+ return (int(255*$V+0.5),int(255*$m+0.5),int(255*$n+0.5)) if ($i == 5);
+ croak("$0 HSV2RGB(): implementation error");
+}
+
+sub RGB2HSV(@)
+{
+ my($R,$G,$B) = &antsFunUsage(3,"cc","R, G, B",@_);
+ my($V,$x,$f,$i,$H);
+
+ $R /= 255; $G /= 255; $B /= 255;
+ croak("$0 RGB2HSV(): R out of range\n") if ($R < 0 || $R > 1);
+ croak("$0 RGB2HSV(): G out of range\n") if ($G < 0 || $G > 1);
+ croak("$0 RGB2HSV(): B out of range\n") if ($B < 0 || $B > 1);
+
+ $x = min($R,$G,$B);
+ $V = max($R,$G,$B);
+ return (0,0,$V) if ($V == $x); # any hue is valid
+
+ $f = ($R == $x) ? $G - $B : (($G == $x) ? $B - $R : $R - $G);
+ $i = ($R == $x) ? 3 : (($G == $x) ? 5 : 1);
+
+ $H = 60 * ($i - $f / ($V - $x));
+ $H = 0 if ($H == 360);
+
+ return ($H, ($V - $x)/$V, $V);
+}
+
+1;