Keep your time right
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

/*-
* 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;
}
}