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