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.
515 lines
14 KiB
515 lines
14 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 "notify.h" |
|
#include "global.h" |
|
#include "message.h" |
|
#include "setting.h" |
|
#include "conv.h" |
|
#include "text.h" |
|
#include "ptz.h" |
|
#include "timer.h" |
|
#include "mem.h" |
|
#include "tz.h" |
|
|
|
#include <assert.h> |
|
#include <utility/date.h> |
|
|
|
#include "logmod.h" |
|
#define MODULENAME "Timezone" |
|
|
|
#define EnvTZTimezone 1 |
|
#define EnvTZOneTimezone 2 |
|
#define SetTZEnvTimezone 3 |
|
#define SetTZValTimezone 4 |
|
#define PrefsTimezone 5 |
|
|
|
#define TIMEZONE_STD 1 |
|
#define TIMEZONE_DST 2 |
|
|
|
#define ENV_TZ "TZ" |
|
#define ENV_TZONE "TZONE" |
|
#define ENV_TZ_FILE "ENV:TZ" |
|
#define ENV_TZONE_FILE "ENV:TZONE" |
|
#define ENV_PREFS_FILE "ENV:Sys/locale.prefs" |
|
|
|
/* |
|
* 2922 is the number of days between 1.1.1970 and 1.1.1978 |
|
* (2 leap years and 6 normal) |
|
* 2922 * 24 * 60 * 60 = 252460800 |
|
*/ |
|
#define AMIGA_OFFSET 252460800 |
|
|
|
static volatile bool TimezoneCheckerRunning = false; |
|
static volatile bool TimezoneCheckerRestart = false; |
|
static volatile bool TimezoneCheckerShutdown = false; |
|
static volatile long TimezoneSource = 0; |
|
static volatile long unixEpochOffset = 0; |
|
|
|
static void StartTimezoneCheck(void); |
|
|
|
static void BuildTimezone(struct PosixLocalTimezone *timezone, int offset, bool std) |
|
{ |
|
long sign = (offset < 0 ? -1 : +1); |
|
long offsetPositive = (offset < 0 ? -offset : offset); |
|
long hoursOffset = (offsetPositive / 60L); |
|
long minutsOffset = (offsetPositive % 60L); |
|
|
|
if (Settings->TimeZoneName != NULL && *Settings->TimeZoneName != '\0' && |
|
((Settings->TimeZoneDst <= 0 && std) || (Settings->TimeZoneDst == 1 && !std))) |
|
SNPrintf(timezone->abbreviation, MAX_ABBR - 1, "%s", Settings->TimeZoneName); |
|
else if (minutsOffset == 0) |
|
{ |
|
SNPrintf(timezone->abbreviation, MAX_ABBR - 1, "GMT%s%ld", |
|
(sign <= 0 ? "+" : "-"), hoursOffset); |
|
} |
|
else |
|
{ |
|
SNPrintf(timezone->abbreviation, MAX_ABBR - 1, "GMT%s%ld:%02ld", |
|
(sign <= 0 ? "+" : "-"), hoursOffset, minutsOffset); |
|
} |
|
|
|
timezone->offset.hours = hoursOffset; |
|
timezone->offset.minutes = minutsOffset; |
|
timezone->offset.seconds = 0; |
|
timezone->offset.sign = -sign; |
|
} |
|
|
|
static void FindCurrentTimezone(struct PosixTimezone *timezone, long opt) |
|
{ |
|
if (opt == TIMEZONE_STD) |
|
{ |
|
StrCopy(timezone->current.abbreviation, timezone->std.abbreviation); |
|
timezone->current.offset = timezone->std.offset; |
|
} |
|
else if (opt == TIMEZONE_DST) |
|
{ |
|
StrCopy(timezone->current.abbreviation, timezone->dst.abbreviation); |
|
timezone->current.offset = timezone->dst.offset; |
|
} |
|
} |
|
|
|
static void SetCurrentTimezone(struct PosixTimezone *timezone) |
|
{ |
|
if (Settings->TimeZoneDst == 0) |
|
FindCurrentTimezone(timezone, TIMEZONE_STD); |
|
else if (Settings->TimeZoneDst == 1) |
|
FindCurrentTimezone(timezone, TIMEZONE_DST); |
|
else if (timezone->source == PosixTypeTimezone && |
|
timezone->dst.abbreviation == NULL) |
|
FindCurrentTimezone(timezone, TIMEZONE_DST); |
|
else |
|
FindCurrentTimezone(timezone, TIMEZONE_STD); |
|
} |
|
|
|
static struct PosixTimezone *BuildAmigaTimezone(struct Locale *locale) |
|
{ |
|
struct PosixTimezone *tz = AllocStructSafe(struct PosixTimezone); |
|
tz->source = CustomTypeTimezone; |
|
long offset = (locale != NULL ? locale->loc_GMTOffset : 0); |
|
BuildTimezone(&tz->std, offset, true); |
|
BuildTimezone(&tz->dst, offset - 60, false); |
|
SetCurrentTimezone(tz); |
|
return tz; |
|
} |
|
|
|
static struct PosixTimezone *BuildSettingsTimezone(void) |
|
{ |
|
struct PosixTimezone *tz = AllocStructSafe(struct PosixTimezone); |
|
tz->source = CustomTypeTimezone; |
|
long value = Settings->TimeZoneValue; |
|
long hours = (value != TZVALUE_DEF ? (value / -100) : 0); |
|
long minutes = (value != TZVALUE_DEF ? (value % -100) : 0); |
|
long offset = hours * 60 + minutes; |
|
BuildTimezone(&tz->std, offset, true); |
|
BuildTimezone(&tz->dst, offset - 60, false); |
|
SetCurrentTimezone(tz); |
|
return tz; |
|
} |
|
|
|
static void ShowTimezones(void) |
|
{ |
|
char timezone[TIMEZONE_TEXT_LEN]; |
|
GetTimezoneText(timezone, TZD_STD); |
|
LogInfo(timezoneStd, timezone); |
|
GetTimezoneText(timezone, TZD_DST); |
|
LogInfo(timezoneDst, timezone); |
|
GetTimezoneText(timezone, TZD_OFFSET_IN_PARENS); |
|
LogNotice(timezoneCur, timezone); |
|
} |
|
|
|
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; |
|
} |
|
|
|
static struct PosixTimezone *ParseSettingsTZ() |
|
{ |
|
struct PosixTimezone *timezone = NULL; |
|
if (Settings->TZ != NULL && *Settings->TZ != '\0') |
|
{ |
|
timezone = InitPosixTimezone(Settings->TZ); |
|
if (timezone != NULL) |
|
LogInfo(tzSettingValid, tzFromSet, SettingKeys->TZ); |
|
else |
|
LogInfo(tzSettingInvalid, tzFromSet, SettingKeys->TZ); |
|
LogDebug(settingValueString, SettingKeys->TZ, Settings->TZ); |
|
} |
|
return timezone; |
|
} |
|
|
|
static struct PosixTimezone *ParseEnvTZ(const char *tz) |
|
{ |
|
struct PosixTimezone *timezone = NULL; |
|
char *var = GetVariable(tz); |
|
|
|
if (var != NULL) |
|
{ |
|
timezone = InitPosixTimezone(var); |
|
if (timezone != NULL) |
|
LogInfo(tzSettingValid, tzFromEnv, tz); |
|
else |
|
LogInfo(tzSettingInvalid, tzFromEnv, tz); |
|
LogDebug(settingValueString, tz, var); |
|
FreeMemSafe(var); |
|
} |
|
return timezone; |
|
} |
|
|
|
static void ShowTimezoneSource(void) |
|
{ |
|
switch (TimezoneSource) |
|
{ |
|
case SetTZValTimezone: |
|
LogNotice(applyTimeZone, tzFromSet, SettingKeys->TimeZoneValue); |
|
break; |
|
case SetTZEnvTimezone: |
|
LogNotice(applyTimeZone, tzFromSet, SettingKeys->TZ); |
|
break; |
|
case EnvTZTimezone: |
|
LogNotice(applyTimeZone, tzFromEnv, ENV_TZ); |
|
break; |
|
case EnvTZOneTimezone: |
|
LogNotice(applyTimeZone, tzFromEnv, ENV_TZONE); |
|
break; |
|
case PrefsTimezone: |
|
LogNotice(applyTimeZone, tzWorkbench, tzPreferences); |
|
break; |
|
default: |
|
break; |
|
} |
|
} |
|
|
|
static void ShowNextTransition(void) |
|
{ |
|
if (NextTransition != NULL) |
|
{ |
|
char *nextTime = GetTimeText((ULONG)NextTransition->time); |
|
const char *nextTrans = NextTransition->isdst ? tzDstAbbr : tzStdAbbr; |
|
LogWarn(tzChange, nextTrans, nextTime); |
|
FreeMemSafe(nextTime); |
|
} |
|
} |
|
|
|
void InitTimezone(void) |
|
{ |
|
CleanupNotifications(); |
|
|
|
struct Locale *locale = OpenLocale(NULL); |
|
struct PosixTimezone *set1Timezone = ParseSettingsTZ(); |
|
struct PosixTimezone *set2Timezone = BuildSettingsTimezone(); |
|
struct PosixTimezone *env1Timezone = ParseEnvTZ(ENV_TZ); |
|
struct PosixTimezone *env2Timezone = ParseEnvTZ(ENV_TZONE); |
|
struct PosixTimezone *prefTimezone = BuildAmigaTimezone(locale); |
|
CloseLocale(locale); |
|
|
|
struct timeval tv; |
|
GetSysTime(&tv); |
|
SetPosixTimezone(set1Timezone, &tv); |
|
SetPosixTimezone(env1Timezone, &tv); |
|
SetPosixTimezone(env2Timezone, &tv); |
|
|
|
long source = 0; |
|
struct PosixTimezone *timezone = NULL; |
|
if (Settings->TimeZoneValue != TZVALUE_DEF) |
|
{ |
|
source = SetTZValTimezone; |
|
timezone = set2Timezone; |
|
} |
|
else if (set1Timezone != NULL) |
|
{ |
|
source = SetTZEnvTimezone; |
|
timezone = set1Timezone; |
|
} |
|
else if (env1Timezone != NULL) |
|
{ |
|
source = EnvTZTimezone; |
|
timezone = env1Timezone; |
|
} |
|
else if (env2Timezone != NULL) |
|
{ |
|
source = EnvTZOneTimezone; |
|
timezone = env2Timezone; |
|
} |
|
else |
|
{ |
|
source = PrefsTimezone; |
|
timezone = prefTimezone; |
|
} |
|
|
|
struct PosixTimezone *activeTimezone = AllocStructSafe(struct PosixTimezone); |
|
CopyMem(timezone, activeTimezone, sizeof(struct PosixTimezone)); |
|
FreePosixTimezone(set1Timezone); |
|
FreePosixTimezone(set2Timezone); |
|
FreePosixTimezone(env1Timezone); |
|
FreePosixTimezone(env2Timezone); |
|
FreePosixTimezone(prefTimezone); |
|
|
|
struct PosixTransitionTime *activeTransition = FindNextTransition(activeTimezone, &tv); |
|
long offset = (activeTimezone->current.offset.sign < 0 ? +1 : -1) * |
|
(activeTimezone->current.offset.hours * 60 * 60 + |
|
activeTimezone->current.offset.minutes * 60 + |
|
activeTimezone->current.offset.seconds); |
|
offset += AMIGA_OFFSET; |
|
|
|
// Switch global time zone variables |
|
Forbid(); |
|
struct PosixTimezone *lastTimezone = Timezone; |
|
Timezone = activeTimezone; |
|
TimezoneSource = source; |
|
NextTransition = activeTransition; |
|
unixEpochOffset = offset; |
|
Permit(); |
|
|
|
switch (source) |
|
{ |
|
case EnvTZTimezone: |
|
WatchFile(ENV_TZ_FILE, ATK_TZ_CHANGED); |
|
break; |
|
case EnvTZOneTimezone: |
|
WatchFile(ENV_TZONE_FILE, ATK_TZONE_CHANGED); |
|
break; |
|
case PrefsTimezone: |
|
WatchFile(ENV_PREFS_FILE, ATK_LOCALE_CHANGED); |
|
break; |
|
default: |
|
break; |
|
} |
|
|
|
ShowTimezoneSource(); |
|
ShowTimezones(); |
|
ShowNextTransition(); |
|
StartTimezoneCheck(); |
|
FreePosixTimezone(lastTimezone); |
|
} |
|
|
|
void Unix2Amiga(struct timeval *utc, struct timeval *tv) |
|
{ |
|
assert(utc != NULL); |
|
assert(tv != NULL); |
|
|
|
tv->tv_secs = utc->tv_secs - AMIGA_OFFSET; |
|
tv->tv_micro = utc->tv_micro; |
|
} |
|
|
|
void UnixUtc2AmigaLocal(struct timeval *utc, struct timeval *tv) |
|
{ |
|
assert(utc != NULL); |
|
assert(tv != NULL); |
|
|
|
tv->tv_secs = utc->tv_secs - unixEpochOffset; |
|
tv->tv_micro = utc->tv_micro; |
|
} |
|
|
|
void GetTimeOfDay(struct timeval *tv) |
|
{ |
|
assert(tv != NULL); |
|
|
|
GetSysTime(tv); |
|
tv->tv_secs = tv->tv_secs + unixEpochOffset; |
|
} |
|
|
|
void GetLocalTimeOfDay(struct timeval *tv) |
|
{ |
|
assert(tv != NULL); |
|
|
|
GetSysTime(tv); |
|
} |
|
|
|
long GetUtcOffsetValue(void) |
|
{ |
|
return (unixEpochOffset - AMIGA_OFFSET); |
|
} |
|
|
|
void SetTimeOfDay(const struct TimerInfo *info, const struct timeval *tv) |
|
{ |
|
struct timeval t; |
|
|
|
assert(tv != NULL); |
|
|
|
t.tv_secs = tv->tv_secs - unixEpochOffset; |
|
t.tv_micro = tv->tv_micro; |
|
SetTime(&t); |
|
} |
|
|
|
void SaveTimeOfDay(const struct timeval *tv) |
|
{ |
|
assert(tv != NULL); |
|
|
|
WriteBattClock((long)tv->tv_secs - unixEpochOffset); |
|
} |
|
|
|
static long GetNextTransition(void) |
|
{ |
|
time_t next = (NextTransition != NULL ? NextTransition->time : 2147483647); |
|
return (long)next; |
|
} |
|
|
|
static long GetNextTransitionCheck(long now) |
|
{ |
|
long diff = GetNextTransition() - now; |
|
diff = (diff > 60 ? 60 : diff); |
|
return diff; |
|
} |
|
|
|
static void CheckTransition(void) |
|
{ |
|
TimezoneCheckerShutdown = false; |
|
TimezoneCheckerRestart = false; |
|
|
|
if (NextTransition == NULL) |
|
{ |
|
LogNotice(timezoneNoTrans); |
|
LogInfo(Text2P, tzChecker, TextStopped); |
|
TimezoneCheckerRunning = false; |
|
return; |
|
} |
|
|
|
LogInfo(Text2P, tzChecker, TextStarted); |
|
|
|
struct timeval tv; |
|
GetSysTime(&tv); |
|
ULONG next, now = (ULONG)(tv.tv_secs); |
|
long diff = GetNextTransitionCheck(now); |
|
|
|
while (!TimezoneCheckerShutdown) |
|
{ |
|
if (diff < 0) |
|
{ |
|
LogTrace(tzCheck); |
|
GetSysTime(&tv); |
|
now = (ULONG)(tv.tv_secs); |
|
next = GetNextTransition(); |
|
|
|
if (now > next) |
|
{ |
|
ShowTimezones(); |
|
LogNotice(tzTrans); |
|
|
|
Forbid(); |
|
SetPosixTimezone(Timezone, &tv); |
|
NextTransition = FindNextTransition(Timezone, &tv); |
|
Permit(); |
|
|
|
ShowTimezones(); |
|
SendWindowMessage(ATK_TZ_CHANGED); |
|
} |
|
|
|
diff = GetNextTransitionCheck(now); |
|
} |
|
|
|
diff--; |
|
Delay(50); |
|
|
|
if (TimezoneCheckerRestart) |
|
{ |
|
LogInfo(Text2P, TextRestarting, tzChecker); |
|
TimezoneCheckerRestart = false; |
|
diff = -1; |
|
} |
|
} |
|
|
|
LogInfo(Text2P, tzChecker, TextStopped); |
|
TimezoneCheckerRunning = false; |
|
TimezoneCheckerShutdown = false; |
|
} |
|
|
|
void StopTimezoneCheck(void) |
|
{ |
|
TimezoneCheckerShutdown = true; |
|
while (TimezoneCheckerRunning) |
|
Delay(10); |
|
} |
|
|
|
static void StartTimezoneCheck(void) |
|
{ |
|
if (TimezoneCheckerRunning) |
|
{ |
|
LogTrace(Text2P, tzChecker, TextAlreadyRunning); |
|
TimezoneCheckerRestart = true; |
|
return; |
|
} |
|
|
|
TimezoneCheckerRunning = true; |
|
LogNotice(Text2P, TextStarting, tzChecker); |
|
|
|
struct Task *task = (struct Task *)CreateNewProcTags( |
|
NP_Entry, (IPTR)CheckTransition, |
|
NP_StackSize, 32 * 1024, |
|
NP_Name, 0, |
|
NP_Input, 0, |
|
NP_Output, 0, |
|
NP_Error, 0, |
|
NP_CloseInput, FALSE, |
|
NP_CloseOutput, FALSE, |
|
NP_CloseError, FALSE, |
|
NP_WindowPtr, 0, |
|
NP_ConsoleTask, 0, |
|
NP_Cli, FALSE, |
|
TAG_DONE); |
|
|
|
if (task == NULL) |
|
{ |
|
LogError(Text2P, TextNoStart, tzChecker); |
|
TimezoneCheckerRunning = false; |
|
} |
|
}
|
|
|