592 lines
14 KiB
C
592 lines
14 KiB
C
/*-
|
|
* Copyright (c) 2017-2020 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 "string.h"
|
|
#include "global.h"
|
|
#include "mem.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;
|
|
}
|
|
|
|
*result = AllocStringSafe(p - start);
|
|
CopyMem((APTR)start, *result, p - start);
|
|
|
|
return ++p;
|
|
}
|
|
|
|
while (*p != '\0' && StrChr("0123456789-+,", *p, 13) == NULL)
|
|
++p;
|
|
|
|
if (p - start < 3)
|
|
return NULL;
|
|
|
|
*result = AllocStringSafe(p - start + 1);
|
|
CopyMem((APTR)start, *result, p - start);
|
|
|
|
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';
|
|
}
|
|
|
|
char *PosixTimezoneSignChar(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;
|
|
}
|
|
|
|
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 == 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 (a == 0 && i != 1)
|
|
{
|
|
j++;
|
|
}
|
|
|
|
if (day == a && week == j)
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
static ULONG FindTransitionTime(int year, struct PosixTransition *transition)
|
|
{
|
|
struct ClockData day;
|
|
ULONG 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;
|
|
}
|
|
|
|
// TODO: Validate sign
|
|
transitionTime +=
|
|
(transition->offset.sign < 0 ? -1 : 1) *
|
|
(transition->offset.hours * 60 * 60 +
|
|
transition->offset.minutes * 60 +
|
|
transition->offset.seconds * 60);
|
|
|
|
return transitionTime;
|
|
}
|
|
|
|
struct PosixTransitionTime *FindNextTransition(time_t now)
|
|
{
|
|
int i = 0;
|
|
|
|
if (Timezone->start.type == 0)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
while (Timezone->transitions[i].time < now)
|
|
{
|
|
i++;
|
|
}
|
|
|
|
return &Timezone->transitions[i];
|
|
}
|
|
|
|
static char *GetVariable(const char *variable)
|
|
{
|
|
unsigned long memsize = 256;
|
|
STRPTR var = (STRPTR)variable;
|
|
|
|
STRPTR buf = (STRPTR)AllocStringSafe(memsize);
|
|
if (!buf)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
if (GetVar(var, buf, memsize - 1, GVF_GLOBAL_ONLY) > 0)
|
|
{
|
|
return (char *)buf;
|
|
}
|
|
|
|
FreeMemSafe(buf);
|
|
return NULL;
|
|
}
|
|
|
|
void CleanupPosixTimezone(void)
|
|
{
|
|
if (Timezone != NULL)
|
|
{
|
|
if (Timezone->std.abbreviation != NULL)
|
|
{
|
|
FreeMemSafe(Timezone->std.abbreviation);
|
|
}
|
|
if (Timezone->dst.abbreviation != NULL)
|
|
{
|
|
FreeMemSafe(Timezone->dst.abbreviation);
|
|
}
|
|
|
|
FreeMemSafe(Timezone);
|
|
Timezone = NULL;
|
|
}
|
|
}
|
|
|
|
static void InitFromTZone(void)
|
|
{
|
|
if (Timezone->dst.abbreviation != NULL)
|
|
{
|
|
// DST is present
|
|
Timezone->current.abbreviation = Timezone->dst.abbreviation;
|
|
Timezone->current.offset = Timezone->dst.offset;
|
|
}
|
|
else
|
|
{
|
|
Timezone->current.abbreviation = Timezone->std.abbreviation;
|
|
Timezone->current.offset = Timezone->std.offset;
|
|
}
|
|
}
|
|
|
|
static void BuildTransitionMap()
|
|
{
|
|
int i = 0;
|
|
bool isdst = false;
|
|
bool north;
|
|
|
|
switch (Timezone->start.type)
|
|
{
|
|
case JulianDay:
|
|
case JulianDayLeap:
|
|
isdst = Timezone->start.day > Timezone->end.day;
|
|
break;
|
|
case DayWeekMonth:
|
|
isdst = Timezone->start.month > Timezone->end.month;
|
|
break;
|
|
default:
|
|
return;
|
|
break;
|
|
}
|
|
|
|
north = isdst ? false : true;
|
|
|
|
Timezone->transitions[0].time = 0;
|
|
Timezone->transitions[0].isdst = isdst;
|
|
|
|
for (i = 0; i < 138 / 2; i++)
|
|
{
|
|
ULONG time;
|
|
|
|
isdst = isdst ? false : true;
|
|
time = north
|
|
? FindTransitionTime(i + 1978, &Timezone->start)
|
|
: FindTransitionTime(i + 1978, &Timezone->end);
|
|
|
|
Timezone->transitions[i * 2 + 1].time = time;
|
|
Timezone->transitions[i * 2 + 1].isdst = isdst;
|
|
|
|
isdst = isdst ? false : true;
|
|
time = north
|
|
? FindTransitionTime(i + 1978, &Timezone->end)
|
|
: FindTransitionTime(i + 1978, &Timezone->start);
|
|
|
|
Timezone->transitions[i * 2 + 2].time = time;
|
|
Timezone->transitions[i * 2 + 2].isdst = isdst;
|
|
}
|
|
|
|
Timezone->transitions[137].time = 2147483647;
|
|
Timezone->transitions[137].isdst = false;
|
|
}
|
|
|
|
int InitPosixTimezone(char **variable)
|
|
{
|
|
int result = 5;
|
|
char *var;
|
|
|
|
Timezone = NULL;
|
|
|
|
var = GetVariable("TZ");
|
|
if (var == NULL)
|
|
{
|
|
var = GetVariable("TZONE");
|
|
result = 4;
|
|
}
|
|
|
|
if (var == NULL)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
*variable = StrDupSafe(var);
|
|
Timezone = AllocStructSafe(struct PosixTimezone);
|
|
|
|
if (!ParsePosixTz(var, Timezone))
|
|
{
|
|
FreeMemSafe(var);
|
|
CleanupPosixTimezone();
|
|
return result - 2;
|
|
}
|
|
|
|
FreeMemSafe(var);
|
|
|
|
if (Timezone->start.type == 0)
|
|
{
|
|
InitFromTZone();
|
|
}
|
|
else
|
|
{
|
|
BuildTransitionMap();
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
bool SetPosixTimezone(struct timeval *tv)
|
|
{
|
|
int i = 0;
|
|
|
|
if (Timezone->start.type == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
while (Timezone->transitions[i].time <= tv->tv_secs)
|
|
{
|
|
i++;
|
|
}
|
|
|
|
i--;
|
|
|
|
if (Timezone->transitions[i].isdst)
|
|
{
|
|
Timezone->current.abbreviation = Timezone->dst.abbreviation;
|
|
Timezone->current.offset = Timezone->dst.offset;
|
|
}
|
|
else
|
|
{
|
|
Timezone->current.abbreviation = Timezone->std.abbreviation;
|
|
Timezone->current.offset = Timezone->std.offset;
|
|
}
|
|
|
|
return true;
|
|
}
|