| /* |
| * 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); |
| } |