# This code converts between GPS and UTC times -- taking leap seconds
# into account.  See the following for a discussion of leap seconds
# and various time reference frames.
# http://tycho.usno.navy.mil/leapsec.html

# Norman Kuring		23-Jun-2011	Initial development.
# Norman Kuring		 6-Oct-2011	Add a utc2gps subroutine.

package GpsToUtc;

use strict;
use Exporter qw(import);
use Time::Local;

our $VERSION     = 1.00;
our @ISA         = qw(Exporter);
our @EXPORT_OK   = qw(read_leap_secs gps2utc utc2gps);

my $gps_epoch = timegm(0,0,0,6,0,80);

# The following file should be periodically updated
# via cron or similar mechanism.
my $lsfile = '/web/aquarius/metadata/leapsec.dat';
my @tbl = ();

sub read_leap_secs{
  my $f = $lsfile;

  my %mon = qw(jan 0 feb 1 mar 2 apr 3 may  4 jun  5
               jul 6 aug 7 sep 8 oct 9 nov 10 dec 11);

  open F,'<',$f or die "Could not open $f ($!)";
  my @recs = <F>;
  close F;

  foreach my $r (@recs){

    my ($year,$mon,$date,$tai_utc_diff) = $r =~
    /^
    \s*
    (\d{4})             # year
    \s+
    ([A-Za-z]{3})       # month
    \s+
    (\d{1,2})           # date
    \s+
    =JD
    \s+
    \d+\.5
    \s+
    TAI-UTC=
    \s*
    (\d+)               # TAI - UTC
    \.0000000
    /x or next;

    defined $mon{lc $mon} or next;

    my $t = timegm(0,0,0,$date,$mon{lc $mon},$year-1900);

    next if $t < $gps_epoch;

    my $gps_utc_diff = $tai_utc_diff - 19;

    push @tbl,[$t,$gps_utc_diff];
  }

  @tbl or die "Could not get leap second data from $f .\n";

  # Make sure the table is in chronological order.
  @tbl = sort {$a->[0] <=> $b->[0]} @tbl;
}

sub gps2utc{
  my $gps = shift;

  my $t = $gps + $gps_epoch;

  my $leap_seconds = 0;

  # Search backwards through the table for the most recent time
  # that is less than the input time.  (For real-time data, this
  # should be the last row of the table.)
  my $row;
  for($row=$#tbl; $row>=0; $row--){
    last if $t > $tbl[$row][0];
  }
  if($row == -1){
    # This should not happen.
    warn "Input time ($gps) predates the times I retrieved from $lsfile .\n";
  }
  else{
    if($t - $tbl[$row][1] >= $tbl[$row][0]){
      $leap_seconds = $tbl[$row][1];
    }
    else{
      # Make a special allowance for the case where the input time
      # is close enough to the leap-second-insertion time that
      # subtraction of the correction would put the time back into
      # the previous leap-second period.  In these cases, use the
      # previous leap second correction or zero if there is no
      # previous row.
      $leap_seconds = $row>0 ? $tbl[$row-1][1] : 0;
    }
  }

  my $utc = $t - $leap_seconds;
}

sub utc2gps{
  my $utc = shift;

  my $leap_seconds = 0;

  # Search backwards through the table for the most recent time
  # that is less than the input time.  (For real-time data, this
  # should be the last row of the table.)
  my $row;
  for($row=$#tbl; $row>=0; $row--){
    last if $utc > $tbl[$row][0];
  }
  if($row == -1){
    # This should not happen.
    warn "Input time ($utc) predates the times I retrieved from $lsfile .\n";
  }
  else{
    $leap_seconds = $tbl[$row][1];
  }

  my $gps = $utc - $gps_epoch + $leap_seconds;
}

1;
