Implemented Clock Class (#977)

* Simplified some stuff, made up-to-date

* Dealt with DST problems

* Made SystemTime.Format clearscope, as there is no reason for this function to be limited to the ui
This commit is contained in:
3saster 2020-08-27 09:43:50 -07:00 committed by GitHub
parent 85759e3bd0
commit 5803b78147
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 227 additions and 27 deletions

View file

@ -570,6 +570,133 @@ CCMD (special)
//==========================================================================
//
// CCMD setdate
//
// Set the time to a specific value
//
//==========================================================================
extern time_t epochoffset;
CCMD(setdate)
{
if (argv.argc() != 3)
{
Printf("setdate HH:MM:SS DD-MM-YYYY: Set the current date\n");
return;
}
time_t today;
time(&today);
struct tm* timeinfo = localtime(&today);
if (timeinfo != nullptr)
{
auto clock = FString(argv[1]).Split(":");
auto date = FString(argv[2]).Split("-");
if(clock.Size() != 3 || date.Size() != 3)
{
Printf("setdate HH:MM:SS DD-MM-YYYY: Set the current date\n");
return;
}
if(!clock[0].IsInt())
{
Printf("Invalid hour\n");
return;
}
if (!clock[1].IsInt())
{
Printf("Invalid minutes\n");
return;
}
if (!clock[2].IsInt())
{
Printf("Invalid seconds\n");
return;
}
if (!date[0].IsInt())
{
Printf("Invalid day\n");
return;
}
if (!date[1].IsInt())
{
Printf("Invalid month\n");
return;
}
if (!date[2].IsInt())
{
Printf("Invalid year\n");
return;
}
//Set Date
timeinfo->tm_hour = int( clock[0].ToLong() );
timeinfo->tm_min = int( clock[1].ToLong() );
timeinfo->tm_sec = int( clock[2].ToLong() );
timeinfo->tm_mday = int( date[0].ToLong() );
timeinfo->tm_mon = int( date[1].ToLong() - 1); // Month interally is 0 - 11
timeinfo->tm_year = int( date[2].ToLong() - 1900 ); // Year interally is 00 - 138
time_t newTime = mktime(timeinfo);
tm* t_old = localtime(&today);
time_t oldTime = mktime(t_old);
if (newTime == -1 || oldTime == -1)
{
Printf("Unable to set the date\n");
return;
}
epochoffset = newTime - oldTime;
// This deals with some inconsistent display behaviour for DST
// In this case, we want to emulate GCC's behaviour
today += epochoffset;
struct tm* t_new = localtime(&today);
if (t_new != nullptr)
{
char timeString[1024];
if (strftime(timeString, sizeof(timeString), "%H", t_new))
{
auto hour = FString(timeString).ToLong();
if (hour - clock[0].ToLong() == -1 || hour - clock[0].ToLong() == 23)
epochoffset += 3600;
else if (hour - clock[0].ToLong() == 1 || hour - clock[0].ToLong() == -23)
epochoffset -= 3600;
}
}
return;
}
else
{
Printf("Unable to set the date\n");
return;
}
}
CCMD(getdate)
{
time_t now;
time(&now);
now += epochoffset;
struct tm* timeinfo = localtime(&now);
if (timeinfo != nullptr)
{
char timeString[1024];
if (strftime(timeString, sizeof(timeString), "%H:%M:%S %d-%m-%Y%n", timeinfo))
Printf(timeString);
else
Printf("Error Retrieving Current Date\n");
}
else
{
Printf("Error Retrieving Current Date\n");
}
}
//========================================================================== //==========================================================================
// //
// CCMD warp // CCMD warp

View file

@ -164,6 +164,8 @@ bool playeringame[MAXPLAYERS];
int consoleplayer; // player taking events int consoleplayer; // player taking events
int gametic; int gametic;
time_t epochoffset = 0; // epoch start in seconds (0 = January 1st, 1970)
CVAR(Bool, demo_compress, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG); CVAR(Bool, demo_compress, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG);
FString newdemoname; FString newdemoname;
FString newdemomap; FString newdemomap;

View file

@ -2669,20 +2669,84 @@ DEFINE_ACTION_FUNCTION_NATIVE(FLevelLocals, setFrozen, setFrozen)
// //
//===================================================================================== //=====================================================================================
static int GetRealTime() extern time_t epochoffset;
static int GetEpochTime()
{ {
time_t now; time_t now;
time(&now); time(&now);
struct tm* timeinfo = localtime(&now); return now != (time_t)(-1) ? now + epochoffset : (time_t)(-1);
return timeinfo ? timeinfo->tm_sec + timeinfo->tm_min * 60 + timeinfo->tm_hour * 3600 : 0;
} }
DEFINE_ACTION_FUNCTION_NATIVE(_AltHUD, GetRealTime, GetRealTime) //Returns an empty string if the Strf tokens are valid, otherwise returns the problematic token
static FString CheckStrfString(FString timeForm)
{
// Valid Characters after %
const char validSingles[] = { 'a','A','b','B','c','C','d','D','e','F','G','g','h','H','I','j','k','l','m','M','n','p','P','r','R','s','S','t','T','u','U','V','w','W','x','X','y','Y','z','Z' };
timeForm.Substitute("%%", "%a"); //Prevent %% from causing tokenizing problems
timeForm = "a" + timeForm; //Prevent %* at the beginning from causing a false error from tokenizing
auto tokens = timeForm.Split("%");
for (auto t : tokens)
{
bool found = false;
// % at end
if (t.Len() == 0) return FString("%");
// Single Character
for (int i = 0; i < sizeof(validSingles)/sizeof(validSingles[0]); i++)
{
if (t[0] == validSingles[i])
{
found = true;
break;
}
}
if (found) continue;
return FString("%") + t[0];
}
return "";
}
static void FormatTime(const FString& timeForm, int timeVal, FString* result)
{
FString error = CheckStrfString(timeForm);
if (!error.IsEmpty())
ThrowAbortException(X_FORMAT_ERROR, "'%s' is not a valid format specifier of SystemTime.Format()", error.GetChars());
time_t val = timeVal;
struct tm* timeinfo = localtime(&val);
if (timeinfo != nullptr)
{
char timeString[1024];
if (strftime(timeString, sizeof(timeString), timeForm, timeinfo))
*result = timeString;
}
}
DEFINE_ACTION_FUNCTION_NATIVE(_SystemTime, Now, GetEpochTime)
{ {
PARAM_PROLOGUE; PARAM_PROLOGUE;
ACTION_RETURN_INT(GetRealTime()); ACTION_RETURN_INT(GetEpochTime());
} }
DEFINE_ACTION_FUNCTION_NATIVE(_SystemTime, Format, FormatTime)
{
PARAM_PROLOGUE;
PARAM_STRING(timeForm);
PARAM_INT(timeVal);
FString result;
FormatTime(timeForm, timeVal, &result);
ACTION_RETURN_STRING(result);
}
//=====================================================================================
//
//
//
//=====================================================================================
DEFINE_ACTION_FUNCTION_NATIVE(_AltHUD, GetLatency, Net_GetLatency) DEFINE_ACTION_FUNCTION_NATIVE(_AltHUD, GetLatency, Net_GetLatency)
{ {
PARAM_PROLOGUE; PARAM_PROLOGUE;

View file

@ -422,6 +422,12 @@ struct GameInfoStruct native
native double normsidemove[2]; native double normsidemove[2];
} }
struct SystemTime
{
native static ui int Now(); // This returns the epoch time
native static clearscope String Format(String timeForm, int timeVal); // This converts an epoch time to a local time, then uses the strftime syntax to format it
}
class Object native class Object native
{ {
const TICRATE = 35; const TICRATE = 35;

View file

@ -770,13 +770,12 @@ class AltHud ui
// for meaning of all display modes // for meaning of all display modes
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
private native static int GetRealTime();
virtual bool DrawTime(int y) virtual bool DrawTime(int y)
{ {
if (hud_showtime > 0 && hud_showtime <= 9) if (hud_showtime > 0 && hud_showtime <= 9)
{ {
int timeSeconds; int timeSeconds;
String timeString;
if (hud_showtime < 8) if (hud_showtime < 8)
{ {
@ -787,33 +786,35 @@ class AltHud ui
? Level.time ? Level.time
: Level.totaltime); : Level.totaltime);
timeSeconds = Thinker.Tics2Seconds(timeTicks); timeSeconds = Thinker.Tics2Seconds(timeTicks);
}
else
{
timeSeconds = GetRealTime();
}
int hours = timeSeconds / 3600; int hours = timeSeconds / 3600;
int minutes = (timeSeconds % 3600) / 60; int minutes = (timeSeconds % 3600) / 60;
int seconds = timeSeconds % 60; int seconds = timeSeconds % 60;
bool showMillis = 1 == hud_showtime; bool showMillis = 1 == hud_showtime;
bool showSeconds = showMillis || (0 == hud_showtime % 2); bool showSeconds = showMillis || (0 == hud_showtime % 2);
String timeString; if (showMillis)
{
if (showMillis) int millis = (Level.time % Thinker.TICRATE) * (1000 / Thinker.TICRATE);
{ timeString = String.Format("%02i:%02i:%02i.%03i", hours, minutes, seconds, millis);
int millis = (Level.time % Thinker.TICRATE) * (1000 / Thinker.TICRATE); }
timeString = String.Format("%02i:%02i:%02i.%03i", hours, minutes, seconds, millis); else if (showSeconds)
{
timeString = String.Format("%02i:%02i:%02i", hours, minutes, seconds);
}
else
{
timeString = String.Format("%02i:%02i", hours, minutes);
}
} }
else if (showSeconds) else if (hud_showtime == 8)
{ {
timeString = String.Format("%02i:%02i:%02i", hours, minutes, seconds); timeString = SystemTime.Format("%H:%M:%S",SystemTime.Now());
} }
else else //if (hud_showtime == 9)
{ {
timeString = String.Format("%02i:%02i", hours, minutes); timeString = SystemTime.Format("%H:%M",SystemTime.Now());
} }
int characterCount = timeString.length(); int characterCount = timeString.length();