/**
\file    time_conversion.c
\brief   GNSS core 'c' function library: converting time information.
\author  Glenn D. MacGougan (GDM)
\date    2007-11-29
\since   2005-07-30

\b REFERENCES \n
- Hofmann-Wellenhof, B., H. Lichtenegger, and J. Collins (1994). GPS Theory and 
  Practice, Third, revised edition. Springer-Verlag, Wien New York. pp. 38-42 \n
- http://aa.usno.navy.mil/data/docs/JulianDate.html - Julian Date Converter \n
- http://aa.usno.navy.mil/faq/docs/UT.html \n
- http://wwwmacho.mcmaster.ca/JAVA/JD.html \n
- Raquet, J. F. (2002), GPS Receiver Design Lecture Notes. Geomatics Engineering, 
  University of Calgary Graduate Course. \n

\b "LICENSE INFORMATION" \n
Copyright (c) 2007, refer to 'author' doxygen tags \n
All rights reserved. \n

Redistribution and use in source and binary forms, with or without
modification, are permitted provided the following conditions are met: \n

- Redistributions of source code must retain te above copyright
  notice, this list of conditions and the following disclaimer. \n
- Redistributions in binary form must reproduce the above copyright
  notice, this list of conditions and the following disclaimer in the
  documentation and/or other materials provided with the distribution. \n
- The name(s) of the contributor(s) may not be used to endorse or promote 
  products derived from this software without specific prior written 
  permission. \n

THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS 
OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 
SUCH DAMAGE.
*/

#include <sys/timeb.h>
#include <time.h>
#include <math.h> // for fmod()
#include "gnss_error.h"
#include "time_conversion.h"
#include "constants.h"

#ifndef WIN32
  #define _CRT_SECURE_NO_DEPRECATE  
#endif

#ifdef WIN32
  #include <windows.h>
#endif


#define TIMECONV_JULIAN_DATE_START_OF_GPS_TIME (2444244.5)  // [days]
#define TIMECONV_JULIAN_DATE_START_OF_PC_TIME  (2440587.5)  // [days]
#define TIMECONV_DAYS_IN_JAN 31
#define TIMECONV_DAYS_IN_MAR 31
#define TIMECONV_DAYS_IN_APR 30
#define TIMECONV_DAYS_IN_MAY 31
#define TIMECONV_DAYS_IN_JUN 30
#define TIMECONV_DAYS_IN_JUL 31
#define TIMECONV_DAYS_IN_AUG 31
#define TIMECONV_DAYS_IN_SEP 30
#define TIMECONV_DAYS_IN_OCT 31
#define TIMECONV_DAYS_IN_NOV 30
#define TIMECONV_DAYS_IN_DEC 31


// A static function to check if the utc input values are valid.
// \return TRUE if valid, FALSE otherwise.
static BOOL TIMECONV_IsUTCTimeValid( 
 const unsigned short     utc_year,      //!< Universal Time Coordinated  [year]
 const unsigned char      utc_month,     //!< Universal Time Coordinated  [1-12 months] 
 const unsigned char      utc_day,       //!< Universal Time Coordinated  [1-31 days]
 const unsigned char      utc_hour,      //!< Universal Time Coordinated  [hours]
 const unsigned char      utc_minute,    //!< Universal Time Coordinated  [minutes]
 const float              utc_seconds    //!< Universal Time Coordinated  [s]
 );
 

