osd-contiki/apps/time/time.c
Ralf Schlatterbeck 743245e230 Add 'x' prefix to time functions
.. to avoid name-clashes with (some) libraries. This now also should
make it work for the 'native' target (untested).
2016-03-29 17:48:59 +02:00

847 lines
22 KiB
C

/**
* \addgroup Time related functions
*
* @{
*/
#include "contiki.h"
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "contiki-lib.h"
#include "xtime.h"
#include "tzparse.h"
#define SECSPERMIN 60
#define MINSPERHOUR 60
#define HOURSPERDAY 24
#define DAYSPERWEEK 7
#define DAYSPERNYEAR 365
#define DAYSPERLYEAR 366
#define SECSPERHOUR (SECSPERMIN * MINSPERHOUR)
#define SECSPERDAY ((long) SECSPERHOUR * HOURSPERDAY)
#define MONSPERYEAR 12
static const int mon_lengths[2][MONSPERYEAR] =
{ { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
, { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
};
/*
* Static timezone information
*/
static struct tzoffset_info localtime_tzoffset;
/* Used for gmtime and localtime, according to manpage on linux the
* internal value may be overwritten "by subsequent calls to any of the
* date and time functions".
*/
static struct xtm tm;
/*
* Internal variables to manage offset of utc from the contiki clock
* and timezone offset from utc in minutes.
* For now we don't manage the sub-second offset -- time of setting the
* clock and the precisiont of the clock in use don't warrant this
* effort.
* The last_seconds is used to check if we had a seconds overflow,
* although this happens only every 136 years :-)
*/
static xtime_t clock_offset;
static uint32_t last_seconds;
static xtime_t
transtime (xtime_t janfirst, int year, const struct tzrule *rp, long offset);
# define LEAP_YEAR(_year) \
((_year % 4) == 0 && (_year % 100 != 0 || _year % 400 == 0))
# define YDAYS(_year) (LEAP_YEAR(year) ? 366 : 365)
struct xtm *xgmtime_r (const xtime_t *timep, struct xtm *ptm)
{
unsigned int year;
int days, month, month_len;
xtime_t t = *timep;
ptm->tm_sec = t % 60;
t /= 60;
ptm->tm_min = t % 60;
t /= 60;
ptm->tm_hour = t % 24;
t /= 24;
ptm->tm_wday = (t+4) % 7;
year = 1970;
days = 0;
while ((days += YDAYS (year)) <= t)
{
year++;
}
ptm->tm_year = year - 1900;
days -= YDAYS (year);
t -= days;
ptm->tm_yday = t;
for (month=0; month<12; month++)
{
if (month == 1)
{
month_len = LEAP_YEAR (year) ? 29 : 28;
}
else
{
int m = month;
if (m >= 7)
{
m -= 1;
}
m &= 1;
month_len = m ? 30 : 31;
}
if (t >= month_len)
{
t -= month_len;
}
else
{
break;
}
}
ptm->tm_mon = month;
ptm->tm_mday = t + 1;
ptm->tm_isdst = 0;
ptm->tm_gmtoff = 0;
ptm->tm_zone = "UTC";
return ptm;
}
struct xtm *xgmtime (const xtime_t *timep)
{
return xgmtime_r (timep, &tm);
}
/*
* Compute is_dst flag of given timestamp
*/
static int is_dst (const xtime_t *timep, const struct tzoffset_info *tzo)
{
xtime_t janfirst = 0;
xtime_t starttime, endtime;
int year = 1970;
int lastdst = 0;
if (tzo->dstname == NULL) {
return 0;
}
for (year = 1970; janfirst < *timep; year++) {
starttime = transtime (janfirst, year, &tzo->start, tzo->stdoffset);
endtime = transtime (janfirst, year, &tzo->end, tzo->dstoffset);
if (starttime <= *timep && endtime <= *timep) {
lastdst = (starttime > endtime);
} else if (starttime > *timep && endtime <= *timep) {
return 0;
} else if (starttime <= *timep && endtime > *timep) {
return 1;
} else if (starttime > *timep && endtime > *timep) {
return lastdst;
}
janfirst += YDAYS (year) * SECSPERDAY;
}
return lastdst;
}
struct xtm *xlocaltime_r (const xtime_t *timep, struct xtm *ptm)
{
const struct tzoffset_info *tzo = &localtime_tzoffset;
int isdst = 0;
long offset = 0;
xtime_t t = *timep;
if (tzo->stdname == NULL) {
set_tz (DEFAULT_TIMEZONE);
}
isdst = is_dst (timep, tzo);
offset = isdst ? tzo->dstoffset : tzo->stdoffset;
t -= offset;
xgmtime_r (&t, ptm);
ptm->tm_isdst = isdst;
ptm->tm_gmtoff = -offset;
ptm->tm_zone = isdst ? tzo->dstname : tzo->stdname;
return ptm;
}
struct xtm *xlocaltime (const xtime_t *timep)
{
return xlocaltime_r (timep, &tm);
}
/**
* \brief Get time in seconds and microseconds
* xgettimeofday will return the clock time as the microseconds part
* while xsettimeofday will *ignore* the microseconds part (for now).
* Note that the contiki clock interface is broken anyway, we can't read
* seconds and sub-seconds atomically. We try to work around this by
* repeatedly reading seconds, sub-seconds, seconds until first and
* second read of seconds match.
*/
int xgettimeofday (struct xtimeval *tv, struct timezone *tz)
{
uint32_t cs;
if (tv) {
int i;
/* Limit tries to get the same second twice to two */
for (i=0; i<2; i++) {
cs = clock_seconds ();
if (cs < last_seconds) {
clock_offset += 0xFFFFFFFFL;
clock_offset ++;
}
last_seconds = cs;
tv->tv_sec = cs + clock_offset;
tv->tv_usec = ((xtime_t)(clock_time () % CLOCK_SECOND))
* 1000000L / CLOCK_SECOND;
if (cs == clock_seconds ()) {
break;
}
}
}
if (tz) {
const struct tzoffset_info *tzo = &localtime_tzoffset;
tz->tz_dsttime = is_dst (&tv->tv_sec, tzo);
if (tz->tz_dsttime) {
tz->tz_minuteswest = -tzo->dstoffset / 60;
} else {
tz->tz_minuteswest = -tzo->stdoffset / 60;
}
}
return 0;
}
/**
* \brief Set time in seconds, microseconds ignored for now
*/
int xsettimeofday (const struct xtimeval *tv, const struct timezone *tz)
{
/* Don't allow setting timezone */
if (tz) {
errno = ERANGE;
return -1;
}
if (tv) {
uint32_t cs;
cs = clock_seconds ();
clock_offset = tv->tv_sec - cs;
}
return 0;
}
/*
* Save timezone names into reserved string buffer and fill in the names
* into the given tzoffset_info.
* Return -1 on error, 0 for success.
*/
static int save_tznames
( const char *stdname, const char *dstname
, size_t stdlen, size_t dstlen
, struct tzoffset_info *tzo
)
{
size_t len = stdlen;
if (stdname == NULL) {
return -1;
}
if (dstname != NULL) {
len += dstlen;
}
if (len + 2 > sizeof (tzo->namebuf)) {
return -1;
}
tzo->stdname = tzo->namebuf;
strncpy (tzo->namebuf, stdname, stdlen);
tzo->namebuf [stdlen] = '\0';
if (dstlen) {
strncpy (tzo->namebuf + stdlen + 1, dstname, dstlen);
tzo->namebuf [stdlen + 1 + dstlen] = '\0';
tzo->dstname = tzo->namebuf + stdlen + 1;
} else {
tzo->dstname = NULL;
}
return 0;
}
/*
* Utility functions for timezone string parsing (POSIX section 8)
* Code adapted from OpenBSD localtime.c 1.57 2015/12/12 21:25:44
* which is in the public domain.
*/
/*
* The DST rules to use if TZ has no rules:
* We default to US rules as of 1999-08-17.
* POSIX 1003.1 section 8.1.1 says that the default DST rules are
* implementation dependent; for historical reasons, US rules are a
* common default.
*/
#ifndef TZDEFRULESTRING
#define TZDEFRULESTRING ",M4.1.0,M10.5.0"
#endif
/*
* Given the Epoch-relative time of January 1, 00:00:00 UTC, in a year, the
* year, a rule, and the offset from UTC at the time that rule takes effect,
* calculate the Epoch-relative time that rule takes effect.
*/
static xtime_t
transtime(xtime_t janfirst, int year, const struct tzrule *rulep, long offset)
{
int leapyear;
xtime_t value;
int i;
int d, m1, yy0, yy1, yy2, dow;
value = 0;
leapyear = LEAP_YEAR (year);
switch (rulep->r_type) {
case JULIAN_DAY:
/*
* Jn - Julian day, 1 == January 1, 60 == March 1 even in leap
* years.
* In non-leap years, or if the day number is 59 or less, just
* add SECSPERDAY times the day number-1 to the time of
* January 1, midnight, to get the day.
*/
value = janfirst + (rulep->r_day - 1) * SECSPERDAY;
if (leapyear && rulep->r_day >= 60) {
value += SECSPERDAY;
}
break;
case DAY_OF_YEAR:
/*
* n - day of year.
* Just add SECSPERDAY times the day number to the time of
* January 1, midnight, to get the day.
*/
value = janfirst + rulep->r_day * SECSPERDAY;
break;
case MONTH_NTH_DAY_OF_WEEK:
/*
* Mm.n.d - nth "dth day" of month m.
*/
value = janfirst;
for (i = 0; i < rulep->r_mon - 1; ++i) {
value += mon_lengths [leapyear][i] * SECSPERDAY;
}
/*
** Use Zeller's Congruence to get day-of-week of first day of
** month.
*/
m1 = (rulep->r_mon + 9) % 12 + 1;
yy0 = (rulep->r_mon <= 2) ? (year - 1) : year;
yy1 = yy0 / 100;
yy2 = yy0 % 100;
dow = ((26 * m1 - 2) / 10 + 1 + yy2 + yy2 / 4 + yy1 / 4 - 2 * yy1) % 7;
if (dow < 0) {
dow += DAYSPERWEEK;
}
/*
** "dow" is the day-of-week of the first day of the month. Get
** the day-of-month (zero-origin) of the first "dow" day of the
** month.
*/
d = rulep->r_day - dow;
if (d < 0) {
d += DAYSPERWEEK;
}
for (i = 1; i < rulep->r_week; ++i) {
if (d + DAYSPERWEEK >= mon_lengths[leapyear][rulep->r_mon - 1]) {
break;
}
d += DAYSPERWEEK;
}
/*
** "d" is the day-of-month (zero-origin) of the day we want.
*/
value += d * SECSPERDAY;
break;
}
/*
** "value" is the Epoch-relative time of 00:00:00 UTC on the day in
** question. To get the Epoch-relative time of the specified local
** time on that day, add the transition time and the current offset
** from UTC.
*/
return value + rulep->r_time + offset;
}
/*
* Given a pointer into a time zone string, scan until a character that is not
* a valid character in a zone name is found. Return a pointer to that
* character.
*/
static const char *getzname (const char *s)
{
char c;
while ((c = *s) != '\0' && !isdigit(c) && c != ',' && c != '-' && c != '+'){
++s;
}
return s;
}
/*
* Given a pointer into an extended time zone string, scan until the ending
* delimiter of the zone name is located. Return a pointer to the delimiter.
*
* As with getzname above, the legal character set is actually quite
* restricted, with other characters producing undefined results.
* We don't do any checking here; checking is done later in common-case code.
*/
static const char *getqzname (const char *strp, const int delim)
{
int c;
while ((c = *strp) != '\0' && c != delim) {
++strp;
}
return strp;
}
/*
* Given a pointer into a time zone string, extract a number from that string.
* Check that the number is within a specified range; if it is not, return
* NULL.
* Otherwise, return a pointer to the first character not part of the number.
*/
static const char *getnum (const char *strp, int *nump, int min, int max)
{
char c;
int num;
if (strp == NULL || !isdigit ((c = *strp))) {
return NULL;
}
num = 0;
do {
num = num * 10 + (c - '0');
if (num > max) {
return NULL; /* illegal value */
}
c = *++strp;
} while (isdigit (c));
if (num < min) {
return NULL; /* illegal value */
}
*nump = num;
return strp;
}
/*
* Given a pointer into a time zone string, extract a number of seconds,
* in hh[:mm[:ss]] form, from the string.
* If any error occurs, return NULL.
* Otherwise, return a pointer to the first character not part of the number
* of seconds.
*/
static const char *getsecs (const char *strp, long *secsp)
{
int num;
/*
* `HOURSPERDAY * DAYSPERWEEK - 1' allows quasi-Posix rules like
* "M10.4.6/26", which does not conform to Posix,
* but which specifies the equivalent of
* ``02:00 on the first Sunday on or after 23 Oct''.
*/
strp = getnum (strp, &num, 0, HOURSPERDAY * DAYSPERWEEK - 1);
if (strp == NULL) {
return NULL;
}
*secsp = num * (long) SECSPERHOUR;
if (*strp == ':') {
++strp;
strp = getnum (strp, &num, 0, MINSPERHOUR - 1);
if (strp == NULL) {
return NULL;
}
*secsp += num * SECSPERMIN;
if (*strp == ':') {
++strp;
/* `SECSPERMIN' allows for leap seconds. */
strp = getnum (strp, &num, 0, SECSPERMIN);
if (strp == NULL) {
return NULL;
}
*secsp += num;
}
}
return strp;
}
/*
* Given a pointer into a time zone string, extract an offset, in
* [+-]hh[:mm[:ss]] form, from the string.
* If any error occurs, return NULL.
* Otherwise, return a pointer to the first character not part of the time.
*/
static const char *getoffset (const char *strp, long *offsetp)
{
int neg = 0;
if (*strp == '-') {
neg = 1;
++strp;
} else if (*strp == '+') {
++strp;
}
strp = getsecs (strp, offsetp);
if (strp == NULL) {
return NULL; /* illegal time */
}
if (neg) {
*offsetp = -*offsetp;
}
return strp;
}
/*
* Parse (optionally extended) timezone name. Return pointer to
* (undelimited) timezone name in tzn and length of same in len.
* Return pointer to first character *after* name, NULL on error.
* Factored from original tzparse function.
*/
static const char *
egettzname (const char *strp, size_t *len, const char **tzn)
{
*tzn = strp;
if (*strp == '<') {
strp++;
*tzn = strp;
strp = getqzname (strp, '>');
if (*strp != '>') {
return NULL;
}
*len = strp - *tzn;
strp++;
} else {
strp = getzname (strp);
*len = strp - *tzn;
}
return strp;
}
/*
** Given a pointer into a time zone string, extract a rule in the form
** date[/time]. See POSIX section 8 for the format of "date" and "time".
** If a valid rule is not found, return NULL.
** Otherwise, return a pointer to the first character not part of the rule.
*/
static const char *getrule (const char *strp, struct tzrule *rulep)
{
if (*strp == 'J') {
/*
* Julian day.
*/
rulep->r_type = JULIAN_DAY;
++strp;
strp = getnum (strp, &rulep->r_day, 1, DAYSPERNYEAR);
} else if (*strp == 'M') {
/*
* Month, week, day.
*/
rulep->r_type = MONTH_NTH_DAY_OF_WEEK;
++strp;
strp = getnum (strp, &rulep->r_mon, 1, MONSPERYEAR);
if (strp == NULL) {
return NULL;
}
if (*strp++ != '.') {
return NULL;
}
strp = getnum (strp, &rulep->r_week, 1, 5);
if (strp == NULL) {
return NULL;
}
if (*strp++ != '.') {
return NULL;
}
strp = getnum (strp, &rulep->r_day, 0, DAYSPERWEEK - 1);
} else if (isdigit (*strp)) {
/*
* Day of year.
*/
rulep->r_type = DAY_OF_YEAR;
strp = getnum (strp, &rulep->r_day, 0, DAYSPERLYEAR - 1);
} else {
return NULL; /* invalid format */
}
if (strp == NULL) {
return NULL;
}
if (*strp == '/') {
/*
* Time specified.
*/
++strp;
strp = getsecs (strp, &rulep->r_time);
} else {
rulep->r_time = 2 * SECSPERHOUR; /* default = 2:00:00 */
}
return strp;
}
/*
* Parse POSIX section 8 TZ string.
* We keep the misnomer "name" for the timezone string.
*/
int tzparse (const char *name, struct tzoffset_info *tzo)
{
const char *stdname;
const char *dstname;
size_t stdlen;
size_t dstlen;
long stdoffset;
long dstoffset;
dstname = NULL;
stdname = name;
name = egettzname (name, &stdlen, &stdname);
if (name == NULL || *name == '\0') {
return -1;
}
name = getoffset (name, &stdoffset);
if (name == NULL) {
return -1;
}
if (*name != '\0') {
name = egettzname (name, &dstlen, &dstname);
if (name == NULL) {
return -1;
}
if (*name != '\0' && *name != ',' && *name != ';') {
name = getoffset (name, &dstoffset);
if (name == NULL) {
return -1;
}
} else {
dstoffset = stdoffset - SECSPERHOUR;
}
if (*name == '\0') {
name = TZDEFRULESTRING;
}
if (*name == ',' || *name == ';') {
struct tzrule start;
struct tzrule end;
++name;
if ((name = getrule (name, &start)) == NULL) {
return -1;
}
if (*name++ != ',') {
return -1;
}
if ((name = getrule (name, &end)) == NULL) {
return -1;
}
if (*name != '\0') {
return -1;
}
if (save_tznames (stdname, dstname, stdlen, dstlen, tzo) != 0) {
return -1;
}
tzo->start = start;
tzo->end = end;
tzo->stdoffset = stdoffset;
tzo->dstoffset = dstoffset;
} else {
return -1;
}
} else {
/* only standard time, no DST */
if (save_tznames (stdname, NULL, stdlen, 0, tzo) != 0) {
return -1;
}
tzo->stdoffset = stdoffset;
tzo->dstoffset = stdoffset;
}
return 0;
}
/*
* Provide a single static timezone which is used by localtime et.al.
*/
int set_tz (const char *tzstring)
{
return tzparse (tzstring, &localtime_tzoffset);
}
static size_t lensecs (long seconds)
{
size_t len = 1;
long secs = abs (seconds);
if (seconds < 0) {
len++;
}
if (secs / 3600 > 9) {
len++;
}
if (secs % 3600) {
len += 3;
if (secs % 60) {
len += 3;
}
}
return len;
}
/*
* Get length of string resulting from serializing rule.
*/
static size_t lenrule (const struct tzrule *rule)
{
size_t len = 0;
if (rule->r_type == JULIAN_DAY) {
len++;
}
if (rule->r_type == JULIAN_DAY || rule->r_type == DAY_OF_YEAR) {
len++;
if (rule->r_day > 9) {
len++;
if (rule->r_day > 99) {
len++;
}
}
} else if (rule->r_type == MONTH_NTH_DAY_OF_WEEK) {
len++;
len++;
if (rule->r_mon > 9) {
len++;
}
len += 4; /* dots and week/day */
if (rule->r_time != 7200) {
len++;
len += lensecs (rule->r_time);
}
}
return len;
}
static int is_extended_name (const char *name)
{
int i;
for (i=0; name [i]; i++) {
if (isdigit (name [i]) || name [i] == '+' || name [i] == '-') {
return 1;
}
}
return 0;
}
/*
* Get length of timezone string resulting from serializing tzo.
*/
static size_t len_tz_r (const struct tzoffset_info *tzo)
{
size_t len = 0;
if (tzo->stdname == NULL) {
return 0;
}
len = strlen (tzo->stdname);
if (is_extended_name (tzo->stdname)) {
len += 2;
}
len += lensecs (tzo->stdoffset);
if (tzo->dstname) {
len += strlen (tzo->dstname);
if (is_extended_name (tzo->dstname)) {
len += 2;
}
if (tzo->dstoffset - tzo->stdoffset != -3600) {
len += lensecs (tzo->dstoffset);
}
len += 2; /* commas */
len += lenrule (&tzo->start);
len += lenrule (&tzo->end);
}
return len;
}
size_t len_tz (void)
{
return len_tz_r (&localtime_tzoffset);
}
void appendsecs (char *buf, long minutes)
{
char *p = buf + strlen (buf);
long min = abs (minutes);
if (minutes < 0) {
*p++ = '-';
}
if (min % 3600 == 0) {
sprintf (p, "%ld", min / 3600);
} else if (min % 60 == 0) {
sprintf (p, "%ld:%ld", min / 3600, (min / 60) % 60);
} else {
sprintf (p, "%ld:%ld:%ld", min / 3600, (min / 60) % 60, min % 60);
}
}
void appendrule (char *buf, const struct tzrule *rule)
{
char *p = buf + strlen (buf);
if (rule->r_type == JULIAN_DAY) {
sprintf (p, "J%d", rule->r_day);
} else if (rule->r_type == DAY_OF_YEAR) {
sprintf (p, "%d", rule->r_day);
} else if (rule->r_type == MONTH_NTH_DAY_OF_WEEK) {
sprintf (p, "M%d.%d.%d", rule->r_mon, rule->r_week, rule->r_day);
p = buf + strlen (buf);
if (rule->r_time != 7200) {
*p++ = '/';
*p = '\0';
appendsecs (p, rule->r_time);
}
}
}
const char *get_tz (char *buf, size_t buflen)
{
const struct tzoffset_info *tzo = &localtime_tzoffset;
if (tzo->stdname == NULL || len_tz_r (tzo) > buflen) {
return NULL;
}
if (is_extended_name (tzo->stdname)) {
sprintf (buf, "<%s>", tzo->stdname);
} else {
strcpy (buf, tzo->stdname);
}
appendsecs (buf, tzo->stdoffset);
if (tzo->dstname != NULL) {
if (is_extended_name (tzo->dstname)) {
sprintf (buf + strlen (buf), "<%s>", tzo->dstname);
} else {
strcat (buf, tzo->dstname);
}
if (tzo->dstoffset - tzo->stdoffset != -3600) {
appendsecs (buf, tzo->dstoffset);
}
strcat (buf, ",");
appendrule (buf, &tzo->start);
strcat (buf, ",");
appendrule (buf, &tzo->end);
}
return buf;
}
/** @} */