492 lines
13 KiB
C++
492 lines
13 KiB
C++
/*
|
|
* TimeUtils.cpp
|
|
*
|
|
* Copyright 2017 Christopher B. Liebman
|
|
*
|
|
* 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.
|
|
*
|
|
*
|
|
* Created on: Jun 7, 2017
|
|
* Author: liebman
|
|
*/
|
|
|
|
#include "TimeUtils.h"
|
|
|
|
//#define DEBUG
|
|
#include "Logger.h"
|
|
|
|
uint8_t TimeUtils::parseSmallDuration(const char* value)
|
|
{
|
|
int i = atoi(value);
|
|
if (i < 0 || i > 255)
|
|
{
|
|
dbprintf("TimeUtils::parseSmallDuration: invalid value %s: using 32 instead!\n", value);
|
|
i = 32;
|
|
}
|
|
return (uint8_t) i;
|
|
}
|
|
|
|
uint8_t TimeUtils::parseOccurrence(const char* occurrence_string)
|
|
{
|
|
int i = atoi(occurrence_string);
|
|
if (i < -5 || i == 0 || i > 5)
|
|
{
|
|
dbprintf("TimeUtils::parseOccurrence: invalid value %s: using 1 instead!\n", occurrence_string);
|
|
i = 1;
|
|
}
|
|
return (uint8_t) i;
|
|
}
|
|
|
|
uint8_t TimeUtils::parseDayOfWeek(const char* dow_string)
|
|
{
|
|
int i = atoi(dow_string);
|
|
if (i < 0 || i > 6)
|
|
{
|
|
dbprintf("TimeUtils::parseDayOfWeek: invalid value %s: using 0 (Sunday) instead!\n", dow_string);
|
|
i = 1;
|
|
}
|
|
return (uint8_t) i;
|
|
}
|
|
|
|
uint8_t TimeUtils::parseMonth(const char* month_string)
|
|
{
|
|
int i = atoi(month_string);
|
|
if (i < 0 || i > 12)
|
|
{
|
|
dbprintf("TimeUtils::parseMonth: invalid value '%s': using 3 (Mar) instead!\n", month_string);
|
|
i = 1;
|
|
}
|
|
return (uint8_t) i;
|
|
}
|
|
|
|
uint8_t TimeUtils::parseHour(const char* hour_string)
|
|
{
|
|
int i = atoi(hour_string);
|
|
if (i < 0 || i > 23)
|
|
{
|
|
dbprintf("TimeUtils::parseMonth: invalid value '%s': using 2 instead!\n", hour_string);
|
|
i = 1;
|
|
}
|
|
return (uint8_t) i;
|
|
}
|
|
|
|
int TimeUtils::parseOffset(const char* offset_string)
|
|
{
|
|
int result = 0;
|
|
char value[11];
|
|
strncpy(value, offset_string, 10);
|
|
if (strchr(value, ':') != NULL)
|
|
{
|
|
int sign = 1;
|
|
char* s;
|
|
|
|
if (value[0] == '-')
|
|
{
|
|
sign = -1;
|
|
s = strtok(&(value[1]), ":");
|
|
}
|
|
else
|
|
{
|
|
s = strtok(value, ":");
|
|
}
|
|
if (s != NULL)
|
|
{
|
|
int h = atoi(s);
|
|
while (h > 11)
|
|
{
|
|
h -= 12;
|
|
}
|
|
|
|
result += h * 3600; // hours to seconds
|
|
s = strtok(NULL, ":");
|
|
}
|
|
if (s != NULL)
|
|
{
|
|
result += atoi(s) * 60; // minutes to seconds
|
|
s = strtok(NULL, ":");
|
|
}
|
|
if (s != NULL)
|
|
{
|
|
result += atoi(s);
|
|
}
|
|
// apply sign
|
|
result *= sign;
|
|
}
|
|
else
|
|
{
|
|
result = atoi(value);
|
|
if (result < -43199 || result > 43199)
|
|
{
|
|
result = 0;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
uint16_t TimeUtils::parsePosition(const char* position_string)
|
|
{
|
|
int result = 0;
|
|
char value[10];
|
|
strncpy(value, position_string, 9);
|
|
if (strchr(value, ':') != NULL)
|
|
{
|
|
char* s = strtok(value, ":");
|
|
|
|
if (s != NULL)
|
|
{
|
|
int h = atoi(s);
|
|
while (h > 11)
|
|
{
|
|
h -= 12;
|
|
}
|
|
|
|
result += h * 3600; // hours to seconds
|
|
s = strtok(NULL, ":");
|
|
}
|
|
if (s != NULL)
|
|
{
|
|
result += atoi(s) * 60; // minutes to seconds
|
|
s = strtok(NULL, ":");
|
|
}
|
|
if (s != NULL)
|
|
{
|
|
result += atoi(s);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
result = atoi(value);
|
|
if (result < 0 || result > 43199)
|
|
{
|
|
result = 0;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
//
|
|
// Modified code from: http://www.jbox.dk/sanos/source/lib/time.c.html
|
|
//
|
|
|
|
#define YEAR0 1900
|
|
#define EPOCH_YR 1970
|
|
#define SECS_DAY (24L * 60L * 60L)
|
|
#define LEAPYEAR(year) (!((year) % 4) && (((year) % 100) || !((year) % 400)))
|
|
#define YEARSIZE(year) (LEAPYEAR(year) ? 366 : 365)
|
|
#define TIME_MAX 2147483647L
|
|
|
|
const int _ytab[2][12] =
|
|
{
|
|
{ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
|
|
{ 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } };
|
|
|
|
// esp version is broken :-(
|
|
time_t TimeUtils::mktime(struct tm *tmbuf)
|
|
{
|
|
long day, year;
|
|
int tm_year;
|
|
int yday, month;
|
|
/*unsigned*/long seconds;
|
|
int overflow;
|
|
|
|
tmbuf->tm_min += tmbuf->tm_sec / 60;
|
|
tmbuf->tm_sec %= 60;
|
|
if (tmbuf->tm_sec < 0)
|
|
{
|
|
tmbuf->tm_sec += 60;
|
|
tmbuf->tm_min--;
|
|
}
|
|
tmbuf->tm_hour += tmbuf->tm_min / 60;
|
|
tmbuf->tm_min = tmbuf->tm_min % 60;
|
|
if (tmbuf->tm_min < 0)
|
|
{
|
|
tmbuf->tm_min += 60;
|
|
tmbuf->tm_hour--;
|
|
}
|
|
day = tmbuf->tm_hour / 24;
|
|
tmbuf->tm_hour = tmbuf->tm_hour % 24;
|
|
if (tmbuf->tm_hour < 0)
|
|
{
|
|
tmbuf->tm_hour += 24;
|
|
day--;
|
|
}
|
|
tmbuf->tm_year += tmbuf->tm_mon / 12;
|
|
tmbuf->tm_mon %= 12;
|
|
if (tmbuf->tm_mon < 0)
|
|
{
|
|
tmbuf->tm_mon += 12;
|
|
tmbuf->tm_year--;
|
|
}
|
|
day += (tmbuf->tm_mday - 1);
|
|
while (day < 0)
|
|
{
|
|
if (--tmbuf->tm_mon < 0)
|
|
{
|
|
tmbuf->tm_year--;
|
|
tmbuf->tm_mon = 11;
|
|
}
|
|
day += _ytab[LEAPYEAR(YEAR0 + tmbuf->tm_year)][tmbuf->tm_mon];
|
|
}
|
|
while (day >= _ytab[LEAPYEAR(YEAR0 + tmbuf->tm_year)][tmbuf->tm_mon])
|
|
{
|
|
day -= _ytab[LEAPYEAR(YEAR0 + tmbuf->tm_year)][tmbuf->tm_mon];
|
|
if (++(tmbuf->tm_mon) == 12)
|
|
{
|
|
tmbuf->tm_mon = 0;
|
|
tmbuf->tm_year++;
|
|
}
|
|
}
|
|
tmbuf->tm_mday = day + 1;
|
|
year = EPOCH_YR;
|
|
if (tmbuf->tm_year < year - YEAR0)
|
|
return (time_t) -1;
|
|
seconds = 0;
|
|
day = 0; // Means days since day 0 now
|
|
overflow = 0;
|
|
|
|
// Assume that when day becomes negative, there will certainly
|
|
// be overflow on seconds.
|
|
// The check for overflow needs not to be done for leapyears
|
|
// divisible by 400.
|
|
// The code only works when year (1970) is not a leapyear.
|
|
tm_year = tmbuf->tm_year + YEAR0;
|
|
|
|
if (TIME_MAX / 365 < tm_year - year)
|
|
overflow++;
|
|
day = (tm_year - year) * 365;
|
|
if (TIME_MAX - day < (tm_year - year) / 4 + 1)
|
|
overflow++;
|
|
day += (tm_year - year) / 4 + ((tm_year % 4) && tm_year % 4 < year % 4);
|
|
day -= (tm_year - year) / 100
|
|
+ ((tm_year % 100) && tm_year % 100 < year % 100);
|
|
day += (tm_year - year) / 400
|
|
+ ((tm_year % 400) && tm_year % 400 < year % 400);
|
|
|
|
yday = month = 0;
|
|
while (month < tmbuf->tm_mon)
|
|
{
|
|
yday += _ytab[LEAPYEAR(tm_year)][month];
|
|
month++;
|
|
}
|
|
yday += (tmbuf->tm_mday - 1);
|
|
if (day + yday < 0)
|
|
overflow++;
|
|
day += yday;
|
|
|
|
tmbuf->tm_yday = yday;
|
|
tmbuf->tm_wday = (day + 4) % 7; // Day 0 was thursday (4)
|
|
|
|
seconds = ((tmbuf->tm_hour * 60L) + tmbuf->tm_min) * 60L + tmbuf->tm_sec;
|
|
|
|
if ((TIME_MAX - seconds) / SECS_DAY < day)
|
|
overflow++;
|
|
seconds += day * SECS_DAY;
|
|
|
|
if (overflow)
|
|
return (time_t) -1;
|
|
|
|
if ((time_t) seconds != seconds)
|
|
return (time_t) -1;
|
|
return (time_t) seconds;
|
|
}
|
|
|
|
struct tm *TimeUtils::gmtime_r(const time_t *timer, struct tm *tmbuf)
|
|
{
|
|
time_t time = *timer;
|
|
unsigned long dayclock, dayno;
|
|
int year = EPOCH_YR;
|
|
|
|
dayclock = (unsigned long) time % SECS_DAY;
|
|
dayno = (unsigned long) time / SECS_DAY;
|
|
|
|
tmbuf->tm_sec = dayclock % 60;
|
|
tmbuf->tm_min = (dayclock % 3600) / 60;
|
|
tmbuf->tm_hour = dayclock / 3600;
|
|
tmbuf->tm_wday = (dayno + 4) % 7; // Day 0 was a thursday
|
|
while (dayno >= (unsigned long) YEARSIZE(year)) {
|
|
dayno -= YEARSIZE(year);
|
|
year++;
|
|
}
|
|
tmbuf->tm_year = year - YEAR0;
|
|
tmbuf->tm_yday = dayno;
|
|
tmbuf->tm_mon = 0;
|
|
while (dayno >= (unsigned long) _ytab[LEAPYEAR(year)][tmbuf->tm_mon]) {
|
|
dayno -= _ytab[LEAPYEAR(year)][tmbuf->tm_mon];
|
|
tmbuf->tm_mon++;
|
|
}
|
|
tmbuf->tm_mday = dayno + 1;
|
|
tmbuf->tm_isdst = 0;
|
|
return tmbuf;
|
|
}
|
|
|
|
//
|
|
// The functions findDOW & findNthDate are from:
|
|
//
|
|
// http://hackaday.com/2012/07/16/automatic-daylight-savings-time-compensation-for-your-clock-projects
|
|
//
|
|
|
|
/*--------------------------------------------------------------------------
|
|
FUNC: 6/11/11 - Returns day of week for any given date
|
|
PARAMS: year, month, date
|
|
RETURNS: day of week (0-7 is Sun-Sat)
|
|
NOTES: Sakamoto's Algorithm
|
|
http://en.wikipedia.org/wiki/Calculating_the_day_of_the_week#Sakamoto.27s_methods
|
|
Altered to use char when possible to save microcontroller ram
|
|
--------------------------------------------------------------------------*/
|
|
uint8_t TimeUtils::findDOW(uint16_t y, uint8_t m, uint8_t d)
|
|
{
|
|
static char t[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
|
|
y -= m < 3;
|
|
return (y + y/4 - y/100 + y/400 + t[m-1] + d) % 7;
|
|
}
|
|
|
|
/*--------------------------------------------------------------------------
|
|
http://hackaday.com/2012/07/16/automatic-daylight-savings-time-compensation-for-your-clock-projects
|
|
FUNC: 6/11/11 - Returns the date for Nth day of month. For instance,
|
|
it will return the numeric date for the 2nd Sunday of April
|
|
PARAMS: year, month, day of week, Nth occurrence of that day in that month
|
|
RETURNS: date
|
|
NOTES: There is no error checking for invalid inputs.
|
|
--------------------------------------------------------------------------*/
|
|
uint8_t TimeUtils::findNthDate(uint16_t year, uint8_t month, uint8_t dow, uint8_t nthWeek)
|
|
{
|
|
dbprintf("findNthDate: year:%u month:%u, dow:%u nthWeek:%d\n", year, month, dow, nthWeek);
|
|
|
|
uint8_t targetDate = 1;
|
|
uint8_t firstDOW = findDOW(year,month,targetDate);
|
|
while (firstDOW != dow) {
|
|
firstDOW = (firstDOW+1)%7;
|
|
targetDate++;
|
|
}
|
|
//Adjust for weeks
|
|
targetDate += (nthWeek-1)*7;
|
|
return targetDate;
|
|
}
|
|
|
|
uint8_t TimeUtils::daysInMonth(uint16_t year, uint8_t month)
|
|
{
|
|
uint8_t days = 31;
|
|
|
|
switch(month)
|
|
{
|
|
case 2:
|
|
days = 28;
|
|
if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
|
|
{
|
|
days = 29;
|
|
}
|
|
break;
|
|
case 4:
|
|
case 6:
|
|
case 9:
|
|
case 11:
|
|
days = 30;
|
|
break;
|
|
}
|
|
return days;
|
|
}
|
|
|
|
uint8_t TimeUtils::findDateForWeek(uint16_t year, uint8_t month, uint8_t dow, int8_t week)
|
|
{
|
|
dbprintf("findDateForWeek: year:%u month:%u, dow:%u week:%d\n", year, month, dow, week);
|
|
|
|
uint8_t weeks[5];
|
|
uint8_t max_day = daysInMonth(year, month);
|
|
int last = 0;
|
|
|
|
if (week >= 0)
|
|
{
|
|
return findNthDate(year, month, dow, week);
|
|
}
|
|
|
|
//
|
|
// find all times this weekday shows up in the month
|
|
// Note that 'last' will end up pointing 1 past the last
|
|
// valid occurrence. -1 will give the last one.
|
|
//
|
|
for(last = 0; last <= 5; ++last)
|
|
{
|
|
weeks[last] = findNthDate(year, month, dow, last+1);
|
|
|
|
dbprintf("findDateForWeek: last:%d date:%u\n", last, weeks[last]);
|
|
|
|
if (weeks[last] > max_day)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
return weeks[last+week];
|
|
}
|
|
|
|
int TimeUtils::computeUTCOffset(time_t now, int tz_offset, TimeChange* tc, int tc_count)
|
|
{
|
|
struct tm tm;
|
|
|
|
//
|
|
// get the current year
|
|
//
|
|
gmtime_r(&now, &tm);
|
|
int year = tm.tm_year;
|
|
|
|
//
|
|
// pre-set the offset to the last timechange of the year
|
|
//
|
|
int offset = tc[tc_count-1].tz_offset;
|
|
|
|
//
|
|
// loop thru each time change entry, converting it to the time in seconds for the
|
|
// current year. If now is greater/equal to the time change the use the new offset.
|
|
// We return the last offset that is greater/equal now.
|
|
//
|
|
for(int i = 0; i < tc_count; ++i)
|
|
{
|
|
dbprintf("TimeUtils::computeUTCOffset: index:%d offset:%d month:%u dow:%u occurrence:%d hour:%u day_offset:%d\n",
|
|
i,
|
|
tc[i].tz_offset,
|
|
tc[i].month,
|
|
tc[i].day_of_week,
|
|
tc[i].occurrence,
|
|
tc[i].hour,
|
|
tc[i].day_offset);
|
|
|
|
tm.tm_sec = 0;
|
|
tm.tm_min = 0;
|
|
tm.tm_hour = tc[i].hour;
|
|
tm.tm_mday = TimeUtils::findDateForWeek(year+1900, tc[i].month, tc[i].day_of_week, tc[i].occurrence);
|
|
tm.tm_mon = tc[i].month-1;
|
|
tm.tm_year = year;
|
|
|
|
dbprintf("computeUTCOffset: tm: %04d/%02d/%02d %02d:%02d:%02d + %d days\n", tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, tc[i].day_offset);
|
|
|
|
// convert to seconds
|
|
time_t tc_time = mktime(&tm);
|
|
// convert to UTC
|
|
tc_time -= tz_offset;
|
|
dbprintf("computeUTCOffset: tc_time: %ld (UTC)\n", tc_time);
|
|
// add in days offset
|
|
tc_time += tc[i].day_offset*86400;
|
|
|
|
dbprintf("computeUTCOffset: now: %ld tc_time: %ld\n", now, tc_time);
|
|
|
|
if (now >= tc_time)
|
|
{
|
|
offset = tc[i].tz_offset;
|
|
dbprintf("computeUTCOffset: now > tc_time, offset: %d\n", offset);
|
|
}
|
|
}
|
|
|
|
return offset;
|
|
}
|