BOOL TIMECONV_IsUTCTimeValid( 
 const unsigned short     utc_year,      //!< Universal Time Coordinated  [year]
 const unsigned char      utc_month,     //!< Universal Time Coordinated  [1-12 months] 
 const unsigned char      utc_day,       //!< Universal Time Coordinated  [1-31 days]
 const unsigned char      utc_hour,      //!< Universal Time Coordinated  [hours]
 const unsigned char      utc_minute,    //!< Universal Time Coordinated  [minutes]
 const float              utc_seconds    //!< Universal Time Coordinated  [s]
 )
{
  unsigned char daysInMonth;
  BOOL result;
  if( utc_month == 0 || utc_month > 12 )
  {
    GNSS_ERROR_MSG( "if( utc_month == 0 || utc_month > 12 )" );
    return FALSE;
  }
  result = TIMECONV_GetNumberOfDaysInMonth( utc_year, utc_month, &daysInMonth );
  if( result == FALSE )
  {
    GNSS_ERROR_MSG( "TIMECONV_GetNumberOfDaysInMonth returned FALSE." );       
    return FALSE;
  }
  if( utc_day == 0 || utc_day > daysInMonth )
  {
    GNSS_ERROR_MSG( "if( utc_day == 0 || utc_day > daysInMonth )" );
    return FALSE;
  }
  if( utc_hour > 23 )
  {
    GNSS_ERROR_MSG( "if( utc_hour > 23 )" );
    return FALSE;
  }
  if( utc_minute > 59 )
  {
    GNSS_ERROR_MSG( "if( utc_minute > 59 )" );
    return FALSE;
  }
  if( utc_seconds > 60 )
  {
    GNSS_ERROR_MSG( "if( utc_seconds > 60 )" );
    return FALSE;
  }

  return TRUE;
}


BOOL TIMECONV_GetSystemTime(
  unsigned short*     utc_year,     //!< Universal Time Coordinated    [year]
  unsigned char*      utc_month,    //!< Universal Time Coordinated    [1-12 months] 
  unsigned char*      utc_day,      //!< Universal Time Coordinated    [1-31 days]
  unsigned char*      utc_hour,     //!< Universal Time Coordinated    [hours]
  unsigned char*      utc_minute,   //!< Universal Time Coordinated    [minutes]
  float*              utc_seconds,  //!< Universal Time Coordinated    [s]
  unsigned char*      utc_offset,   //!< Integer seconds that GPS is ahead of UTC time, always positive             [s], obtained from a look up table
  double*             julian_date,  //!< Number of days since noon Universal Time Jan 1, 4713 BCE (Julian calendar) [days]
  unsigned short*     gps_week,     //!< GPS week (0-1024+)            [week]
  double*             gps_tow       //!< GPS time of week (0-604800.0) [s]
  )
{
  BOOL result;

#ifdef WIN32
  struct _timeb timebuffer; // found in <sys/timeb.h>   
#else
  struct timeb timebuffer;
#endif
  double timebuffer_time_in_days;
  double timebuffer_time_in_seconds;
  //char *timeline; // for debugging

#ifndef _CRT_SECURE_NO_DEPRECATE
  if( _ftime_s( &timebuffer ) != 0 )
  {
    GNSS_ERROR_MSG( "if( _ftime_s( &timebuffer ) != 0 )" );
    return FALSE;
  }
#else

#ifdef WIN32
  _ftime( &timebuffer );
#else
  ftime( &timebuffer );
#endif 

#endif
  //timeline = ctime( & ( timebuffer.time ) ); // for debugging
  //printf( "%s\n", timeline ); // for debugging

  timebuffer_time_in_seconds = timebuffer.time + timebuffer.millitm / 1000.0; // [s] with ms resolution

  // timebuffer_time_in_seconds is the time in seconds since midnight (00:00:00), January 1, 1970, 
  // coordinated universal time (UTC). Julian date for (00:00:00), January 1, 1970 is: 2440587.5 [days]

  // convert timebuffer.time from seconds to days
  timebuffer_time_in_days = timebuffer_time_in_seconds/SECONDS_IN_DAY; // days since julian date 2440587.5000000 [days]

  // convert to julian date
  *julian_date = TIMECONV_JULIAN_DATE_START_OF_PC_TIME + timebuffer_time_in_days;

  result = TIMECONV_DetermineUTCOffset( *julian_date, utc_offset );
  if( result == FALSE )
  {
    GNSS_ERROR_MSG( "TIMECONV_DetermineUTCOffset returned FALSE." );
    return FALSE;
  }

  result = TIMECONV_GetGPSTimeFromJulianDate(
    *julian_date,
    *utc_offset,
    gps_week,
    gps_tow );
  if( result == FALSE )
  {
    GNSS_ERROR_MSG( "TIMECONV_GetGPSTimeFromJulianDate returned FALSE." );
    return FALSE;
  }

  result = TIMECONV_GetUTCTimeFromJulianDate(
    *julian_date,
    utc_year,
    utc_month,
    utc_day,
    utc_hour,
    utc_minute,
    utc_seconds );
  if( result == FALSE )
  {
    GNSS_ERROR_MSG( "TIMECONV_GetUTCTimeFromJulianDate" );
    return FALSE;
  }

  return TRUE;
}


