mirror of
https://github.com/brazilofmux/tinymux
synced 2026-08-13 00:23:11 -04:00
The flip: FUNCTION/XFUNCTION/FUN::fun/delim_check and the module interfaces take `const UTF8 * const fargs[]`. Double-const is load-bearing: C++ qualification conversion needs const at both pointer levels, so builder-side `UTF8 *[]` arrays convert implicitly — the evaluator, the JIT marshaller, and every owner site need zero casts, and slot reassignment inside bodies becomes a compile error for free. The conversions: the flip landed first so the compiler enumerated every violation; this commit is that inventory worked to zero — ~250 sites across funceval, funceval2, functions, funmath, help, mail, session, powers, levels, predicates, conf, walkdb, stringutil, timeutil/ date_scan (regenerated, one-line diff), exp3, and mux_main, each classified per docs/campaign-2136-const-fargs.md's four recipes. New idioms (functions.h): trim_space_sep_n() — non-destructive trim for (pointer, length) consumers, so trim-then-scan sites need no copy at all; FargVec — the argv counterpart of FargCopy for CS_ARGV handlers. countwords() and DecodeListOfIntegers() rewritten non-destructive. The flip deleted more than it added: #2157's fun_munge list1 copy, the engine_com help-topic copy, fun_index's in-place NUL write, and five const_casts (process_sex x4, sha1_helper). const_cast budget: zero added. Trap recorded in the brief: an old-signature definition doesn't fail the build — it becomes a C++ overload, and the new-signature symbol stays undefined until dlopen(RTLD_NOW). delim_check, the conn_bridge bridges, the dbt_spike stub, and exp3::Call were all silently shadowed; muxscript was the only host that noticed, because netmux's own net.cpp resolved the flat-namespace lookup. After any signature flip, grep the old spelling. Verified: make test EXPECT_CONFIG="jit=yes" (35 passed / 0 failed) and make test-scenario, including the new tests/scenario/sidefx_fargs.py that live-probes the class-3 wrappers smoke never touches (pemit/ trigger/link/tel/wipe/destroy). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
364 lines
15 KiB
C++
364 lines
15 KiB
C++
/*! \file timeutil.h
|
|
* \brief CLinearTimeAbsolute and CLinearTimeDelta modules.
|
|
*
|
|
* Date/Time code based on algorithms presented in "Calendrical Calculations",
|
|
* Cambridge Press, 1998.
|
|
*/
|
|
|
|
#ifndef TIMEUTIL_H
|
|
#define TIMEUTIL_H
|
|
|
|
#include <chrono>
|
|
#include <memory>
|
|
#include <atomic>
|
|
#include <cstdint>
|
|
#include <string>
|
|
#include <thread>
|
|
#include <mutex>
|
|
#include <condition_variable>
|
|
|
|
using UnderlyingTickType = std::int64_t;
|
|
using HectoNanoseconds = std::chrono::duration<UnderlyingTickType, std::ratio<1, 10'000'000>>;
|
|
|
|
typedef struct
|
|
{
|
|
short iYear; // 1900 would be stored as 1900.
|
|
unsigned short iMonth; // January is 1. December is 12.
|
|
unsigned short iDayOfWeek; // 0 is Sunday, 1 is Monday, etc.
|
|
unsigned short iDayOfMonth; // Day of Month, 1..31
|
|
unsigned short iDayOfYear; // January 1st is 1, etc.
|
|
unsigned short iHour;
|
|
unsigned short iMinute;
|
|
unsigned short iSecond;
|
|
unsigned short iMillisecond; // Milliseconds less than a second.
|
|
unsigned short iMicrosecond; // Microseconds less than a Millisecond.
|
|
unsigned short iNanosecond; // Nanoseconds less than a Microsecond.
|
|
} FIELDEDTIME;
|
|
|
|
class CLinearTimeDelta;
|
|
|
|
class LIBMUX_API CLinearTimeAbsolute
|
|
{
|
|
friend class CLinearTimeDelta;
|
|
friend LIBMUX_API bool operator<(const CLinearTimeAbsolute& lta, const CLinearTimeAbsolute& ltb) noexcept;
|
|
friend LIBMUX_API bool operator>(const CLinearTimeAbsolute& lta, const CLinearTimeAbsolute& ltb) noexcept;
|
|
friend LIBMUX_API bool operator==(const CLinearTimeAbsolute& lta, const CLinearTimeAbsolute& ltb) noexcept;
|
|
friend LIBMUX_API bool operator<=(const CLinearTimeAbsolute& lta, const CLinearTimeAbsolute& ltb) noexcept;
|
|
friend LIBMUX_API bool operator>=(const CLinearTimeAbsolute& lta, const CLinearTimeAbsolute& ltb) noexcept;
|
|
friend LIBMUX_API CLinearTimeAbsolute operator+(const CLinearTimeAbsolute& lta, const CLinearTimeDelta& ltd) noexcept;
|
|
friend LIBMUX_API CLinearTimeAbsolute operator-(const CLinearTimeAbsolute& lta, const CLinearTimeDelta& ltd) noexcept;
|
|
friend LIBMUX_API CLinearTimeDelta operator-(const CLinearTimeAbsolute& lta, const CLinearTimeAbsolute& ltb);
|
|
|
|
private:
|
|
UnderlyingTickType m_tAbsolute;
|
|
static int m_nCount;
|
|
static UTF8 m_Buffer[I64BUF_SIZE * 2];
|
|
|
|
public:
|
|
//! Default constructor - initializes to epoch (zero)
|
|
CLinearTimeAbsolute() noexcept;
|
|
|
|
//! Constructor from origin time plus offset
|
|
CLinearTimeAbsolute(const CLinearTimeAbsolute& ltaOrigin, const CLinearTimeDelta& ltdOffset) noexcept;
|
|
|
|
//! Copy constructor
|
|
CLinearTimeAbsolute(const CLinearTimeAbsolute& lta) noexcept;
|
|
|
|
//! Assignment operator
|
|
CLinearTimeAbsolute& operator=(const CLinearTimeAbsolute& lta) noexcept;
|
|
|
|
//! Add time delta to this time
|
|
CLinearTimeAbsolute& operator+=(const CLinearTimeDelta& ltdOffset) noexcept;
|
|
|
|
//! Subtract time delta from this time
|
|
CLinearTimeAbsolute& operator-=(const CLinearTimeDelta& ltdOffset) noexcept;
|
|
|
|
//! Get UTC time
|
|
void GetUTC();
|
|
|
|
//! Get local time
|
|
void GetLocal();
|
|
|
|
//! Return a unique string representation
|
|
void ReturnUniqueString(UTF8* buffer, size_t nBuffer) const;
|
|
|
|
//! Return date as string with optional fractional digits
|
|
const UTF8* ReturnDateString(int nFracDigits = 0) const;
|
|
|
|
//! Convert time to fielded representation
|
|
bool ReturnFields(FIELDEDTIME* arg_tStruct) const;
|
|
|
|
//! Get seconds since Unix epoch
|
|
UnderlyingTickType ReturnSeconds() const noexcept;
|
|
|
|
//! Get seconds as string with optional fractional digits
|
|
const UTF8* ReturnSecondsString(int nFracDigits = 0) const;
|
|
|
|
//! Get raw 100ns ticks
|
|
UnderlyingTickType Return100ns() const noexcept;
|
|
|
|
//! Set time from seconds since Unix epoch
|
|
void SetSeconds(UnderlyingTickType arg_tSeconds) noexcept;
|
|
|
|
//! Set time from seconds string
|
|
bool SetSecondsString(const UTF8* arg_szSeconds);
|
|
|
|
//! Set time from fielded time
|
|
bool SetFields(FIELDEDTIME* arg_tStruct);
|
|
|
|
//! Set time from string representation
|
|
bool SetString(const UTF8* arg_tBuffer);
|
|
|
|
//! Set raw 100ns ticks
|
|
void Set100ns(UnderlyingTickType arg_t100ns) noexcept;
|
|
|
|
//! Convert UTC time to local time
|
|
void UTC2Local();
|
|
|
|
//! Convert local time to UTC
|
|
void Local2UTC();
|
|
};
|
|
|
|
//! Less than comparison
|
|
LIBMUX_API bool operator<(const CLinearTimeAbsolute& lta, const CLinearTimeAbsolute& ltb) noexcept;
|
|
|
|
//! Greater than comparison
|
|
LIBMUX_API bool operator>(const CLinearTimeAbsolute& lta, const CLinearTimeAbsolute& ltb) noexcept;
|
|
|
|
//! Equality comparison
|
|
LIBMUX_API bool operator==(const CLinearTimeAbsolute& lta, const CLinearTimeAbsolute& ltb) noexcept;
|
|
|
|
//! Less than or equal comparison
|
|
LIBMUX_API bool operator<=(const CLinearTimeAbsolute& lta, const CLinearTimeAbsolute& ltb) noexcept;
|
|
|
|
//! Greater than or equal comparison
|
|
LIBMUX_API bool operator>=(const CLinearTimeAbsolute& lta, const CLinearTimeAbsolute& ltb) noexcept;
|
|
|
|
//! Add time delta to absolute time
|
|
LIBMUX_API CLinearTimeAbsolute operator+(const CLinearTimeAbsolute& lta, const CLinearTimeDelta& ltd) noexcept;
|
|
|
|
//! Subtract time delta from absolute time
|
|
LIBMUX_API CLinearTimeAbsolute operator-(const CLinearTimeAbsolute& lta, const CLinearTimeDelta& ltd) noexcept;
|
|
|
|
LIBMUX_API bool FieldedTimeToLinearTime(FIELDEDTIME *ft, int64_t *plt);
|
|
LIBMUX_API bool LinearTimeToFieldedTime(int64_t lt, FIELDEDTIME *ft);
|
|
|
|
class LIBMUX_API CLinearTimeDelta {
|
|
public:
|
|
// --- Chrono Integration ---
|
|
// Define the equivalent chrono duration type publicly
|
|
using HectoNanoseconds = std::chrono::duration<UnderlyingTickType, std::ratio<1, 10'000'000>>;
|
|
|
|
// Constructor from chrono duration (explicit to avoid accidental conversions)
|
|
explicit CLinearTimeDelta(const HectoNanoseconds& duration) noexcept;
|
|
|
|
// Conversion function to chrono duration
|
|
HectoNanoseconds ToChronoDuration() const noexcept;
|
|
|
|
// Optional: Conversion operator (allows implicit conversion TO chrono)
|
|
// operator HectoNanoseconds() const noexcept;
|
|
|
|
// Optional: Setter from chrono duration
|
|
void SetChronoDuration(const HectoNanoseconds& duration) noexcept;
|
|
|
|
// --- Existing Interface (Keep for compatibility) ---
|
|
|
|
// Static buffer - still recommend changing this eventually for thread safety
|
|
// For now, keep the original signature
|
|
UTF8* ReturnSecondsString(int nFracDigits = 7); // Default precision matches 100ns
|
|
|
|
// Constructors
|
|
CLinearTimeDelta() noexcept;
|
|
CLinearTimeDelta(UnderlyingTickType arg_t100ns) noexcept; // Use standard type
|
|
CLinearTimeDelta(CLinearTimeAbsolute t0, CLinearTimeAbsolute t1);
|
|
|
|
// Conversions (Consider adding const)
|
|
void ReturnTimeValueStruct(struct timeval* tv) const;
|
|
#ifdef HAVE_NANOSLEEP
|
|
void ReturnTimeSpecStruct(struct timespec* ts) const;
|
|
#endif // HAVE_NANOSLEEP
|
|
|
|
// Setters
|
|
void SetTimeValueStruct(const struct timeval* tv); // Pass const*
|
|
void SetMilliseconds(long arg_dwMilliseconds); // Or std::int64_t?
|
|
void SetSecondsString(const UTF8* arg_szSeconds); // Use const*
|
|
void SetSeconds(UnderlyingTickType arg_tSeconds); // Use standard type
|
|
void Set100ns(UnderlyingTickType arg_t100ns) noexcept; // Use standard type
|
|
|
|
// Getters (Mark const)
|
|
long ReturnMilliseconds() const; // Return type may truncate
|
|
UnderlyingTickType ReturnMicroseconds() const; // Return type fits
|
|
UnderlyingTickType Return100ns() const noexcept;
|
|
long ReturnDays() const; // Return type may truncate
|
|
long ReturnSeconds() const; // Return type may truncate
|
|
|
|
// Operators
|
|
CLinearTimeDelta& operator+=(const CLinearTimeDelta& ltd) noexcept;
|
|
CLinearTimeDelta& operator-=(const CLinearTimeDelta& ltd) noexcept;
|
|
|
|
// Comparison operators (declare as friends or non-members)
|
|
friend LIBMUX_API bool operator==(const CLinearTimeDelta& lta, const CLinearTimeDelta& ltb) noexcept;
|
|
friend LIBMUX_API bool operator!=(const CLinearTimeDelta& lta, const CLinearTimeDelta& ltb) noexcept;
|
|
friend LIBMUX_API bool operator<=(const CLinearTimeDelta& lta, const CLinearTimeDelta& ltb) noexcept;
|
|
friend LIBMUX_API bool operator<(const CLinearTimeDelta& lta, const CLinearTimeDelta& ltb) noexcept;
|
|
friend LIBMUX_API bool operator>=(const CLinearTimeDelta& lta, const CLinearTimeDelta& ltb) noexcept;
|
|
friend LIBMUX_API bool operator>(const CLinearTimeDelta& lta, const CLinearTimeDelta& ltb) noexcept;
|
|
|
|
private:
|
|
UnderlyingTickType m_tDelta; // The core data: count of 100ns intervals
|
|
|
|
// Still unsafe static buffer - keep for now
|
|
static UTF8 m_Buffer[I64BUF_SIZE * 2];
|
|
|
|
// Make relevant conversion factors accessible if needed internally
|
|
// static constexpr UnderlyingTickType FACTOR_100NS_PER_SECOND = 10'000'000; // etc.
|
|
};
|
|
|
|
// Non-member operators (use UnderlyingTickType, check division by zero)
|
|
LIBMUX_API CLinearTimeDelta operator-(const CLinearTimeAbsolute& ltaA, const CLinearTimeAbsolute& ltaB);
|
|
LIBMUX_API CLinearTimeDelta operator-(const CLinearTimeDelta& lta, const CLinearTimeDelta& ltb) noexcept;
|
|
LIBMUX_API CLinearTimeDelta operator*(const CLinearTimeDelta& ltd, int Scale);
|
|
LIBMUX_API UnderlyingTickType operator/(const CLinearTimeDelta& ltdA, const CLinearTimeDelta& ltdB);
|
|
LIBMUX_API bool operator==(const CLinearTimeDelta& lta, const CLinearTimeDelta& ltb) noexcept;
|
|
LIBMUX_API bool operator!=(const CLinearTimeDelta& lta, const CLinearTimeDelta& ltb) noexcept;
|
|
LIBMUX_API bool operator<=(const CLinearTimeDelta& lta, const CLinearTimeDelta& ltb) noexcept;
|
|
LIBMUX_API bool operator<(const CLinearTimeDelta& lta, const CLinearTimeDelta& ltb) noexcept;
|
|
LIBMUX_API bool operator>=(const CLinearTimeDelta& lta, const CLinearTimeDelta& ltb) noexcept;
|
|
LIBMUX_API bool operator>(const CLinearTimeDelta& lta, const CLinearTimeDelta& ltb) noexcept;
|
|
|
|
class LIBMUX_API mux_alarm
|
|
{
|
|
private:
|
|
bool alarm_set_{};
|
|
std::thread alarm_thread_;
|
|
mutable std::mutex mutex_; // mutable for const worker_started()
|
|
std::condition_variable cv_;
|
|
bool shutdown_{};
|
|
std::chrono::milliseconds alarm_period_{0};
|
|
bool wake_{}; // spurious-wakeup guard
|
|
bool thread_started_{}; // worker created lazily; see set()
|
|
|
|
void alarm_proc();
|
|
|
|
// Create the worker thread if it does not exist yet. Caller must hold
|
|
// mutex_. Deliberately NOT called from the constructor -- see alarm.cpp.
|
|
//
|
|
void start_thread_locked();
|
|
|
|
public:
|
|
std::atomic<bool> alarmed{};
|
|
mux_alarm();
|
|
~mux_alarm();
|
|
static void sleep(CLinearTimeDelta sleep_period);
|
|
static void surrender_slice();
|
|
void set(CLinearTimeDelta alarm_period);
|
|
void clear();
|
|
|
|
// True once the worker has been created (first successful set()).
|
|
// For unit tests that must lock "lazy start" without relying on an
|
|
// intermittent static-init deadlock. Thread-safe.
|
|
bool worker_started() const;
|
|
};
|
|
|
|
extern LIBMUX_API mux_alarm alarm_clock;
|
|
|
|
#define FACTOR_NANOSECONDS_PER_100NS 100
|
|
#define FACTOR_100NS_PER_MICROSECOND 10
|
|
#define FACTOR_100NS_PER_MILLISECOND 10000
|
|
#define EPOCH_OFFSET INT64_C(116444736000000000)
|
|
#define EARLIEST_VALID_DATE INT64_C(-9106391088000000000)
|
|
#define LATEST_VALID_DATE INT64_C(9222834959999999999)
|
|
#define TIMEUTIL_TIME_T_MIN_VALUE INT64_C(-922283539200)
|
|
#define TIMEUTIL_TIME_T_MAX_VALUE INT64_C(910638979199)
|
|
extern LIBMUX_API const int64_t FACTOR_MS_PER_SECOND;
|
|
extern LIBMUX_API const int64_t FACTOR_US_PER_SECOND;
|
|
extern LIBMUX_API const int64_t FACTOR_100NS_PER_SECOND;
|
|
extern LIBMUX_API const int64_t FACTOR_100NS_PER_MINUTE;
|
|
extern LIBMUX_API const int64_t FACTOR_100NS_PER_HOUR;
|
|
extern LIBMUX_API const int64_t FACTOR_100NS_PER_DAY;
|
|
extern LIBMUX_API const int64_t FACTOR_100NS_PER_WEEK;
|
|
|
|
const CLinearTimeDelta time_200us = 200*FACTOR_100NS_PER_MICROSECOND;
|
|
const CLinearTimeDelta time_5ms = 5*FACTOR_100NS_PER_MILLISECOND;
|
|
const CLinearTimeDelta time_250ms = 250*FACTOR_100NS_PER_MILLISECOND;
|
|
const CLinearTimeDelta time_1s = FACTOR_100NS_PER_SECOND;
|
|
const CLinearTimeDelta time_5s = 5*FACTOR_100NS_PER_SECOND;
|
|
const CLinearTimeDelta time_15s = 15*FACTOR_100NS_PER_SECOND;
|
|
const CLinearTimeDelta time_30s = 30*FACTOR_100NS_PER_SECOND;
|
|
const CLinearTimeDelta time_45s = 45*FACTOR_100NS_PER_SECOND;
|
|
const CLinearTimeDelta time_15m = 15*FACTOR_100NS_PER_MINUTE;
|
|
const CLinearTimeDelta time_30m = 30*FACTOR_100NS_PER_MINUTE;
|
|
const CLinearTimeDelta time_1w = FACTOR_100NS_PER_WEEK;
|
|
|
|
namespace TimezoneCache {
|
|
LIBMUX_API void initialize(void);
|
|
LIBMUX_API CLinearTimeDelta queryLocalOffsetAtUTC(const CLinearTimeAbsolute& utc_lta, bool* is_dst, std::string* tz_name = nullptr);
|
|
LIBMUX_API CLinearTimeDelta getCurrentLocalOffset(bool* is_dst);
|
|
}
|
|
|
|
// Each inline guards the INT64_MIN/-1 overflow (#805): on two's-complement
|
|
// hosts INT64_MIN / -1 is undefined and traps (#DE/SIGFPE) on x86, so a single
|
|
// softcode idiv()/mod()/remainder() would crash the whole server. Return the
|
|
// two's-complement-wrap result (INT64_MIN for division, 0 for remainder),
|
|
// matching the already-guarded out-of-line i64Mod/i64Remainder.
|
|
#ifdef SMALLEST_INT_GTE_NEG_QUOTIENT
|
|
LIBMUX_API int64_t i64Mod(int64_t x, int64_t y);
|
|
LIBMUX_API int64_t i64FloorDivision(int64_t x, int64_t y);
|
|
inline int64_t i64Division(int64_t x, int64_t y) { return (INT64_MIN == x && -1 == y) ? INT64_MIN : x / y; }
|
|
inline int64_t i64Remainder(int64_t x, int64_t y) { return (INT64_MIN == x && -1 == y) ? 0 : x % y; }
|
|
LIBMUX_API int iFloorDivisionMod(int x, int y, int *piMod);
|
|
#else // SMALLEST_INT_GTE_NEG_QUOTIENT
|
|
inline int64_t i64Mod(int64_t x, int64_t y) { return (INT64_MIN == x && -1 == y) ? 0 : x % y; }
|
|
inline int64_t i64FloorDivision(int64_t x, int64_t y) { return (INT64_MIN == x && -1 == y) ? INT64_MIN : x / y; }
|
|
LIBMUX_API int64_t i64Division(int64_t x, int64_t y);
|
|
LIBMUX_API int64_t i64Remainder(int64_t x, int64_t y);
|
|
inline int iFloorDivisionMod(int x, int y, int *piMod) \
|
|
{ \
|
|
*piMod = x % y; \
|
|
return x / y; \
|
|
}
|
|
#endif // SMALLEST_INT_GTE_NEG_QUOTIENT
|
|
|
|
// #1861 / #1472: signed int64 add/sub/mul is undefined on overflow. Softcode
|
|
// historically wraps (inc/dec already do so via uint64_t). Perform the
|
|
// operation in unsigned modular arithmetic and convert back so optimizers
|
|
// and sanitizers cannot invent crash or reordering UB.
|
|
//
|
|
inline int64_t i64Add(int64_t a, int64_t b)
|
|
{
|
|
return static_cast<int64_t>(
|
|
static_cast<uint64_t>(a) + static_cast<uint64_t>(b));
|
|
}
|
|
inline int64_t i64Sub(int64_t a, int64_t b)
|
|
{
|
|
return static_cast<int64_t>(
|
|
static_cast<uint64_t>(a) - static_cast<uint64_t>(b));
|
|
}
|
|
inline int64_t i64Mul(int64_t a, int64_t b)
|
|
{
|
|
return static_cast<int64_t>(
|
|
static_cast<uint64_t>(a) * static_cast<uint64_t>(b));
|
|
}
|
|
|
|
LIBMUX_API int iMod(int x, int y);
|
|
LIBMUX_API int iFloorDivision(int x, int y);
|
|
LIBMUX_API int64_t i64FloorDivisionMod(int64_t x, int64_t y, int64_t *piMod);
|
|
LIBMUX_API bool ParseDate(CLinearTimeAbsolute <a, const UTF8 *pDateString, bool *pbZoneSpecified);
|
|
LIBMUX_API void ParseDecimalSeconds(size_t n, const UTF8 *p, unsigned short *iMilli,
|
|
unsigned short *iMicro, unsigned short *iNano);
|
|
LIBMUX_API bool isLeapYear(long iYear);
|
|
LIBMUX_API void ConvertToSecondsString(UTF8 *buffer, int64_t n64, int nFracDigits);
|
|
LIBMUX_API bool ParseFractionalSecondsString(int64_t &i64, const UTF8 *str);
|
|
LIBMUX_API void GetUTCLinearTime(int64_t *plt);
|
|
LIBMUX_API bool do_convtime(const UTF8 *str, FIELDEDTIME *ft);
|
|
LIBMUX_API CLinearTimeDelta QueryLocalOffsetAtUTC
|
|
(
|
|
const CLinearTimeAbsolute <a,
|
|
bool *pisDST
|
|
);
|
|
|
|
extern LIBMUX_API const UTF8 *monthtab[12];
|
|
extern LIBMUX_API const char daystab[12];
|
|
extern LIBMUX_API const UTF8 *DayOfWeekString[7];
|
|
|
|
#endif // TIMEUTIL_H
|