diff --git a/include/lgi/common/DateTime.h b/include/lgi/common/DateTime.h --- a/include/lgi/common/DateTime.h +++ b/include/lgi/common/DateTime.h @@ -1,450 +1,467 @@ /// \file /// \author Matthew Allen /** * \defgroup Time Time and date handling * \ingroup Lgi */ #ifndef __DATE_TIME_H #define __DATE_TIME_H #include #include "lgi/common/StringClass.h" #define GDTF_DEFAULT 0 /// Format the date as DD/MM/YYYY /// \ingroup Time #define GDTF_DAY_MONTH_YEAR 0x001 /// Format the date as MM/DD/YYYY /// \ingroup Time #define GDTF_MONTH_DAY_YEAR 0x002 /// Format the date as YYYY/MM/DD /// \ingroup Time #define GDTF_YEAR_MONTH_DAY 0x004 /// The bit mask for the date /// \ingroup Time #define GDTF_DATE_MASK 0x00f /// Format the time as HH:MM and an am/pm marker /// \ingroup Time #define GDTF_12HOUR 0x010 /// Format the time as 24 hour time /// \ingroup Time #define GDTF_24HOUR 0x020 /// The bit mask for the time /// \ingroup Time #define GDTF_TIME_MASK 0x0f0 /// Format the date with a leading zero /// \ingroup Time #define GDTF_DAY_LEADINGZ 0x100 /// Format the month with a leading zero /// \ingroup Time #define GDTF_MONTH_LEADINGZ 0x200 +class LDateTime; + class LgiClass LTimeStamp { uint64_t ts = 0; public: constexpr static uint64_t WINDOWS_TICK = 10000000; constexpr static uint64_t SEC_TO_UNIX_EPOCH = 11644473600LL; LTimeStamp(time_t unixTime = 0) { if (unixTime) *this = unixTime; } uint64_t &Get() { return ts; } // Convert from unit time to LGI time stamp LTimeStamp &operator =(const time_t unixTime); + // Convert from LDateTime + LTimeStamp &operator =(const LDateTime &dt); // Convert from LGI time stamp to unix time - operator time_t() const; + time_t Unix() const; + + operator bool() const + { + return ts != 0; + } + + bool operator <(const LTimeStamp &b) const { return ts < b.ts; } + bool operator <=(const LTimeStamp &b) const { return ts <= b.ts; } + bool operator >(const LTimeStamp &b) const { return ts > b.ts; } + bool operator >=(const LTimeStamp &b) const { return ts >= b.ts; } + int64_t operator -(const LTimeStamp &b) const { return (int64_t)ts - (int64_t)b.ts; } + LTimeStamp &operator +=(int64_t i) { ts += i; return *this; } + LTimeStamp &operator -=(int64_t i) { ts -= i; return *this; } }; /// A date/time class /// /// This class interacts with system times represented as 64bit ints. The various OS support different /// formats for that 64bit int values. /// - On windows the system times are in 100-nanosecond intervals since /// January 1, 1601 (UTC), as per the FILETIME structure. /// - On Posix systems (Linux/Mac) the 64bit values are in milliseconds since January 1, 1800 UTC. /// This is just (unixTime + Offset1800) * 1000. /// If you are serializing these 64bit values you should take that into account, they are NOT cross platform. /// The LDirectory class uses the same 64bit values as accepted here for the file's last modified timestamp /// etc. To convert the 64bit values to seconds, divide by LDateTime::Second64Bit, useful for calculating /// the time in seconds between 2 LDateTime objects. /// /// \ingroup Time class LgiClass LDateTime // This class can't have a virtual table, because it's used in // LArray's which initialize with all zero bytes. { /// 1 - DaysInMonth int16 _Day; /// #### int16 _Year; /// Milliseconds: 0-999 int16 _Thousands; /// 1-12 int16 _Month; /// 0-59 int16 _Seconds; /// 0-59 int16 _Minutes; /// 0-23 (24hr) int16 _Hours; /// The current timezone of this object, defaults to the system timezone int16 _Tz; // in minutes (+10 == 600 etc) /// Combination of (#GDTF_DAY_MONTH_YEAR or #GDTF_MONTH_DAY_YEAR or #GDTF_YEAR_MONTH_DAY) and (#GDTF_12HOUR or #GDTF_24HOUR) uint16 _Format; /// The default formatting of datetimes static uint16 DefaultFormat; /// The default date separator character static char DefaultSeparator; public: LDateTime(const char *Init = NULL); LDateTime(time_t unixTime); LDateTime(const LTimeStamp &Ts); LDateTime(const LDateTime &dt) { *this = dt; } ~LDateTime(); /// Resolution of a second when using 64 bit int's /// \sa LDateTime::Get(int64), LDateTime::Set(int64) #ifdef WIN32 static constexpr int64_t Second64Bit = 10000000; #else static constexpr int64_t Second64Bit = 1000; #endif static constexpr int64_t MinuteLength = 60; // seconds static constexpr int64_t HourLength = MinuteLength * 60; // seconds static constexpr int64_t DayLength = HourLength * 24; // seconds static constexpr const char *WeekdaysShort[7] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; static constexpr const char *WeekdaysLong[7] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; static constexpr const char *MonthsShort[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; static constexpr const char *MonthsLong[12] = {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}; // On Posix systems this allows representation of times before 1/1/1970. // The Lgi epoch is considered to be 1/1/1800 instead and this offset converts // from one to the other. static constexpr int64_t Offset1800 = 5364662400; // Seconds from 1/1/1800 to 1/1/1970 /// Returns true if all the components are in a valid range bool IsValid() const; /// Sets the date to an NULL state void Empty(); /// Returns the day int Day() const { return _Day; } /// Sets the day void Day(int d) { _Day = d; } /// Returns the month int Month() const { return _Month; } /// Sets the month void Month(int m) { _Month = m; } /// Sets the month by it's name void Month(char *m); /// Returns the year int Year() const { return _Year; } /// Sets the year void Year(int y) { _Year = y; } /// Returns the millisecond part of the time int Thousands() const { return _Thousands; } /// Sets the millisecond part of the time void Thousands(int t) { _Thousands = t; } /// Returns the seconds part of the time int Seconds() const { return _Seconds; } /// Sets the seconds part of the time void Seconds(int s) { _Seconds = s; } /// Returns the minutes part of the time int Minutes() const { return _Minutes; } /// Sets the minutes part of the time void Minutes(int m) { _Minutes = m; } /// Returns the hours part of the time int Hours() const { return _Hours; } /// Sets the hours part of the time void Hours(int h) { _Hours = h; } /// Returns the timezone of this current date time object in minutes (+10 = 600) int GetTimeZone() const { return _Tz; } /// Returns the timezone in hours double GetTimeZoneHours() const { return (double)_Tz / 60.0; } /// Sets the timezone of this current object.in minutes (+10 = 600) void SetTimeZone ( /// The new timezone (in minutes, 600 = +10:00) int Tz, /// True if you want to convert the date and time to the new zone, /// False if you want to leave the date/time as it is. bool ConvertTime ); /// Set this object to UTC timezone, changing the other members as /// needed LDateTime &ToUtc(bool AssumeLocal = false) { if (AssumeLocal) _Tz = SystemTimeZone(); SetTimeZone(0, true); return *this; } /// \returns the UTC version of this object. LDateTime Utc() const { LDateTime dt = *this; return dt.ToUtc(); } /// Changes the timezone to the local zone, changing other members /// as needed. LDateTime &ToLocal(bool AssumeUtc = false) { if (AssumeUtc) _Tz = 0; SetTimeZone(SystemTimeZone(), true); return *this; } /// \returns the local time version of this object. LDateTime Local() const { LDateTime dt = *this; return dt.ToLocal(); } /// Gets the current formatting of the date, the format only effects /// the representation of the date when converted to/from a string. /// \returns a bit mask of (#GDTF_DAY_MONTH_YEAR or #GDTF_MONTH_DAY_YEAR or #GDTF_YEAR_MONTH_DAY) and (#GDTF_12HOUR or #GDTF_24HOUR) uint16 GetFormat() const { return _Format; } /// Sets the current formatting of the date, the format only effects /// the representation of the date when converted to/from a string void SetFormat ( /// a bit mask of (#GDTF_DAY_MONTH_YEAR or #GDTF_MONTH_DAY_YEAR or #GDTF_YEAR_MONTH_DAY) and (#GDTF_12HOUR or #GDTF_24HOUR) uint16 f ) { _Format = f; } /// \returns zero based index of weekday, or -1 if not found. static int IsWeekDay(const char *s); /// \returns zero based index of month, or -1 if not found. static int IsMonth(const char *s); /// The default format for the date when formatted as a string static uint16 GetDefaultFormat(); /// Sets the default format for the date when formatted as a string static void SetDefaultFormat(uint16 f) { DefaultFormat = f; } /// Gets the data and time as a LString LString Get() const; /// Gets the date and time as a string /// \sa LDateTime::GetFormat() void Get(char *Str, size_t SLen) const; /// Gets the data and time as a 64 bit int (os specific) bool Get(LTimeStamp &s) const; /// Gets just the date as a string /// \sa LDateTime::GetFormat() /// \returns The number of characters written to 'Str' int GetDate(char *Str, size_t SLen) const; /// Gets just the date as a LString /// \sa LDateTime::GetFormat() LString GetDate() const; /// Gets just the time as a string /// \sa LDateTime::GetFormat() /// \returns The number of characters written to 'Str' int GetTime(char *Str, size_t SLen) const; /// Gets just the time as a LString /// \sa LDateTime::GetFormat() LString GetTime() const; // Unix epoch support uint64_t GetUnix(); bool SetUnix(uint64_t s); /// Returns the 64bit LTimeStamp. - uint64 Ts() const; + LTimeStamp Ts() const; /// Get the timestamp in a format compatible with the current operating system APIs. /// \returns the timestamp or zero on error. uint64_t OsTime() const; bool OsTime(uint64_t ts); /// Get the current time... static LDateTime Now(); /// Sets the date and time to the system clock LDateTime &SetNow(); /// Parses a date time from a string /// \sa LDateTime::GetFormat() bool Set(const char *Str); /// Sets the date and time from a 64 bit int (os specific) - bool Set(const LTimeStamp &s); + bool Set(LTimeStamp &s); /// Sets the time from a unit time_t bool Set(time_t tt); /// Parses the date from a string /// \sa LDateTime::GetFormat() bool SetDate(const char *Str); /// Parses the time from a string /// \sa LDateTime::GetFormat() bool SetTime(const char *Str); /// Parses the date time from a free form string bool Parse(LString s); /// Set to a tm struct bool Set(struct tm *t, bool inferTimezone); /// Describes the perios between this and 'to' in the form: /// ##d ##h ##m ##s /// Order of the dates isn't important. LString DescribePeriod(LDateTime to); static LString DescribePeriod(double seconds); /// \returns true if 'd' is on the same day as this object bool IsSameDay(LDateTime &d) const; /// \returns true if 'd' is on the same month as this object bool IsSameMonth(LDateTime &d) const; /// \returns true if 'd' is on the same year as this object bool IsSameYear(LDateTime &d) const; /// \returns the start of day, same date but time: 00:00:00 LDateTime StartOfDay() const; /// \returns the end of day, same date but time: 23:59:59 LDateTime EndOfDay() const; /// \returns whether a year is a leap year or not bool IsLeapYear ( /// Pass a specific year here, or ignore to return if the current Date/Time is in a leap year. int Year = -1 ) const; /// Returns the day of the week as an index, 0=sun, 1=mon, 2=teus etc int DayOfWeek() const; /// \returns the number of days in the current month int DaysInMonth() const; /// Adds a number of seconds to the current date/time void AddSeconds(int64 Seconds); /// Adds a number of minutes to the current date/time void AddMinutes(int64 Minutes); /// Adds a number of hours to the current date/time void AddHours(int64 Hours); /// Adds a number of days to the current date/time bool AddDays(int64 Days); /// Adds a number of months to the current date/time void AddMonths(int64 Months); /// Adds years void AddYears(int yr) { _Year += yr; } /// The system timezone including daylight savings offset in minutes, +10 would return 600 static int SystemTimeZone(bool ForceUpdate = false); /// Any daylight savings offset applied to TimeZone(), in minutes. e.g. to retreive the /// timezone uneffected by DST use TimeZone() - TimeZoneOffset(). static int SystemTimeZoneOffset(); /// Daylight savings info record struct LgiClass LDstInfo { /// Timestamp where the DST timezone changes to 'Offset' LTimeStamp Utc; /// The new offset in minutes (e.g. 600 = +10 hours) int Offset; LDateTime GetLocal(); }; /// Retreives daylight savings start and end events for a given period. One event will be emitted /// for the current DST/TZ setting at the datetime specified by 'Start', followed by any changes that occur /// between that and the 'End' datetime. To retreive just the DST info for start, use NULL for end. static bool GetDaylightSavingsInfo ( /// [Out] The array to receive DST info. At minimum one record will be returned /// matching the TZ in place for the start datetime. LArray &Out, /// [In] The start date that you want DST info for. LDateTime &Start, /// [Optional In] The end of the period you want DST info for. LDateTime *End = 0 ); /// Using the DST info this will convert 'dt' from UTC to local static bool DstToLocal(LArray &Dst, LDateTime &dt); /// Decodes an email date into the current instance bool Decode(const char *In); /// Returns a month index from a month name static int MonthFromName(const char *Name); // File int Sizeof(); bool Serialize(class LFile &f, bool Write); bool Serialize(class LDom *Props, char *Name, bool Write); // operators bool operator <(const LDateTime &dt) const; bool operator <=(const LDateTime &dt) const; bool operator >(const LDateTime &dt) const; bool operator >=(const LDateTime &dt) const; bool operator ==(const LDateTime &dt) const; bool operator !=(const LDateTime &dt) const; int Compare(const LDateTime *d) const; LDateTime operator -(const LDateTime &dt); LDateTime operator +(const LDateTime &dt); int DiffMonths(const LDateTime &dt); LDateTime &operator =(struct tm *t); LDateTime &operator =(const LDateTime &t); LDateTime &operator =(LDateTime const *t) { if (t) *this = *t; return *this; } /// LDom interface. /// /// Even though we don't inherit from a LDom class this class supports the same /// interface for ease of use. Currently there are cases where LDateTime is used /// in LArray's which don't implement calling a constructor (they init with all /// zeros). bool GetVariant(const char *Name, class LVariant &Value, char *Array = NULL); bool SetVariant(const char *Name, class LVariant &Value, char *Array = NULL); bool CallMethod(const char *Name, class LVariant *ReturnValue, LArray &Args); }; /// Time zone information struct LTimeZone { public: /// The offset from UTC float Offset; /// The name of the zone const char *Text; }; /// A list of all known timezones. extern LTimeZone LTimeZones[]; #ifdef _DEBUG LgiFunc bool LDateTime_Test(); #endif #endif diff --git a/src/common/Db/DbTable.cpp b/src/common/Db/DbTable.cpp --- a/src/common/Db/DbTable.cpp +++ b/src/common/Db/DbTable.cpp @@ -1,1147 +1,1147 @@ #include "lgi/common/Lgi.h" #include "lgi/common/DbTable.h" /////////////////////////////////////////////////////////////////// #define OBJ_HEAD(magic) \ char *Start = p.c; \ uint32_t *Sz = NULL; \ if (Write) \ { \ *p.u32++ = magic; \ Sz = p.u32++; \ } \ else if (*p.u32 != FieldMagic) \ return false; \ else \ { \ p.u32++; \ Sz = p.u32++; \ } #define SERIALIZE(type, var) \ if (Write) *p.type++ = var; \ else var = *p.type++; #define SERIALIZE_FN(fn, type) \ if (Write) *p.type++ = fn(); \ else fn(*p.type++); #define SERIALIZE_CAST(cast, type, var) \ if (Write) *p.type++ = var; \ else var = (cast) *p.type++; #define OBJ_TAIL() \ if (Write) \ *Sz = (uint32_t) (p.c - Start); \ else \ p.c = Start + *Sz; \ LAssert(Sizeof() == *Sz); \ return true; #define DB_DATE_SZ \ ( \ 2 + /* Year */ \ 1 + /* Month */ \ 1 + /* Day */ \ 1 + /* Hour */ \ 1 + /* Min */ \ 1 + /* Sec */ \ 2 /* TimeZone */ \ ) /////////////////////////////////////////////////////////////////// enum OffType { VariableOff, FixedOff }; enum DbMagic { TableMagic = Lgi4CC("tbl\0"), FieldMagic = Lgi4CC("fld\0"), RowMagic = Lgi4CC("row\0"), }; /////////////////////////////////////////////////////////////////// inline bool IsFixed(LVariantType t) { return t != GV_STRING && t != GV_BINARY; } struct Info { LVariantType Type; int Index; Info(LVariantType t = GV_NULL, int index = -1) { Type = t; Index = index; } bool operator !=(const Info &i) { return Type != i.Type && Index != i.Index; } bool operator ==(const Info &i) { return !(*this != i); } }; ////////////////////////////////////////////////////////////////////////////////////// struct DbTablePriv { // Fields unsigned Fixed; // Count of fixed size fields. unsigned FixedSz; // Byte size of fixed size fields. unsigned Variable; // Count of variable sized fields. LArray Fields; LArray FixedOffsets; LHashTbl, Info> Map; // Rows int Rows; LDbRow *First, *Last; LArray Data; bool Dirty; // Indexes LArray Indexes; // Methods DbTablePriv() : Map(0, Info()) { First = Last = NULL; Rows = 0; Fixed = 0; Variable = 0; FixedSz = 0; Dirty = false; } ~DbTablePriv() { Indexes.DeleteObjects(); } void SetDirty(bool b = true) { Dirty = b; } LDbField *FindField(int Id) { for (unsigned i=0; i 0) { LAssert(!"Adding fields with records not supported yet."); return false; } LDbField f; f.Id = Id; f.Type = Type; f.Offset = -1; if (IsFixed(Type)) Fields.AddAt(Fixed, f); else // Is variable size Fields.New() = f; return OffsetFields(); } bool DeleteField(int Id) { for (unsigned i=0; i 0); Fixed--; } else { LAssert(i >= Fixed && Variable > 0); Variable--; } Fields.DeleteAt(i, true); } } return OffsetFields(); } bool DeleteRow(LDbRow *r) { if (r->Prev) { r->Prev->Next = r->Next; } else { LAssert(r == First); First = r->Next; } if (r->Next) { r->Next->Prev = r->Prev; } else { LAssert(r == Last); Last = r->Prev; } r->Prev = NULL; r->Next = NULL; Rows--; DeleteObj(r); return true; } }; ////////////////////////////////////////////////////////////////////////////////////// DbIndex::DbIndex(DbTablePriv *priv) { d = priv; } DbIndex::~DbIndex() { LAssert(d->Indexes.HasItem(this)); d->Indexes.Delete(this); } DbArrayIndex::DbArrayIndex(DbTablePriv *priv) : DbIndex(priv) { Fld.Id = -1; Fld.Type = GV_NULL; Fld.Offset = -1; } bool DbArrayIndex::OnNew(LDbRow *r) { return Delete(r); } bool DbArrayIndex::OnDelete(LDbRow *r) { Add(r); return true; } struct CompareParams { int Id; bool Ascend; CompareParams(int i, bool a) { Id = i; Ascend = a; } }; bool DbArrayIndex::Sort(LDbField *fld, bool ascend) { if (!fld) return false; Fld = *fld; Ascend = ascend; CompareParams p(Fld.Id, Ascend); switch (Fld.Type) { case GV_INT32: case GV_INT64: LArray::Sort([&p](auto a, auto b) { int64 A = (*a)->GetInt(p.Id); int64 B = (*b)->GetInt(p.Id); return (int) (p.Ascend ? A - B : B - A); }); break; case GV_STRING: LArray::Sort([&p](auto a, auto b) { const char *A = (*a)->GetStr(p.Id); if (!A) A = ""; const char *B = (*b)->GetStr(p.Id); if (!B) B = ""; return p.Ascend ? stricmp(A, B) : stricmp(B, A); }); break; case GV_DATETIME: LArray::Sort([&p](auto a, auto b) { const LDateTime *A = (*a)->GetDate(p.Id); const LDateTime *B = (*b)->GetDate(p.Id); if (!A || !B) { LAssert(0); return 0; } - uint64 UtcA, UtcB; + LTimeStamp UtcA, UtcB; if (!A->Get(UtcA) || !B->Get(UtcB)) { LAssert(0); return 0; } int64 r = p.Ascend ? UtcA - UtcB : UtcB - UtcA; if (r < 0) return -1; if (r > 0) return 1; return 0; }); break; default: LAssert(0); return false; } return true; } bool DbArrayIndex::Resort() { return Sort(&Fld, Ascend); } /////////////////////////////////////////////////////////////////////////////////// size_t LDbDate::Sizeof() { return DB_DATE_SZ; } bool LDbDate::Serialize(LPointer &p, LDateTime &dt, bool Write) { #ifdef _DEBUG char *Start = p.c; #endif SERIALIZE_FN(dt.Year, u16); SERIALIZE_FN(dt.Month, u8); SERIALIZE_FN(dt.Day, u8); SERIALIZE_FN(dt.Hours, u8); SERIALIZE_FN(dt.Minutes, u8); SERIALIZE_FN(dt.Seconds, u8); uint16 Tz = dt.GetTimeZone(); SERIALIZE(u16, Tz); if (!Write) dt.SetTimeZone(Tz, false); LAssert(p.c - Start == DB_DATE_SZ); return true; } /////////////////////////////////////////////////////////////////////////////////// int LDbField::DataSize() { switch (Type) { case GV_BOOL: return 1; case GV_INT32: return 4; case GV_INT64: return 8; case GV_DATETIME: return DB_DATE_SZ; default: LAssert(!"Impl me."); break; } return 0; } size_t LDbField::Sizeof() { return 8 + sizeof(Id) + 2 + // Type sizeof(Offset); } bool LDbField::Serialize(LPointer &p, bool Write) { OBJ_HEAD(FieldMagic); SERIALIZE(s32, Id); SERIALIZE(s32, Offset); SERIALIZE_CAST(LVariantType, u16, Type); OBJ_TAIL(); } /////////////////////////////////////////////////////////////////////////////////// int LDbRow::HeaderSz = sizeof(uint32_t) << 1; // Magic + Size LDbRow::LDbRow(DbTablePriv *priv) { d = priv; Next = NULL; Prev = NULL; Pos = -1; Base.c = NULL; Offsets[FixedOff] = d->FixedOffsets.AddressOf(); Offsets[VariableOff] = NULL; } LDbRow::~LDbRow() { } size_t LDbRow::GetFields() { return d->Fields.Length(); } LDbField &LDbRow::GetField(size_t Idx) { return d->Fields[Idx]; } struct VarBlock : public LRange { int Index; }; uint32_t LDbRow::GetInitialSize() { return HeaderSz + d->FixedSz + (d->Variable * sizeof(uint32_t)); } bool LDbRow::Compact() { if (Edit.Length()) { // The variable sized fields can get fragmented, this function removes unused space. LArray v; for (unsigned i=0; iVariable; i++) { if (Offsets[VariableOff][i] >= 0) { VarBlock &b = v.New(); b.Index = i; b.Start = Offsets[VariableOff][i]; b.Len = strlen(Base.c + b.Start) + 1; } } v.Sort([](auto a, auto b) { return (int) (a->Start - b->Start); }); uint32_t Pos = GetInitialSize(); for (unsigned i=0; i (ssize_t)Pos) { // Move block down memcpy(Base.c + Pos, Base.c + b.Start, b.Len); Offsets[VariableOff][b.Index] = Pos; b.Start = Pos; } Pos = (int32) (b.Start + b.Len); } if (Base.u32[1] > Pos) Base.u32[1] = Pos; } return true; } LString LDbRow::ToString() { LString::Array a; a.SetFixedLength(false); for (unsigned i=0; iFields.Length(); i++) { LDbField &f = d->Fields[i]; switch (f.Type) { case GV_INT32: case GV_INT64: a.New().Printf(LPrintfInt64, GetInt(f.Id)); break; case GV_STRING: { LString s = GetStr(f.Id); if (s.Length() > 0) a.New() = s; else a.New() = "NULL"; break; } case GV_DATETIME: { const LDateTime *dt = GetDate(f.Id); if (dt) a.New() = dt->Get(); else a.New() = "NULL"; break; } default: LAssert(0); break; } } LString Sep(", "); return Sep.Join(a); } uint32_t LDbRow::Size(uint32_t Set) { if (Base.c) { if (Set) Base.u32[1] = Set; return Base.u32[1]; } LAssert(0); return 0; } bool LDbRow::Delete() { return d->DeleteRow(this); } bool LDbRow::CopyProps(LDataPropI &p) { for (size_t i=0; iMap.Find(id); if (i.Index < 0 || i.Type != GV_STRING || !Base.c) return NULL; LAssert((unsigned)i.Index < d->Variable); return Base.c + Offsets[VariableOff][i.Index]; } bool LDbRow::StartEdit() { if (Edit.Length() == 0) { if (Base.c) { if (!Edit.Length(Base.u32[1])) return false; memcpy(Edit.AddressOf(), Base.c, Base.u32[1]); } else { int InitialSize = GetInitialSize(); if (!Edit.Length(InitialSize)) return false; Base.c = Edit.AddressOf(); Base.u32[0] = RowMagic; Base.u32[1] = InitialSize; // Initialize fixed fields to zero memset(Base.c + 8, 0, d->FixedSz); if (d->Variable > 0) { // And the variable offset table to -1 memset(Base.c + 8 + d->FixedSz, 0xff, InitialSize - d->FixedSz); } } PostEdit(); } return true; } void LDbRow::PostEdit() { Base.c = Edit.AddressOf(); if (Base.c) { Size((uint32_t)Edit.Length()); Offsets[VariableOff] = (int32*) Edit.AddressOf(8 + d->FixedSz); } } Store3Status LDbRow::SetStr(int id, const char *str) { Info i = d->Map.Find(id); if (i.Index < 0) return Store3Error; if (!StartEdit()) return Store3Error; size_t len = str ? strlen(str) + 1 : 1; Offsets[VariableOff][i.Index] = (int32)Edit.Length(); if (str) Edit.Add((char*)str, len); else Edit.Add((char*)"", 1); PostEdit(); d->SetDirty(); return Store3Success; } int64 LDbRow::GetInt(int id) { Info i = d->Map.Find(id); if (i.Index < 0 || !Base.c) return -1; LPointer p = { Base.s8 + Offsets[FixedOff][i.Index] }; if (i.Type == GV_INT32) return *p.s32; if (i.Type == GV_INT64) return *p.s64; return -1; } Store3Status LDbRow::SetInt(int id, int64 val) { Info i = d->Map.Find(id); if (i.Index < 0) return Store3Error; if (!Base.c) StartEdit(); LPointer p = { Base.s8 + Offsets[FixedOff][i.Index] }; if (i.Type == GV_INT32) { *p.s32 = (int32)val; d->SetDirty(); return Store3Success; } if (i.Type == GV_INT64) { *p.s64 = val; d->SetDirty(); return Store3Success; } return Store3Error; } const LDateTime *LDbRow::GetDate(int id) { Info i = d->Map.Find(id); if (i.Index < 0 || !Base.c) return NULL; LPointer p = { Base.s8 + Offsets[FixedOff][i.Index] }; if (i.Type != GV_DATETIME) return NULL; LDbDate dd; dd.Serialize(p, Cache, false); return &Cache; } Store3Status LDbRow::SetDate(int id, const LDateTime *dt) { Info i = d->Map.Find(id); if (i.Index < 0 || !dt) return Store3Error; if (!Base.c) StartEdit(); LPointer p = { Base.s8 + Offsets[FixedOff][i.Index] }; if (i.Type != GV_DATETIME) return Store3Error; LDbDate dd; LDateTime n = *dt; dd.Serialize(p, n, true); d->SetDirty(); return Store3Success; } LVariant *LDbRow::GetVar(int id) { LAssert(0); return NULL; } Store3Status LDbRow::SetVar(int id, LVariant *i) { LAssert(0); return Store3Error; } LDataPropI *LDbRow::GetObj(int id) { LAssert(0); return NULL; } Store3Status LDbRow::SetObj(int id, LDataPropI *i) { LAssert(0); return Store3Error; } LDataIt LDbRow::GetList(int id) { LAssert(0); return NULL; } Store3Status LDbRow::SetRfc822(LStreamI *Rfc822Msg) { LAssert(0); return Store3Error; } //////////////////////////////////////////////////////////////////////////////////// LDbTable::LDbTable(const char *File) { d = new DbTablePriv; if (File) Serialize(File, false); } LDbTable::~LDbTable() { Empty(); DeleteObj(d); } bool LDbTable::AddField(int Id, LVariantType Type) { return d->AddField(Id, Type); } bool LDbTable::DeleteField(int Id) { return d->DeleteField(Id); } int LDbTable::GetFields() { return (int)d->Fields.Length(); } LDbField &LDbTable::GetField(int Idx) { return d->Fields[Idx]; } bool LDbTable::Iterate(LDbRow *&Ptr) { if (Ptr && Ptr->d != d) { LAssert(!"Wrong table."); return false; } if (Ptr) Ptr = Ptr->Next; else Ptr = d->First; return Ptr != NULL; } int LDbTable::GetRows() { return d->Rows; } LDbRow *LDbTable::NewRow() { LDbRow *r = new LDbRow(d); if (!r) return NULL; if (d->Last) { LAssert(d->Last->Next == NULL); d->Last->Next = r; r->Prev = d->Last; d->Last = r; d->Rows++; } else { LAssert(d->Rows == 0); d->First = d->Last = r; d->Rows = 1; } for (unsigned i=0; iIndexes.Length(); i++) d->Indexes[i]->OnNew(r); return r; } bool LDbTable::Empty() { d->Indexes.DeleteObjects(); LDbRow *n; for (LDbRow *r = d->First; r; r = n) { n = r->Next; delete r; } d->First = d->Last = NULL; d->Rows = 0; d->Fixed = 0; d->FixedSz = 0; d->Variable = 0; d->Fields.Empty(); d->FixedOffsets.Empty(); d->Map.Empty(); d->Data.Empty(); return true; } bool LDbTable::DeleteRow(LDbRow *r) { if (!r || r->d != d) return false; for (unsigned i=0; iIndexes.Length(); i++) d->Indexes[i]->OnDelete(r); return d->DeleteRow(r); } DbArrayIndex *LDbTable::Sort(int Id, bool Ascending) { LDbField *f = d->FindField(Id); if (!f) return NULL; // Collect all the records DbArrayIndex *i = new DbArrayIndex(d); if (!i) return NULL; if (!i->Length(d->Rows)) { delete i; return NULL; } int Idx = 0; for (LDbRow *r = d->First; r; r = r->Next) (*i)[Idx++] = r; LAssert(Idx == d->Rows); // Sort them i->Sort(f, Ascending); // Save the index d->Indexes.Add(i); return i; } bool LDbTable::Serialize(const char *Path, bool Write) { LFile f; if (!f.Open(Path, Write ? O_WRITE : O_READ)) return false; if (Write) { if (d->Fields.Length() > 0) { size_t Sz = sizeof(uint32_t) + d->Fields.Length() * d->Fields[0].Sizeof(); if (d->Data.Length() < Sz) d->Data.Length(Sz); LPointer p = {(int8*)d->Data.AddressOf()}; *p.u32++ = TableMagic; for (unsigned i=0; iFields.Length(); i++) { if (!d->Fields[i].Serialize(p, true)) return false; } f.SetSize(0); size_t Bytes = p.c - d->Data.AddressOf(); LAssert(Bytes == Sz); if (f.Write(d->Data.AddressOf(), Bytes) != Bytes) return false; } for (LDbRow *r = d->First; r; r = r->Next) { // Fix the size before we write LAssert(r->Base.u32[0] == RowMagic); if (r->Edit.Length()) r->Compact(); if (f.Write(r->Base.c, r->Size()) != r->Size()) return false; } } else { Empty(); // Read all the data into memory if (!d->Data.Length((size_t)f.GetSize())) return false; auto Rd = f.Read(d->Data.AddressOf(0), (ssize_t)f.GetSize()); if (Rd != f.GetSize()) return false; LPointer p = {(int8*)d->Data.AddressOf()}; if (*p.u32++ != TableMagic) return false; // Create all the fields char *End = p.c + d->Data.Length(); d->Fixed = 0; d->FixedSz = 0; d->Variable = 0; d->Map.Empty(); d->FixedOffsets.Empty(); while (p.c < End - 8 && *p.u32 == FieldMagic) { LDbField &f = d->Fields.New(); if (!f.Serialize(p, false)) return false; if (IsFixed(f.Type)) { d->FixedSz += f.DataSize(); d->FixedOffsets[d->Fixed] = f.Offset; d->Map.Add(f.Id, Info(f.Type, d->Fixed++)); } else { d->Map.Add(f.Id, Info(f.Type, d->Variable++)); } } // Create the rows d->Rows = 0; while (p.c < End - 8 && *p.u32 == RowMagic) { LDbRow *r = new LDbRow(d); if (!r) return false; r->Base = p; r->Offsets[FixedOff] = d->FixedOffsets.AddressOf(); r->Offsets[VariableOff] = (int32*) (r->Base.c + 8 + d->FixedSz); if (d->Last) { r->Prev = d->Last; d->Last->Next = r; d->Last = r; d->Rows++; } else { d->First = d->Last = r; d->Rows = 1; } p.c += p.u32[1]; } } return true; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// enum TestFields { TestUid = 100, TestInt32, TestString, TestDate, TestString2, }; LString LDbTable::ToString() { LString::Array a; a.SetFixedLength(false); for (LDbRow *r = NULL; Iterate(r); ) { LString s = r->ToString(); a.Add(s); } LString Sep("\n"); return Sep.Join(a); } bool LDbTable::UnitTests() { LDbTable t; t.AddField(TestUid, GV_INT64); t.AddField(TestInt32, GV_INT32); t.AddField(TestString, GV_STRING); t.AddField(TestDate, GV_DATETIME); t.AddField(TestString2, GV_STRING); const char *Days[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; for (unsigned i=0; iSetInt(TestUid, 1000+i); r->SetInt(TestInt32, i * 3); if (i % 2) { r->SetStr(TestString, Days[i]); r->SetStr(TestString2, "asdasdasdasdasd"); } else { r->SetStr(TestString2, "asdasdasdasdasd"); r->SetStr(TestString, Days[i]); } } LgiTrace("Export table:\n"); for (LDbRow *r = NULL; t.Iterate(r); ) { int64 Id = r->GetInt(TestUid); int64 Int = r->GetInt(TestInt32); auto s = r->GetStr(TestString); auto s2 = r->GetStr(TestString2); LgiTrace("\t%i: %i, %s, %s\n", (int)Id, (int)Int, s, s2); } const char *File = "test.db"; t.Serialize(File, true); LDbTable in; if (!in.Serialize(File, false)) return false; LgiTrace("Import table:\n"); LDbRow *Nine = NULL; for (LDbRow *r = NULL; in.Iterate(r); ) { auto Start = LMicroTime(); auto Id = r->GetInt(TestUid); auto Int = r->GetInt(TestInt32); auto s = r->GetStr(TestString); auto s2 = r->GetStr(TestString2); auto Time = LMicroTime() - Start; LgiTrace("\t%i: %i, %s, %s (%.4fms)\n", (int)Id, (int)Int, s, s2, (double)Time/1000.0); if (Int == 9) Nine = r; else if (Int == 12) r->SetStr(TestString2, "This is a new string."); else if (Int == 15) r->SetStr(TestString, NULL); else if (Int == 6) r->SetInt(TestInt32, 66); } if (!Nine) return false; in.DeleteRow(Nine); LgiTrace("Post delete:\n"); for (LDbRow *r = NULL; in.Iterate(r); ) { int64 Id = r->GetInt(TestUid); int64 Int = r->GetInt(TestInt32); auto s = r->GetStr(TestString); auto s2 = r->GetStr(TestString2); LgiTrace("\t%i: %i, %s, %s\n", (int)Id, (int)Int, s, s2); } if (!in.Serialize(File, true)) return false; if (!t.Serialize(File, false)) return false; LgiTrace("Reread:\n"); for (LDbRow *r = NULL; t.Iterate(r); ) { int64 Id = r->GetInt(TestUid); int64 Int = r->GetInt(TestInt32); auto s = r->GetStr(TestString); auto s2 = r->GetStr(TestString2); LgiTrace("\t%i: %i, %s, %s\n", (int)Id, (int)Int, s, s2); } return true; } diff --git a/src/common/General/DateTime.cpp b/src/common/General/DateTime.cpp --- a/src/common/General/DateTime.cpp +++ b/src/common/General/DateTime.cpp @@ -1,2337 +1,2348 @@ /* ** FILE: LDateTime.cpp ** AUTHOR: Matthew Allen ** DATE: 11/11/98 ** DESCRIPTION: Scribe Date Time Object ** ** Copyright (C) 1998, Matthew Allen ** fret@memecode.com */ #define _DEFAULT_SOURCE #include #include #include #include #include #if defined(MAC) #include #endif #ifdef WINDOWS #include #endif #include "lgi/common/Lgi.h" #include "lgi/common/DateTime.h" #include "lgi/common/DocView.h" constexpr const char *LDateTime::WeekdaysShort[7]; constexpr const char *LDateTime::WeekdaysLong[7]; constexpr const char *LDateTime::MonthsShort[12]; constexpr const char *LDateTime::MonthsLong[12]; #if !defined(WINDOWS) #define MIN_YEAR 1800 #endif #if defined(LINUX) #define USE_ZDUMP 1 #elif defined(HAIKU) #include "lgi/common/TimeZoneInfo.h" #endif #define DEBUG_DST_INFO 0 ////////////////////////////////////////////////////////////////////////////// uint16 LDateTime::DefaultFormat = GDTF_DEFAULT; char LDateTime::DefaultSeparator = '/'; uint16 LDateTime::GetDefaultFormat() { if (DefaultFormat == GDTF_DEFAULT) { #ifdef WIN32 TCHAR s[80] = _T("1"); GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_IDATE, s, CountOf(s)); switch (_tstoi(s)) { case 0: DefaultFormat = GDTF_MONTH_DAY_YEAR; break; default: case 1: DefaultFormat = GDTF_DAY_MONTH_YEAR; break; case 2: DefaultFormat = GDTF_YEAR_MONTH_DAY; break; } GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_ITIME, s, sizeof(s)); if (_tstoi(s) == 1) { DefaultFormat |= GDTF_24HOUR; } else { DefaultFormat |= GDTF_12HOUR; } if (GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SDATE, s, sizeof(s))) DefaultSeparator = (char)s[0]; if (GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SSHORTDATE, s, sizeof(s))) { char Sep[] = { DefaultSeparator, '/', '\\', '-', '.', 0 }; LString Str = s; auto t = Str.SplitDelimit(Sep); for (int i=0; i= low && (v) <= high) bool LDateTime::IsValid() const { return InRange(_Day, 1, 31) && InRange(_Year, 1600, 2100) && InRange(_Thousands, 0, 999) && InRange(_Month, 1, 12) && InRange(_Seconds, 0, 59) && InRange(_Minutes, 0, 59) && InRange(_Hours, 0, 23) && InRange(_Tz, -780, 780); } void LDateTime::SetTimeZone(int NewTz, bool ConvertTime) { if (ConvertTime && NewTz != _Tz) { // printf("SetTimeZone: %i\n", NewTz - _Tz); AddMinutes(NewTz - _Tz); } _Tz = NewTz; } int LDateTime::SystemTimeZone(bool ForceUpdate) { if (ForceUpdate || CurTz == NO_ZONE) { CurTz = 0; CurTzOff = 0; #ifdef MAC #ifdef LGI_COCOA NSTimeZone *timeZone = [NSTimeZone localTimeZone]; if (timeZone) { NSDate *Now = [NSDate date]; CurTz = (int) [timeZone secondsFromGMTForDate:Now] / 60; CurTzOff = [timeZone daylightSavingTimeOffsetForDate:Now] / 60; CurTz -= CurTzOff; } #elif defined LGI_CARBON CFTimeZoneRef tz = CFTimeZoneCopySystem(); CFAbsoluteTime now = CFAbsoluteTimeGetCurrent(); Boolean dst = CFTimeZoneIsDaylightSavingTime(tz, now); if (dst) { CFAbsoluteTime next = CFTimeZoneGetNextDaylightSavingTimeTransition(tz, now); CurTz = CFTimeZoneGetSecondsFromGMT(tz, next + 100) / 60; } else { CurTz = CFTimeZoneGetSecondsFromGMT(tz, now) / 60; } CurTzOff = CFTimeZoneGetDaylightSavingTimeOffset(tz, now) / 60; CFRelease(tz); #endif #elif defined(WIN32) timeb tbTime; ftime(&tbTime); CurTz = -tbTime.timezone; TIME_ZONE_INFORMATION Tzi; if (GetTimeZoneInformation(&Tzi) == TIME_ZONE_ID_DAYLIGHT) CurTzOff = -Tzi.DaylightBias; #elif defined(LINUX) || defined(HAIKU) int six_months = (365 * 24 * 60 * 60) / 2; time_t now = 0, then = 0; time (&now); then = now - six_months; tm now_tz, then_tz; tm *t = localtime_r(&now, &now_tz); if (t) { localtime_r(&then, &then_tz); CurTz = now_tz.tm_gmtoff / 60; if (now_tz.tm_isdst) { CurTzOff = (now_tz.tm_gmtoff - then_tz.tm_gmtoff) / 60; CurTz = then_tz.tm_gmtoff / 60; } else // This is not DST so there is no offset right? CurTzOff = 0; // (then_tz.tm_gmtoff - now_tz.tm_gmtoff) / 60; } else return NO_ZONE; #else #error "Impl me." #endif } return CurTz + CurTzOff; } int LDateTime::SystemTimeZoneOffset() { if (CurTz == NO_ZONE) SystemTimeZone(); return CurTzOff; } #if defined WIN32 LDateTime ConvertSysTime(SYSTEMTIME &st, int year) { LDateTime n; if (st.wYear) { n.Year(st.wYear); n.Month(st.wMonth); n.Day(st.wDay); } else { n.Year(year); n.Month(st.wMonth); // Find the 'nth' matching weekday, starting from the first day in the month n.Day(1); LDateTime c = n; for (int i=0; iCompare(b); } #elif USE_ZDUMP static bool ParseValue(char *s, LString &var, LString &val) { if (!s) return false; char *e = strchr(s, '='); if (!e) return false; *e++ = 0; var = s; val = e; *e = '='; return var != 0 && val != 0; } #endif /* Testing code... LDateTime Start, End; LArray Info; Start.Set("1/1/2010"); End.Set("31/12/2014"); LDateTime::GetDaylightSavingsInfo(Info, Start, &End); LStringPipe p; for (int i=0; i,int> { MonthHash() { for (int i=0; i &Info, LDateTime &Start, LDateTime *End) { bool Status = false; #if defined(WIN32) TIME_ZONE_INFORMATION Tzi; auto r = GetTimeZoneInformation(&Tzi); if (r > TIME_ZONE_ID_UNKNOWN) { Info.Length(0); // Find the dates for the previous year from Start. This allows // us to cover the start of the current year. LDateTime s = ConvertSysTime(Tzi.StandardDate, Start.Year() - 1); LDateTime d = ConvertSysTime(Tzi.DaylightDate, Start.Year() - 1); // Create initial Info entry, as the last change in the previous year auto *i = &Info.New(); if (s < d) { // Year is: Daylight->Standard->Daylight LDateTime tmp = d; i->Offset = -(Tzi.Bias + Tzi.DaylightBias); tmp.AddMinutes(-i->Offset); - i->UtcTimeStamp = tmp.Ts(); + i->Utc = tmp; } else { // Year is: Standard->Daylight->Standard LDateTime tmp = s; i->Offset = -(Tzi.Bias + Tzi.StandardBias); tmp.AddMinutes(-i->Offset); - i->UtcTimeStamp = tmp.Ts();; + i->Utc = tmp; } for (auto y=Start.Year(); y<=(End?End->Year():Start.Year()); y++) { if (s < d) { // Cur year, first event: end of DST i = &Info.New(); auto tmp = ConvertSysTime(Tzi.StandardDate, y); i->Offset = -(Tzi.Bias + Tzi.StandardBias); tmp.AddMinutes(-i->Offset); - i->UtcTimeStamp = tmp.Ts(); + i->Utc = tmp; // Cur year, second event: start of DST i = &Info.New(); tmp = ConvertSysTime(Tzi.DaylightDate, y); i->Offset = -(Tzi.Bias + Tzi.DaylightBias); tmp.AddMinutes(-i->Offset); - i->UtcTimeStamp = tmp.Ts(); + i->Utc = tmp; } else { // Cur year, first event: start of DST i = &Info.New(); auto tmp = ConvertSysTime(Tzi.DaylightDate, Start.Year()); i->Offset = -(Tzi.Bias + Tzi.DaylightBias); tmp.AddMinutes(-i->Offset); - i->UtcTimeStamp = tmp.Ts(); + i->Utc = tmp; // Cur year, second event: end of DST i = &Info.New(); tmp = ConvertSysTime(Tzi.StandardDate, Start.Year()); i->Offset = -(Tzi.Bias + Tzi.StandardBias); tmp.AddMinutes(-i->Offset); - i->UtcTimeStamp = tmp.Ts(); + i->Utc = tmp; } } Status = true; } #elif defined(MAC) LDateTime From = Start; From.AddMonths(-6); LDateTime To = End ? *End : Start; To.AddMonths(6); auto ToUnix = To.GetUnix(); auto tz = [NSTimeZone systemTimeZone]; auto startDate = [[NSDate alloc] initWithTimeIntervalSince1970:(From.Ts() / Second64Bit) - Offset1800]; while (startDate) { auto next = [tz nextDaylightSavingTimeTransitionAfterDate:startDate]; auto &i = Info.New(); auto nextTs = [next timeIntervalSince1970]; i.UtcTimeStamp = (nextTs + Offset1800) * Second64Bit; i.Offset = (int)([tz secondsFromGMTForDate:[next dateByAddingTimeInterval:60]]/60); #if DEBUG_DST_INFO { LDateTime dt; dt.Set(i.UtcTimeStamp); LgiTrace("%s:%i - Ts=%s Off=%i\n", _FL, dt.Get().Get(), i.Offset); } #endif if (nextTs >= ToUnix) break; [startDate release]; startDate = next; } if (startDate) [startDate release]; #elif USE_ZDUMP if (!Zdump.Length()) { static bool First = true; auto linkLoc = "/etc/localtime"; #if defined(LINUX) auto zoneLoc = "/usr/share/zoneinfo"; #elif defined(HAIKU) auto zoneLoc = "/boot/system/data/zoneinfo"; #else #error "Impl me" #endif if (!LFileExists(linkLoc)) { if (First) { LgiTrace("%s:%i - LDateTime::GetDaylightSavingsInfo error: '%s' doesn't exist.\n" " It should link to something in the '%s' tree.\n", _FL, linkLoc, zoneLoc); #ifdef HAIKU LgiTrace(" To fix that: pkgman install timezone_data and then create the '%s' link.\n", linkLoc); #endif } return First = false; } auto f = popen(LString::Fmt("zdump -v %s", linkLoc), "r"); if (f) { char s[1024]; size_t r; LStringPipe p(1024); while ((r = fread(s, 1, sizeof(s), f)) > 0) p.Write(s, (int)r); fclose(f); Zdump = p.NewLStr().Split("\n"); } else { if (First) { LgiTrace("%s:%i - LDateTime::GetDaylightSavingsInfo error: zdump didn't run.\n", _FL); #ifdef HAIKU LgiTrace("To fix that: pkgman install timezone_data\n"); #endif } return First = false; } } MonthHash Lut; LDateTime Prev; int PrevOff = 0; for (auto Line: Zdump) { auto l = Line.SplitDelimit(" \t"); if (l.Length() >= 16 && l[0].Equals("/etc/localtime")) { // /etc/localtime Sat Oct 3 15:59:59 2037 UTC = Sun Oct 4 01:59:59 2037 EST isdst=0 gmtoff=36000 // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 LDateTime Utc; Utc.Year(l[5].Int()); #if DEBUG_DST_INFO if (Utc.Year() < 2020) continue; // printf("DST: %s\n", Line.Get()); #endif auto Tm = l[4].SplitDelimit(":"); if (Tm.Length() != 3) { #if DEBUG_DST_INFO printf("%s:%i - Tm '%s' has wrong parts: %s\n", _FL, l[4].Get(), Line.Get()); #endif continue; } Utc.Hours(Tm[0].Int()); Utc.Minutes(Tm[1].Int()); Utc.Seconds(Tm[2].Int()); if (Utc.Minutes() < 0) { #if DEBUG_DST_INFO printf("%s:%i - Mins is zero: %s\n", _FL, l[4].Get()); #endif continue; } int m = Lut.Find(l[2]); if (!m) { #if DEBUG_DST_INFO printf("%s:%i - Unknown month '%s'\n", _FL, l[2].Get()); #endif continue; } Utc.Day(l[3].Int()); Utc.Month(m); LString Var, Val; if (!ParseValue(l[14], Var, Val) || Var != "isdst") { #if DEBUG_DST_INFO printf("%s:%i - Unknown value for isdst\n", _FL); #endif continue; } if (!ParseValue(l[15], Var, Val) || Var != "gmtoff") { #if DEBUG_DST_INFO printf("%s:%i - Unknown value for isdst\n", _FL); #endif continue; } int Off = atoi(Val) / 60; if (Utc.Ts() == 0) continue; if (Prev.Year() && Prev < Start && Start < Utc) { // Emit initial entry for 'start' auto &inf = Info.New(); Prev.Get(inf.Utc); inf.Offset = PrevOff; #if DEBUG_DST_INFO printf("Info: Start=%s %i\n", Prev.Get().Get(), inf.Offset); #endif } if (Utc > Start) { // Emit furthur entries for DST events between start and end. auto &inf = Info.New(); Utc.Get(inf.Utc); inf.Offset = Off; #if DEBUG_DST_INFO printf("Info: Next=%s %i\n", Utc.Get().Get(), inf.Offset); #endif if (End && Utc > *End) { // printf("Utc after end: %s > %s\n", Utc.Get().Get(), End->Get().Get()); break; } } Prev = Utc; PrevOff = Off; } } Status = Info.Length() > 1; #elif defined(HAIKU) LTimeZoneInfo tzinfo; if (!tzinfo.Read()) { #if DEBUG_DST_INFO LgiTrace("%s:%i - info read failed.\n", _FL); #endif return false; } Status = tzinfo.GetDaylightSavingsInfo(Info, Start, End); #if DEBUG_DST_INFO if (!Status) printf("%s:%i - GetDaylightSavingsInfo failed.\n", _FL); #endif #else LAssert(!"Not implemented."); #endif return Status; } bool LDateTime::DstToLocal(LArray &Dst, LDateTime &dt) { if (dt.GetTimeZone()) { LAssert(!"Should be a UTC date."); return true; } #if DEBUG_DST_INFO LgiTrace("DstToLocal: %s\n", dt.Get().Get()); #endif LAssert(Dst.Length() > 1); // Needs to have at least 2 entries...? for (size_t i=0; i= start && dt < end; if (InRange) { dt.SetTimeZone(a.Offset, true); #if DEBUG_DST_INFO LgiTrace("\tRng[%i]: %s -> %s, SetTimeZone(%g), dt=%s\n", (int)i, start.Get().Get(), end.Get().Get(), (double)a.Offset/60.0, dt.Get().Get()); #endif return true; } } auto Last = Dst.Last(); LDateTime d; d.Set(Last.Utc); if (dt >= d && dt.Year() == d.Year()) { // If it's after the last DST change but in the same year... it's ok... // Just use the last offset. dt.SetTimeZone(Last.Offset, true); return true; } #if DEBUG_DST_INFO for (auto d: Dst) LgiTrace("Dst: %s = %i\n", d.GetLocal().Get().Get(), d.Offset); #endif LgiTrace("%s:%i - No valid DST range for: %s\n", _FL, dt.Get().Get()); LAssert(!"No valid DST range for this date."); return false; } int LDateTime::DayOfWeek() const { int Index = 0; int Day = IsLeapYear() ? 29 : 28; switch (_Year / 100) { case 19: { Index = 3; break; } case 20: { Index = 2; break; } } // get year right int y = _Year % 100; int r = y % 12; Index = (Index + (y / 12) + r + (r / 4)) % 7; // get month right if (_Month % 2 == 0) { // even month if (_Month > 2) Day = _Month; } else { // odd month switch (_Month) { case 1: { Day = 31; if (IsLeapYear()) { Index = Index > 0 ? Index - 1 : Index + 6; } break; } case 11: case 3: { Day = 7; break; } case 5: { Day = 9; break; } case 7: { Day = 11; break; } case 9: { Day = 5; break; } } } // get day right int Diff = Index - (Day - _Day); while (Diff < 0) Diff += 7; return Diff % 7; } LDateTime LDateTime::Now() { LDateTime dt; dt.SetNow(); return dt; } LDateTime &LDateTime::SetNow() { #ifdef WIN32 SYSTEMTIME stNow; FILETIME ftNow; GetSystemTime(&stNow); SystemTimeToFileTime(&stNow, &ftNow); uint64 i64 = ((uint64)ftNow.dwHighDateTime << 32) | ftNow.dwLowDateTime; - Set(i64); + OsTime(i64); #else time_t now; time(&now); struct tm *time = localtime(&now); if (time) *this = time; #ifndef LGI_STATIC else { LgiTrace("%s:%i - Error: localtime failed, now=%u\n", _FL, now); } #endif #endif return *this; } #define Convert24HrTo12Hr(h) ( (h) == 0 ? 12 : (h) > 12 ? (h) % 12 : (h) ) #define Convert24HrToAmPm(h) ( (h) >= 12 ? "p" : "a" ) LString LDateTime::GetDate() const { char s[32]; int Ch = GetDate(s, sizeof(s)); return LString(s, Ch); } int LDateTime::GetDate(char *Str, size_t SLen) const { int Ch = 0; if (Str && SLen > 0) { switch (_Format & GDTF_DATE_MASK) { case GDTF_MONTH_DAY_YEAR: Ch += sprintf_s(Str+Ch, SLen-Ch, _Format&GDTF_MONTH_LEADINGZ?"%2.2i" :"%i" , _Month); Ch += sprintf_s(Str+Ch, SLen-Ch, _Format&GDTF_DAY_LEADINGZ ?"%c%2.2i":"%c%i", DefaultSeparator, _Day); Ch += sprintf_s(Str+Ch, SLen-Ch, "%c%i", DefaultSeparator, _Year); break; default: case GDTF_DAY_MONTH_YEAR: Ch += sprintf_s(Str+Ch, SLen-Ch, _Format&GDTF_DAY_LEADINGZ ?"%2.2i" :"%i" , _Day); Ch += sprintf_s(Str+Ch, SLen-Ch, _Format&GDTF_MONTH_LEADINGZ?"%c%2.2i":"%c%i", DefaultSeparator, _Month); Ch += sprintf_s(Str+Ch, SLen-Ch, "%c%i", DefaultSeparator, _Year); break; case GDTF_YEAR_MONTH_DAY: Ch += sprintf_s(Str+Ch, SLen-Ch, "%i", _Year); Ch += sprintf_s(Str+Ch, SLen-Ch, _Format&GDTF_MONTH_LEADINGZ?"%c%2.2i":"%c%i", DefaultSeparator, _Month); Ch += sprintf_s(Str+Ch, SLen-Ch, _Format&GDTF_DAY_LEADINGZ ?"%c%2.2i":"%c%i", DefaultSeparator, _Day); break; } } return Ch; } LString LDateTime::GetTime() const { char s[32]; int Ch = GetTime(s, sizeof(s)); return LString(s, Ch); } int LDateTime::GetTime(char *Str, size_t SLen) const { int Ch = 0; if (Str && SLen > 0) { switch (_Format & GDTF_TIME_MASK) { case GDTF_12HOUR: default: { Ch += sprintf_s(Str, SLen, "%i:%2.2i:%2.2i%s", Convert24HrTo12Hr(_Hours), _Minutes, _Seconds, Convert24HrToAmPm(_Hours)); break; } case GDTF_24HOUR: { Ch += sprintf_s(Str, SLen, "%i:%2.2i:%2.2i", _Hours, _Minutes, _Seconds); break; } } } return Ch; } -uint64 LDateTime::Ts() const +LTimeStamp LDateTime::Ts() const { LTimeStamp ts; Get(ts); - return ts.Get(); + return ts; } uint64_t LDateTime::GetUnix() { LTimeStamp s; Get(s); #if defined(WINDOWS) return s.Get() / LDateTime::Second64Bit / 116445168000000000LL; #else return s.Get() / LDateTime::Second64Bit - Offset1800; #endif } bool LDateTime::SetUnix(uint64 s) { #if defined(WINDOWS) return Set(s * LDateTime::Second64Bit + 116445168000000000LL); #else return Set((s + Offset1800) * LDateTime::Second64Bit); #endif } -bool LDateTime::Set(const LTimeStamp &s) +bool LDateTime::Set(LTimeStamp &s) { #if defined WIN32 FILETIME Utc; SYSTEMTIME System; // Adjust to the desired timezone uint64 u = s.Get() + ((int64)_Tz * 60 * Second64Bit); Utc.dwHighDateTime = u >> 32; Utc.dwLowDateTime = u & 0xffffffff; if (!FileTimeToSystemTime(&Utc, &System)) return false; _Year = System.wYear; _Month = System.wMonth; _Day = System.wDay; _Hours = System.wHour; _Minutes = System.wMinute; _Seconds = System.wSecond; _Thousands = System.wMilliseconds; return true; #else time_t t = s; Set(t); _Thousands = s % Second64Bit; return true; #endif } bool LDateTime::Set(struct tm *t, bool inferTimezone) { if (!t) return false; _Year = t->tm_year + 1900; _Month = t->tm_mon + 1; _Day = t->tm_mday; _Hours = t->tm_hour; _Minutes = t->tm_min; _Seconds = t->tm_sec; _Thousands = 0; if (inferTimezone) { #ifdef WINDOWS #define timegm _mkgmtime #endif auto diff = timegm(t) - mktime(t); _Tz = (int16)(diff / 60); } return true; } bool LDateTime::Set(time_t tt) { struct tm *t; if (_Tz) tt += _Tz * 60; #if !defined(_MSC_VER) || _MSC_VER < _MSC_VER_VS2005 t = gmtime(&tt); if (t) #else struct tm tmp; if (_gmtime64_s(t = &tmp, &tt) == 0) #endif { return Set(t, false); } return false; } uint64_t LDateTime::OsTime() const { #ifdef WINDOWS FILETIME Utc; SYSTEMTIME System; System.wYear = _Year; System.wMonth = limit(_Month, 1, 12); System.wDay = limit(_Day, 1, 31); System.wHour = limit(_Hours, 0, 23); System.wMinute = limit(_Minutes, 0, 59); System.wSecond = limit(_Seconds, 0, 59); System.wMilliseconds = limit(_Thousands, 0, 999); System.wDayOfWeek = DayOfWeek(); if (SystemTimeToFileTime(&System, &Utc)) { uint64_t s = ((uint64_t)Utc.dwHighDateTime << 32) | Utc.dwLowDateTime; if (_Tz) // Adjust for timezone s -= (int64)_Tz * 60 * Second64Bit; return s; } else { DWORD Err = GetLastError(); LAssert(!"SystemTimeToFileTime failed."); } #else if (_Year < MIN_YEAR) return 0; struct tm t; ZeroObj(t); t.tm_year = _Year - 1900; t.tm_mon = _Month - 1; t.tm_mday = _Day; t.tm_hour = _Hours; t.tm_min = _Minutes; t.tm_sec = _Seconds; t.tm_isdst = -1; time_t sec = timegm(&t); if (sec == -1) return 0; if (_Tz) { // Adjust the output to UTC from the current timezone. sec -= _Tz * 60; } return sec; #endif return 0; } bool LDateTime::OsTime(uint64_t ts) { return Set((time_t)ts); } bool LDateTime::Get(LTimeStamp &s) const { #ifdef WINDOWS if (!IsValid()) { LAssert(!"Needs a valid date."); return false; } - s = OsTime(); + s.Get() = OsTime(); if (!s) return false; return true; #else if (_Year < MIN_YEAR) return false; auto sec = OsTime(); s = (uint64)(sec + Offset1800) * Second64Bit + _Thousands; return true; #endif } LString LDateTime::Get() const { char buf[32]; int Ch = GetDate(buf, sizeof(buf)); buf[Ch++] = ' '; Ch += GetTime(buf+Ch, sizeof(buf)-Ch); return LString(buf, Ch); } void LDateTime::Get(char *Str, size_t SLen) const { if (Str) { GetDate(Str, SLen); size_t len = strlen(Str); if (len < SLen - 1) { Str[len++] = ' '; GetTime(Str+len, SLen-len); } } } bool LDateTime::Set(const char *Str) { if (!Str) return false; if (Strlen(Str) > 100) return false; char Local[256]; strcpy_s(Local, sizeof(Local), Str); char *Sep = strchr(Local, ' '); if (Sep) { *Sep++ = 0; if (!SetTime(Sep)) return false; } if (!SetDate(Local)) return false; return true; } void LDateTime::Month(char *m) { int i = IsMonth(m); if (i >= 0) _Month = i + 1; } int DateComponent(const char *s) { int64 i = Atoi(s); return i ? (int)i : LDateTime::IsMonth(s); } bool LDateTime::SetDate(const char *Str) { bool Status = false; if (Str) { auto T = LString(Str).SplitDelimit("/-.,_\\"); if (T.Length() == 3) { int i[3] = { DateComponent(T[0]), DateComponent(T[1]), DateComponent(T[2]) }; int fmt = _Format & GDTF_DATE_MASK; // Do some guessing / overrides. // Don't let _Format define the format completely. if (i[0] > 1000) { fmt = GDTF_YEAR_MONTH_DAY; } else if (i[2] > 1000) { if (i[0] > 12) fmt = GDTF_DAY_MONTH_YEAR; else if (i[1] > 12) fmt = GDTF_MONTH_DAY_YEAR; } switch (fmt) { case GDTF_MONTH_DAY_YEAR: { _Month = i[0]; _Day = i[1]; _Year = i[2]; break; } case GDTF_DAY_MONTH_YEAR: { _Day = i[0]; _Month = i[1]; _Year = i[2]; break; } case GDTF_YEAR_MONTH_DAY: { _Year = i[0]; _Month = i[1]; _Day = i[2]; break; } default: { _Year = i[2]; if ((DefaultFormat & GDTF_DATE_MASK) == GDTF_MONTH_DAY_YEAR) { // Assume m/d/yyyy _Day = i[1]; _Month = i[0]; } else { // Who knows??? // Assume d/m/yyyy _Day = i[0]; _Month = i[1]; } break; } } if (_Year < 100) { LAssert(_Day < 1000 && _Month < 1000); if (_Year >= 80) _Year += 1900; else _Year += 2000; } Status = true; } else { // Fall back to fuzzy matching auto T = LString(Str).SplitDelimit(" ,"); MonthHash Lut; int FMonth = 0; int FDay = 0; int FYear = 0; for (unsigned i=0; i 0) { if (i >= 1000) { FYear = i; } else if (i < 32) { FDay = i; } } } else { int i = Lut.Find(p); if (i) FMonth = i; } } if (FMonth && FDay) { Day(FDay); Month(FMonth); } if (FYear) { Year(FYear); } else { LDateTime Now; Now.SetNow(); Year(Now.Year()); } } } return Status; } bool LDateTime::SetTime(const char *Str) { if (!Str) return false; auto T = LString(Str).SplitDelimit(":."); if (T.Length() < 2 || T.Length() > 4) return false; #define SetClamp(out, in, minVal, maxVal) \ out = (int)Atoi(in.Get(), 10, 0); \ if (out > maxVal) out = maxVal; \ else if (out < minVal) out = minVal SetClamp(_Hours, T[0], 0, 23); SetClamp(_Minutes, T[1], 0, 59); SetClamp(_Seconds, T[2], 0, 59); _Thousands = 0; const char *s = T.Last(); if (s) { if (strchr(s, 'p') || strchr(s, 'P')) { if (_Hours != 12) _Hours += 12; } else if (strchr(s, 'a') || strchr(s, 'A')) { if (_Hours == 12) _Hours -= 12; } } if (T.Length() > 3) { LString t = "0."; t += s; _Thousands = (int) (t.Float() * 1000); } return true; } int LDateTime::IsWeekDay(const char *s) { for (unsigned n=0; n= 4) { Year((int)t[0].Int()); Month((int)t[1].Int()); Day((int)t[2].Int()); } else if (t[2].Length() >= 4) { Day((int)t[0].Int()); Month((int)t[1].Int()); Year((int)t[2].Int()); } else { LAssert(!"Unknown date format?"); return false; } } } else if (a[i].Length() == 4) Year((int)a[i].Int()); else if (!Day()) Day((int)a[i].Int()); } else if (IsAlpha(*c)) { int WkDay = IsWeekDay(c); if (WkDay >= 0) continue; int Mnth = IsMonth(c); if (Mnth >= 0) Month(Mnth + 1); } else if (*c == '-' || *c == '+') { c++; if (strlen(c) == 4) { // Timezone.. int64 Tz = a[i].Int(); int Hrs = (int) (Tz / 100); int Min = (int) (Tz % 100); SetTimeZone(Hrs * 60 + Min, false); } } } return IsValid(); } int LDateTime::Sizeof() { return sizeof(int) * 7; } bool LDateTime::Serialize(LFile &f, bool Write) { int32 i; if (Write) { #define wf(fld) i = fld; f << i; wf(_Day); wf(_Month); wf(_Year); wf(_Thousands); wf(_Seconds); wf(_Minutes); wf(_Hours); } else { #define rf(fld) f >> i; fld = i; rf(_Day); rf(_Month); rf(_Year); rf(_Thousands); rf(_Seconds); rf(_Minutes); rf(_Hours); } return true; } /* bool LDateTime::Serialize(ObjProperties *Props, char *Name, bool Write) { #ifndef LGI_STATIC if (Props && Name) { struct _Date { uint8_t Day; uint8_t Month; int16_t Year; uint8_t Hour; uint8_t Minute; uint16_t ThouSec; }; LAssert(sizeof(_Date) == 8); if (Write) { _Date d; d.Day = _Day; d.Month = _Month; d.Year = _Year; d.Hour = _Hours; d.Minute = _Minutes; d.ThouSec = (_Seconds * 1000) + _Thousands; return Props->Set(Name, &d, sizeof(d)); } else // Read { void *Ptr; int Len; if (Props->Get(Name, Ptr, Len) && sizeof(_Date) == Len) { _Date *d = (_Date*) Ptr; _Day = d->Day; _Month = d->Month; _Year = d->Year; _Hours = d->Hour; _Minutes = d->Minute; _Seconds = d->ThouSec / 1000; _Thousands = d->ThouSec % 1000; return true; } } } #endif return false; } */ int LDateTime::Compare(const LDateTime *Date) const { // this - *Date auto ThisTs = IsValid() ? Ts() : 0; auto DateTs = Date->IsValid() ? Date->Ts() : 0; + if (ThisTs.Get() & 0x800000000000000) + { + Get(ThisTs); + } + // If these ever fire, the cast to int64_t will overflow - LAssert((ThisTs & 0x800000000000000) == 0); - LAssert((DateTs & 0x800000000000000) == 0); + LAssert((ThisTs.Get() & 0x800000000000000) == 0); + LAssert((DateTs.Get() & 0x800000000000000) == 0); int64_t Diff = (int64_t)ThisTs - DateTs; if (Diff < 0) return -1; return Diff > 0 ? 1 : 0; } #define DATETIME_OP(op) \ bool LDateTime::operator op(const LDateTime &dt) const \ { \ auto a = Ts(); \ auto b = dt.Ts(); \ return a op b; \ } DATETIME_OP(<) DATETIME_OP(<=) DATETIME_OP(>) DATETIME_OP(>=) bool LDateTime::operator ==(const LDateTime &dt) const { return _Year == dt._Year && _Month == dt._Month && _Day == dt._Day && _Hours == dt._Hours && _Minutes == dt._Minutes && _Seconds == dt._Seconds && _Thousands == dt._Thousands; } bool LDateTime::operator !=(const LDateTime &dt) const { return _Year != dt._Year || _Month != dt._Month || _Day != dt._Day || _Hours != dt._Hours || _Minutes != dt._Minutes || _Seconds != dt._Seconds || _Thousands != dt._Thousands; } int LDateTime::DiffMonths(const LDateTime &dt) { int a = (Year() * 12) + Month(); int b = (dt.Year() * 12) + dt.Month(); return b - a; } LDateTime LDateTime::operator -(const LDateTime &dt) { LTimeStamp a, b; Get(a); dt.Get(b); /// Resolution of a second when using 64 bit timestamps int64 Sec = Second64Bit; int64 Min = 60 * Sec; int64 Hr = 60 * Min; int64 Day = 24 * Hr; int64 d = (int64)a.Get() - (int64)b.Get(); LDateTime r; r._Day = (int16) (d / Day); d -= r._Day * Day; r._Hours = (int16) (d / Hr); d -= r._Hours * Hr; r._Minutes = (int16) (d / Min); d -= r._Minutes * Min; r._Seconds = (int16) (d / Sec); #ifdef WIN32 d -= r._Seconds * Sec; r._Thousands = (int16) (d / 10000); #else r._Thousands = 0; #endif return r; } LDateTime LDateTime::operator +(const LDateTime &dt) { LDateTime s = *this; s.AddMonths(dt.Month()); s.AddDays(dt.Day()); s.AddHours(dt.Hours()); s.AddMinutes(dt.Minutes()); // s.AddSeconds(dt.Seconds()); return s; } LDateTime &LDateTime::operator =(const LDateTime &t) { _Day = t._Day; _Year = t._Year; _Thousands = t._Thousands; _Month = t._Month; _Seconds = t._Seconds; _Minutes = t._Minutes; _Hours = t._Hours; _Tz = t._Tz; _Format = t._Format; return *this; } LDateTime &LDateTime::operator =(struct tm *time) { if (time) { _Seconds = time->tm_sec; _Minutes = time->tm_min; _Hours = time->tm_hour; _Day = time->tm_mday; _Month = time->tm_mon + 1; _Year = time->tm_year + 1900; } else Empty(); return *this; } bool LDateTime::IsSameDay(LDateTime &d) const { return Day() == d.Day() && Month() == d.Month() && Year() == d.Year(); } bool LDateTime::IsSameMonth(LDateTime &d) const { return Day() == d.Day() && Month() == d.Month(); } bool LDateTime::IsSameYear(LDateTime &d) const { return Year() == d.Year(); } LDateTime LDateTime::StartOfDay() const { LDateTime dt = *this; dt.Hours(0); dt.Minutes(0); dt.Seconds(0); dt.Thousands(0); return dt; } LDateTime LDateTime::EndOfDay() const { LDateTime dt = *this; dt.Hours(23); dt.Minutes(59); dt.Seconds(59); dt.Thousands(999); return dt; } bool LDateTime::IsLeapYear(int Year) const { if (Year < 0) Year = _Year; if (Year % 4 != 0) return false; if (Year % 400 == 0) return true; if (Year % 100 == 0) return false; return true; } int LDateTime::DaysInMonth() const { if (_Month == 2 && IsLeapYear()) { return 29; } short DaysInMonth[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; return _Month >= 1 && _Month <= 12 ? DaysInMonth[_Month-1] : 0; } void LDateTime::AddSeconds(int64 Seconds) { LTimeStamp i; if (Get(i)) { i.Get() += Seconds * Second64Bit; Set(i); } } void LDateTime::AddMinutes(int64 Minutes) { LTimeStamp i; if (Get(i)) { int64 delta = Minutes * 60 * Second64Bit; i.Get() += delta; Set(i); } } void LDateTime::AddHours(int64 Hours) { LTimeStamp i; if (Get(i)) { i.Get() += Hours * HourLength * Second64Bit; Set(i); } } bool LDateTime::AddDays(int64 Days) { if (!Days) return true; LTimeStamp Ts; if (!Get(Ts)) return false; Ts.Get() += Days * LDateTime::DayLength * Second64Bit; bool b = Set(Ts); return b; } void LDateTime::AddMonths(int64 Months) { int64 m = _Month + Months; do { if (m < 1) { _Year--; m += 12; } else if (m > 12) { _Year++; m -= 12; } else { break; } } while (1); _Month = (int16) m; if (_Day > DaysInMonth()) _Day = DaysInMonth(); } LString LDateTime::DescribePeriod(double seconds) { int mins = (int) (seconds / 60); seconds -= mins * 60; int hrs = mins / 60; mins -= hrs * 60; int days = hrs / 24; hrs -= days * 24; LString s; if (days > 0) s.Printf("%id %ih %im %is", days, hrs, mins, (int)seconds); else if (hrs > 0) s.Printf("%ih %im %is", hrs, mins, (int)seconds); else if (mins > 0) s.Printf("%im %is", mins, (int)seconds); else s.Printf("%is", (int)seconds); return s; } LString LDateTime::DescribePeriod(LDateTime to) { auto ThisTs = Ts(); auto ToTs = to.Ts(); auto diff = ThisTs < ToTs ? ToTs - ThisTs : ThisTs - ToTs; auto seconds = (double)diff / LDateTime::Second64Bit; return DescribePeriod(seconds); } int LDateTime::MonthFromName(const char *Name) { if (Name) { for (int m=0; m<12; m++) { if (strnicmp(Name, MonthsShort[m], strlen(MonthsShort[m])) == 0) { return m + 1; break; } } } return -1; } bool LDateTime::Decode(const char *In) { // Test data: // // Tue, 6 Dec 2005 1:25:32 -0800 Empty(); if (!In) { LAssert(0); return false; } bool Status = false; // Tokenize delimited by whitespace LString::Array T = LString(In).SplitDelimit(", \t\r\n"); if (T.Length() < 2) { if (T[0].IsNumeric()) { // Some sort of timestamp? uint64_t Ts = Atoi(T[0].Get()); if (Ts > 0) { return SetUnix(Ts); } else return false; } else { // What now? return false; } } else { bool GotDate = false; for (unsigned i=0; i 31) { // Y/M/D? Year((int)Date[0].Int()); Day((int)Date[2].Int()); } else if (Date[2].Int() > 31) { // D/M/Y? Day((int)Date[0].Int()); Year((int)Date[2].Int()); } else { // Ambiguous year... bool YrFirst = true; if (Date[0].Length() == 1) YrFirst = false; // else we really can't tell.. just go with year first if (YrFirst) { Year((int)Date[0].Int()); Day((int)Date[2].Int()); } else { Day((int)Date[0].Int()); Year((int)Date[2].Int()); } LDateTime Now; Now.SetNow(); if (Year() + 2000 <= Now.Year()) Year(2000 + Year()); else Year(1900 + Year()); } if (Date[1].IsNumeric()) Month((int)Date[1].Int()); else { int m = MonthFromName(Date[1]); if (m > 0) Month(m); } GotDate = true; Status = true; } else if (s.Find(":") >= 0) { // whole time // Do some validation bool Valid = true; for (char *c = s; *c && Valid; c++) { if (!(IsDigit(*c) || *c == ':')) Valid = false; } if (Valid) { LString::Array Time = s.Split(":"); if (Time.Length() == 2 || Time.Length() == 3) { // Hour int i = (int) Time[0].Int(); if (i >= 0) Hours(i); if (s.Lower().Find("p") >= 0) { if (Hours() < 12) Hours(Hours() + 12); } // Minute i = (int) Time[1].Int(); if (i >= 0) Minutes(i); if (Time.Length() == 3) { // Second i = (int) Time[2].Int(); if (i >= 0) Seconds(i); } Status = true; } } } else if (IsAlpha(s(0))) { // text int m = MonthFromName(s); if (m > 0) Month(m); } else if (strchr("+-", *s)) { // timezone DoTimeZone: LDateTime Now; double OurTmz = (double)Now.SystemTimeZone() / 60; if (s && strchr("-+", *s) && strlen(s) == 5) { #if 1 int i = atoi(s); int hr = i / 100; int min = i % 100; SetTimeZone(hr * 60 + min, false); #else // adjust for timezone char Buf[32]; memcpy(Buf, s, 3); Buf[3] = 0; double TheirTmz = atof(Buf); memcpy(Buf+1, s + 3, 2); TheirTmz += (atof(Buf) / 60); if (Tz) { *Tz = TheirTmz; } double AdjustHours = OurTmz - TheirTmz; AddMinutes((int) (AdjustHours * 60)); #endif } else { // assume GMT AddMinutes((int) (OurTmz * 60)); } } else if (s.IsNumeric()) { int Count = 0; for (char *c = s; *c; c++) { if (!IsDigit(*c)) break; Count++; } if (Count <= 2) { if (Day()) { // We already have a day... so this might be // a 2 digit year... LDateTime Now; Now.SetNow(); int Yr = atoi(s); if (2000 + Yr <= Now.Year()) Year(2000 + Yr); else Year(1900 + Yr); } else { // A day number (hopefully)? Day((int)s.Int()); } } else if (Count == 4) { if (!Year()) { // A year! Year((int)s.Int()); Status = true; } else { goto DoTimeZone; } // My one and only Y2K fix // d.Year((Yr < 100) ? (Yr > 50) ? 1900+Yr : 2000+Yr : Yr); } } } } return Status; } bool LDateTime::GetVariant(const char *Name, LVariant &Dst, char *Array) { LDomProperty p = LStringToDomProp(Name); switch (p) { case DateYear: // Type: Int32 Dst = Year(); break; case DateMonth: // Type: Int32 Dst = Month(); break; case DateDay: // Type: Int32 Dst = Day(); break; case DateHour: // Type: Int32 Dst = Hours(); break; case DateMinute: // Type: Int32 Dst = Minutes(); break; case DateSecond: // Type: Int32 Dst = Seconds(); break; case DateDate: // Type: String { char s[32]; GetDate(s, sizeof(s)); Dst = s; break; } case DateTime: // Type: String { char s[32]; GetTime(s, sizeof(s)); Dst = s; break; } case TypeString: // Type: String case DateDateAndTime: // Type: String { char s[32]; Get(s, sizeof(s)); Dst = s; break; } case TypeInt: // Type: Int64 case DateTimestamp: // Type: Int64 { LTimeStamp i; if (Get(i)) Dst = (int64)i.Get(); break; } case DateSecond64Bit: { Dst = Second64Bit; break; } default: { return false; } } return true; } bool LDateTime::SetVariant(const char *Name, LVariant &Value, char *Array) { LDomProperty p = LStringToDomProp(Name); switch (p) { case DateYear: Year(Value.CastInt32()); break; case DateMonth: Month(Value.CastInt32()); break; case DateDay: Day(Value.CastInt32()); break; case DateHour: Hours(Value.CastInt32()); break; case DateMinute: Minutes(Value.CastInt32()); break; case DateSecond: Seconds(Value.CastInt32()); break; case DateDate: SetDate(Value.Str()); break; case DateTime: SetTime(Value.Str()); break; case DateDateAndTime: Set(Value.Str()); break; case DateTimestamp: Set((uint64)Value.CastInt64()); break; default: return false; } return true; } bool LDateTime::CallMethod(const char *Name, LVariant *ReturnValue, LArray &Args) { switch (LStringToDomProp(Name)) { case DateSetNow: SetNow(); if (ReturnValue) *ReturnValue = true; break; case DateSetStr: if (Args.Length() < 1) return false; bool Status; if (Args[0]->Type == GV_INT64) Status = Set((uint64) Args[0]->Value.Int64); else Status = Set(Args[0]->Str()); if (ReturnValue) *ReturnValue = Status; break; case DateGetStr: { char s[256] = ""; Get(s, sizeof(s)); if (ReturnValue) *ReturnValue = s; break; } default: return false; } return true; } #ifdef _DEBUG #define DATE_ASSERT(i) \ if (!(i)) \ { \ LAssert(!"LDateTime unit test failed."); \ return false; \ } bool LDateTime_Test() { // Check 64bit get/set LDateTime t("1/1/2017 0:0:0"); LTimeStamp i; DATE_ASSERT(t.Get(i)); LgiTrace("Get='%s'\n", t.Get().Get()); uint64 i2 = i + (24ULL * 60 * 60 * LDateTime::Second64Bit); LDateTime t2; t2.SetFormat(GDTF_DAY_MONTH_YEAR); t2.Set(i2); LString s = t2.Get(); LgiTrace("Set='%s'\n", s.Get()); DATE_ASSERT(!stricmp(s, "2/1/2017 12:00:00a") || !stricmp(s, "2/01/2017 12:00:00a")); t.SetNow(); LgiTrace("Now.Local=%s Tz=%.2f\n", t.Get().Get(), t.GetTimeZoneHours()); t2 = t; t2.ToUtc(); LgiTrace("Now.Utc=%s Tz=%.2f\n", t2.Get().Get(), t2.GetTimeZoneHours()); t2.ToLocal(); LgiTrace("Now.Local=%s Tz=%.2f\n", t2.Get().Get(), t2.GetTimeZoneHours()); DATE_ASSERT(t == t2); return true; } #endif //////////////////////////////////////////////////////////////////////////////////////////////////// LTimeStamp <imeStamp::operator =(const time_t unixTime) { #if defined(WINDOWS) ts = (unixTime + SEC_TO_UNIX_EPOCH) * WINDOWS_TICK; #else ts = (unixTime + LDateTime::Offset1800) * LDateTime::Second64Bit; #endif return *this; } -LTimeStamp::operator time_t() const +LTimeStamp <imeStamp::operator =(const LDateTime &dt) +{ + dt.Get(*this); + return *this; +} + +time_t LTimeStamp::Unix() const { #if defined(WINDOWS) + return (ts / WINDOWS_TICK) - SEC_TO_UNIX_EPOCH; #else + return (ts / LDateTime::Second64Bit) - LDateTime::Offset1800; #endif - - return 0; } diff --git a/src/common/Widgets/Graph.cpp b/src/common/Widgets/Graph.cpp --- a/src/common/Widgets/Graph.cpp +++ b/src/common/Widgets/Graph.cpp @@ -1,1017 +1,1017 @@ #include "lgi/common/Lgi.h" #include "lgi/common/Graph.h" #include "lgi/common/DocView.h" #include "lgi/common/DisplayString.h" #include "lgi/common/FileSelect.h" #include "lgi/common/Menu.h" #include #define SELECTION_SIZE 2 struct GraphAv { uint64 Sum; uint64 Count; }; struct DataSeriesPriv { LColour colour; LArray values; }; struct LGraphPriv { constexpr static int AxisMarkPx = 8; LGraph *View = NULL; int XAxis = 0, YAxis = 0; LVariantType XType, YType; LVariant MaxX, MinX; LVariant MaxY, MinY; LArray Data; LGraph::Style Style; LPoint MouseLoc; bool ShowCursor = false; LString LabelX, LabelY; double Zoom = 1.0, Px = 0.0, Py = 0.0; // Averages bool Average; LArray Ave; int BucketSize; // Selection LAutoPtr Select; LArray Selection; LGraphPriv(LGraph *view) : View(view) { #if 1 Style = LGraph::PointGraph; #else Style = LGraph::LineGraph; #endif Empty(); } ~LGraphPriv() { Empty(); } void Empty() { Zoom = 1.0; Px = 0.0; Py = 0.0; LabelX.Empty(); LabelY.Empty(); Average = false; BucketSize = 500; Data.DeleteObjects(); XType = GV_NULL; YType = GV_NULL; MinX.Empty(); MinY.Empty(); MaxX.Empty(); MaxY.Empty(); } LVariantType GuessType(char *s) { bool Dot = false; bool Num = false; bool Alpha = false; bool Delims = false; while (s && *s) { if (IsAlpha(*s)) Alpha = true; else if (IsDigit(*s)) Num = true; else if (strchr("/\\-_:", *s)) Delims = true; else if (*s == '.') Dot = true; s++; } if (Num) { if (Delims) return GV_DATETIME; if (Dot) return GV_DOUBLE; return GV_INT64; } else { return GV_STRING; } } bool Convert(LVariant &v, LVariantType type, char *in) { if (!in) return false; switch (type) { case GV_DOUBLE: v = atof(in); break; case GV_DATETIME: { LDateTime dt; dt.SetFormat(0); dt.Set(in); v = &dt; break; } case GV_INT64: v = (int64_t)atoi64(in); break; case GV_STRING: v = in; break; default: LAssert(!"Not impl."); break; } return true; } int Compare(LVariant &a, LVariant &b) { // a - b if (a.Type != b.Type) { LAssert(!"Only defined for comparing values of the same type."); return 0; } switch (a.Type) { case GV_DOUBLE: { double d = a.Value.Dbl - b.Value.Dbl; if (d < 0) return -1; else if (d > 0) return 1; break; } case GV_DATETIME: { return a.Value.Date->Compare(b.Value.Date); break; } case GV_INT64: { int64 i = a.Value.Int64 - b.Value.Int64; if (i < 0) return -1; else if (i > 0) return 1; break; } case GV_STRING: { return stricmp(a.Str(), b.Str()); break; } default: { LAssert(!"Not impl."); break; } } return 0; } LVariant ViewToData(int coord, int pixels, LVariant &min, LVariant &max) { LVariant r; if (pixels <= 0) return r; switch (min.Type) { case GV_DATETIME: { - uint64 Min, Max; + LTimeStamp Min, Max; min.Value.Date->Get(Min); max.Value.Date->Get(Max); uint64 ts = Min + ( ((uint64)coord * (Max - Min)) / pixels); LDateTime dt; dt.Set(ts); r = &dt; break; } case GV_INT64: { int64 Min, Max; Min = min.CastInt64(); Max = max.CastInt64(); r = Min + (((int64)coord * (Max - Min)) / pixels); break; } case GV_DOUBLE: { double Min, Max; Min = min.CastDouble(); Max = max.CastDouble(); r = Min + (((double)coord * (Max - Min)) / pixels); break; } default: LAssert(0); break; } return r; } int DataToView(LVariant &v, int pixels, LVariant &min, LVariant &max) { if (v.Type != min.Type || v.Type != max.Type) { LAssert(!"Incompatible types."); return 0; } switch (v.Type) { case GV_DATETIME: { - uint64 Min, Max, Val; + LTimeStamp Min, Max, Val; min.Value.Date->Get(Min); max.Value.Date->Get(Max); v.Value.Date->Get(Val); int64 Range = Max - Min; LAssert(Range > 0); return (int) ((Val - Min) * (pixels - 1) / Range); break; } case GV_INT64: { int64 Min, Max, Val; Min = min.CastInt64(); Max = max.CastInt64(); Val = v.CastInt64(); int64 Range = Max - Min; LAssert(Range > 0); return (int) ((Val - Min) * (pixels - 1) / Range); break; } case GV_DOUBLE: { double Min, Max, Val; Min = min.CastDouble(); Max = max.CastDouble(); Val = v.CastDouble(); double Range = Max - Min; LAssert(Range > 0); return (int) ((Val - Min) * (pixels - 1) / Range); break; } default: LAssert(0); break; } return 0; } LString DataToString(LVariant &v) { LString s; switch (v.Type) { case GV_DATETIME: { if (v.Value.Date->Hours() || v.Value.Date->Minutes()) s = v.Value.Date->Get(); else s = v.Value.Date->GetDate(); break; } case GV_INT64: { s.Printf(LPrintfInt64, v.CastInt64()); break; } case GV_INT32: { s.Printf("%" PRIi32, v.CastInt32()); break; } case GV_DOUBLE: { s.Printf("%g", v.CastDouble()); break; } default: { LAssert(!"Impl me."); break; } } return s; } void DrawAxis(LSurface *pDC, LRect &r, int xaxis, LVariant &min, LVariant &max, LString &label) { LVariant v = min; bool First = true; bool Loop = true; if (min.Type == GV_NULL || max.Type == GV_NULL) return; int x = xaxis ? r.x1 : r.x2; int y = xaxis ? r.y1 : r.y2; int pixels = xaxis ? r.X() : r.Y(); int64 int_range = 0; double dbl_inc = 0.0; int64 int64_inc = 0; int date_inc = 1; auto Fnt = View->GetFont(); Fnt->Colour(L_TEXT, L_WORKSPACE); LArray Values; while (Loop) { Values.Add(v); switch (v.Type) { default: { Loop = false; return; break; } case GV_DATETIME: { if (First) { - uint64 s, e; + LTimeStamp s, e; min.Value.Date->Get(s); max.Value.Date->Get(e); int64 period = e - s; double days = (double)period / LDateTime::DayLength; if (days > 7) date_inc = (int) (days / 5); else date_inc = 1; v.Value.Date->SetTime("0:0:0"); } v.Value.Date->AddDays(date_inc); Loop = *v.Value.Date < *max.Value.Date; break; } case GV_INT64: { if (First) { int64 int64_range = max.CastInt64() - min.CastInt64(); int64 rng = int64_range; int p = 0; while (rng > 10) { p++; rng /= 10; } while (rng < 1) { p--; rng *= 10; } int64_inc = (int64) pow(10.0, p); int64 d = (int64)((v.CastInt64() + int64_inc) / int64_inc); v = d * int64_inc; } else { v = v.CastInt64() + int64_inc; } Loop = v.CastInt64() < max.CastInt64(); break; } case GV_DOUBLE: { if (First) { double dbl_range = max.CastDouble() - min.CastDouble(); double rng = dbl_range; if (std::abs(rng - 0.0) > 0.0001) { int p = 0; while (rng > 10) { p++; rng /= 10; } while (rng < 1) { p--; rng *= 10; } dbl_inc = pow(10.0, p); int d = (int)((v.CastDouble() + dbl_inc) / dbl_inc); v = (double)d * dbl_inc; } else v = 0.0; } else { v = v.CastDouble() + dbl_inc; } Loop = v.CastDouble() < max.CastDouble(); break; } } First = false; } Values.Add(max); for (int i=0; iLine(dx, dy, dx, dy + 5); else pDC->Line(dx, dy, dx - 5, dy); } if (label) { LDisplayString ds(Fnt, label); ds.Draw(pDC, r.Center().x, r.y2-ds.Y()); } } LColour GenerateColour() { LColour c; c.SetHLS((uint16_t) (Data.Length() * 360 / 8), 255, 128); c.ToRGB(); return c; } }; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// LGraph::DataSeries::DataSeries(LGraphPriv *graphPriv, const char *name) { d = new DataSeriesPriv; priv = graphPriv; Name(name); } LGraph::DataSeries::~DataSeries() { } LColour LGraph::DataSeries::GetColour() { return d->colour; } void LGraph::DataSeries::SetColour(LColour c) { d->colour = c; } bool LGraph::DataSeries::AddPair(char *x, char *y, void *UserData) { if (!x || !y) return false; if (priv->XType == GV_NULL) priv->XType = priv->GuessType(x); if (priv->YType == GV_NULL) priv->YType = priv->GuessType(y); Pair &p = d->values.New(); p.UserData = UserData; if (priv->Convert(p.x, priv->XType, x)) { if (priv->MaxX.IsNull() || priv->Compare(p.x, priv->MaxX) > 0) priv->MaxX = p.x; if (priv->MinX.IsNull() || priv->Compare(p.x, priv->MinX) < 0) priv->MinX = p.x; } else { d->values.PopLast(); return false; } if (priv->Convert(p.y, priv->YType, y)) { if (priv->MaxY.IsNull() || priv->Compare(p.y, priv->MaxY) > 0) priv->MaxY = p.y; if (priv->MinY.IsNull() || priv->Compare(p.y, priv->MinY) < 0) priv->MinY = p.y; } else { d->values.PopLast(); return false; } return true; } bool LGraph::DataSeries::SetDataSource(LDbRecordset *Rs, int XAxis, int YAxis) { if (!Rs) return false; priv->XType = GV_NULL; priv->YType = GV_NULL; if (XAxis >= 0) priv->XAxis = XAxis; if (YAxis >= 0) priv->YAxis = YAxis; if (Rs->Fields() >= 2) { int Idx = 0; for (bool b = Rs->MoveFirst(); b; b = Rs->MoveNext(), Idx++) { if (priv->XAxis < 0 || priv->YAxis < 0) { for (int i=0; iFields(); i++) { char *s = (*Rs)[i]; LVariantType t = priv->GuessType(s); if (t != GV_NULL && t != GV_STRING) { if (priv->XAxis < 0) { priv->XAxis = i; priv->XType = t; } else if (priv->YAxis < 0) { priv->YAxis = i; priv->YType = t; } else break; } } } if (priv->XAxis >= 0 && priv->YAxis >= 0) AddPair((*Rs)[priv->XAxis], (*Rs)[priv->YAxis]); } } return true; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// LGraph::LGraph(int Id, int XAxis, int YAxis) { d = new LGraphPriv(this); d->XAxis = XAxis; d->YAxis = YAxis; SetPourLargest(true); } LGraph::~LGraph() { DeleteObj(d); } void LGraph::Empty() { d->Empty(); Invalidate(); } LGraph::DataSeries *LGraph::GetData(const char *Name, bool Create) { for (auto s: d->Data) if (!Stricmp(s->Name(), Name)) return s; if (!Create) return NULL; auto s = new DataSeries(d, Name); if (!s) return NULL; s->SetColour(d->GenerateColour()); d->Data.Add(s); return s; } LGraph::DataSeries *LGraph::GetDataAt(size_t index) { return d->Data.IdxCheck(index) ? d->Data[index] : NULL; } size_t LGraph::GetDataLength() { return d->Data.Length(); } void LGraph::SetStyle(Style s) { d->Style = s; Invalidate(); } LGraph::Style LGraph::GetStyle() { return d->Style; } enum Msg { IDM_LINE = 100, IDM_POINT, IDM_AVERAGE, IDM_AVERAGE_SAVE, IDM_SHOW_CURSOR, }; void LGraph::OnMouseClick(LMouse &m) { if (m.IsContextMenu()) { LSubMenu s; m.ToScreen(); auto CursorItem = s.AppendItem("Show Cursor", IDM_SHOW_CURSOR); if (CursorItem) CursorItem->Checked(d->ShowCursor); auto style = s.AppendSub("Style"); style->AppendItem("Line", IDM_LINE); style->AppendItem("Point", IDM_POINT); auto a = s.AppendSub("Average"); auto i = a->AppendItem("Show", IDM_AVERAGE); i->Checked(d->Average); a->AppendItem("Save", IDM_AVERAGE_SAVE); switch (s.Float(this, m.x, m.y)) { case IDM_SHOW_CURSOR: ShowCursor(!d->ShowCursor); break; case IDM_LINE: SetStyle(LineGraph); break; case IDM_POINT: SetStyle(PointGraph); break; case IDM_AVERAGE: d->Average = !d->Average; Invalidate(); break; case IDM_AVERAGE_SAVE: { if (!d->Ave.Length()) { LgiMsg(this, "No average calculated.", "LGraph"); break; } auto s = new LFileSelect(this); s->Name("average.csv"); char Desktop[MAX_PATH_LEN]; LGetSystemPath(LSP_DESKTOP, Desktop, sizeof(Desktop)); s->InitialDir(Desktop); s->Save([this](auto dlg, auto status) { if (status) { LFile o; if (!o.Open(dlg->Name(), O_WRITE)) { LgiMsg(this, "Failed to open file for writing.", "LGraph"); return; } o.SetSize(0); switch (d->MinX.Type) { case GV_INT64: { auto XRange = d->MaxX.CastInt64() - d->MinX.CastInt64() + 1; for (int b=0; bBucketSize; b++) { GraphAv &g = d->Ave[b]; int64 x = d->MinX.CastInt64() + (((b * d->BucketSize) + (d->BucketSize >> 1)) / XRange); int64 y = (g.Count) ? g.Sum / g.Count : 0; o.Print(LPrintfInt64 "," LPrintfInt64 "\n", x, y); } break; } case GV_DOUBLE: { double XRange = d->MaxX.CastDouble() - d->MinX.CastDouble(); for (int b=0; bBucketSize; b++) { GraphAv &g = d->Ave[b]; double x = d->MinX.CastDouble() + ( ((double)b+0.5) * XRange / d->BucketSize); int64 y = (g.Count) ? g.Sum / g.Count : 0; o.Print("%f," LPrintfInt64 "\n", x, y); } break; } default: LAssert(0); break; } } delete dlg; }); break; } } } else if (m.Left() && m.Down()) { d->Select.Reset(new LPoint); d->Select->x = m.x; d->Select->y = m.y; Invalidate(); } } LArray *LGraph::GetSelection() { return &d->Selection; } bool LGraph::ShowCursor() { return d->ShowCursor; } void LGraph::ShowCursor(bool show) { if (show ^ d->ShowCursor) { d->ShowCursor = show; Invalidate(); } } const char *LGraph::GetLabel(bool XAxis) { if (XAxis) return d->LabelX; else return d->LabelY; } void LGraph::SetLabel(bool XAxis, const char *Label) { if (XAxis) d->LabelX = Label; else d->LabelY = Label; Invalidate(); } LGraph::Range LGraph::GetRange(bool XAxis) { Range r; if (XAxis) { r.Min = d->MinX; r.Max = d->MaxX; } else { r.Min = d->MinY; r.Max = d->MaxY; } return r; } void LGraph::SetRange(bool XAxis, Range r) { if (XAxis) { d->MinX = r.Min; d->MaxX = r.Max; } else { d->MinY = r.Min; d->MaxY = r.Max; } Invalidate(); } void LGraph::OnMouseMove(LMouse &m) { d->MouseLoc = m; if (d->ShowCursor) Invalidate(); } bool LGraph::OnMouseWheel(double Lines) { LMouse m; GetMouse(m); if (m.Ctrl()) d->Zoom -= Lines / 30; else if (m.Shift()) d->Px -= Lines / (50 * d->Zoom); else d->Py -= Lines / (50 * d->Zoom); Invalidate(); return true; } void LGraph::OnPaint(LSurface *pDC) { LAutoPtr DoubleBuf; if (d->ShowCursor) DoubleBuf.Reset(new LDoubleBuffer(pDC)); pDC->Colour(L_WORKSPACE); pDC->Rectangle(); LColour cBorder(222, 222, 222); LRect c = GetClient(); LRect data = c; data.Inset(20, 20); data.x2 -= 40; data.SetSize((int)(d->Zoom * data.X()), (int)(d->Zoom * data.Y())); data.Offset((int)(d->Px * data.X()), (int)(d->Py * data.Y())); LRect y = data; y.x2 = y.x1 + 60; data.x1 = y.x2 + 1; LRect x = data; x.y1 = x.y2 - 60; y.y2 = data.y2 = x.y1 - 1; pDC->Colour(cBorder); pDC->Box(&data); // Draw axis d->DrawAxis(pDC, x, true, d->MinX, d->MaxX, d->LabelX); d->DrawAxis(pDC, y, false, d->MinY, d->MaxY, d->LabelY); if (d->ShowCursor) { // Draw in cursor... if (data.Overlap(d->MouseLoc)) { // X axis cursor info auto xCur = d->ViewToData(d->MouseLoc.x - data.x1, data.X(), d->MinX, d->MaxX); pDC->VLine(d->MouseLoc.x, data.y1, data.y2 + d->AxisMarkPx); LDisplayString dsX(GetFont(), d->DataToString(xCur)); dsX.Draw(pDC, d->MouseLoc.x - (dsX.X() >> 1), data.y2 + d->AxisMarkPx + dsX.Y()); // Y axis auto yCur = d->ViewToData(data.y2 - d->MouseLoc.y, data.Y(), d->MinY, d->MaxY); pDC->HLine(data.x1 - d->AxisMarkPx, data.x2, d->MouseLoc.y); LDisplayString dsY(GetFont(), d->DataToString(yCur)); dsY.Draw(pDC, data.x1 - d->AxisMarkPx - dsY.X(), d->MouseLoc.y - (dsY.Y() >> 1)); } } // Draw data int cx, cy, px, py; pDC->Colour(LColour(0, 0, 222)); if (d->Average && !d->Ave.Length()) { for (auto data: d->Data) { auto &values = data->d->values; for (int i=0; iDataToView(p.x, d->BucketSize, d->MinX, d->MaxX); d->Ave[Bucket].Sum += p.y.CastInt64(); d->Ave[Bucket].Count++; } } } switch (d->Style) { case LineGraph: { for (auto data: d->Data) { auto &values = data->d->values; pDC->Colour(data->GetColour()); for (int i=0; iDataToView(p.x, x.X(), d->MinX, d->MaxX); cy = y.y2 - (int)d->DataToView(p.y, y.Y(), d->MinY, d->MaxY); if (i) pDC->Line(cx, cy, px, py); px = cx; py = cy; } } break; } case PointGraph: { for (auto data: d->Data) { auto &values = data->d->values; pDC->Colour(data->GetColour()); for (int i=0; iDataToView(p.x, x.X(), d->MinX, d->MaxX); int ymap = (int)d->DataToView(p.y, y.Y(), d->MinY, d->MaxY); // LgiTrace("%s -> %i (%s, %s)\n", p.x.Value.Date->Get().Get(), xmap, d->MinX.Value.Date->Get().Get(), d->MaxX.Value.Date->Get().Get()); cx = x.x1 + xmap; cy = y.y2 - ymap; pDC->Set(cx, cy); if (d->Select && abs(d->Select->x - cx) < SELECTION_SIZE && abs(d->Select->y - cy) < SELECTION_SIZE) { d->Selection.Add(&p); } } if (d->Average) { int px = -1, py = -1; pDC->Colour(LColour(255, 0, 0)); for (int b=0; bBucketSize; b++) { if (d->Ave[b].Count) { int cx = x.x1 + (((b * x.X()) + (x.X() >> 1)) / d->BucketSize); LVariant v = d->Ave[b].Sum / d->Ave[b].Count; int cy = y.y2 - (int)d->DataToView(v, y.Y(), d->MinY, d->MaxY); if (py >= 0) { pDC->Line(cx, cy, px, py); } px = cx; py = cy; } } } } if (d->Select) { d->Select.Reset(); SendNotify(); } break; } } }