#ifdef WIN32
BOOL TIMECONV_SetSystemTime(
  const unsigned short  utc_year,     //!< Universal Time Coordinated    [year]
  const unsigned char   utc_month,    //!< Universal Time Coordinated    [1-12 months] 
  const unsigned char   utc_day,      //!< Universal Time Coordinated    [1-31 days]
  const unsigned char   utc_hour,     //!< Universal Time Coordinated    [hours]
  const unsigned char   utc_minute,   //!< Universal Time Coordinated    [minutes]
  const float           utc_seconds   //!< Universal Time Coordinated    [s]
  )
{
  BOOL result;
  SYSTEMTIME t;
  double julian_date = 0;
  unsigned char day_of_week = 0;

  result = TIMECONV_GetJulianDateFromUTCTime(
    utc_year,
    utc_month,
    utc_day,
    utc_hour,
    utc_minute,
    utc_seconds,
    &julian_date
    );
  if( !result )
  {
    GNSS_ERROR_MSG( "TIMECONV_GetJulianDateFromUTCTime returned FALSE.");
    return FALSE;
  }

  result = TIMECONV_GetDayOfWeekFromJulianDate( julian_date, &day_of_week );
  if( !result )
  {
    GNSS_ERROR_MSG( "TIMECONV_GetDayOfWeekFromJulianDate returned FALSE.");
    return FALSE;
  }
  
  t.wDayOfWeek = day_of_week;
  t.wYear = utc_year;
  t.wMonth = utc_month;
  t.wDay = utc_day;
  t.wHour = utc_hour;
  t.wMinute = utc_minute;
  t.wSecond = (WORD)(floor(utc_seconds));
  t.wMilliseconds = (WORD)((utc_seconds - t.wSecond)*1000);

  // Set the PC system time.
  result = SetSystemTime( &t );
  
  return result;
}
#endif


BOOL TIMECONV_GetDayOfWeekFromJulianDate(
  const double julian_date,   //!< Number of days since noon Universal Time Jan 1, 4713 BCE (Julian calendar) [days]
  unsigned char *day_of_week  //!< 0-Sunday, 1-Monday, 2-Tuesday, 3-Wednesday, 4-Thursday, 5-Friday, 6-Saturday [].
  )
{
  // "If the Julian date of noon is applied to the entire midnight-to-midnight civil 
  // day centered on that noon,[5] rounding Julian dates (fractional days) for the 
  // twelve hours before noon up while rounding those after noon down, then the remainder 
  // upon division by 7 represents the day of the week, with 0 representing Monday, 
  // 1 representing Tuesday, and so forth. Now at 17:48, Wednesday December 3 2008 (UTC) 
  // the nearest noon JDN is 2454804 yielding a remainder of 2." (http://en.wikipedia.org/wiki/Julian_day, 2008-12-03)
  int dow = 0;
  int jd = 0;

  if( julian_date - floor(julian_date) > 0.5 )
  {
    jd = (int)floor(julian_date+0.5);
  }
  else
  {
    jd = (int)floor(julian_date);
  }
  dow = jd%7; // 0 is monday, 1 is tuesday, etc

  switch( dow )
  {
    case 0: *day_of_week = 1; break;
    case 1: *day_of_week = 2; break;
    case 2: *day_of_week = 3; break;
    case 3: *day_of_week = 4; break;
    case 4: *day_of_week = 5; break;
    case 5: *day_of_week = 6; break;
    case 6: *day_of_week = 0; break;
    default: return FALSE; break;
  }
  
  return TRUE;
}


