523 lines
13 KiB
C
523 lines
13 KiB
C
/*-
|
|
* Copyright (c) 2017-2021 Carsten Sonne Larsen <cs@innolan.net>
|
|
* All rights reserved.
|
|
*
|
|
* 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.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
|
|
*
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include <stddef.h>
|
|
|
|
#include <exec/types.h>
|
|
#include <proto/exec.h>
|
|
#include <proto/utility.h>
|
|
|
|
#include "ptz.h"
|
|
#include "mem.h"
|
|
#include "string.h"
|
|
|
|
#include "logmod.h"
|
|
#define MODULENAME "Posix TZ"
|
|
|
|
/*
|
|
* Parse integers in POSIX TZ string.
|
|
*/
|
|
static const char *ParseInt(const char *p, int min, int max, int *result)
|
|
{
|
|
CONST_STRPTR start = (CONST_STRPTR)p;
|
|
LONG c, value;
|
|
|
|
c = StrToLong(start, &value);
|
|
|
|
if (c == -1 || value < min || value > max)
|
|
return NULL;
|
|
|
|
*result = (int)value;
|
|
return (const char *)(start + c);
|
|
}
|
|
|
|
/*
|
|
* Parse abbreviation part of POSIX TZ string.
|
|
*/
|
|
static const char *ParseAbbr(const char *p, char *result)
|
|
{
|
|
const char *start = p;
|
|
|
|
if (*p == '<')
|
|
{
|
|
start++;
|
|
while (*++p != '>')
|
|
{
|
|
if (*p == '\0')
|
|
return NULL;
|
|
}
|
|
|
|
int l = (p - start > MAX_ABBR - 1 ? MAX_ABBR - 1 : p - start);
|
|
CopyMem((APTR)start, (APTR)result, (ULONG)l);
|
|
|
|
return ++p;
|
|
}
|
|
|
|
while (*p != '\0' && StrChr("0123456789-+,", *p, 13) == NULL)
|
|
++p;
|
|
|
|
if (p - start < 3)
|
|
return NULL;
|
|
|
|
int l = (p - start > MAX_ABBR - 1 ? MAX_ABBR - 1 : p - start);
|
|
CopyMem((APTR)start, (APTR)result, (ULONG)l);
|
|
|
|
return p;
|
|
}
|
|
|
|
/*
|
|
* Parse time part of POSIX TZ string in format:
|
|
* [+|-]hh[:mm[:ss]]
|
|
*/
|
|
static const char *ParseTime(const char *p, int min_hour, int max_hour, int sign, struct PosixTimezoneOffset *result)
|
|
{
|
|
int seconds = 0;
|
|
int minutes = 0;
|
|
int hours = 0;
|
|
|
|
if (p == NULL)
|
|
return NULL;
|
|
|
|
if (*p == '+' || *p == '-')
|
|
{
|
|
if (*p++ == '-')
|
|
{
|
|
sign = -sign;
|
|
}
|
|
}
|
|
|
|
p = ParseInt(p, min_hour, max_hour, &hours);
|
|
|
|
if (p == NULL)
|
|
return NULL;
|
|
|
|
if (*p == ':')
|
|
{
|
|
p = ParseInt(p + 1, 0, 59, &minutes);
|
|
if (p == NULL)
|
|
return NULL;
|
|
|
|
if (*p == ':')
|
|
{
|
|
p = ParseInt(p + 1, 0, 59, &seconds);
|
|
if (p == NULL)
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
result->sign = sign;
|
|
result->seconds = seconds;
|
|
result->minutes = minutes;
|
|
result->hours = hours;
|
|
|
|
return p;
|
|
}
|
|
|
|
/*
|
|
* Parse transition part of POSIX TZ string in format:
|
|
* ( Jn | n | Mm.w.d ) [ / time ]
|
|
*/
|
|
static const char *ParseTransition(const char *p, struct PosixTransition *result)
|
|
{
|
|
if (p == NULL)
|
|
return NULL;
|
|
|
|
if (*p == ',')
|
|
{
|
|
if (*++p == 'M')
|
|
{
|
|
int month = 0;
|
|
if ((p = ParseInt(p + 1, 1, 12, &month)) != NULL && *p == '.')
|
|
{
|
|
int week = 0;
|
|
if ((p = ParseInt(p + 1, 1, 5, &week)) != NULL && *p == '.')
|
|
{
|
|
int weekday = 0;
|
|
if ((p = ParseInt(p + 1, 0, 6, &weekday)) != NULL)
|
|
{
|
|
result->type = DayWeekMonth;
|
|
result->month = month;
|
|
result->week = week;
|
|
result->weekday = weekday;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (*p == 'J')
|
|
{
|
|
int day = 0;
|
|
if ((p = ParseInt(p + 1, 1, 365, &day)) != NULL)
|
|
{
|
|
result->type = JulianDay;
|
|
result->day = day;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int day = 0;
|
|
if ((p = ParseInt(p, 0, 365, &day)) != NULL)
|
|
{
|
|
result->type = JulianDayLeap;
|
|
result->day = day;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (p != NULL)
|
|
{
|
|
// Default is 02:00:00
|
|
result->offset.sign = 1;
|
|
result->offset.hours = 2;
|
|
result->offset.minutes = 0;
|
|
result->offset.seconds = 0;
|
|
|
|
if (*p == '/')
|
|
{
|
|
p = ParseTime(p + 1, -167, 167, 1, &result->offset);
|
|
}
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
/*
|
|
* Parse POSIX TZ string in format:
|
|
* std offset dst [offset],start[/time],end[/time]
|
|
*/
|
|
static bool ParsePosixTz(const char *tz, struct PosixTimezone *result)
|
|
{
|
|
const char *p = tz;
|
|
if (*p == ':')
|
|
return false;
|
|
|
|
p = ParseAbbr(p, result->std.abbreviation);
|
|
p = ParseTime(p, 0, 24, -1, &result->std.offset);
|
|
|
|
if (p == NULL)
|
|
return false;
|
|
|
|
if (*p == '\0')
|
|
return true;
|
|
|
|
p = ParseAbbr(p, result->dst.abbreviation);
|
|
if (p == NULL)
|
|
return false;
|
|
|
|
// Default is one hour ahead of std
|
|
result->dst.offset.sign = result->std.offset.sign;
|
|
result->dst.offset.hours = (result->dst.offset.sign > 0)
|
|
? result->std.offset.hours + 1
|
|
: result->std.offset.hours - 1;
|
|
result->dst.offset.minutes = result->std.offset.minutes;
|
|
result->dst.offset.seconds = result->std.offset.seconds;
|
|
|
|
if (*p == '\0')
|
|
return true;
|
|
|
|
if (*p != ',')
|
|
{
|
|
p = ParseTime(p, 0, 24, -1, &result->dst.offset);
|
|
}
|
|
|
|
p = ParseTransition(p, &result->start);
|
|
p = ParseTransition(p, &result->end);
|
|
|
|
return p != NULL && *p == '\0';
|
|
}
|
|
|
|
static int FindWeekDay(int year, int month, int day)
|
|
{
|
|
int h = 0;
|
|
int q = day;
|
|
int m = month - 1;
|
|
int Y = year;
|
|
|
|
// Adjust year if January or February
|
|
if (m <= 1)
|
|
{
|
|
Y--;
|
|
}
|
|
|
|
// Adjust month to [3:14) for March to February
|
|
if (m <= 1)
|
|
{
|
|
m += 13;
|
|
}
|
|
else
|
|
{
|
|
m += 1;
|
|
}
|
|
|
|
h = (q + ((13 * (m + 1)) / 5) + Y + (Y / 4) - (Y / 100) + (Y / 400)) % 7;
|
|
h = (h + 6) % 7;
|
|
|
|
return h;
|
|
}
|
|
|
|
static int FindDay(int year, int month, int week, int day)
|
|
{
|
|
static int daysInMonth[] = {
|
|
31, 28, 31, 30, 31, 30,
|
|
31, 31, 30, 31, 30, 31};
|
|
|
|
int days = daysInMonth[month - 1];
|
|
|
|
if (month == 2 && (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0)))
|
|
{
|
|
days = 29;
|
|
}
|
|
|
|
if (week == 1)
|
|
{
|
|
int i;
|
|
for (i = 1; i <= days; i++)
|
|
{
|
|
int a = FindWeekDay(year, month, i);
|
|
if (day == a)
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
}
|
|
else if (week == 5)
|
|
{
|
|
int i;
|
|
for (i = (days - 6); i <= days; i++)
|
|
{
|
|
int a = FindWeekDay(year, month, i);
|
|
if (a == day)
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int i, j = 1;
|
|
|
|
for (i = 1; i <= days; i++)
|
|
{
|
|
int a = FindWeekDay(year, month, i);
|
|
if (day == a && week == j)
|
|
{
|
|
return i;
|
|
}
|
|
|
|
if (a == 0 && i != 1)
|
|
{
|
|
j++;
|
|
}
|
|
}
|
|
}
|
|
|
|
LogError("Failed to find transition date");
|
|
return 1;
|
|
}
|
|
|
|
static time_t FindTransitionTime(int year, struct PosixTransition *transition)
|
|
{
|
|
struct ClockData day;
|
|
signed long transitionTime = 0;
|
|
|
|
day.sec = 0;
|
|
day.min = 0;
|
|
day.hour = 0;
|
|
day.wday = 0;
|
|
|
|
switch (transition->type)
|
|
{
|
|
case JulianDayLeap:
|
|
day.mday = 1;
|
|
day.month = 1;
|
|
day.year = year;
|
|
transitionTime = Date2Amiga(&day);
|
|
transitionTime += transition->day * 24 * 60 * 60;
|
|
case JulianDay:
|
|
if ((transition->day > 31 + 28) && (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0)))
|
|
{
|
|
transitionTime -= 24 * 60 * 60;
|
|
}
|
|
break;
|
|
case DayWeekMonth:
|
|
day.mday = FindDay(year, transition->month, transition->week, transition->weekday);
|
|
day.month = transition->month;
|
|
day.year = year;
|
|
transitionTime = Date2Amiga(&day);
|
|
break;
|
|
default:
|
|
day.mday = 1;
|
|
day.month = 1;
|
|
day.year = year;
|
|
transitionTime = Date2Amiga(&day);
|
|
break;
|
|
}
|
|
|
|
transitionTime +=
|
|
(transition->offset.sign < 0 ? -1 : 1) *
|
|
(transition->offset.hours * 60 * 60 +
|
|
transition->offset.minutes * 60 +
|
|
transition->offset.seconds * 60);
|
|
|
|
return transitionTime;
|
|
}
|
|
|
|
static void BuildTransitionMap(struct PosixTimezone *ptz)
|
|
{
|
|
int i = 0;
|
|
bool isdst = false;
|
|
bool north;
|
|
|
|
switch (ptz->start.type)
|
|
{
|
|
case JulianDay:
|
|
case JulianDayLeap:
|
|
isdst = ptz->start.day > ptz->end.day;
|
|
break;
|
|
case DayWeekMonth:
|
|
isdst = ptz->start.month > ptz->end.month;
|
|
break;
|
|
default:
|
|
return;
|
|
break;
|
|
}
|
|
|
|
north = isdst ? false : true;
|
|
|
|
ptz->transitions[0].time = 0;
|
|
ptz->transitions[0].isdst = isdst;
|
|
|
|
for (i = 0; i < MAX_TRANSITIONS / 2 - 1; i++)
|
|
{
|
|
time_t time;
|
|
isdst = isdst ? false : true;
|
|
time = north
|
|
? FindTransitionTime(i + 1978, &ptz->start)
|
|
: FindTransitionTime(i + 1978, &ptz->end);
|
|
|
|
ptz->transitions[i * 2 + 1].time = time;
|
|
ptz->transitions[i * 2 + 1].isdst = isdst;
|
|
|
|
isdst = isdst ? false : true;
|
|
time = north
|
|
? FindTransitionTime(i + 1978, &ptz->end)
|
|
: FindTransitionTime(i + 1978, &ptz->start);
|
|
|
|
ptz->transitions[i * 2 + 2].time = time;
|
|
ptz->transitions[i * 2 + 2].isdst = isdst;
|
|
}
|
|
|
|
ptz->transitions[MAX_TRANSITIONS - 1].time = 2147483647;
|
|
ptz->transitions[MAX_TRANSITIONS - 1].isdst = false;
|
|
}
|
|
|
|
char *TimezoneSignChar(struct PosixLocalTimezone *ptz)
|
|
{
|
|
static char *positive = "+";
|
|
static char *negative = "-";
|
|
|
|
if (ptz == NULL)
|
|
{
|
|
return positive;
|
|
}
|
|
|
|
if (ptz->offset.hours == 0 && ptz->offset.minutes == 0 && ptz->offset.seconds == 0)
|
|
{
|
|
return positive;
|
|
}
|
|
|
|
if (ptz->offset.sign >= 0)
|
|
{
|
|
return positive;
|
|
}
|
|
|
|
return negative;
|
|
}
|
|
|
|
void FreePosixTimezone(struct PosixTimezone *ptz)
|
|
{
|
|
if (ptz == NULL)
|
|
return;
|
|
|
|
FreeMemSafe(ptz);
|
|
}
|
|
|
|
struct PosixTimezone *InitPosixTimezone(char *variable)
|
|
{
|
|
struct PosixTimezone *ptz = AllocStructSafe(struct PosixTimezone);
|
|
ptz->source = PosixTypeTimezone;
|
|
|
|
if (ParsePosixTz(variable, ptz))
|
|
{
|
|
if (ptz->start.type != 0)
|
|
{
|
|
BuildTransitionMap(ptz);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FreePosixTimezone(ptz);
|
|
ptz = NULL;
|
|
}
|
|
|
|
return ptz;
|
|
}
|
|
|
|
void SetPosixTimezone(struct PosixTimezone *ptz, struct timeval *tv)
|
|
{
|
|
if (ptz == NULL || ptz->start.type == 0)
|
|
return;
|
|
|
|
int i = 0;
|
|
time_t time = (time_t)tv->tv_secs;
|
|
while (i < MAX_TRANSITIONS && ptz->transitions[i].time <= time)
|
|
i++;
|
|
|
|
if (ptz->transitions[--i].isdst)
|
|
{
|
|
StrCopy(ptz->current.abbreviation, ptz->dst.abbreviation);
|
|
ptz->current.offset = ptz->dst.offset;
|
|
}
|
|
else
|
|
{
|
|
StrCopy(ptz->current.abbreviation, ptz->std.abbreviation);
|
|
ptz->current.offset = ptz->std.offset;
|
|
}
|
|
}
|
|
|
|
struct PosixTransitionTime *FindNextTransition(struct PosixTimezone *ptz, struct timeval *tv)
|
|
{
|
|
if (ptz->start.type == 0)
|
|
return NULL;
|
|
|
|
int i = 0;
|
|
time_t time = (time_t)tv->tv_secs;
|
|
while (i < MAX_TRANSITIONS && ptz->transitions[i].time < time)
|
|
i++;
|
|
|
|
return &ptz->transitions[i];
|
|
}
|