You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
522 lines
13 KiB
522 lines
13 KiB
/*- |
|
* 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]; |
|
}
|
|
|