BOOL TIMECONV_GetJulianDateFromGPSTime(
  const unsigned short    gps_week,      //!< GPS week (0-1024+)             [week]
  const double            gps_tow,       //!< GPS time of week (0-604800.0)  [s]
  const unsigned char     utc_offset,    //!< Integer seconds that GPS is ahead of UTC time, always positive [s]
  double*                 julian_date    //!< Number of days since noon Universal Time Jan 1, 4713 BCE (Julian calendar) [days]
  )
{   
  if( gps_tow < 0.0  || gps_tow > 604800.0 )
  {
    GNSS_ERROR_MSG( "if( gps_tow < 0.0  || gps_tow > 604800.0 )" );
    return FALSE;  
  }

  // GPS time is ahead of UTC time and Julian time by the UTC offset
  *julian_date = ((double)gps_week + (gps_tow-(double)utc_offset)/604800.0)*7.0 + TIMECONV_JULIAN_DATE_START_OF_GPS_TIME;  
  return TRUE;
}


BOOL TIMECONV_GetJulianDateFromUTCTime(
 const unsigned short     utc_year,      //!< Universal Time Coordinated  [year]
 const unsigned char      utc_month,     //!< Universal Time Coordinated  [1-12 months] 
 const unsigned char      utc_day,       //!< Universal Time Coordinated  [1-31 days]
 const unsigned char      utc_hour,      //!< Universal Time Coordinated  [hours]
 const unsigned char      utc_minute,    //!< Universal Time Coordinated  [minutes]
 const float              utc_seconds,   //!< Universal Time Coordinated  [s]
 double*                  julian_date    //!< Number of days since noon Universal Time Jan 1, 4713 BCE (Julian calendar) [days]
 )
{   
  double y; // temp for year
  double m; // temp for month
  BOOL result;

  // Check the input.
  result = TIMECONV_IsUTCTimeValid( utc_year, utc_month, utc_day, utc_hour, utc_minute, utc_seconds );
  if( result == FALSE )
  {
    GNSS_ERROR_MSG( "TIMECONV_IsUTCTimeValid returned FALSE." );
    return FALSE;
  }

  if( utc_month <= 2 )
  {
    y = utc_year - 1;
    m = utc_month + 12;
  }
  else 
  {
    y = utc_year;
    m = utc_month;
  }

  *julian_date = (int)(365.25*y) + (int)(30.6001*(m+1.0)) + utc_day + utc_hour/24.0 + utc_minute/1440.0 + utc_seconds/86400.0 + 1720981.5;
  return TRUE;
}



BOOL TIMECONV_GetGPSTimeFromJulianDate(
  const double            julian_date, //!< Number of days since noon Universal Time Jan 1, 4713 BCE (Julian calendar) [days]
  const unsigned char     utc_offset,  //!< Integer seconds that GPS is ahead of UTC time, always positive [s]
  unsigned short*         gps_week,    //!< GPS week (0-1024+)            [week]
  double*                 gps_tow      //!< GPS time of week [s]
  )
{
  // Check the input.
  if( julian_date < 0.0 )
  {
    GNSS_ERROR_MSG( "if( julian_date < 0.0 )" );
    return FALSE;
  }

  *gps_week = (unsigned short)((julian_date - TIMECONV_JULIAN_DATE_START_OF_GPS_TIME)/7.0); //

  *gps_tow   = (julian_date - TIMECONV_JULIAN_DATE_START_OF_GPS_TIME)*SECONDS_IN_DAY; // seconds since start of gps time [s]
  *gps_tow  -= (*gps_week)*SECONDS_IN_WEEK;                                  // seconds into the current week [s] 

  // however, GPS time is ahead of utc time by the UTC offset (and thus the Julian date as well)
  *gps_tow += utc_offset;
  if( *gps_tow > SECONDS_IN_WEEK )
  {
    *gps_tow  -= SECONDS_IN_WEEK;
    *gps_week += 1;
  }
  return TRUE;
}


