blob: bc569409bf323e2c96805bbef4e6341808585a0b [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
/*-
* Copyright (c) 1988 University of Utah.
* Copyright (c) 1982, 1990, 1993
* The Regents of the University of California. All rights reserved.
*
* This code is derived from software contributed to Berkeley by
* the Systems Programming Group of the University of Utah Computer
* Science Department.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND 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 REGENTS OR 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.
*
* from: Utah $Hdr: clock.c 1.18 91/01/21$
* from: @(#)clock.c 8.2 (Berkeley) 1/12/94
* from: NetBSD: clock_subr.c,v 1.6 2001/07/07 17:04:02 thorpej Exp
* and
* from: src/sys/i386/isa/clock.c,v 1.176 2001/09/04
*/
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include "os/mynewt.h"
#include <datetime/datetime.h>
#define days_in_year(y) (leapyear(y) ? 366 : 365)
#define FEBRUARY 2
#define days_in_month(y, m) \
(month_days[(m) - 1] + (m == FEBRUARY ? leapyear(y) : 0))
/* Day of week. Days are counted from 1/1/1970, which was a Thursday */
#define day_of_week(days) (((days) + 4) % 7)
static const int month_days[12] = {
31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};
#define POSIX_BASE_YEAR 1970
#define SECDAY (24 * 60 * 60)
/*
* This inline avoids some unnecessary modulo operations
* as compared with the usual macro:
* ( ((year % 4) == 0 &&
* (year % 100) != 0) ||
* ((year % 400) == 0) )
* It is otherwise equivalent.
*/
static int
leapyear(int year)
{
int rv = 0;
if ((year & 3) == 0) {
rv = 1;
if ((year % 100) == 0) {
rv = 0;
if ((year % 400) == 0)
rv = 1;
}
}
return (rv);
}
int
clocktime_to_timeval(const struct clocktime *ct, const struct os_timezone *tz, struct os_timeval *tv)
{
int i, year, days;
year = ct->year;
/* Sanity checks. */
if (year < POSIX_BASE_YEAR ||
ct->mon < 1 || ct->mon > 12 ||
ct->day < 1 || ct->day > days_in_month(year, ct->mon) ||
ct->hour < 0 || ct->hour > 23 ||
ct->min < 0 || ct->min > 59 ||
ct->sec < 0 || ct->sec > 59 ||
ct->usec < 0 || ct->usec > 999999) {
return (OS_EINVAL);
}
/*
* Compute days since start of time
* First from years, then from months.
*/
days = 0;
for (i = POSIX_BASE_YEAR; i < year; i++)
days += days_in_year(i);
/* Months */
for (i = 1; i < ct->mon; i++)
days += days_in_month(year, i);
days += (ct->day - 1);
tv->tv_sec = (((int64_t)days * 24 + ct->hour) * 60 + ct->min) * 60 +
ct->sec;
tv->tv_usec = ct->usec;
/* Convert localtime to utctime */
if (tz != NULL) {
tv->tv_sec += tz->tz_minuteswest * 60;
tv->tv_sec -= tz->tz_dsttime ? 3600 : 0;
}
return (0);
}
int
timeval_to_clocktime(const struct os_timeval *tv, const struct os_timezone *tz,
struct clocktime *ct)
{
int i, year, days;
int64_t rsec; /* remainder seconds */
int64_t secs;
secs = tv->tv_sec;
if (tz != NULL) {
/* Convert utctime to localtime */
secs -= tz->tz_minuteswest * 60;
secs += tz->tz_dsttime ? 3600 : 0;
}
if (secs < 0 || tv->tv_usec < 0 || tv->tv_usec > 999999) {
return (OS_EINVAL);
}
days = secs / SECDAY;
rsec = secs % SECDAY;
ct->dow = day_of_week(days);
/* Subtract out whole years, counting them in i. */
for (year = POSIX_BASE_YEAR; days >= days_in_year(year); year++)
days -= days_in_year(year);
ct->year = year;
/* Subtract out whole months, counting them in i. */
for (i = 1; days >= days_in_month(year, i); i++)
days -= days_in_month(year, i);
ct->mon = i;
/* Days are what is left over (+1) from all that. */
ct->day = days + 1;
/* Hours, minutes, seconds are easy */
ct->hour = rsec / 3600;
rsec = rsec % 3600;
ct->min = rsec / 60;
rsec = rsec % 60;
ct->sec = rsec;
ct->usec = tv->tv_usec;
return (0);
}
static const char *
parse_number(const char *str, int digits, int *val)
{
const char *cp;
const char *end;
*val = 0;
cp = str;
end = str + digits;
while (cp < end) {
if (!isdigit((int) *cp)) {
return (NULL);
}
*val *= 10;
*val += (*cp - '0');
cp++;
}
return (end);
}
/*
* Parse the datetime string in RFC 3339 format. Some examples of valid
* datetime strings are:
* 2016-03-02T22:44:00 UTC time (implicit)
* 2016-03-02T22:44:00Z UTC time (explicit)
* 2016-03-02T22:44:00-08:00 PST timezone
* 2016-03-02T22:44:00.1 fractional seconds
* 2016-03-02T22:44:00.101+05:30 fractional seconds with timezone
*/
int
datetime_parse(const char *input, struct os_timeval *tv, struct os_timezone *tz)
{
int digits, sign;
int off_hour, off_min;
const char *cp;
const char *ep;
struct clocktime ct;
bzero(&ct, sizeof(struct clocktime));
bzero(tv, sizeof(struct os_timeval));
bzero(tz, sizeof(struct os_timezone)); /* default to UTC time */
cp = input;
cp = parse_number(cp, 4, &ct.year);
if (cp == NULL || *cp != '-') {
goto err;
}
cp = parse_number(cp + 1, 2, &ct.mon);
if (cp == NULL || *cp != '-') {
goto err;
}
cp = parse_number(cp + 1, 2, &ct.day);
if (cp == NULL || *cp != 'T') {
goto err;
}
cp = parse_number(cp + 1, 2, &ct.hour);
if (cp == NULL || *cp != ':') {
goto err;
}
cp = parse_number(cp + 1, 2, &ct.min);
if (cp == NULL || *cp != ':') {
goto err;
}
cp = parse_number(cp + 1, 2, &ct.sec);
if (cp == NULL) {
goto err;
}
/* parse fractional seconds if specified */
if (*cp == '.') {
ep = ++cp;
while (isdigit((int) *ep)) {
ep++;
}
digits = ep - cp;
if (digits <= 0 || digits > 6) {
goto err;
}
cp = parse_number(cp, digits, &ct.usec);
if (cp == NULL) {
goto err;
}
/*
* The number of digits in the fractional seconds determines
* the resolution.
*
* .1 1 part out of 10 100000 usec
* .01 1 part out of 100 10000 usec
* .001 1 part out of 1000 1000 usec
* .0001 1 part out of 10000 100 usec
* .00001 1 part out of 100000 10 usec
* .000001 1 part out of 1000000 1 usec
*/
while (digits++ < 6) {
ct.usec *= 10;
}
}
if (*cp == 'Z' || *cp == 'z') {
cp++;
} else if (*cp == '+' || *cp == '-') {
sign = (*cp == '+') ? +1 : -1;
cp = parse_number(cp + 1, 2, &off_hour);
if (cp == NULL || *cp != ':') {
goto err;
}
cp = parse_number(cp + 1, 2, &off_min);
if (cp == NULL) {
goto err;
}
if (off_hour < 0 || off_hour > 23 || off_min < 0 || off_min > 59) {
goto err;
}
/*
* Allow time zone offsets of up to 18 hours from the GMT.
* https://docs.oracle.com/javase/8/docs/api/java/time/ZoneOffset.html
*/
tz->tz_minuteswest = off_hour * 60 + off_min;
if (tz->tz_minuteswest > 18 * 60) {
goto err;
}
/*
* Positive GMT offsets (i.e. timezones to the east of GMT) are
* represented with a negative 'tz_minuteswest' value.
*/
if (sign > 0) {
tz->tz_minuteswest = -tz->tz_minuteswest;
}
} else {
/*
* 'time offset' is not specified so date/time defaults to UTC.
*/
}
if (*cp != '\0') {
goto err;
}
if (clocktime_to_timeval(&ct, tz, tv) != 0) {
goto err;
}
return (0);
err:
return (-1);
}
int
datetime_format(const struct os_timeval *tv, const struct os_timezone *tz,
char *ostr, int olen)
{
char *cp;
int rc, rlen, minswest;
int off_hour, off_min, sign;
struct clocktime ct;
rc = timeval_to_clocktime(tv, tz, &ct);
if (rc != 0) {
goto err;
}
cp = ostr;
rlen = olen;
rc = snprintf(cp, rlen, "%04d-%02d-%02dT%02d:%02d:%02d.%06d",
ct.year, ct.mon, ct.day, ct.hour, ct.min, ct.sec, ct.usec);
cp += rc;
rlen -= rc;
if (rc < 0 || rlen <= 0) {
goto err;
}
if (tz != NULL) {
minswest = tz->tz_minuteswest;
if (tz->tz_dsttime) {
minswest -= 60;
}
} else {
minswest = 0;
}
if (minswest <= 0) {
sign = '+';
minswest = -minswest;
} else {
sign = '-';
}
off_hour = minswest / 60;
off_min = minswest % 60;
rc = snprintf(cp, rlen, "%c%02d:%02d", sign, off_hour, off_min);
cp += rc;
rlen -= rc;
if (rc < 0 || rlen <= 0) {
goto err;
}
return (0);
err:
return (-1);
}