/*- * Copyright (c) 2017-2021 Carsten Sonne Larsen * 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 #include #include #include #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]; }