BOOL TIMECONV_GetUTCTimeFromJulianDate(
  const double        julian_date,  //!< Number of days since noon Universal Time Jan 1, 4713 BCE (Julian calendar) [days]
  unsigned short*     utc_year,     //!< Universal Time Coordinated    [year]
  unsigned char*      utc_month,    //!< Universal Time Coordinated    [1-12 months] 
  unsigned char*      utc_day,      //!< Universal Time Coordinated    [1-31 days]
  unsigned char*      utc_hour,     //!< Universal Time Coordinated    [hours]
  unsigned char*      utc_minute,   //!< Universal Time Coordinated    [minutes]
  float*              utc_seconds   //!< Universal Time Coordinated    [s]
  )
{
  int a, b, c, d, e; // temporary values
  
  unsigned short year;  
  unsigned char month;
  unsigned char day;
  unsigned char hour;
  unsigned char minute;        
  unsigned char days_in_month = 0;  
  double td; // temporary double
  double seconds;
  BOOL result;

  // Check the input.
  if( julian_date < 0.0 )
  {
    GNSS_ERROR_MSG( "if( julian_date < 0.0 )" );
    return FALSE;
  }
  
  a = (int)(julian_date+0.5);
  b = a + 1537;
  c = (int)( ((double)b-122.1)/365.25 );
  d = (int)(365.25*c);
  e = (int)( ((double)(b-d))/30.6001 );
  
  td      = b - d - (int)(30.6001*e) + fmod( julian_date+0.5, 1.0 );   // [days]
  day     = (unsigned char)td;   
  td     -= day;
  td     *= 24.0;        // [hours]
  hour    = (unsigned char)td;
  td     -= hour;
  td     *= 60.0;        // [minutes]
  minute  = (unsigned char)td;
  td     -= minute;
  td     *= 60.0;        // [s]
  seconds = td;
  month   = (unsigned char)(e - 1 - 12*(int)(e/14));
  year    = (unsigned short)(c - 4715 - (int)( (7.0+(double)month) / 10.0 ));
  
  // check for rollover issues
  if( seconds >= 60.0 )
  {
    seconds -= 60.0;
    minute++;
    if( minute >= 60 )
    {
      minute -= 60;
      hour++;
      if( hour >= 24 )
      {
        hour -= 24;
        day++;
        
        result = TIMECONV_GetNumberOfDaysInMonth( year, month, &days_in_month );
        if( result == FALSE )
        {
          GNSS_ERROR_MSG( "TIMECONV_GetNumberOfDaysInMonth returned FALSE." );
          return FALSE;
        }
        
        if( day > days_in_month )
        {
          day = 1;
          month++;
          if( month > 12 )
          {
            month = 1;
            year++;
          }
        }
      }
    }
  }   
  
  *utc_year       = year;
  *utc_month      = month;
  *utc_day        = day;
  *utc_hour       = hour;
  *utc_minute     = minute;
  *utc_seconds    = (float)seconds;   

  return TRUE;
}

BOOL TIMECONV_GetGPSTimeFromUTCTime(
  unsigned short     utc_year,     //!< Universal Time Coordinated    [year]
  unsigned char      utc_month,    //!< Universal Time Coordinated    [1-12 months] 
  unsigned char      utc_day,      //!< Universal Time Coordinated    [1-31 days]
  unsigned char      utc_hour,     //!< Universal Time Coordinated    [hours]
  unsigned char      utc_minute,   //!< Universal Time Coordinated    [minutes]
  float              utc_seconds,  //!< Universal Time Coordinated    [s]
  unsigned short*    gps_week,     //!< GPS week (0-1024+)            [week]
  double*            gps_tow       //!< GPS time of week (0-604800.0) [s]
  )
{
  double julian_date=0.0;
  unsigned char utc_offset=0;
  BOOL result;

  // Check the input.
  result = TIMECONV_IsUTCTimeValid( utc_year, utc_month, utc_day, utc_hour, utc_minute, utc_seconds );
  if( result == FALSE )
  {
    GNSS_ERROR_MSG( "TIMECONV_IsUTCTimeValid returned FALSE." );
    return FALSE;
  }

  result = TIMECONV_GetJulianDateFromUTCTime(
    utc_year,
    utc_month,
    utc_day,
    utc_hour,
    utc_minute,
    utc_seconds,
    &julian_date );
  if( result == FALSE )
  {
    GNSS_ERROR_MSG( "TIMECONV_GetJulianDateFromUTCTime returned FALSE." );
    return FALSE;
  }

  result = TIMECONV_DetermineUTCOffset( julian_date, &utc_offset );
  if( result == FALSE )
  {
    GNSS_ERROR_MSG( "TIMECONV_DetermineUTCOffset returned FALSE." );
    return FALSE;
  }

  result = TIMECONV_GetGPSTimeFromJulianDate(
    julian_date,
    utc_offset,
    gps_week,
    gps_tow );
  if( result == FALSE )
  {
    GNSS_ERROR_MSG( "TIMECONV_GetGPSTimeFromJulianDate returned FALSE." );
    return FALSE;
  }

  return TRUE;
}



