You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1875 lines
56 KiB
C

/***************************************************************************
* Generic utility routines
*
* This file is part of the miniSEED Library.
*
* Copyright (c) 2023 Chad Trabant, EarthScope Data Services
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
***************************************************************************/
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "gmtime64.h"
#include "libmseed.h"
static nstime_t ms_time2nstime_int (int year, int day, int hour,
int min, int sec, uint32_t nsec);
/** @cond UNDOCUMENTED */
/* Global set of allocation functions, defaulting to system malloc(), realloc() and free() */
LIBMSEED_MEMORY libmseed_memory = { .malloc = malloc, .realloc = realloc, .free = free };
/* Global pre-allocation block size, default 1 MiB on Windows, disabled otherwise */
#if defined(LMP_WIN)
size_t libmseed_prealloc_block_size = 1048576;
#else
size_t libmseed_prealloc_block_size = 0;
#endif
/* Internal realloc() wrapper that allocates in libmseed_prealloc_block_size blocks */
void *
libmseed_memory_prealloc (void *ptr, size_t size, size_t *currentsize)
{
size_t newsize;
void *newptr;
if (!currentsize)
return NULL;
if (libmseed_prealloc_block_size == 0)
return NULL;
/* No additional memory needed if request already satisfied */
if (size < *currentsize)
return ptr;
/* Calculate new size needed for request by adding blocks */
newsize = *currentsize + libmseed_prealloc_block_size;
while (newsize < size)
newsize += libmseed_prealloc_block_size;
newptr = libmseed_memory.realloc (ptr, newsize);
if (newptr)
*currentsize = newsize;
return newptr;
}
/* A constant number of seconds between the NTP and POSIX/Unix time epoch */
#define NTPPOSIXEPOCHDELTA 2208988800LL
#define NTPEPOCH2NSTIME(X) (MS_EPOCH2NSTIME (X - NTPPOSIXEPOCHDELTA))
/* Embedded leap second list */
static LeapSecond embedded_leapsecondlist[] = {
{.leapsecond = NTPEPOCH2NSTIME (2272060800), .TAIdelta = 10, .next = &embedded_leapsecondlist[1]},
{.leapsecond = NTPEPOCH2NSTIME (2287785600), .TAIdelta = 11, .next = &embedded_leapsecondlist[2]},
{.leapsecond = NTPEPOCH2NSTIME (2303683200), .TAIdelta = 12, .next = &embedded_leapsecondlist[3]},
{.leapsecond = NTPEPOCH2NSTIME (2335219200), .TAIdelta = 13, .next = &embedded_leapsecondlist[4]},
{.leapsecond = NTPEPOCH2NSTIME (2366755200), .TAIdelta = 14, .next = &embedded_leapsecondlist[5]},
{.leapsecond = NTPEPOCH2NSTIME (2398291200), .TAIdelta = 15, .next = &embedded_leapsecondlist[6]},
{.leapsecond = NTPEPOCH2NSTIME (2429913600), .TAIdelta = 16, .next = &embedded_leapsecondlist[7]},
{.leapsecond = NTPEPOCH2NSTIME (2461449600), .TAIdelta = 17, .next = &embedded_leapsecondlist[8]},
{.leapsecond = NTPEPOCH2NSTIME (2492985600), .TAIdelta = 18, .next = &embedded_leapsecondlist[9]},
{.leapsecond = NTPEPOCH2NSTIME (2524521600), .TAIdelta = 19, .next = &embedded_leapsecondlist[10]},
{.leapsecond = NTPEPOCH2NSTIME (2571782400), .TAIdelta = 20, .next = &embedded_leapsecondlist[11]},
{.leapsecond = NTPEPOCH2NSTIME (2603318400), .TAIdelta = 21, .next = &embedded_leapsecondlist[12]},
{.leapsecond = NTPEPOCH2NSTIME (2634854400), .TAIdelta = 22, .next = &embedded_leapsecondlist[13]},
{.leapsecond = NTPEPOCH2NSTIME (2698012800), .TAIdelta = 23, .next = &embedded_leapsecondlist[14]},
{.leapsecond = NTPEPOCH2NSTIME (2776982400), .TAIdelta = 24, .next = &embedded_leapsecondlist[15]},
{.leapsecond = NTPEPOCH2NSTIME (2840140800), .TAIdelta = 25, .next = &embedded_leapsecondlist[16]},
{.leapsecond = NTPEPOCH2NSTIME (2871676800), .TAIdelta = 26, .next = &embedded_leapsecondlist[17]},
{.leapsecond = NTPEPOCH2NSTIME (2918937600), .TAIdelta = 27, .next = &embedded_leapsecondlist[18]},
{.leapsecond = NTPEPOCH2NSTIME (2950473600), .TAIdelta = 28, .next = &embedded_leapsecondlist[19]},
{.leapsecond = NTPEPOCH2NSTIME (2982009600), .TAIdelta = 29, .next = &embedded_leapsecondlist[20]},
{.leapsecond = NTPEPOCH2NSTIME (3029443200), .TAIdelta = 30, .next = &embedded_leapsecondlist[21]},
{.leapsecond = NTPEPOCH2NSTIME (3076704000), .TAIdelta = 31, .next = &embedded_leapsecondlist[22]},
{.leapsecond = NTPEPOCH2NSTIME (3124137600), .TAIdelta = 32, .next = &embedded_leapsecondlist[23]},
{.leapsecond = NTPEPOCH2NSTIME (3345062400), .TAIdelta = 33, .next = &embedded_leapsecondlist[24]},
{.leapsecond = NTPEPOCH2NSTIME (3439756800), .TAIdelta = 34, .next = &embedded_leapsecondlist[25]},
{.leapsecond = NTPEPOCH2NSTIME (3550089600), .TAIdelta = 35, .next = &embedded_leapsecondlist[26]},
{.leapsecond = NTPEPOCH2NSTIME (3644697600), .TAIdelta = 36, .next = &embedded_leapsecondlist[27]},
{.leapsecond = NTPEPOCH2NSTIME (3692217600), .TAIdelta = 37, .next = NULL}};
/* Global variable to hold a leap second list */
LeapSecond *leapsecondlist = &embedded_leapsecondlist[0];
/* Days in each month, for non-leap and leap years */
static const int monthdays[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
static const int monthdays_leap[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
/* Determine if year is a leap year */
#define LEAPYEAR(year) (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0))
/* Check that a year is in a valid range */
#define VALIDYEAR(year) (year >= 1678 && year <= 2262)
/* Check that a month is in a valid range */
#define VALIDMONTH(month) (month >= 1 && month <= 12)
/* Check that a day-of-month is in a valid range */
#define VALIDMONTHDAY(year, month, mday) (mday >= 0 && mday <= (LEAPYEAR (year) ? monthdays_leap[month - 1] : monthdays[month - 1]))
/* Check that a day-of-year is in a valid range */
#define VALIDYEARDAY(year, yday) (yday >= 1 && yday <= (365 + (LEAPYEAR (year) ? 1 : 0)))
/* Check that an hour is in a valid range */
#define VALIDHOUR(hour) (hour >= 0 && hour <= 23)
/* Check that a minute is in a valid range */
#define VALIDMIN(min) (min >= 0 && min <= 59)
/* Check that a second is in a valid range */
#define VALIDSEC(sec) (sec >= 0 && sec <= 60)
/* Check that a nanosecond is in a valid range */
#define VALIDNANOSEC(nanosec) (nanosec >= 0 && nanosec <= 999999999)
/** @endcond */
/**********************************************************************/ /**
* @brief Parse network, station, location and channel from an FDSN Source ID
*
* FDSN Source Identifiers are defined at:
* https://docs.fdsn.org/projects/source-identifiers/
*
* Parse a source identifier into separate components, expecting:
* \c "FDSN:NET_STA_LOC_CHAN", where \c CHAN="BAND_SOURCE_POSITION"
*
* The CHAN value will be converted to a SEED channel code if
* possible. Meaning, if the BAND, SOURCE, and POSITION are single
* characters, the underscore delimiters will not be included in the
* returned channel.
*
* Identifiers may contain additional namespace identifiers, e.g.:
* \c "FDSN:AGENCY:NET_STA_LOC_CHAN"
*
* Such additional namespaces are not part of the Source ID standard
* as of this writing and support is included for specialized usage or
* future identifier changes.
*
* Memory for each component must already be allocated. If a specific
* component is not desired set the appropriate argument to NULL.
*
* @param[in] sid Source identifier
* @param[out] net Network code
* @param[out] sta Station code
* @param[out] loc Location code
* @param[out] chan Channel code
*
* @retval 0 on success
* @retval -1 on error
*
* \ref MessageOnError - this function logs a message on error
***************************************************************************/
int
ms_sid2nslc (const char *sid, char *net, char *sta, char *loc, char *chan)
{
size_t idlen;
const char *cid;
char *id;
char *ptr, *top, *next;
int sepcnt = 0;
if (!sid)
{
ms_log (2, "Required argument not defined: 'sid'\n");
return -1;
}
/* Handle the FDSN: namespace identifier */
if (!strncmp (sid, "FDSN:", 5))
{
/* Advance sid pointer to last ':', skipping all namespace identifiers */
sid = strrchr (sid, ':') + 1;
/* Verify 5 delimiters */
cid = sid;
while ((cid = strchr (cid, '_')))
{
cid++;
sepcnt++;
}
if (sepcnt != 5)
{
ms_log (2, "Incorrect number of identifier delimiters (%d): %s\n", sepcnt, sid);
return -1;
}
idlen = strlen (sid) + 1;
if (!(id = libmseed_memory.malloc (idlen)))
{
ms_log (2, "Error duplicating identifier\n");
return -1;
}
memcpy (id, sid, idlen);
/* Network */
top = id;
if ((ptr = strchr (top, '_')))
{
next = ptr + 1;
*ptr = '\0';
if (net)
strcpy (net, top);
top = next;
}
/* Station */
if ((ptr = strchr (top, '_')))
{
next = ptr + 1;
*ptr = '\0';
if (sta)
strcpy (sta, top);
top = next;
}
/* Location, potentially empty */
if ((ptr = strchr (top, '_')))
{
next = ptr + 1;
*ptr = '\0';
if (loc)
strcpy (loc, top);
top = next;
}
/* Channel */
if (*top && chan)
{
/* Map extended channel to SEED channel if possible, otherwise direct copy */
if (ms_xchan2seedchan(chan, top))
{
strcpy (chan, top);
}
}
/* Free duplicated ID */
if (id)
libmseed_memory.free (id);
}
else
{
ms_log (2, "Unrecognized identifier: %s\n", sid);
return -1;
}
return 0;
} /* End of ms_sid2nslc() */
/**********************************************************************/ /**
* @brief Convert network, station, location and channel to an FDSN Source ID
*
* FDSN Source Identifiers are defined at:
* https://docs.fdsn.org/projects/source-identifiers/
*
* Create a source identifier from individual network,
* station, location and channel codes with the form:
* \c FDSN:NET_STA_LOC_CHAN, where \c CHAN="BAND_SOURCE_POSITION"
*
* Memory for the source identifier must already be allocated. If a
* specific component is NULL it will be empty in the resulting
* identifier.
*
* The \a chan value will be converted to extended channel format if
* it appears to be in SEED channel form. Meaning, if the \a chan is
* 3 characters with no delimiters, it will be converted to \c
* "BAND_SOURCE_POSITION" form by adding delimiters between the codes.
*
* @param[out] sid Destination string for source identifier
* @param sidlen Maximum length of \a sid
* @param flags Currently unused, set to 0
* @param[in] net Network code
* @param[in] sta Station code
* @param[in] loc Location code
* @param[in] chan Channel code
*
* @returns length of source identifier
* @retval -1 on error
*
* \ref MessageOnError - this function logs a message on error
***************************************************************************/
int
ms_nslc2sid (char *sid, int sidlen, uint16_t flags,
const char *net, const char *sta, const char *loc, const char *chan)
{
char *sptr = sid;
char xchan[6] = {0};
int needed = 0;
if (!sid)
{
ms_log (2, "Required argument not defined: 'sid'\n");
return -1;
}
if (sidlen < 13)
{
ms_log (2, "Length of destination SID buffer must be at least 13 bytes\n");
return -1;
}
*sptr++ = 'F';
*sptr++ = 'D';
*sptr++ = 'S';
*sptr++ = 'N';
*sptr++ = ':';
needed = 5;
if (net)
{
while (*net)
{
if ((sptr - sid) < sidlen)
*sptr++ = *net;
net++;
needed++;
}
}
if ((sptr - sid) < sidlen)
*sptr++ = '_';
needed++;
if (sta)
{
while (*sta)
{
if ((sptr - sid) < sidlen)
*sptr++ = *sta;
sta++;
needed++;
}
}
if ((sptr - sid) < sidlen)
*sptr++ = '_';
needed++;
if (loc)
{
while (*loc)
{
if ((sptr - sid) < sidlen)
*sptr++ = *loc;
loc++;
needed++;
}
}
if ((sptr - sid) < sidlen)
*sptr++ = '_';
needed++;
if (chan)
{
/* Map SEED channel to extended channel if possible, otherwise direct copy */
if (!ms_seedchan2xchan (xchan, chan))
{
chan = xchan;
}
while (*chan)
{
if ((sptr - sid) < sidlen)
*sptr++ = *chan;
chan++;
needed++;
}
}
if ((sptr - sid) < sidlen)
*sptr = '\0';
else
*--sptr = '\0';
if (needed >= sidlen)
{
ms_log (2, "Provided SID destination (%d bytes) is not big enough for the needed %d bytes\n", sidlen, needed);
return -1;
}
return (sptr - sid);
} /* End of ms_nslc2sid() */
/**********************************************************************/ /**
* @brief Convert SEED 2.x channel to extended channel
*
* The SEED 2.x channel at \a seedchan must be a 3-character string.
* The \a xchan buffer must be at least 6 bytes, for the extended
* channel (band,source,position) and the terminating NULL.
*
* This functionality simply maps patterns, it does not check the
* validity of any codes.
*
* @param[out] xchan Destination for extended channel string, must be at least 6 bytes
* @param[in] seedchan Source string, must be a 3-character string
*
* @retval 0 on successful mapping of channel
* @retval -1 on error
***************************************************************************/
int
ms_seedchan2xchan (char *xchan, const char *seedchan)
{
if (!seedchan || !xchan)
return -1;
if (seedchan[0] &&
seedchan[1] &&
seedchan[2] &&
seedchan[3] == '\0')
{
xchan[0] = seedchan[0]; /* Band code */
xchan[1] = '_';
xchan[2] = seedchan[1]; /* Source (aka instrument) code */
xchan[3] = '_';
xchan[4] = seedchan[2]; /* Position (aka orientation) code */
xchan[5] = '\0';
return 0;
}
return -1;
} /* End of ms_seedchan2xchan() */
/**********************************************************************/ /**
* @brief Convert extended channel to SEED 2.x channel
*
* The extended channel at \a xchan must be a 5-character string.
*
* The \a seedchan buffer must be at least 4 bytes, for the SEED
* channel and the terminating NULL. Alternatively, \a seedchan may
* be set to NULL in which case this function becomes a test for
* whether the \a xchan _could_ be mapped without actually doing the
* conversion. Finally, \a seedchan can be the same buffer as \a
* xchan for an in-place conversion.
*
* This routine simply maps patterns, it does not check the validity
* of any specific codes.
*
* @param[out] seedchan Destination for SEED channel string, must be at least 4 bytes
* @param[in] xchan Source string, must be a 5-character string
*
* @retval 0 on successful mapping of channel
* @retval -1 on error
***************************************************************************/
int
ms_xchan2seedchan (char *seedchan, const char *xchan)
{
if (!xchan)
return -1;
if (xchan[0] &&
xchan[1] == '_' &&
xchan[2] &&
xchan[3] == '_' &&
xchan[4] &&
xchan[5] == '\0')
{
if (seedchan)
{
seedchan[0] = xchan[0]; /* Band code */
seedchan[1] = xchan[2]; /* Source (aka instrument) code */
seedchan[2] = xchan[4]; /* Position (aka orientation) code */
seedchan[3] = '\0';
}
return 0;
}
return -1;
} /* End of ms_xchan2seedchan() */
// For utf8d table and utf8length_int() basics:
// Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>
// See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
static const uint8_t utf8d[] = {
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 00..1f
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 20..3f
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 40..5f
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 60..7f
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, // 80..9f
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, // a0..bf
8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // c0..df
0xa,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x4,0x3,0x3, // e0..ef
0xb,0x6,0x6,0x6,0x5,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8, // f0..ff
0x0,0x1,0x2,0x3,0x5,0x8,0x7,0x1,0x1,0x1,0x4,0x6,0x1,0x1,0x1,0x1, // s0..s0
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1,1,1,1,1,1, // s1..s2
1,2,1,1,1,1,1,2,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1, // s3..s4
1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,3,1,1,1,1,1,1, // s5..s6
1,3,1,1,1,1,1,3,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // s7..s8
};
/***************************************************************************
* Determine length of UTF-8 bytes in string.
*
* Calculate the length of the string until either the maximum length,
* terminating NULL, or an invalid UTF-8 codepoint are reached.
*
* To determine if the length calculated stopped due to invalid UTF-8
* codepoint, the return value can be tested for maximum length and
* used to test for a terminating NULL, ala:
*
* length = utf8length_int (str, maxlength);
*
* if (length < maxlength && str[length])
* String contains invalid UTF-8 within maxlength bytes
*
* Returns the number of UTF-8 codepoint bytes (not including the null terminator).
***************************************************************************/
static int
utf8length_int (const char *str, int maxlength)
{
uint32_t state = 0;
uint32_t type;
int length = 0;
int offset;
for (offset = 0; str[offset] && offset < maxlength; offset++)
{
type = utf8d[(uint8_t)str[offset]];
state = utf8d[256 + state * 16 + type];
/* A valid codepoint was found, update length */
if (state == 0)
length = offset + 1;
}
return length;
} /* End of utf8length_int() */
/**********************************************************************/ /**
* @brief Copy string, removing spaces, always terminated
*
* Copy up to \a length bytes of UTF-8 characters from \a source to \a
* dest while removing all spaces. The result is left justified and
* always null terminated.
*
* The destination string must have enough room needed for the
* non-space characters within \a length and the null terminator, a
* maximum of \a length + 1.
*
* @param[out] dest Destination for terminated string
* @param[in] source Source string
* @param[in] length Length of characters for destination string in bytes
*
* @returns the number of characters (not including the null terminator) in
* the destination string
***************************************************************************/
int
ms_strncpclean (char *dest, const char *source, int length)
{
int sidx;
int didx;
if (!dest)
return 0;
if (!source)
{
*dest = '\0';
return 0;
}
length = utf8length_int (source, length);
for (sidx = 0, didx = 0; sidx < length; sidx++)
{
if (*(source + sidx) == '\0')
{
break;
}
if (*(source + sidx) != ' ')
{
*(dest + didx) = *(source + sidx);
didx++;
}
}
*(dest + didx) = '\0';
return didx;
} /* End of ms_strncpclean() */
/**********************************************************************/ /**
* @brief Copy string, removing trailing spaces, always terminated
*
* Copy up to \a length bytes of UTF-8 characters from \a source to \a
* dest without any trailing spaces. The result is left justified and
* always null terminated.
*
* The destination string must have enough room needed for the
* characters within \a length and the null terminator, a maximum of
* \a length + 1.
*
* @param[out] dest Destination for terminated string
* @param[in] source Source string
* @param[in] length Length of characters for destination string in bytes
*
* @returns The number of characters (not including the null terminator) in
* the destination string
***************************************************************************/
int
ms_strncpcleantail (char *dest, const char *source, int length)
{
int idx;
int pretail;
if (!dest)
return 0;
if (!source)
{
*dest = '\0';
return 0;
}
length = utf8length_int (source, length);
*(dest + length) = '\0';
pretail = 0;
for (idx = length - 1; idx >= 0; idx--)
{
if (!pretail && *(source + idx) == ' ')
{
*(dest + idx) = '\0';
}
else
{
pretail++;
*(dest + idx) = *(source + idx);
}
}
return pretail;
} /* End of ms_strncpcleantail() */
/**********************************************************************/ /**
* @brief Copy fixed number of characters into unterminated string
*
* Copy \a length bytes of UTF-8 characters from \a source to \a dest,
* padding the right side with spaces and leave open-ended, aka
* un-terminated. The result is left justified and \e never null
* terminated.
*
* The destination string must have enough room for \a length characters.
*
* @param[out] dest Destination for unterminated string
* @param[in] source Source string
* @param[in] length Length of characters for destination string in bytes
*
* @returns the number of characters copied from the source string
***************************************************************************/
int
ms_strncpopen (char *dest, const char *source, int length)
{
int didx;
int dcnt = 0;
int utf8max;
if (!dest)
return 0;
if (!source)
{
for (didx = 0; didx < length; didx++)
{
*(dest + didx) = ' ';
}
return 0;
}
utf8max = utf8length_int (source, length);
for (didx = 0; didx < length; didx++)
{
if (didx < utf8max)
{
*(dest + didx) = *(source + didx);
dcnt++;
}
else
{
*(dest + didx) = ' ';
}
}
return dcnt;
} /* End of ms_strncpopen() */
/**********************************************************************/ /**
* @brief Compute the month and day-of-month from a year and day-of-year
*
* @param[in] year Year in range 1000-2262
* @param[in] yday Day of year in range 1-365 or 366 for leap year
* @param[out] month Month in range 1-12
* @param[out] mday Day of month in range 1-31
*
* @retval 0 on success
* @retval -1 on error
*
* \ref MessageOnError - this function logs a message on error
***************************************************************************/
int
ms_doy2md (int year, int yday, int *month, int *mday)
{
int idx;
const int(*days)[12];
if (!month || !mday)
{
ms_log (2, "Required argument not defined: 'month' or 'mday'\n");
return -1;
}
if (!VALIDYEAR (year))
{
ms_log (2, "year (%d) is out of range\n", year);
return -1;
}
if (!VALIDYEARDAY (year, yday))
{
ms_log (2, "day-of-year (%d) is out of range for year %d\n", yday, year);
return -1;
}
days = (LEAPYEAR (year)) ? &monthdays_leap : &monthdays;
for (idx = 0; idx < 12; idx++)
{
yday -= (*days)[idx];
if (yday <= 0)
{
*month = idx + 1;
*mday = (*days)[idx] + yday;
break;
}
}
return 0;
} /* End of ms_doy2md() */
/**********************************************************************/ /**
* @brief Compute the day-of-year from a year, month and day-of-month
*
* @param[in] year Year in range 1000-2262
* @param[in] month Month in range 1-12
* @param[in] mday Day of month in range 1-31 (or appropriate last day)
* @param[out] yday Day of year in range 1-366 or 366 for leap year
*
* @retval 0 on success
* @retval -1 on error
*
* \ref MessageOnError - this function logs a message on error
***************************************************************************/
int
ms_md2doy (int year, int month, int mday, int *yday)
{
int idx;
const int(*days)[12];
if (!yday)
{
ms_log (2, "Required argument not defined: 'yday'\n");
return -1;
}
if (!VALIDYEAR (year))
{
ms_log (2, "year (%d) is out of range\n", year);
return -1;
}
if (!VALIDMONTH (month))
{
ms_log (2, "month (%d) is out of range\n", month);
return -1;
}
if (!VALIDMONTHDAY (year, month, mday))
{
ms_log (2, "day-of-month (%d) is out of range for year %d and month %d\n", mday, year, month);
return -1;
}
days = (LEAPYEAR (year)) ? &monthdays_leap : &monthdays;
*yday = 0;
month--;
for (idx = 0; idx < 12; idx++)
{
if (idx == month)
{
*yday += mday;
break;
}
*yday += (*days)[idx];
}
return 0;
} /* End of ms_md2doy() */
/**********************************************************************/ /**
* @brief Convert an ::nstime_t to individual date-time components
*
* Each of the destination date-time are optional, pass NULL if the
* value is not desired.
*
* @param[in] nstime Time value to convert
* @param[out] year Year with century, like 2018
* @param[out] yday Day of year, 1 - 366
* @param[out] hour Hour, 0 - 23
* @param[out] min Minute, 0 - 59
* @param[out] sec Second, 0 - 60, where 60 is a leap second
* @param[out] nsec Nanoseconds, 0 - 999999999
*
* @retval 0 on success
* @retval -1 on error
***************************************************************************/
int
ms_nstime2time (nstime_t nstime, uint16_t *year, uint16_t *yday,
uint8_t *hour, uint8_t *min, uint8_t *sec, uint32_t *nsec)
{
struct tm tms;
int64_t isec;
int32_t ifract;
/* Reduce to Unix/POSIX epoch time and fractional seconds */
isec = MS_NSTIME2EPOCH (nstime);
ifract = (int)(nstime - (isec * NSTMODULUS));
/* Adjust for negative epoch times */
if (nstime < 0 && ifract != 0)
{
isec -= 1;
ifract = NSTMODULUS - (-ifract);
}
if (year || yday || hour || min || sec)
if (!(ms_gmtime64_r (&isec, &tms)))
return -1;
if (year)
*year = tms.tm_year + 1900;
if (yday)
*yday = tms.tm_yday + 1;
if (hour)
*hour = tms.tm_hour;
if (min)
*min = tms.tm_min;
if (sec)
*sec = tms.tm_sec;
if (nsec)
*nsec = ifract;
return 0;
} /* End of ms_nstime2time() */
/**********************************************************************/ /**
* @brief Convert an ::nstime_t to a time string
*
* Create a time string representation of a high precision epoch time
* in ISO 8601 and SEED formats.
*
* The provided \a timestr buffer must have enough room for the
* resulting time string, a maximum of 36 characters + terminating NULL.
*
* The \a subseconds flag controls whether the subsecond portion of
* the time is included or not. The value of \a subseconds is ignored
* when the \a format is \c NANOSECONDEPOCH. When non-zero subseconds
* are "trimmed" using these flags there is no rounding, instead it is
* simple truncation.
*
* @param[in] nstime Time value to convert
* @param[out] timestr Buffer for ISO time string
* @param timeformat Time string format, one of @ref ms_timeformat_t
* @param subseconds Inclusion of subseconds, one of @ref ms_subseconds_t
*
* @returns Pointer to the resulting string or NULL on error.
*
* \ref MessageOnError - this function logs a message on error
***************************************************************************/
char *
ms_nstime2timestr (nstime_t nstime, char *timestr,
ms_timeformat_t timeformat, ms_subseconds_t subseconds)
{
struct tm tms = {0};
int64_t rawisec;
int rawnanosec;
int64_t isec;
int nanosec;
int microsec;
int submicro;
int printed = 0;
int expected = 0;
if (!timestr)
{
ms_log (2, "Required argument not defined: 'timestr'\n");
return NULL;
}
/* Reduce to Unix/POSIX epoch time and fractional nanoseconds */
isec = rawisec = MS_NSTIME2EPOCH (nstime);
nanosec = rawnanosec = (int)(nstime - (isec * NSTMODULUS));
/* Adjust for negative epoch times */
if (nstime < 0 && nanosec != 0)
{
isec -= 1;
nanosec = NSTMODULUS - (-nanosec);
rawnanosec *= -1;
}
/* Determine microsecond and sub-microsecond values */
microsec = nanosec / 1000;
submicro = nanosec - (microsec * 1000);
/* Calculate date-time parts if needed by format */
if (timeformat == ISOMONTHDAY || timeformat == ISOMONTHDAY_Z ||
timeformat == ISOMONTHDAY_DOY || timeformat == ISOMONTHDAY_DOY_Z ||
timeformat == ISOMONTHDAY_SPACE || timeformat == ISOMONTHDAY_SPACE_Z ||
timeformat == SEEDORDINAL)
{
if (!(ms_gmtime64_r (&isec, &tms)))
{
ms_log (2, "Error converting epoch-time of (%" PRId64 ") to date-time components\n", isec);
return NULL;
}
}
/* Print no subseconds */
if (subseconds == NONE ||
(subseconds == MICRO_NONE && microsec == 0) ||
(subseconds == NANO_NONE && nanosec == 0) ||
(subseconds == NANO_MICRO_NONE && nanosec == 0))
{
switch (timeformat)
{
case ISOMONTHDAY:
case ISOMONTHDAY_Z:
case ISOMONTHDAY_SPACE:
case ISOMONTHDAY_SPACE_Z:
expected = (timeformat == ISOMONTHDAY_Z || timeformat == ISOMONTHDAY_SPACE_Z) ? 20 : 19;
printed = snprintf (timestr, expected + 1, "%4d-%02d-%02d%c%02d:%02d:%02d%s",
tms.tm_year + 1900, tms.tm_mon + 1, tms.tm_mday,
(timeformat == ISOMONTHDAY_SPACE || timeformat == ISOMONTHDAY_SPACE_Z) ? ' ' : 'T',
tms.tm_hour, tms.tm_min, tms.tm_sec,
(timeformat == ISOMONTHDAY_Z || timeformat == ISOMONTHDAY_SPACE_Z) ? "Z" : "");
break;
case ISOMONTHDAY_DOY:
case ISOMONTHDAY_DOY_Z:
expected = (timeformat == ISOMONTHDAY_DOY_Z) ? 26 : 25;
printed = snprintf (timestr, expected + 1, "%4d-%02d-%02dT%02d:%02d:%02d%s (%03d)",
tms.tm_year + 1900, tms.tm_mon + 1, tms.tm_mday,
tms.tm_hour, tms.tm_min, tms.tm_sec,
(timeformat == ISOMONTHDAY_DOY_Z) ? "Z" : "",
tms.tm_yday + 1);
break;
case SEEDORDINAL:
expected = 17;
printed = snprintf (timestr, expected + 1, "%4d,%03d,%02d:%02d:%02d",
tms.tm_year + 1900, tms.tm_yday + 1,
tms.tm_hour, tms.tm_min, tms.tm_sec);
break;
case UNIXEPOCH:
expected = -1;
printed = snprintf (timestr, 22, "%" PRId64, rawisec);
break;
case NANOSECONDEPOCH:
expected = -1;
printed = snprintf (timestr, 22, "%" PRId64, nstime);
break;
}
}
/* Print microseconds */
else if (subseconds == MICRO ||
(subseconds == MICRO_NONE && microsec) ||
(subseconds == NANO_MICRO && submicro == 0) ||
(subseconds == NANO_MICRO_NONE && submicro == 0))
{
switch (timeformat)
{
case ISOMONTHDAY:
case ISOMONTHDAY_Z:
case ISOMONTHDAY_SPACE:
case ISOMONTHDAY_SPACE_Z:
expected = (timeformat == ISOMONTHDAY_Z || timeformat == ISOMONTHDAY_SPACE_Z) ? 27 : 26;
printed = snprintf (timestr, expected + 1, "%4d-%02d-%02d%c%02d:%02d:%02d.%06d%s",
tms.tm_year + 1900, tms.tm_mon + 1, tms.tm_mday,
(timeformat == ISOMONTHDAY_SPACE || timeformat == ISOMONTHDAY_SPACE_Z) ? ' ' : 'T',
tms.tm_hour, tms.tm_min, tms.tm_sec, microsec,
(timeformat == ISOMONTHDAY_Z || timeformat == ISOMONTHDAY_SPACE_Z) ? "Z" : "");
break;
case ISOMONTHDAY_DOY:
case ISOMONTHDAY_DOY_Z:
expected = (timeformat == ISOMONTHDAY_DOY_Z) ? 33 : 32;
printed = snprintf (timestr, expected + 1, "%4d-%02d-%02dT%02d:%02d:%02d.%06d%s (%03d)",
tms.tm_year + 1900, tms.tm_mon + 1, tms.tm_mday,
tms.tm_hour, tms.tm_min, tms.tm_sec, microsec,
(timeformat == ISOMONTHDAY_DOY_Z) ? "Z" : "",
tms.tm_yday + 1);
break;
case SEEDORDINAL:
expected = 24;
printed = snprintf (timestr, expected + 1, "%4d,%03d,%02d:%02d:%02d.%06d",
tms.tm_year + 1900, tms.tm_yday + 1,
tms.tm_hour, tms.tm_min, tms.tm_sec, microsec);
break;
case UNIXEPOCH:
expected = -1;
printed = snprintf (timestr, 22, "%" PRId64 ".%06d", rawisec, rawnanosec / 1000);
break;
case NANOSECONDEPOCH:
expected = -1;
printed = snprintf (timestr, 22, "%" PRId64, nstime);
break;
}
}
/* Print nanoseconds */
else if (subseconds == NANO ||
(subseconds == NANO_NONE && nanosec) ||
(subseconds == NANO_MICRO && submicro) ||
(subseconds == NANO_MICRO_NONE && submicro))
{
switch (timeformat)
{
case ISOMONTHDAY:
case ISOMONTHDAY_Z:
case ISOMONTHDAY_SPACE:
case ISOMONTHDAY_SPACE_Z:
expected = (timeformat == ISOMONTHDAY_Z || timeformat == ISOMONTHDAY_SPACE_Z) ? 30 : 29;
printed = snprintf (timestr, expected + 1, "%4d-%02d-%02d%c%02d:%02d:%02d.%09d%s",
tms.tm_year + 1900, tms.tm_mon + 1, tms.tm_mday,
(timeformat == ISOMONTHDAY_SPACE || timeformat == ISOMONTHDAY_SPACE_Z) ? ' ' : 'T',
tms.tm_hour, tms.tm_min, tms.tm_sec, nanosec,
(timeformat == ISOMONTHDAY_Z || timeformat == ISOMONTHDAY_SPACE_Z) ? "Z" : "");
break;
case ISOMONTHDAY_DOY:
case ISOMONTHDAY_DOY_Z:
expected = (timeformat == ISOMONTHDAY_DOY_Z) ? 36 : 35;
printed = snprintf (timestr, expected + 1, "%4d-%02d-%02dT%02d:%02d:%02d.%09d%s (%03d)",
tms.tm_year + 1900, tms.tm_mon + 1, tms.tm_mday,
tms.tm_hour, tms.tm_min, tms.tm_sec, nanosec,
(timeformat == ISOMONTHDAY_DOY_Z) ? "Z" : "",
tms.tm_yday + 1);
break;
case SEEDORDINAL:
expected = 27;
printed = snprintf (timestr, 28, "%4d,%03d,%02d:%02d:%02d.%09d",
tms.tm_year + 1900, tms.tm_yday + 1,
tms.tm_hour, tms.tm_min, tms.tm_sec, nanosec);
break;
case UNIXEPOCH:
expected = -1;
printed = snprintf (timestr, 22, "%" PRId64 ".%09d", rawisec, rawnanosec);
break;
case NANOSECONDEPOCH:
expected = -1;
printed = snprintf (timestr, 22, "%" PRId64, nstime);
break;
}
}
/* Otherwise this is a unhandled combination of values, timeformat and subseconds */
else
{
ms_log (2, "Unhandled combination of timeformat and subseconds, please report!\n");
ms_log (2, " nstime: %" PRId64 ", isec: %" PRId64 ", nanosec: %d, mirosec: %d, submicro: %d\n",
nstime, isec, nanosec, microsec, submicro);
ms_log (2, " timeformat: %d, subseconds: %d\n", (int)timeformat, (int)subseconds);
return NULL;
}
if (expected == 0 || (expected > 0 && printed != expected))
{
ms_log (2, "Time string not generated with the expected length\n");
return NULL;
}
return timestr;
} /* End of ms_nstime2timestr() */
/**********************************************************************/ /**
* @brief Convert an ::nstime_t to a time string with 'Z' suffix
*
* @deprecated This function should be replaced with desired use of
* timeformat values with the '_Z' designator.
*
* This function is a thin wrapper of @ref ms_nstime2timestr
*
* @param[in] nstime Time value to convert
* @param[out] timestr Buffer for ISO time string
* @param timeformat Time string format, one of @ref ms_timeformat_t
* @param subseconds Inclusion of subseconds, one of @ref ms_subseconds_t
*
* @returns Pointer to the resulting string or NULL on error.
*
* \ref MessageOnError - this function logs a message on error
***************************************************************************/
char *
ms_nstime2timestrz (nstime_t nstime, char *timestr,
ms_timeformat_t timeformat, ms_subseconds_t subseconds)
{
if (timeformat == ISOMONTHDAY)
timeformat = ISOMONTHDAY_Z;
else if (timeformat == ISOMONTHDAY_DOY)
timeformat = ISOMONTHDAY_DOY_Z;
else if (timeformat == ISOMONTHDAY_SPACE)
timeformat = ISOMONTHDAY_SPACE_Z;
return ms_nstime2timestr (nstime, timestr, timeformat, subseconds);
} /* End of ms_nstime2timestrz() */
/***************************************************************************
* INTERNAL Convert specified date-time values to a high precision epoch time.
*
* This is an internal version which does no range checking, it is
* assumed that checking the range for each value has already been
* done.
*
* Returns high-precision epoch time value
***************************************************************************/
static nstime_t
ms_time2nstime_int (int year, int yday, int hour, int min, int sec, uint32_t nsec)
{
nstime_t nstime;
int shortyear;
int a4, a100, a400;
int intervening_leap_days;
int days;
shortyear = year - 1900;
a4 = (shortyear >> 2) + 475 - !(shortyear & 3);
a100 = a4 / 25 - (a4 % 25 < 0);
a400 = a100 >> 2;
intervening_leap_days = (a4 - 492) - (a100 - 19) + (a400 - 4);
days = (365 * (shortyear - 70) + intervening_leap_days + (yday - 1));
nstime = (nstime_t) (60 * (60 * ((nstime_t)24 * days + hour) + min) + sec) * NSTMODULUS + nsec;
return nstime;
} /* End of ms_time2nstime_int() */
/**********************************************************************/ /**
* @brief Convert specified date-time values to a high precision epoch time.
*
* @param[in] year Year with century, like 2018
* @param[in] yday Day of year, 1 - 366
* @param[in] hour Hour, 0 - 23
* @param[in] min Minute, 0 - 59
* @param[in] sec Second, 0 - 60, where 60 is a leap second
* @param[in] nsec Nanoseconds, 0 - 999999999
*
* @returns epoch time on success and ::NSTERROR on error.
*
* \ref MessageOnError - this function logs a message on error
***************************************************************************/
nstime_t
ms_time2nstime (int year, int yday, int hour, int min, int sec, uint32_t nsec)
{
if (!VALIDYEAR (year))
{
ms_log (2, "year (%d) is out of range\n", year);
return NSTERROR;
}
if (!VALIDYEARDAY (year, yday))
{
ms_log (2, "day-of-year (%d) is out of range for year %d\n", yday, year);
return NSTERROR;
}
if (!VALIDHOUR (hour))
{
ms_log (2, "hour (%d) is out of range\n", hour);
return NSTERROR;
}
if (!VALIDMIN (min))
{
ms_log (2, "minute (%d) is out of range\n", min);
return NSTERROR;
}
if (!VALIDSEC (sec))
{
ms_log (2, "second (%d) is out of range\n", sec);
return NSTERROR;
}
if (!VALIDNANOSEC (nsec))
{
ms_log (2, "nanosecond (%u) is out of range\n", nsec);
return NSTERROR;
}
return ms_time2nstime_int (year, yday, hour, min, sec, nsec);
} /* End of ms_time2nstime() */
/**********************************************************************/ /**
* @brief Convert a time string to a high precision epoch time.
*
* Detected time formats:
* -# ISO month-day as \c "YYYY-MM-DD[THH:MM:SS.FFFFFFFFF]"
* -# ISO ordinal as \c "YYYY-DDD[THH:MM:SS.FFFFFFFFF]"
* -# SEED ordinal as \c "YYYY,DDD[,HH,MM,SS,FFFFFFFFF]"
* -# Year as \c "YYYY"
* -# Unix/POSIX epoch time value as \c "[+-]#########.#########"
*
* Following determination of the format, non-epoch value conversion
* is performed by the ms_mdtimestr2nstime() and
* ms_seedtimestr2nstime() routines.
*
* Four-digit values are treated as a year, unless a sign [+-] is
* specified and then they are treated as epoch values. For
* consistency, a caller is recommened to always include a sign with
* epoch values.
*
* Note that this routine does some sanity checking of the time string
* contents, but does _not_ perform robust date-time validation.
*
* @param[in] timestr Time string to convert
*
* @returns epoch time on success and ::NSTERROR on error.
*
* \ref MessageOnError - this function logs a message on error
*
* @see ms_mdtimestr2nstime()
* @see ms_seedtimestr2nstime()
***************************************************************************/
nstime_t
ms_timestr2nstime (const char *timestr)
{
const char *cp;
const char *firstdelimiter = NULL;
const char *separator = NULL;
int delimiters = 0;
int numberlike = 0;
int error = 0;
int length;
int fields;
int64_t sec = 0;
double fsec = 0.0;
nstime_t nstime;
if (!timestr)
{
ms_log (2, "Required argument not defined: 'timestr'\n");
return NSTERROR;
}
/* Determine first delimiter,
* delimiter count before date-time separator,
* number-like character count,
* while checking for allowed characters */
for (cp = timestr; *cp; cp++)
{
if (*cp == '-' || *cp == '/' || *cp == ',' || *cp == ':' || *cp == '.')
{
if (!firstdelimiter)
firstdelimiter = cp;
/* Count delimiters before the first date-time separator */
if (!separator)
delimiters++;
/* If minus (and first character) or period, it is number-like */
if ((*cp == '-' && cp == timestr) || *cp == '.')
numberlike++;
}
/* If plus (and first character) it is number-like */
else if (*cp == '+' && cp == timestr)
{
numberlike++;
}
/* Date-time separator, there can only be one */
else if (!separator && (*cp == 'T' || *cp == ' '))
{
separator = cp;
}
/* Accumulate digits as number-like */
else if (*cp >= '0' && *cp <= '9')
{
numberlike++;
}
/* Done if at trailing 'Z' designator */
else if ((*cp == 'Z' || *cp == 'z') && *(cp+1) == '\0')
{
cp++;
break;
}
/* Anything else is an error */
else
{
cp++;
error = 1;
break;
}
}
length = cp - timestr;
/* If the time string is all number-like characters assume it is an epoch time.
* Unless it is 4 characters, which could be a year, unless it starts with a sign. */
if (!error && length == numberlike &&
(length != 4 || (length == 4 && (timestr[0] == '-' || timestr[0] == '+'))))
{
fields = sscanf (timestr, "%" SCNd64 "%lf", &sec, &fsec);
if (fields < 1)
{
ms_log (2, "Could not convert epoch value: '%s'\n", cp);
return NSTERROR;
}
/* Convert seconds and fractional seconds to nanoseconds, return combination */
nstime = MS_EPOCH2NSTIME (sec);
if (fsec != 0.0)
{
if (nstime >= 0)
nstime += (int)(fsec * 1000000000.0 + 0.5);
else
nstime -= (int)(fsec * 1000000000.0 + 0.5);
}
return nstime;
}
/* Otherwise, a non-epoch value time string */
else if (!error && length >= 4 && length <= 32)
{
if (firstdelimiter)
{
/* ISO month-day "YYYY-MM-DD[THH:MM:SS.FFFFFFFFF]", allow for a colloquial forward-slash flavor */
if ((*firstdelimiter == '-' || *firstdelimiter == '/') && delimiters == 2)
{
return ms_mdtimestr2nstime (timestr);
}
/* ISO ordinal "YYYY-DDD[THH:MM:SS.FFFFFFFFF]" */
else if (*firstdelimiter == '-' && delimiters == 1)
{
return ms_seedtimestr2nstime (timestr);
}
/* SEED ordinal "YYYY,DDD[,HH,MM,SS,FFFFFFFFF]" */
else if (*firstdelimiter == ',')
{
return ms_seedtimestr2nstime (timestr);
}
}
/* 4-digit year "YYYY" */
else if (length == 4 && !separator)
{
return ms_seedtimestr2nstime (timestr);
}
}
ms_log (2, "Unrecognized time string: '%s'\n", timestr);
return NSTERROR;
} /* End of ms_timestr2nstime() */
/**********************************************************************/ /**
* @brief Convert a time string (year-month-day) to a high precision
* epoch time.
*
* The time format expected is "YYYY[-MM-DD HH:MM:SS.FFFFFFFFF]", the
* delimiter can be a dash [-], comma[,], slash [/], colon [:], or
* period [.]. Additionally a 'T' or space may be used between the
* date and time fields. The fractional seconds ("FFFFFFFFF") must
* begin with a period [.] if present.
*
* The time string can be "short" in which case the omitted values are
* assumed to be zero (with the exception of month and day which are
* assumed to be 1). For example, specifying "YYYY-MM-DD" assumes HH,
* MM, SS and FFFF are 0. The year is required, otherwise there
* wouldn't be much for a date.
*
* @param[in] timestr Time string in ISO-style, month-day format
*
* @returns epoch time on success and ::NSTERROR on error.
*
* \ref MessageOnError - this function logs a message on error
***************************************************************************/
nstime_t
ms_mdtimestr2nstime (const char *timestr)
{
int fields;
int year = 0;
int mon = 1;
int mday = 1;
int yday = 1;
int hour = 0;
int min = 0;
int sec = 0;
double fsec = 0.0;
int nsec = 0;
if (!timestr)
{
ms_log (2, "Required argument not defined: 'timestr'\n");
return NSTERROR;
}
fields = sscanf (timestr, "%d%*[-,/:.]%d%*[-,/:.]%d%*[-,/:.Tt ]%d%*[-,/:.]%d%*[-,/:.]%d%lf",
&year, &mon, &mday, &hour, &min, &sec, &fsec);
/* Convert fractional seconds to nanoseconds */
if (fsec != 0.0)
{
nsec = (int)(fsec * 1000000000.0 + 0.5);
}
if (fields < 1)
{
ms_log (2, "Cannot parse time string: %s\n", timestr);
return NSTERROR;
}
if (!VALIDYEAR (year))
{
ms_log (2, "year (%d) is out of range\n", year);
return NSTERROR;
}
if (!VALIDMONTH (mon))
{
ms_log (2, "month (%d) is out of range\n", mon);
return NSTERROR;
}
if (!VALIDMONTHDAY (year, mon, mday))
{
ms_log (2, "day-of-month (%d) is out of range for year %d and month %d\n", mday, year, mon);
return NSTERROR;
}
if (!VALIDHOUR (hour))
{
ms_log (2, "hour (%d) is out of range\n", hour);
return NSTERROR;
}
if (!VALIDMIN (min))
{
ms_log (2, "minute (%d) is out of range\n", min);
return NSTERROR;
}
if (!VALIDSEC (sec))
{
ms_log (2, "second (%d) is out of range\n", sec);
return NSTERROR;
}
if (!VALIDNANOSEC (nsec))
{
ms_log (2, "fractional second (%d) is out of range\n", nsec);
return NSTERROR;
}
/* Convert month and day-of-month to day-of-year */
if (ms_md2doy (year, mon, mday, &yday))
{
return NSTERROR;
}
return ms_time2nstime_int (year, yday, hour, min, sec, nsec);
} /* End of ms_mdtimestr2nstime() */
/**********************************************************************/ /**
* @brief Convert an SEED-style (ordinate date, i.e. day-of-year) time
* string to a high precision epoch time.
*
* The time format expected is "YYYY[,DDD,HH,MM,SS.FFFFFFFFF]", the
* delimiter can be a dash [-], comma [,], colon [:] or period [.].
* Additionally a [T] or space may be used to seprate the day and hour
* fields. The fractional seconds ("FFFFFFFFF") must begin with a
* period [.] if present.
*
* The time string can be "short" in which case the omitted values are
* assumed to be zero (with the exception of DDD which is assumed to
* be 1): "YYYY,DDD,HH" assumes MM, SS and FFFFFFFF are 0. The year
* is required, otherwise there wouldn't be much for a date.
*
* @param[in] seedtimestr Time string in SEED-style, ordinal date format
*
* @returns epoch time on success and ::NSTERROR on error.
*
* \ref MessageOnError - this function logs a message on error
***************************************************************************/
nstime_t
ms_seedtimestr2nstime (const char *seedtimestr)
{
int fields;
int year = 0;
int yday = 1;
int hour = 0;
int min = 0;
int sec = 0;
double fsec = 0.0;
int nsec = 0;
if (!seedtimestr)
{
ms_log (2, "Required argument not defined: 'seedtimestr'\n");
return NSTERROR;
}
fields = sscanf (seedtimestr, "%d%*[-,:.]%d%*[-,:.Tt ]%d%*[-,:.]%d%*[-,:.]%d%lf",
&year, &yday, &hour, &min, &sec, &fsec);
/* Convert fractional seconds to nanoseconds */
if (fsec != 0.0)
{
nsec = (int)(fsec * 1000000000.0 + 0.5);
}
if (fields < 1)
{
ms_log (2, "Cannot parse time string: %s\n", seedtimestr);
return NSTERROR;
}
if (!VALIDYEAR (year))
{
ms_log (2, "year (%d) is out of range\n", year);
return NSTERROR;
}
if (!VALIDYEARDAY (year, yday))
{
ms_log (2, "day-of-year (%d) is out of range for year %d\n", yday, year);
return NSTERROR;
}
if (!VALIDHOUR (hour))
{
ms_log (2, "hour (%d) is out of range\n", hour);
return NSTERROR;
}
if (!VALIDMIN (min))
{
ms_log (2, "minute (%d) is out of range\n", min);
return NSTERROR;
}
if (!VALIDSEC (sec))
{
ms_log (2, "second (%d) is out of range\n", sec);
return NSTERROR;
}
if (!VALIDNANOSEC (nsec))
{
ms_log (2, "fractional second (%d) is out of range\n", nsec);
return NSTERROR;
}
return ms_time2nstime_int (year, yday, hour, min, sec, nsec);
} /* End of ms_seedtimestr2nstime() */
/**********************************************************************/ /**
* @brief Calculate the time of a sample in an array
*
* Given a time, sample offset and sample rate/period calculate the
* time of the sample at the offset.
*
* If \a samprate is negative the negated value is interpreted as a
* sample period in seconds, otherwise the value is assumed to be a
* sample rate in Hertz.
*
* If leap seconds have been loaded into the internal library list:
* when a time span, from start to offset, completely contains a leap
* second, starts before and ends after, the calculated sample time
* will be adjusted (reduced) by one second.
* @note On the epoch time scale the value of a leap second is the
* same as the second following the leap second, without external
* information the values are ambiguous.
* \sa ms_readleapsecondfile()
*
* @param[in] time Time value for first sample in array
* @param[in] offset Offset of sample to calculate time of
* @param[in] samprate Sample rate (when positive) or period (when negative)
*
* @returns Time of the sample at specified offset
***************************************************************************/
nstime_t
ms_sampletime (nstime_t time, int64_t offset, double samprate)
{
nstime_t span = 0;
LeapSecond *lslist = leapsecondlist;
if (offset > 0)
{
/* Calculate time span using sample rate */
if (samprate > 0.0)
span = (nstime_t) (((double)offset / samprate * NSTMODULUS) + 0.5);
/* Calculate time span using sample period */
else if (samprate < 0.0)
span = (nstime_t) (((double)offset * -samprate * NSTMODULUS) + 0.5);
}
/* Check if the time range contains a leap second, if list is available */
if (lslist)
{
while (lslist)
{
if (lslist->leapsecond > time &&
lslist->leapsecond <= (time + span - NSTMODULUS))
{
span -= NSTMODULUS;
break;
}
lslist = lslist->next;
}
}
return (time + span);
} /* End of ms_sampletime() */
/**********************************************************************/ /**
* @brief Determine the absolute value of an input double
*
* Actually just test if the input double is positive multiplying by
* -1.0 if not and return it.
*
* @param[in] val Value for which to determine absolute value
*
* @returns the positive value of input double.
***************************************************************************/
double
ms_dabs (double val)
{
if (val < 0.0)
val *= -1.0;
return val;
} /* End of ms_dabs() */
/**********************************************************************/ /**
* @brief Runtime test for host endianess
* @returns 0 if the host is little endian, otherwise 1.
***************************************************************************/
inline int
ms_bigendianhost (void)
{
const uint16_t endian = 256;
return *(const uint8_t *)&endian;
} /* End of ms_bigendianhost() */
/**********************************************************************/ /**
* @brief Read leap second file specified by an environment variable
*
* Leap seconds are loaded into the library's global leapsecond list.
*
* @param[in] envvarname Environment variable that identifies the leap second file
*
* @returns positive number of leap seconds read
* @retval -1 on file read error
* @retval -2 when the environment variable is not set
*
* \ref MessageOnError - this function logs a message on error
***************************************************************************/
int
ms_readleapseconds (const char *envvarname)
{
char *filename;
if ((filename = getenv (envvarname)))
{
return ms_readleapsecondfile (filename);
}
return -2;
} /* End of ms_readleapseconds() */
/**********************************************************************/ /**
* @brief Read leap second from the specified file
*
* Leap seconds are loaded into the library's global leapsecond list.
*
* The file is expected to be standard IETF leap second list format.
* The list is usually available from:
* https://www.ietf.org/timezones/data/leap-seconds.list
*
* @param[in] filename File containing leap second list
*
* @returns positive number of leap seconds read on success
* @retval -1 on error
*
* \ref MessageOnError - this function logs a message on error
***************************************************************************/
int
ms_readleapsecondfile (const char *filename)
{
FILE *fp = NULL;
LeapSecond *ls = NULL;
LeapSecond *lastls = NULL;
int64_t expires;
char readline[200];
char *cp;
int64_t leapsecond;
int TAIdelta;
int fields;
int count = 0;
if (!filename)
{
ms_log (2, "Required argument not defined: 'filename'\n");
return -1;
}
if (!(fp = fopen (filename, "rb")))
{
ms_log (2, "Cannot open leap second file %s: %s\n", filename, strerror (errno));
return -1;
}
/* Detatch embedded leap second list */
if (leapsecondlist == &embedded_leapsecondlist[0])
{
leapsecondlist = NULL;
}
/* Free existing leapsecondlist */
while (leapsecondlist != NULL)
{
LeapSecond *next = leapsecondlist->next;
libmseed_memory.free (leapsecondlist);
leapsecondlist = next;
}
while (fgets (readline, sizeof (readline) - 1, fp))
{
/* Guarantee termination */
readline[sizeof (readline) - 1] = '\0';
/* Terminate string at first newline character if any */
if ((cp = strchr (readline, '\n')))
*cp = '\0';
/* Skip empty lines */
if (!strlen (readline))
continue;
/* Check for and parse expiration date */
if (!strncmp (readline, "#@", 2))
{
expires = 0;
fields = sscanf (readline, "#@ %" SCNd64, &expires);
if (fields == 1)
{
/* Convert expires to Unix epoch */
expires = expires - NTPPOSIXEPOCHDELTA;
/* Compare expire time to current time */
if (time (NULL) > expires)
{
char timestr[100];
ms_nstime2timestr (MS_EPOCH2NSTIME (expires), timestr, 0, 0);
ms_log (1, "Warning: leap second file (%s) has expired as of %s\n",
filename, timestr);
}
}
continue;
}
/* Skip comment lines */
if (*readline == '#')
continue;
fields = sscanf (readline, "%" SCNd64 " %d ", &leapsecond, &TAIdelta);
if (fields == 2)
{
if ((ls = (LeapSecond *)libmseed_memory.malloc (sizeof (LeapSecond))) == NULL)
{
ms_log (2, "Cannot allocate LeapSecond entry, out of memory?\n");
return -1;
}
/* Convert NTP epoch time to Unix epoch time and then to nttime_t */
ls->leapsecond = MS_EPOCH2NSTIME (leapsecond - NTPPOSIXEPOCHDELTA);
ls->TAIdelta = TAIdelta;
ls->next = NULL;
count++;
/* Add leap second to global list */
if (!leapsecondlist)
{
leapsecondlist = ls;
lastls = ls;
}
else
{
lastls->next = ls;
lastls = ls;
}
}
else
{
ms_log (1, "Unrecognized leap second file line: '%s'\n", readline);
}
}
if (ferror (fp))
{
ms_log (2, "Error reading leap second file (%s): %s\n", filename, strerror (errno));
return -1;
}
fclose (fp);
return count;
} /* End of ms_readleapsecondfile() */