AmiTimeKeeper/ptz.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];
}