BOOL TIMECONV_GetGPSTimeFromRinexTime(
  unsigned short     utc_year,     //!< Universal Time Coordinated    [year]
  unsigned char      utc_month,    //!< Universal Time Coordinated    [1-12 months] 
  unsigned char      utc_day,      //!< Universal Time Coordinated    [1-31 days]
  unsigned char      utc_hour,     //!< Universal Time Coordinated    [hours]
  unsigned char      utc_minute,   //!< Universal Time Coordinated    [minutes]
  float              utc_seconds,  //!< Universal Time Coordinated    [s]
  unsigned short*    gps_week,     //!< GPS week (0-1024+)            [week]
  double*            gps_tow       //!< GPS time of week (0-604800.0) [s]
  )
{
  double julian_date=0.0;
  unsigned char utc_offset=0;
  BOOL result;

  // Check the input.
  result = TIMECONV_IsUTCTimeValid( utc_year, utc_month, utc_day, utc_hour, utc_minute, utc_seconds );
  if( result == FALSE )
  {
    GNSS_ERROR_MSG( "TIMECONV_IsUTCTimeValid returned FALSE." );
    return FALSE;
  }

  result = TIMECONV_GetJulianDateFromUTCTime(
    utc_year,
    utc_month,
    utc_day,
    utc_hour,
    utc_minute,
    utc_seconds,
    &julian_date );
  if( result == FALSE )
  {
    GNSS_ERROR_MSG( "TIMECONV_GetJulianDateFromUTCTime returned FALSE." );
    return FALSE;
  }

  result = TIMECONV_GetGPSTimeFromJulianDate(
    julian_date,
    utc_offset,
    gps_week,
    gps_tow );
  if( result == FALSE )
  {
    GNSS_ERROR_MSG( "TIMECONV_GetGPSTimeFromJulianDate returned FALSE." );
    return FALSE;
  }

  return TRUE;
}



BOOL TIMECONV_GetUTCTimeFromGPSTime(
  unsigned short     gps_week,     //!< GPS week (0-1024+)            [week]
  double             gps_tow,      //!< GPS time of week (0-604800.0) [s]
  unsigned short*    utc_year,     //!< Universal Time Coordinated    [year]
  unsigned char*     utc_month,    //!< Universal Time Coordinated    [1-12 months] 
  unsigned char*     utc_day,      //!< Universal Time Coordinated    [1-31 days]
  unsigned char*     utc_hour,     //!< Universal Time Coordinated    [hours]
  unsigned char*     utc_minute,   //!< Universal Time Coordinated    [minutes]
  float*             utc_seconds   //!< Universal Time Coordinated    [s]
  )
{
  double julian_date = 0.0; 
  unsigned char utc_offset = 0;
  int i;
  BOOL result;

  if( gps_tow < 0.0 || gps_tow > 604800.0 )
  {
    GNSS_ERROR_MSG( "if( gps_tow < 0.0 || gps_tow > 604800.0 )" );
    return FALSE;  
  }

  // iterate to get the right utc offset
  for( i = 0; i < 4; i++ )
  {
    result = TIMECONV_GetJulianDateFromGPSTime(
      gps_week,
      gps_tow,
      utc_offset,
      &julian_date );
    if( result == FALSE )
    {
      GNSS_ERROR_MSG( "TIMECONV_GetJulianDateFromGPSTime returned FALSE." );
      return FALSE;
    }

    result = TIMECONV_DetermineUTCOffset( julian_date, &utc_offset );
    if( result == FALSE )
    {
      GNSS_ERROR_MSG( "TIMECONV_DetermineUTCOffset returned FALSE." );
      return FALSE;
    }
  }

  result = TIMECONV_GetUTCTimeFromJulianDate(
    julian_date,
    utc_year,
    utc_month,
    utc_day,
    utc_hour,
    utc_minute,
    utc_seconds );
  if( result == FALSE )
  {
    GNSS_ERROR_MSG( "TIMECONV_GetUTCTimeFromJulianDate returned FALSE." );
    return FALSE;
  }

  return TRUE;
}



BOOL TIMECONV_DetermineUTCOffset(
  double julian_date,       //!< Number of days since noon Universal Time Jan 1, 4713 BCE (Julian calendar) [days]
  unsigned char* utc_offset //!< Integer seconds that GPS is ahead of UTC time, always positive             [s], obtained from a look up table
  )
{
  if( julian_date < 0.0 )
  {
    GNSS_ERROR_MSG( "if( julian_date < 0.0 )" );
    return FALSE;
  }

  if(      julian_date < 2444786.5000 ) *utc_offset = 0;
  else if( julian_date < 2445151.5000 ) *utc_offset = 1;
  else if( julian_date < 2445516.5000 ) *utc_offset = 2;
  else if( julian_date < 2446247.5000 ) *utc_offset = 3;
  else if( julian_date < 2447161.5000 ) *utc_offset = 4;
  else if( julian_date < 2447892.5000 ) *utc_offset = 5;
  else if( julian_date < 2448257.5000 ) *utc_offset = 6;
  else if( julian_date < 2448804.5000 ) *utc_offset = 7;
  else if( julian_date < 2449169.5000 ) *utc_offset = 8;
  else if( julian_date < 2449534.5000 ) *utc_offset = 9;
  else if( julian_date < 2450083.5000 ) *utc_offset = 10;
  else if( julian_date < 2450630.5000 ) *utc_offset = 11;
  else if( julian_date < 2451179.5000 ) *utc_offset = 12;  
  else if( julian_date < 2453736.5000 ) *utc_offset = 13;  
  else                                  *utc_offset = 14;

  return TRUE;
}  


  

BOOL TIMECONV_GetNumberOfDaysInMonth(
  const unsigned short year,        //!< Universal Time Coordinated    [year]
  const unsigned char month,        //!< Universal Time Coordinated    [1-12 months] 
  unsigned char* days_in_month      //!< Days in the specified month   [1-28|29|30|31 days]
  )
{
  BOOL is_a_leapyear;
  unsigned char utmp = 0;
  
  is_a_leapyear = TIMECONV_IsALeapYear( year );
  
  switch(month)
  {
  case  1: utmp = TIMECONV_DAYS_IN_JAN; break;
  case  2: if( is_a_leapyear ){ utmp = 29; }else{ utmp = 28; }break;    
  case  3: utmp = TIMECONV_DAYS_IN_MAR; break;
  case  4: utmp = TIMECONV_DAYS_IN_APR; break;
  case  5: utmp = TIMECONV_DAYS_IN_MAY; break;
  case  6: utmp = TIMECONV_DAYS_IN_JUN; break;
  case  7: utmp = TIMECONV_DAYS_IN_JUL; break;
  case  8: utmp = TIMECONV_DAYS_IN_AUG; break;
  case  9: utmp = TIMECONV_DAYS_IN_SEP; break;
  case 10: utmp = TIMECONV_DAYS_IN_OCT; break;
  case 11: utmp = TIMECONV_DAYS_IN_NOV; break;
  case 12: utmp = TIMECONV_DAYS_IN_DEC; break;
  default: 
    { 
      GNSS_ERROR_MSG( "unexpected default case." ); 
      return FALSE; 
      break;    
    }
  }
  
  *days_in_month = utmp;

  return TRUE;
}

  


BOOL TIMECONV_IsALeapYear( const unsigned short year )
{
  BOOL is_a_leap_year = FALSE;

  if( (year%4) == 0 )
  {
    is_a_leap_year = TRUE;
    if( (year%100) == 0 )
    {
      if( (year%400) == 0 )
      {
        is_a_leap_year = TRUE;
      }
      else
      {
        is_a_leap_year = FALSE;
      }
    }
  }
  if( is_a_leap_year )
  {
    return TRUE;
  }
  else
  {    
    return FALSE;
  }
}





BOOL TIMECONV_GetDayOfYear(
  const unsigned short utc_year,    // Universal Time Coordinated           [year]
  const unsigned char  utc_month,   // Universal Time Coordinated           [1-12 months] 
  const unsigned char  utc_day,     // Universal Time Coordinated           [1-31 days]
  unsigned short*      dayofyear    // number of days into the year (1-366) [days]
  )
{
  unsigned char days_in_feb = 0;
  BOOL result;
  result = TIMECONV_GetNumberOfDaysInMonth( utc_year, 2, &days_in_feb );
  if( result == FALSE )
  {
    GNSS_ERROR_MSG( "TIMECONV_GetNumberOfDaysInMonth returned FALSE." );
    return FALSE;
  }

  switch( utc_month )
  {
  case  1: *dayofyear = utc_day; break;
  case  2: *dayofyear = (unsigned short)(TIMECONV_DAYS_IN_JAN               + utc_day); break;
  case  3: *dayofyear = (unsigned short)(TIMECONV_DAYS_IN_JAN + days_in_feb + utc_day); break;
  case  4: *dayofyear = (unsigned short)(62          + days_in_feb + utc_day); break;
  case  5: *dayofyear = (unsigned short)(92          + days_in_feb + utc_day); break;
  case  6: *dayofyear = (unsigned short)(123         + days_in_feb + utc_day); break;
  case  7: *dayofyear = (unsigned short)(153         + days_in_feb + utc_day); break;
  case  8: *dayofyear = (unsigned short)(184         + days_in_feb + utc_day); break;
  case  9: *dayofyear = (unsigned short)(215         + days_in_feb + utc_day); break;
  case 10: *dayofyear = (unsigned short)(245         + days_in_feb + utc_day); break;
  case 11: *dayofyear = (unsigned short)(276         + days_in_feb + utc_day); break;
  case 12: *dayofyear = (unsigned short)(306         + days_in_feb + utc_day); break;
  default: 
    {
      GNSS_ERROR_MSG( "unexpected default case." );
      return FALSE; 
      break;
    }
  }

  return TRUE;
}


BOOL TIMECONV_GetGPSTimeFromYearAndDayOfYear(
 const unsigned short year,      // The year [year]
 const unsigned short dayofyear, // The number of days into the year (1-366) [days]
 unsigned short*      gps_week,  //!< GPS week (0-1024+)            [week]
 double*              gps_tow    //!< GPS time of week (0-604800.0) [s]
 )
{
  BOOL result;
  double julian_date = 0;

  if( gps_week == NULL )
  {
    GNSS_ERROR_MSG( "if( gps_week == NULL )" );
    return FALSE;
  }
  if( gps_tow == NULL )
  {
    GNSS_ERROR_MSG( "if( gps_tow == NULL )" );
    return FALSE;
  }
  if( dayofyear > 366 )
  {
    GNSS_ERROR_MSG( "if( dayofyear > 366 )" );
    return FALSE;
  }

  result = TIMECONV_GetJulianDateFromUTCTime(
    year,
    1,
    1,
    0,
    0,
    0,
    &julian_date 
    );
  if( result == FALSE )
  {
    GNSS_ERROR_MSG( "TIMECONV_GetJulianDateFromUTCTime returned FALSE." );
    return FALSE;
  }

  julian_date += dayofyear - 1; // at the start of the day so -1.

  result = TIMECONV_GetGPSTimeFromJulianDate(
    julian_date,
    0,
    gps_week,
    gps_tow );
  if( result == FALSE )
  {
    GNSS_ERROR_MSG( "TIMECONV_GetGPSTimeFromJulianDate returned FALSE." );
    return FALSE;
  }

  return TRUE;
}
