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,477 +1,481 @@ /// \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; +/// Wrapper around a uint64_t timestamp. 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(uint64_t sysTime) { ts = sysTime; } LTimeStamp(time_t unixTime = 0) { if (unixTime) *this = unixTime; } uint64_t Get() const { return ts; } uint64_t &Ref() { 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 time_t Unix() const; // operator bool() const { return ts != 0; } bool Valid() const { return ts != 0; } LTimeStamp operator +(uint64_t offset) { LTimeStamp t; t.Ref() = ts + offset; return t; } 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; } }; +/// 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(); +}; + +/// Time zone information +struct LgiClass LTimeZone +{ +public: + /// The offset from UTC + float Offset; + /// The name of the zone + const char *Text; + + /// A list of all known timezones. + static LTimeZone *GetTimeZones(); + + /// Retrieves 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); +}; /// 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 ); /// Figure out the right TZ offset for a given date according to the DST /// rules for the current timezone. bool InferTimeZone(bool ConvertTime = true); /// 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 time_t GetUnix(); bool SetUnix(time_t tt); /// Returns the 64bit LTimeStamp. 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); /// 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); #ifdef WINDOWS operator SYSTEMTIME() const; LDateTime &operator =(const SYSTEMTIME &st); #endif operator struct tm() const; #ifdef _DEBUG static bool UnitTests(); #endif }; -/// 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 WINDOWS +LgiExtern LDateTime ConvertSysTime(SYSTEMTIME &st, int year); +#endif #endif diff --git a/include/lgi/common/Store3Defs.h b/include/lgi/common/Store3Defs.h --- a/include/lgi/common/Store3Defs.h +++ b/include/lgi/common/Store3Defs.h @@ -1,456 +1,460 @@ #ifndef _STORE3_DEFS_H_ #define _STORE3_DEFS_H_ ////////////////////////////////////////////////////////////////////////////////////////////////////// // CORE DEFINITIONS (relevant to any implementation) ////////////////////////////////////////////////////////////////////////////////////////////////////// /// Load state /// /// This is used by both objects AND iterators enum Store3State { /// The object is not loaded at all.. Store3Unloaded, /// The object is currently loading (in some worker thread or something) /// Best to not try and access anything like data fields. Store3Loading, /// The mail object has only it's headers loaded, and not the body. Store3Headers, /// The object is fully loaded and can be accessed normally. Store3Loaded, }; inline const char *toString(Store3State s) { #define _(s) case s: return #s; switch (s) { _(Store3Unloaded) _(Store3Loading) _(Store3Headers) _(Store3Loaded) } #undef _ LAssert(0); return "#invalidStore3State"; } /// Folder system type enum Store3SystemFolder { Store3SystemNone, Store3SystemInbox, Store3SystemTrash, Store3SystemOutbox, Store3SystemSent, Store3SystemCalendar, Store3SystemContacts, Store3SystemSpam, }; inline const char *toString(Store3SystemFolder s) { #define _(s) case s: return #s; switch (s) { _(Store3SystemNone) _(Store3SystemInbox) _(Store3SystemTrash) _(Store3SystemOutbox) _(Store3SystemSent) _(Store3SystemCalendar) _(Store3SystemContacts) _(Store3SystemSpam) } #undef _ LAssert(0); return "#invalidStore3SystemFolder"; } /// This defines the possible outcomes of calling a function. enum Store3Status { // Open mail store specific: //-------------------------------------------------------------------------------- /// The store file is missing Store3Missing = -1, /// A format upgrade is required to open the store. Store3UpgradeRequired = -2, /// Function not implemented. Store3NotImpl = -3, /// Missing permissions for operation. Store3NoPermissions = -4, // General: //-------------------------------------------------------------------------------- /// The method failed and no action was taken. Store3Error = 0, /// The method succeeded but the action was not completed immediately, notification /// of the actions completion will come later via the callback interface. Store3Delayed = 1, /// The method succeeded and the action has been already completed. Store3Success = 2, }; /// Possible parts of UI enum Store3UiFields { Store3UiCurrentPos, // [Set] sets the current progress value Store3UiMaxPos, // [Set] sets the maximum progress value Store3UiStatus, // [Set] set a status/progress string Store3UiError, // [Set] set an error string Store3UiInteractive, // [Get] returns a bool if the user is expecting interaction Store3UiCancel, // [Get] returns a bool indicating if the user has cancelled the operation Store3UiNewFormat, // [Get] returns a integer/enum describing the new format to use }; enum Store3Backend { Store3Sqlite, Store3Imap, Store3Mapi, Store3Webdav, }; ////////////////////////////////////////////////////////////////////////////////////////////////////// // MAIL/CALENDAR CLIENT RELATED DEFINITIONS ////////////////////////////////////////////////////////////////////////////////////////////////////// enum EmailFlags { // Mail flags MAIL_SENT = (1 << 0), MAIL_RECEIVED = (1 << 1), MAIL_CREATED = (1 << 2), MAIL_FORWARDED = (1 << 3), MAIL_REPLIED = (1 << 4), MAIL_ATTACHMENTS = (1 << 5), MAIL_READ = (1 << 6), // #define MAIL_MARK (1 << 7), // Deprecated MAIL_READY_TO_SEND = (1 << 8), // If this flag is set then the user // wants to send the mail on the next // run. When the user just saves a new // message in the outbox this isn't set // and isn't sent until they go in and // say "send". At which point this flag // is set and the message sent MAIL_READ_RECEIPT = (1 << 9), MAIL_IGNORE = (1 << 10), MAIL_FIXED_WIDTH_FONT = (1 << 11), MAIL_BOUNCED = (1 << 12), // The bounce source mail MAIL_BOUNCE = (1 << 13), // The outgoing copy of a bounced mail MAIL_SHOW_IMAGES = (1 << 14), // User selected to show images in HTML MAIL_NEW = (1 << 18), // Mail is new, cleared after the OnNew event happens MAIL_STORED_FLAT = (1 << 19), // Message is signed and/or encrypted and needs // to be stored in such a way as the RFC822 image // is not damaged. // Bayesian filter flags: MAIL_BAYES_HAM = (1 << 16), // Bayesian classified originally as ham MAIL_BAYES_SPAM = (1 << 17), // Bayesian classified originally as spam MAIL_HAM_DB = (1 << 20), // In Bayesian ham word database MAIL_SPAM_DB = (1 << 21), // In Bayesian spam word database }; extern LString EmailFlagsToStr(int flags); enum EmailAddressType { MAIL_ADDR_TO = 0, MAIL_ADDR_CC = 1, MAIL_ADDR_BCC = 2, MAIL_ADDR_FROM = 3, }; enum EmailPriority { // FIELD_PRIORITY is equivilent to the header field: X-Priority // 1 - High // ... // 5 - Low MAIL_PRIORITY_HIGH = 1, MAIL_PRIORITY_NORMAL = 3, MAIL_PRIORITY_LOW = 5, }; enum CalendarReminderType { CalEmail, CalPopup, CalScriptCallback, CalMaxType, }; enum CalendarReminderUnits { CalMinutes, CalHours, CalDays, CalWeeks, CalMaxUnit, }; enum CalendarShowTimeAs { CalFree = 0, CalTentative = 1, CalBusy = 2, CalOut = 3 }; enum CalendarType { CalEvent, CalTodo, CalJournal, CalRequest, CalReply, + + CalTypeMax, }; - +CalendarType ParseCalendarType(const char *type); +const char *ToString(CalendarType type); enum CalendarPrivacyType { CalDefaultPriv = 0, CalPublic, CalPrivate }; // To convert to string use 'Store3ItemTypeName' enum Store3ItemTypes { MAGIC_NONE = 0x00000000, MAGIC_BASE = 0xAAFF0000, MAGIC_MAIL = (MAGIC_BASE+1), // Mail item MAGIC_CONTACT = (MAGIC_BASE+2), // Contact item MAGIC_FOLDER_OLD = (MAGIC_BASE+3), // Old Store1 folder of items MAGIC_MAILBOX = (MAGIC_BASE+4), // Root of mail tree (nothing-abstract) MAGIC_ATTACHMENT = (MAGIC_BASE+5), // Mail attachment MAGIC_ANY = (MAGIC_BASE+6), // Trash folder type (accepts any object) MAGIC_FILTER = (MAGIC_BASE+7), // Used to match messages against MAGIC_FOLDER = (MAGIC_BASE+8), // Folder v2 MAGIC_CONDITION = (MAGIC_BASE+9), // Filter condition MAGIC_ACTION = (MAGIC_BASE+10), // Filter action MAGIC_CALENDAR = (MAGIC_BASE+11), // Calendar event MAGIC_ATTENDEE = (MAGIC_BASE+12), // Event attendee MAGIC_GROUP = (MAGIC_BASE+13), // Group of contacts MAGIC_CALENDAR_FILE = (MAGIC_BASE+14), // Calendar attachment MAGIC_MAX = (MAGIC_BASE+15) // One past the end }; extern const char *Store3ItemTypeName(Store3ItemTypes t); // When setting this via LDataI::SetInt the return value is: // - true if you need to mark the object dirty so that it gets saved // - false if the flags is stored elsewhere and you don't have to save. enum Store3Fields { FIELD_NULL = 0, FIELD_FLAGS = 1, // (EmailFlags) The message flags FIELD_TO = 2, // (DIterator) List of recipients FIELD_CC = 3, // (EmailAddressType) FIELD_FROM = 4, // (Store3Addr*) The From address FIELD_REPLY = 5, // (Store3Addr*) The Reply-To address FIELD_SUBJECT = 6, // (char*) Subject of the message FIELD_TEXT = 7, // (char*) Textual body FIELD_MESSAGE_ID = 8, // (char*) The message ID FIELD_DATE_RECEIVED = 9, // (LDateTime*) Received date FIELD_INTERNET_HEADER = 10, // (char*) The internet headers // Contact fields FIELD_FIRST_NAME = 11, // (char*) First name FIELD_LAST_NAME = 12, // (char*) Last/surname FIELD_EMAIL = 13, // (char*) The default email address // \sa 'FIELD_ALT_EMAIL' FIELD_HOME_STREET = 14, // (char*) FIELD_HOME_SUBURB = 15, // (char*) FIELD_HOME_POSTCODE = 16, // (char*) FIELD_HOME_STATE = 17, // (char*) FIELD_HOME_COUNTRY = 18, // (char*) FIELD_WORK_PHONE = 19, // (char*) This was already defined FIELD_HOME_PHONE = 20, // (char*) FIELD_HOME_MOBILE = 21, // (char*) FIELD_HOME_IM = 22, // (char*) FIELD_HOME_FAX = 23, // (char*) FIELD_HOME_WEBPAGE = 24, // (char*) FIELD_NICK = 25, // (char*) FIELD_SPOUSE = 26, // (char*) FIELD_NOTE = 27, // (char*) FIELD_PLUGIN_ASSOC = 28, // (char*) // More fields FIELD_SIZE = 29, // (uint64) FIELD_DATE_SENT = 30, // (LDateTime*) FIELD_COLUMN = 31, // (uint64) // FIELD_BCC = 32, // Deprecated FIELD_MIME_TYPE = 33, // (char*) The MIME type FIELD_PRIORITY = 34, // (EmailPriority) FIELD_FOLDER_OPEN = 35, // (bool) True if the folder is expanded to show child folders // FIELD_CODE_PAGE = 36, // Deprecated FIELD_COLOUR = 37, // (uint32) Rgba32 FIELD_ALTERNATE_HTML = 38, // (char*) The HTML part of the message FIELD_CONTENT_ID = 39, // (char*) An attachment content ID // Filter fields FIELD_FILTER_NAME = 40, // (char*) // FIELD_ACT_TYPE = 46, // Deprecated // FIELD_ACT_ARG = 47, // Deprecated // FIELD_DIGEST_INDEX = 48, // Deprecated FIELD_COMBINE_OP = 49, FIELD_FILTER_INDEX = 50, FIELD_FILTER_SCRIPT = 52, FIELD_STOP_FILTERING = 54, FIELD_FILTER_INCOMING = 55, FIELD_FILTER_OUTGOING = 56, FIELD_FILTER_CONDITIONS_XML = 57, FIELD_FILTER_ACTIONS_XML = 58, FIELD_FILTER_INTERNAL = 59, // Calendar fields // FIELD_CAL_START_LOCAL = 60, // **deprecated** // FIELD_CAL_END_LOCAL = 61, // **deprecated** FIELD_CAL_SUBJECT = 62, // (char*) event title FIELD_CAL_LOCATION = 63, // (char*) location of event FIELD_CAL_ALL_DAY = 64, // (bool) All Day setting // FIELD_CAL_REMINDER_ACTION = 65, // **deprecated** (CalendarReminderType) The reminder type // FIELD_CAL_REMINDER_UNIT = 66, // **deprecated** (CalendarReminderUnits) The unit of time FIELD_CAL_SHOW_TIME_AS = 67, // (CalendarShowTimeAs) Busy/Free etc FIELD_CAL_RECUR_FREQ = 68, // (CalRecurFreq) Base time unit of recurring: e.g. Day/Week/Month/Year. FIELD_CAL_RECUR_INTERVAL = 69, // (int) Number of FIELD_CAL_RECUR_FREQ units of time between recurring events. FIELD_CAL_RECUR_FILTER_DAYS = 70 , // (int) Bitmask of days: Bit 0 = Sunday, Bit 1 = Monday etc FIELD_CAL_RECUR_FILTER_MONTHS = 71, // (int) Bitmask of months: Bit 0 = January, Bit 1 = Feburary etc FIELD_CAL_RECUR_FILTER_YEARS = 72, // (char*) Comma separated list of years to filter on. FIELD_CAL_NOTES = 73, // (char*) Textual notes FIELD_CAL_TYPE = 74, // (CalendarType) The type of event FIELD_CAL_COMPLETED = 75, // (int) 0 -> 100% FIELD_CAL_START_UTC = 76, // (LDateTime*) The start time and date FIELD_CAL_END_UTC = 77, // (LDateTime*) The end time and date FIELD_CAL_RECUR_FILTER_POS = 78, // (char*) Comma separated list of integers defining positions in the month to filter on. FIELD_CAL_RECUR_END_DATE = 79, // (LDateTime*) The end of recurence if FIELD_CAL_RECUR_END_TYPE == CalEndOnDate. FIELD_CAL_RECUR_END_COUNT = 80, // (int) Times to recur if FIELD_CAL_RECUR_END_TYPE == CalEndOnCount. FIELD_CAL_RECUR_END_TYPE = 81, // (CalRecurEndType) Which ending to use... needs an enum FIELD_CAL_RECUR = 82, // (int) true if the event recurs. - FIELD_CAL_TIMEZONE = 83, // (char*) The timezone as text + FIELD_CAL_TIMEZONE = 83, // (char*) The timezone as text in the form [,] + // Where offsetInMinutes is numeric and tzIdentifier is textual, usually in the form: Continent/City FIELD_CAL_PRIVACY = 84, // (CalendarPrivacyType) The privacy setting FIELD_ATTENDEE_JSON = 85, FIELD_CAL_ATTACHMENTS = 86, // List of attached files // 2nd lot of contact fields FIELD_WORK_STREET = 90, // (char*) FIELD_WORK_SUBURB = 91, // (char*) FIELD_WORK_POSTCODE = 92, // (char*) FIELD_WORK_STATE = 93, // (char*) FIELD_WORK_COUNTRY = 94, // (char*) // #define FIELD_WORK_PHONE is previously defined FIELD_WORK_MOBILE = 95, // (char*) FIELD_WORK_IM = 96, // (char*) FIELD_WORK_FAX = 97, // (char*) FIELD_WORK_WEBPAGE = 98, // (char*) FIELD_COMPANY = 99, // (char*) /* Deprecated FIELD_CONTACT_CUST_FLD1 = 100, // (char*) FIELD_CONTACT_CUST_VAL1 = 101, // (char*) FIELD_CONTACT_CUST_FLD2 = 102, // (char*) FIELD_CONTACT_CUST_VAL2 = 103, // (char*) FIELD_CONTACT_CUST_FLD3 = 104, // (char*) FIELD_CONTACT_CUST_VAL3 = 105, // (char*) FIELD_CONTACT_CUST_FLD4 = 106, // (char*) FIELD_CONTACT_CUST_VAL4 = 107, // (char*) */ FIELD_CONTACT_IMAGE = 108, // (LSurface*) FIELD_CONTACT_JSON = 109, // (char*) // Misc additional fields FIELD_LABEL = 110, // (char*) Mail label FIELD_CHARSET = 111, // (char*) A character set FIELD_ALT_EMAIL = 112, // (char*) Comma separated list of alternative, // non-default email addresses. The default addr // is stored under 'FIELD_EMAIL' FIELD_UID = 113, // (char*) FIELD_TITLE = 114, // (char*) FIELD_TIMEZONE = 115, // (char*) FIELD_REFERENCES = 116, // (char*) FIELD_SERVER_UID = 117, // (int64) Server identifier FIELD_FOLDER_PERM_READ = 118, // (int64) FIELD_FOLDER_PERM_WRITE = 119, // (int64) FIELD_GROUP_NAME = 120, // (char*) The name of a contact group FIELD_HTML_CHARSET = 121, // (char*) The character set of the HTML body part FIELD_POSITION = 122, // (char*) Role in organisation FIELD_GROUP_LIST = 123, // (char*) FIELD_FOLDER_THREAD = 124, // (int64) FIELD_ACCOUNT_ID = 125, // (int64) The ID of an account FIELD_FROM_CONTACT_NAME = 126, // (char*) Not used at the Store3 level FIELD_FWD_MSG_ID = 127, // (char*) Mail::FwdMsgId FIELD_FOLDER_TYPE = 128, // (Store3ItemTypes) FIELD_FOLDER_NAME = 129, // (char*) Name of the folder FIELD_UNREAD = 130, // (int64) Count of unread items FIELD_SORT = 131, // (int64) Sort setting FIELD_STATUS = 132, // (Store3Status) FIELD_VERSION = 133, // (int64) FIELD_ID = 134, // (int64) FIELD_READONLY = 135, // (bool) FIELD_NAME = 136, // (char*) FIELD_WIDTH = 137, // (int64) // #define FIELD_ATTACHMENT_CONTENT 139 - Deprecated // #define FIELD_ATTACHMENTS 140 - Deprecated, use FIELD_MIME_SEG instead FIELD_CACHE_FILENAME = 141, // (char*) IMAP backend: the filename FIELD_CACHE_FLAGS = 142, // (char*) IMAP backend: IMAP flags FIELD_DONT_SHOW_PREVIEW = 143, // (bool) FIELD_STORE_PASSWORD = 144, // (char*) FIELD_LOADED = 145, // (Store3State) FIELD_DEBUG = 146, // (char*) FIELD_MIME_SEG = 147, // (LDataIt) FIELD_STORE_TYPE = 148, // (Store3Backend) FIELD_BOUNCE_MSG_ID = 149, // (char*) FIELD_FOLDER_INDEX = 150, // (int64) FIELD_FORMAT = 151, // (int64) FIELD_SYSTEM_FOLDER = 152, // (Store3SystemFolder) FIELD_ERROR = 153, // (char*) An error message FIELD_TYPE = 154, // (char*) The type of the object FIELD_ATTACHMENTS_DATA = 155, // Meta field for specifying attachment data contents FIELD_ATTACHMENTS_NAME = 156, // Meta field for specifying attachment file names FIELD_MEMBER_OF_GROUP = 157, // Meta field for specifying membership of a content group FIELD_TEMP_PATH = 158, // (char*) A temporary path to store files... FIELD_HTML_RELATED = 159, // Array of related attachments for the HTML content. // Pass this to an email's SetObj member to add a // related attachment. FIELD_CAL_REMINDERS = 160, // Individual reminders as CSV, fields are: // Number, CalendarReminderUnits, CalendarReminderType, Param // // CalEmail: param option email address to send to as well as the guests // CalPopup: not used // CalScriptCallback: not impl (but will be the script function and args) // FIELD_CAL_LAST_CHECK = 161, // (LDateTime) Ts the calendar event was last checked for reminders FIELD_DATE_MODIFIED = 162, // (LDateTime) Ts of modification FIELD_INBOX = 163, // (LDataFolderI*) Inbox for mail store FIELD_OUTBOX = 164, // (LDataFolderI*) Outbox for mail store FIELD_SENT = 165, // (LDataFolderI*) Sent folder for mail store FIELD_TRASH = 166, // (LDataFolderI*) Trash folder for mail store FIELD_IMAP_SEQ = 167, // (uint32_t) IMAP sequence number FIELD_CAL_STATUS = 168, // (char*) Status of the vCal event. FIELD_STORE_STATUS = 169, // (ScribeAccountletStatusIcon) Status (icon) of a LDataStoreI FIELD_RECEIVED_DOMAIN = 170, // (char*) First "Received:" header domain. (See also SdReceivedDomain) FIELD_FOLDER_ITEMS = 171, // (int64) Number of items in a folder.. FIELD_PARENT = 172, // (LDataFolderI*) Parent of store3 item FIELD_URI = 173, // (char*) FIELD_MAX, }; #endif diff --git a/include/lgi/common/TimeZoneInfo.h b/include/lgi/common/TimeZoneInfo.h --- a/include/lgi/common/TimeZoneInfo.h +++ b/include/lgi/common/TimeZoneInfo.h @@ -1,522 +1,521 @@ // For reading timezone info files: // https://www.rfc-editor.org/rfc/rfc8536.html #pragma once #ifdef HAIKU #include #endif #include "lgi/common/DateTime.h" class LTimeZoneInfo { - static int TimeToSeconds(LString t) + static int64_t TimeToSeconds(LString t) { auto parts = t.SplitDelimit(":"); auto hrs = parts[0].Int(); auto min = parts.IdxCheck(1) ? parts[1].Int() : 0; auto sec = parts.IdxCheck(2) ? parts[2].Int() : 0; return (hrs * 3600) + (min * 60) + sec; } struct Header { char magic[4]; char version; char reserved[15]; uint32_t UtCount; uint32_t StdCount; uint32_t LeapCount; uint32_t TimeCount; uint32_t TypeCount; uint32_t CharCount; }; struct LocalTimeType { int32_t UtOffset; uint8_t IsDst; uint8_t Idx; }; struct LeapSecond1 { uint32_t occur; uint32_t corr; }; struct LeapSecond2 { uint64_t occur; uint32_t corr; }; struct PosixTransition { int JulianIdx = -1; // 0 - 365 int Month = 0; // 1 - 12 int Week = 0; // 1 - 5 int Day = 0; // 0 - 6 LString time; int OffsetSeconds(int StdOffsetSeconds) { int sec = StdOffsetSeconds; if (time) { auto TwoAm = 2 * 3600; auto TimeOff = TimeToSeconds(time); auto Offset = TimeOff - TwoAm; sec += Offset; } return sec; } LDateTime Get(int year) { LDateTime dt; if (JulianIdx >= 0) { dt.SetTimeZone(0, false); dt.Year(year); dt.Month(1); dt.Day(1); dt.AddDays(JulianIdx); } else if (Month) { dt.SetTimeZone(0, false); dt.Year(year); dt.Month(Month); dt.Day(1); if (Week == 5) { // Find last instance of 'Day' dt.Day(dt.DaysInMonth()); while (dt.Day() > 1 && dt.DayOfWeek() != Day) dt.AddDays(-1); } else { // Find 'Week'th instance of 'Day' int CurWk = Week; int MonthsDays = dt.DaysInMonth(); while (dt.Day() <= MonthsDays) { int CurDay = dt.DayOfWeek(); if (CurDay == Day) { if (--CurWk == 0) break; } dt.AddDays(1); } } } else LAssert(0); if (time) dt.Hours(time.Int()); else dt.Hours(2); return dt; } bool Set(LString spec) { auto parts = spec.SplitDelimit("/"); auto s = parts[0]; if (parts.Length() > 1) time = parts[1]; if (s(0) == 'J') { JulianIdx = s(1, -1).Int() - 1; return JulianIdx >= 0 && JulianIdx < 365; } else if (s(0) == 'M') { auto parts = s(1, -1).SplitDelimit("."); if (parts.Length() != 3) return false; Month = parts[0].Int(); Week = parts[1].Int(); Day = parts[2].Int(); return Month >= 1 && Month <= 12; } else { JulianIdx = s.Int(); return JulianIdx >= 0 && JulianIdx < 365; } } }; enum TVersion { Version1 = 0, Version2 = '2', Version3 = '3', }; LString data; LPointer ptr; char *end = NULL; bool Eof() { return ptr.c >= end; } ssize_t Remaining() { return end - ptr.c; } Header hdr; LArray TransitionTimes1; LArray TransitionTimes2; LArray TransitionTypes; LArray LocalTimeTypes; LString TimeZoneDesignations; LArray LeapSeconds1; LArray LeapSeconds2; LArray StandardWall; LArray UtLocal; // Format: // https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_03 // Examples: // https://support.cyberdata.net/portal/en/kb/articles/010d63c0cfce3676151e1f2d5442e311 LString footer; bool Read(Header &h) { h.magic[0] = *ptr.c++; h.magic[1] = *ptr.c++; h.magic[2] = *ptr.c++; h.magic[3] = *ptr.c++; if (Strncmp(h.magic, "TZif", 4)) return OnError("bad magic value"); h.version = *ptr.u8++; ptr.c += 15; h.UtCount = ntohl(*ptr.u32++); h.StdCount = ntohl(*ptr.u32++); h.LeapCount = ntohl(*ptr.u32++); h.TimeCount = ntohl(*ptr.u32++); h.TypeCount = ntohl(*ptr.u32++); h.CharCount = ntohl(*ptr.u32++); return !Eof(); } bool Read(LocalTimeType &t) { if (Remaining() < 6) return false; t.UtOffset = ntohl(*ptr.s32++); t.IsDst = *ptr.u8++; t.Idx = *ptr.u8++; return true; } bool Read(LeapSecond1 &ls) { if (Remaining() < 8) return false; ls.occur = ntohl(*ptr.u32++); ls.corr = ntohl(*ptr.u32++); return true; } bool Read(LeapSecond2 &ls) { if (Remaining() < 12) return false; ls.occur = ntohll(*ptr.u64++); ls.corr = ntohl(*ptr.u32++); return true; } template bool Read(LArray &out, size_t elem) { if (elem == 0) return true; size_t bytes = sizeof(T) * elem; if (!out.Length(elem)) return OnError("alloc failed"); if (Remaining() < bytes) return OnError("not enough data left"); memcpy(out.AddressOf(), ptr.vp, bytes); ptr.c += bytes; return true; } bool Read(LString &s) { char *start = ptr.c; while (!Eof() && *ptr.c) ptr.c++; if (ptr.c > start) if (!s.Set(start, ptr.c - start)) return OnError("alloc failed"); ptr.c++; return true; } bool Read(TVersion ver) { if (ver == Version1) { if (!Read(TransitionTimes1, hdr.TimeCount)) return OnError("failed to read transitions"); } else if (ver == Version2) { if (!Read(TransitionTimes2, hdr.TimeCount)) return OnError("failed to read transitions"); for (auto &i: TransitionTimes2) i = ntohll(i); } else return OnError("unsupported version"); if (!Read(TransitionTypes, hdr.TimeCount)) return OnError("failed to read transition types"); if (!LocalTimeTypes.Length(hdr.TypeCount)) return OnError("alloc failed"); for (unsigned i=0; i &Info, LDateTime &Start, LDateTime *End) { auto InRange = [&](LDateTime &dt) { if (End) return dt >= Start && dt < *End; return dt.Year() == (Start.Year()-1) || dt.Year() == Start.Year(); }; for (unsigned i=0; iGet().Get() : NULL); #endif for (int year = Start.Year()-1; year <= (End ? End->Year() : Start.Year()); year++) { // Figure out the start and end DST for 'year' int DstOffset = endTrans.OffsetSeconds(StdOffsetSeconds) / 60; auto startDt = startTrans.Get(year); startDt.SetTimeZone(StdOffsetSeconds / 60, false); if (InRange(startDt)) { auto &s = Info.New(); s.Utc = startDt.Ts(); s.Offset = DstOffset; } auto endDt = endTrans.Get(year); endDt.SetTimeZone(StdOffsetSeconds / 60, false); #if 0 printf("\tstart=%s %i, end=%s %i\n", startDt.Get().Get(), InRange(startDt), endDt.Get().Get(), InRange(endDt)); #endif if (InRange(endDt)) { auto &e = Info.New(); e.Utc = endDt.Ts(); e.Offset = StdOffsetSeconds / 60; } } } return Info.Length() > 0; } }; diff --git a/include/lgi/common/vCard-vCal.h b/include/lgi/common/vCard-vCal.h --- a/include/lgi/common/vCard-vCal.h +++ b/include/lgi/common/vCard-vCal.h @@ -1,124 +1,123 @@ #ifndef _VCARD_VCAL_H #define _VCARD_VCAL_H // #include "ScribeDefs.h" #include "Store3.h" #define FIELD_PERMISSIONS 2000 class VIoPriv; class VIo { public: struct Parameter { LString Field; LString Value; void Set(const char *f, const char *v) { Field = f; Value = v; } }; struct TimeZoneSection { int From, To; LDateTime Start; LString Rule; LString Name; }; struct TimeZoneInfo { LString Name; TimeZoneSection Normal; TimeZoneSection Daylight; }; class ParamArray : public LArray { public: ParamArray(const char *Init = NULL) { if (Init) { LString s = Init; LString::Array a = s.SplitDelimit(","); for (unsigned i=0; i #include #include #include #include #if defined(MAC) #include #endif #ifdef WINDOWS #include #define timegm _mkgmtime #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) AddMinutes(NewTz - _Tz); _Tz = NewTz; } LDateTime::operator struct tm() const { struct tm 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; return t; } #ifdef WINDOWS LDateTime::operator SYSTEMTIME() const { 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(); return System; } LDateTime &LDateTime::operator =(const SYSTEMTIME &st) { _Year = st.wYear; _Month = st.wMonth; _Day = st.wDay; _Hours = st.wHour; _Minutes = st.wMinute; _Seconds = st.wSecond; _Thousands = st.wMilliseconds; return *this; } #endif bool LDateTime::InferTimeZone(bool ConvertTime) { #ifdef WINDOWS SYSTEMTIME System = *this; // Compare the non-year parts of the SYSTEMTIMEs auto SysCmp = [](SYSTEMTIME &a, SYSTEMTIME &b) { #define CMP(name) if (auto d = a.name - b.name) return d; CMP(wMonth); CMP(wDay); CMP(wHour); CMP(wMinute); CMP(wSecond); CMP(wMilliseconds); return 0; }; auto MakeAbsolute = [this](SYSTEMTIME &s) { if (s.wYear == 0) { // Convert from relative to absolute... LDateTime dt = *this; dt.Month(s.wMonth); int days = dt.DaysInMonth(); if (s.wDay == 5) { // Use the final day of the month for (int day = days; day > 0; day--) { dt.Day(day); if (dt.DayOfWeek() == s.wDayOfWeek) break; } s.wDay = dt.Day(); } else if (s.wDay > 0) { // Find the 'wDay'th instance of 'wDayOfWeek' int week = 1; for (int day = 1; day <= days; day++) { dt.Day(day); if (dt.DayOfWeek() == s.wDayOfWeek) { if (week++ == s.wDay) break; } } s.wDay = dt.Day(); } else LAssert(!"unexpected relative date"); } }; // 'System' is currently in 'UTC' but we want the local version of the time, // not in the _Tz timezone, but the effective timezone for that // actual moment in question, which could have a different DST offset. TIME_ZONE_INFORMATION tzi = {}; if (!GetTimeZoneInformationForYear(_Year, NULL, &tzi)) return false; MakeAbsolute(tzi.StandardDate); MakeAbsolute(tzi.DaylightDate); auto order = SysCmp(tzi.DaylightDate, tzi.StandardDate); // order > 0 = DaylightDate is after StandardDate // order < 0 = DaylightDate is before StandardDate LAssert(order != 0); auto a = SysCmp(System, tzi.StandardDate); // a > 0 = System is after StandardDate // a < 0 = System is before StandardDate auto b = SysCmp(System, tzi.DaylightDate); // b > 0 = System is after DaylightDate // b < 0 = System is before DaylightDate int tz = (int16)-tzi.Bias; if (order > 0) { // year is: DST -> Normal -> DST if (a < 0 || b > 0) tz -= (int16)tzi.DaylightBias; } else { // year is: Normal -> DST -> Normal if (a < 0 && b > 0) tz -= (int16)tzi.DaylightBias; } if (ConvertTime) SetTimeZone(tz - _Tz, true); else _Tz = tz; return true; #else auto tt = GetUnix(); auto local = localtime(&tt); if (!local) return false; auto tz = local->tm_gmtoff / 60; if (ConvertTime) SetTimeZone(tz - _Tz, true); else _Tz = (int16)tz; return true; #endif return false; } 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->Utc = tmp; - } - else - { - // Year is: Standard->Daylight->Standard - LDateTime tmp = s; - i->Offset = -(Tzi.Bias + Tzi.StandardBias); - tmp.AddMinutes(-i->Offset); - 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->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->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->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->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().Get() / Second64Bit) - Offset1800]; - while (startDate) - { - auto next = [tz nextDaylightSavingTimeTransitionAfterDate:startDate]; - auto &i = Info.New(); - - auto nextTs = (time_t)[next timeIntervalSince1970]; - i.Utc = nextTs; - i.Offset = (int)([tz secondsFromGMTForDate:[next dateByAddingTimeInterval:60]]/60); - - #if DEBUG_DST_INFO - { - LDateTime dt; - dt.Set(i.Utc); - 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().Valid() == 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()); - static bool first = true; - if (first) - { - first = false; - 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; auto sysTz = SystemTimeZone(); if (_Tz == sysTz) GetLocalTime(&stNow); else GetSystemTime(&stNow); #if 1 *this = stNow; if (_Tz && _Tz != sysTz) { // Adjust to this objects timezone... auto tz = _Tz; _Tz = 0; SetTimeZone(tz, true); } #else // This is actually less efficient. FILETIME ftNow; SystemTimeToFileTime(&stNow, &ftNow); uint64 i64 = ((uint64)ftNow.dwHighDateTime << 32) | ftNow.dwLowDateTime; OsTime(i64); #endif #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; } LTimeStamp LDateTime::Ts() const { LTimeStamp ts; Get(ts); return ts; } time_t LDateTime::GetUnix() { /* This isn't unit time? LTimeStamp s; Get(s); #if defined(WINDOWS) return s.Get() / LDateTime::Second64Bit / 116445168000000000LL; #else return s.Get() / LDateTime::Second64Bit - Offset1800; #endif */ struct tm t = *this; time_t tt = timegm(&t); if (_Tz) tt -= _Tz * 60; return tt; } bool LDateTime::SetUnix(time_t tt) { /* This isn't unit time? #if defined(WINDOWS) return Set(s * LDateTime::Second64Bit + 116445168000000000LL); #else return Set((s + Offset1800) * LDateTime::Second64Bit); #endif */ 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; } bool LDateTime::Set(const LTimeStamp &s) { #if defined WIN32 return OsTime(s.Get()); #else SetUnix(s.Unix()); _Thousands = s.Get() % 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) { auto diff = timegm(t) - mktime(t); _Tz = (int16)(diff / 60); } return true; } uint64_t LDateTime::OsTime() const { #ifdef WINDOWS FILETIME Utc; SYSTEMTIME System = *this; 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) { #ifdef WINDOWS if (_Tz) // Adjust for timezone ts += (int64)_Tz * 60 * Second64Bit; FILETIME Utc; Utc.dwHighDateTime = ts >> 32; Utc.dwLowDateTime = ts & 0xffffffff; SYSTEMTIME System; if (!FileTimeToSystemTime(&Utc, &System)) { DWORD Err = GetLastError(); LAssert(!"FileTimeToSystemTime failed."); return false; } *this = System; return true; #else return SetUnix((time_t)ts); #endif } bool LDateTime::Get(LTimeStamp &s) const { #ifdef WINDOWS if (!IsValid()) { LAssert(!"Needs a valid date."); return false; } s.Ref() = OsTime(); if (!s.Valid()) return false; return true; #else if (_Year < MIN_YEAR) return false; auto sec = OsTime(); s.Ref() = (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() : LTimeStamp(); auto DateTs = Date->IsValid() ? Date->Ts() : LTimeStamp(); if (ThisTs.Get() & 0x800000000000000) { Get(ThisTs); } // If these ever fire, the cast to int64_t will overflow LAssert((ThisTs.Get() & 0x800000000000000) == 0); LAssert((DateTs.Get() & 0x800000000000000) == 0); auto Diff = 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.Ref() += Seconds * Second64Bit; Set(i); } } void LDateTime::AddMinutes(int64 Minutes) { LTimeStamp i; if (Get(i)) { int64 delta = Minutes * 60 * Second64Bit; i.Ref() += delta; Set(i); } } void LDateTime::AddHours(int64 Hours) { LTimeStamp i; if (Get(i)) { i.Ref() += Hours * HourLength * Second64Bit; Set(i); } } bool LDateTime::AddDays(int64 Days) { if (!Days) return true; LTimeStamp Ts; if (!Get(Ts)) return false; Ts.Ref() += 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; int years = days / 365; // leap year, sheap year lol days -= years * 365; LString s; if (years > 0) s.Printf("%iyrs %id %ih", years, days, hrs); else if (days > 0) s.Printf("%id %ih %im", days, hrs, mins); 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; \ } static bool CompareDateStr(LString a, LString b) { auto delim = "/: ap"; auto aParts = a.SplitDelimit(delim); auto bParts = a.SplitDelimit(delim); if (aParts.Length() != 6 || bParts.Length() != 6) return false; for (int i=0; i= 8); // Check 64bit get/set LDateTime t("1/1/2017 0:0:0"), t2; LTimeStamp i; DATE_ASSERT(t.Get(i)); // LgiTrace("Get='%s'\n", t.Get().Get()); auto i2 = i + (24ULL * 60 * 60 * LDateTime::Second64Bit); t2.SetFormat(GDTF_DAY_MONTH_YEAR); t2.Set(i2); LString s = t2.Get(); // LgiTrace("Set='%s'\n", s.Get()); DATE_ASSERT(CompareDateStr(s, "2/1/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); // Check get/set Unix time t.ToUtc(); t.SetUnix(tt = 1704067200); s = t.Get(); DATE_ASSERT(CompareDateStr(s, Jan1)); auto new_tt = t.GetUnix(); DATE_ASSERT(new_tt == tt); // Now do it with a timezone set... t.SetTimeZone(t.SystemTimeZone(), false); t.SetUnix(tt); DATE_ASSERT(CompareDateStr(s, Jan1)); new_tt = t.GetUnix(); DATE_ASSERT(new_tt == tt); // Check various Add### functions // Check infer timezone t.Empty(); t.Set(Jan1); DATE_ASSERT(t.InferTimeZone(false)); DATE_ASSERT(t.GetTimeZone() == 660); t.SetTimeZone(660, false); // Now try something right on the edge of the DST change... just before... t.SetUnix(1712412000); // 7/4/2024 1:00:00 +1100 t.SetTimeZone(0, true); DATE_ASSERT(t.InferTimeZone(false)); DATE_ASSERT(t.GetTimeZone() == 660); // Just after... t.SetUnix(1712426400); // 7/4/2024 4:00:00 +1000 t.SetTimeZone(0, true); DATE_ASSERT(t.InferTimeZone(false)); DATE_ASSERT(t.GetTimeZone() == 600); 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 <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 } diff --git a/src/common/General/TimeZone.cpp b/src/common/General/TimeZone.cpp --- a/src/common/General/TimeZone.cpp +++ b/src/common/General/TimeZone.cpp @@ -1,84 +1,465 @@ -#include "lgi/common/Mem.h" -#include "lgi/common/File.h" -#include "lgi/common/Properties.h" +#include "lgi/common/Lgi.h" #include "lgi/common/DateTime.h" // From HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones -LTimeZone LTimeZones[] = +static LTimeZone AllTimeZones[] = { {-12.00, "Dateline (Eniwetok, Kwajalein)"}, {-11.00, "Samoa (Midway Island, Samoa)"}, {-10.00, "Hawaiian (Hawaii)"}, {-9.00, "Alaskan (Alaska)"}, {-8.00, "Pacific (Pacific Time (US & Canada); Tijuana)"}, {-7.00, "Mountain (Mountain Time (US & Canada))"}, {-7.00, "US Mountain (Arizona)"}, {-6.00, "Canada Central (Saskatchewan)"}, {-6.00, "Central America (Central America)"}, {-6.00, "Central (Central Time (US & Canada))"}, {-6.00, "Mexico (Mexico City)"}, {-5.00, "Eastern (Eastern Time (US & Canada))"}, {-5.00, "SA Pacific (Bogota, Lima, Quito)"}, {-5.00, "US Eastern (Indiana (East))"}, {-4.00, "Atlantic (Atlantic Time (Canada))"}, {-4.00, "Pacific SA (Santiago)"}, {-4.00, "SA Western (Caracas, La Paz)"}, {-3.00, "East South America (Brasilia)"}, {-3.00, "Greenland (Greenland)"}, {-3.00, "SA Eastern (Buenos Aires, Georgetown)"}, {-2.50, "Newfoundland (Newfoundland)"}, {-2.00, "Mid-Atlantic (Mid-Atlantic)"}, {-1.00, "Azores (Azores)"}, {-1.00, "Cape Verde (Cape Verde Is.)"}, {0.00, "GMT"}, {1.00, "Central Europe (Belgrade, Bratislava, Budapest, Ljubljana, Prague)"}, {1.00, "Central European (Sarajevo, Skopje, Sofija, Vilnius, Warsaw, Zagreb)"}, {1.00, "Romance (Brussels, Copenhagen, Madrid, Paris)"}, {1.00, "West Central Africa (West Central Africa)"}, {1.00, "West Europe (Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna)"}, {2.00, "East Europe (Bucharest)"}, {2.00, "Egypt (Cairo)"}, {2.00, "FLE (Helsinki, Riga, Tallin, Vilnius)"}, {2.00, "GTB (Athens, Istanbul, Minsk)"}, {2.00, "Jerusalem (Jerusalem)"}, {2.00, "South Africa (Harare, Pretoria)"}, {3.00, "Arab (Kuwait, Riyadh)"}, {3.00, "Arabic (Baghdad)"}, {3.00, "East Africa (Nairobi)"}, {3.00, "Russian (Moscow, St. Petersburg, Volgograd)"}, {3.50, "Iran (Tehran)"}, {4.00, "Arabian (Abu Dhabi, Muscat)"}, {4.00, "Caucasus (Baku, Tbilisi, Yerevan)"}, {4.50, "Afghanistan (Kabul)"}, {5.00, "Ekaterinburg (Ekaterinburg)"}, {5.00, "West Asia (Islamabad, Karachi, Tashkent)"}, {5.50, "India (Calcutta, Chennai, Mumbai, New Delhi)"}, {5.75, "Nepal (Kathmandu)"}, {6.00, "Central Asia (Astana, Dhaka)"}, {6.00, "N. Central Asia (Almaty, Novosibirsk)"}, {6.00, "Sri Lanka (Sri Jayawardenepura)"}, {6.50, "Myanmar (Rangoon)"}, {7.00, "North Asia (Krasnoyarsk)"}, {7.00, "SE Asia (Bangkok, Hanoi, Jakarta)"}, {8.00, "China (Beijing, Chongqing, Hong Kong, Urumqi)"}, {8.00, "North Asia East (Irkutsk, Ulaan Bataar)"}, {8.00, "Malay Peninsula (Kuala Lumpur, Singapore)"}, {8.00, "Taipei (Taipei)"}, {8.00, "West Australia (Perth)"}, {9.00, "Korea (Seoul)"}, {9.00, "Tokyo (Osaka, Sapporo, Tokyo)"}, {9.00, "Yakutsk (Yakutsk)"}, {9.50, "AUS Central (Darwin)"}, {9.50, "Cen. Australia (Adelaide)"}, {10.00, "AUS Eastern (Canberra, Melbourne, Sydney)"}, {10.00, "East Australia (Brisbane)"}, {10.00, "Tasmania (Hobart)"}, {10.00, "Vladivostok (Vladivostok)"}, {10.00, "West Pacific (Guam, Port Moresby)"}, {11.00, "Central Pacific (Magadan, Solomon Is., New Caledonia)"}, {12.00, "Fiji (Fiji, Kamchatka, Marshall Is.)"}, {12.00, "New Zealand (Auckland, Wellington)"}, {13.00, "Tonga (Nuku'alofa)"}, {0, 0} }; + +LTimeZone *LTimeZone::GetTimeZones() +{ + return AllTimeZones; +} + +LDateTime LDstInfo::GetLocal() +{ + LDateTime d; + d.Set(Utc); + d.SetTimeZone(0, false); + d.SetTimeZone(Offset, true); + return d; +} + +bool LTimeZone::GetDaylightSavingsInfo(LArray &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->Utc = tmp; + } + else + { + // Year is: Standard->Daylight->Standard + LDateTime tmp = s; + i->Offset = -(Tzi.Bias + Tzi.StandardBias); + tmp.AddMinutes(-i->Offset); + 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->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->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->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->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().Get() / Second64Bit) - Offset1800]; + while (startDate) + { + auto next = [tz nextDaylightSavingTimeTransitionAfterDate:startDate]; + auto &i = Info.New(); + + auto nextTs = (time_t)[next timeIntervalSince1970]; + i.Utc = nextTs; + i.Offset = (int)([tz secondsFromGMTForDate:[next dateByAddingTimeInterval:60]]/60); + + #if DEBUG_DST_INFO + { + LDateTime dt; + dt.Set(i.Utc); + 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().Valid() == 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 LTimeZone::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()); + static bool first = true; + if (first) + { + first = false; + LAssert(!"No valid DST range for this date."); + } + return false; +} + diff --git a/src/common/Text/vCard-vCal.cpp b/src/common/Text/vCard-vCal.cpp --- a/src/common/Text/vCard-vCal.cpp +++ b/src/common/Text/vCard-vCal.cpp @@ -1,1670 +1,1591 @@ #include #include #include #include "lgi/common/Lgi.h" #include "lgi/common/vCard-vCal.h" #include "lgi/common/Json.h" #include "lgi/common/TextConvert.h" #include "ScribeDefs.h" #define DEBUG_LOGGING 0 -#define Push(s) Write(s, (int)strlen(s)) +#define Push(s) Write(s, strlen(s)) #define ClearFields() \ Field.Empty(); \ Params.Empty(); \ Data.Empty() #define IsType(str) (Params.Find(str) != 0) -struct TzMap -{ - int Offset; - const char *Name; -}; - -#define TZ(h,m) (((h)*100) + (m)) -static TzMap Timezones[] = -{ - {TZ(11,0), "Australia/Sydney"}, -}; - -int TimezoneToOffset(const char *Tz) -{ - if (!Tz) - return 0; - - for (int i=0; iName, Tz)) - return t->Offset; - } - - return (int)Atoi(Tz); -} - #if 1 bool IsVar(char *field, const char *s) { if (!s) return false; char *dot = strchr(field, '.'); if (dot) return _stricmp(dot + 1, s) == 0; return _stricmp(field, s) == 0; } #else #define IsVar(field, str) (field != 0 && _stricmp(field, str) == 0) #endif char *DeEscape(char *s, bool QuotedPrintable) { if (!s) return 0; char *i = s; char *o = s; while (*i) { if (*i == '\\') { i++; switch (*i) { case 'n': case 'N': *o++ = '\n'; break; case ',': case ';': case ':': *o++ = *i; break; default: *o++ = '\\'; i--; break; } } else if (QuotedPrintable && *i == '=' && i[1] && i[2]) { i++; char h[3] = { i[0], i[1], 0}; *o++ = htoi(h); i++; } else { *o++ = *i; } i++; } *o = 0; return s; } ///////////////////////////////////////////////////////////// // General IO class class VIoPriv { public: LStringPipe Buf; }; VIo::VIo() : d(new VIoPriv) { } VIo::~VIo() { DeleteObj(d); } bool VIo::ParseDate(LDateTime &Out, char *In) { bool Status = false; if (In) { Out.SetTimeZone(0, false); auto v = LString(In).SplitDelimit("T"); if (v.Length() > 0) { if (v[0].Length() == 8) { - auto Year = v[0](0, 4); + auto Year = v[0](0, 4); auto Month = v[0](4, 6); - auto Day = v[0](6, 8); + auto Day = v[0](6, 8); Out.Year ((int)Year.Int()); Out.Month((int)Month.Int()); Out.Day ((int)Day.Int()); Status = true; } char *t = v[1]; if (t && strlen(t) >= 6) { - char Hour[3] = {t[0], t[1], 0}; + char Hour[3] = {t[0], t[1], 0}; char Minute[3] = {t[2], t[3], 0}; char Second[3] = {t[4], t[5], 0}; Out.Hours(atoi(Hour)); Out.Minutes(atoi(Minute)); Out.Seconds(atoi(Second)); Status = true; } } } return Status; } bool VIo::ParseDuration(LDateTime &Out, int &Sign, char *In) { bool Status = false; if (In) { Sign = 1; if (*In == '-') { Sign = -1; In++; } if (toupper(*In++) == 'P' && toupper(*In++) == 'T') { while (*In) { int i = atoi(In); while (IsDigit(*In)) In++; switch (toupper(*In++)) { case 'W': { Out.Day(Out.Day() + (i * 7)); break; } case 'D': { Out.Day(Out.Day() + i); break; } case 'H': { Out.Hours(Out.Hours() + i); break; } case 'M': { Out.Minutes(Out.Minutes() + i); break; } case 'S': { Out.Seconds(Out.Seconds() + i); break; } } } Status = true; } } return Status; } void VIo::Fold(LStreamI &o, const char *i, int pre_chars, const char *encoding) { bool base64 = !Stricmp(encoding, "base64"); bool quotePrintable = !base64; int x = pre_chars; for (const char *s=i; s && *s;) { if (x >= 74) { // wrapping o.Write(i, (int)(s-i)); o.Write((char*)"\r\n\t", 3); x = 0; i = s; } else if (quotePrintable && (*s == '=' || ((((uint8_t)*s) & 0x80) != 0))) { // quoted printable o.Write(i, (int)(s-i)); LStreamPrint(&o, "=%02.2x", (uint8_t)*s); x += 3; i = ++s; } else if (*s == '\n') { // new line o.Write(i, (int)(s-i)); o.Write((char*)"\\n", 2); x += 2; i = ++s; } else if (*s == '\r') { o.Write(i, (int)(s-i)); i = ++s; } else { s++; x++; } } - o.Write(i, (int)strlen(i)); + o.Write(i, strlen(i)); } -char *VIo::Unfold(char *In) +LString VIo::UnMultiLine(char *In) { - if (In) + if (!In) + return LString(); + + LStringPipe p; + char *n; + for (auto i = In; i && *i; i = n) { - LStringPipe p(256); - - for (char *i=In; i && *i; i++) + n = stristr(i, "\\n"); + if (n) { - if (*i == '\n') - { - if (i[1] && strchr(" \t", i[1])) - { - i++; - } - else - { - p.Write(i, 1); - } - } - else - { - p.Write(i, 1); - } + p.Write(i, (int)(n-i)); + p.Push((char*)"\n"); + n += 2; } - - return p.NewStr(); + else + { + p.Push(i); + } } - return 0; -} - -char *VIo::UnMultiLine(char *In) -{ - if (In) - { - LStringPipe p; - char *n; - for (char *i=In; i && *i; i=n) - { - n = stristr(i, "\\n"); - if (n) - { - p.Write(i, (int)(n-i)); - p.Push((char*)"\n"); - n += 2; - } - else - { - p.Push(i); - } - } - - return p.NewStr(); - } - - return 0; + return p.NewLStr(); } ///////////////////////////////////////////////////////////// // VCard class bool VCard::Import(LDataPropI *c, LStreamI *s) { bool Status = false; if (!c || !s) return false; LString Field; ParamArray Params; LString Data; ssize_t PrefEmail = -1; LString::Array Emails; while (ReadField(*s, Field, &Params, Data)) { if (_stricmp(Field, "begin") == 0 && _stricmp(Data, "vcard") == 0) { while (ReadField(*s, Field, &Params, Data)) { if (_stricmp(Field, "end") == 0 && _stricmp(Data, "vcard") == 0) goto ExitLoop; if (IsVar(Field, "n")) { auto Name = Data.SplitDelimit(";", -1, false); + char *Last = Name[0]; char *First = Name[1]; - char *Last = Name[0]; char *Title = Name[3]; if (First) { c->SetStr(FIELD_FIRST_NAME, First); Status = true; } if (Last) { c->SetStr(FIELD_LAST_NAME, Last); Status = true; } if (Title) { c->SetStr(FIELD_TITLE, Title); Status = true; } } else if (IsVar(Field, "nickname")) { c->SetStr(FIELD_NICK, Data); } else if (IsVar(Field, "tel")) { auto Phone = Data.SplitDelimit(";", -1, false); for (uint32_t p=0; pSetStr(FIELD_WORK_MOBILE, Phone[p]); } else { c->SetStr(FIELD_HOME_MOBILE, Phone[p]); } } else if (IsType("fax")) { if (IsType("Work")) { c->SetStr(FIELD_WORK_FAX, Phone[p]); } else { c->SetStr(FIELD_HOME_FAX, Phone[p]); } } else { if (IsType("Work")) { c->SetStr(FIELD_WORK_PHONE, Phone[p]); } else { c->SetStr(FIELD_HOME_PHONE, Phone[p]); } } } } else if (IsVar(Field, "email")) { if (IsType("pref")) { PrefEmail = Emails.Length(); } Emails.Add(Data); } else if (IsVar(Field, "org")) { auto Org = Data.SplitDelimit(";", -1, false); if (Org[0]) c->SetStr(FIELD_COMPANY, Org[0]); } else if (IsVar(Field, "adr")) { bool IsWork = IsType("work"); // bool IsHome = IsType("home"); auto Addr = Data.SplitDelimit(";", -1, false); if (Addr[2]) { auto A = Addr[2].SplitDelimit("\r\n"); if (A.Length() > 1) { c->SetStr(IsWork ? FIELD_WORK_STREET : FIELD_HOME_STREET, A[0]); if (A[1]) c->SetStr(IsWork ? FIELD_WORK_SUBURB : FIELD_HOME_SUBURB, A[1]); if (A[2]) c->SetStr(IsWork ? FIELD_WORK_COUNTRY : FIELD_HOME_COUNTRY, A[2]); } else { c->SetStr(IsWork ? FIELD_WORK_STREET : FIELD_HOME_STREET, Addr[2]); } } - if (Addr[3]) c->SetStr(IsWork ? FIELD_WORK_SUBURB : FIELD_HOME_SUBURB, Addr[3]); - if (Addr[4]) c->SetStr(IsWork ? FIELD_WORK_STATE : FIELD_HOME_STATE, Addr[4]); + if (Addr[3]) c->SetStr(IsWork ? FIELD_WORK_SUBURB : FIELD_HOME_SUBURB, Addr[3]); + if (Addr[4]) c->SetStr(IsWork ? FIELD_WORK_STATE : FIELD_HOME_STATE, Addr[4]); if (Addr[5]) c->SetStr(IsWork ? FIELD_WORK_POSTCODE : FIELD_HOME_POSTCODE, Addr[5]); - if (Addr[6]) c->SetStr(IsWork ? FIELD_WORK_COUNTRY : FIELD_HOME_COUNTRY, Addr[6]); + if (Addr[6]) c->SetStr(IsWork ? FIELD_WORK_COUNTRY : FIELD_HOME_COUNTRY, Addr[6]); } else if (IsVar(Field, "note")) { c->SetStr(FIELD_NOTE, Data); } else if (IsVar(Field, "uid")) { auto n = Data.SplitDelimit(";", -1, false); c->SetStr(FIELD_UID, n[0]); } else if (IsVar(Field, "x-perm")) { int Perms = atoi(Data); c->SetInt(FIELD_PERMISSIONS, Perms); } else if (IsVar(Field, "url")) { - bool IsWork = IsType("work"); - bool IsHome = IsType("home"); - if (IsWork) - { + if (IsType("work")) c->SetStr(FIELD_HOME_WEBPAGE, Data); - } - else if (IsHome) - { + else if (IsType("home")) c->SetStr(FIELD_WORK_WEBPAGE, Data); - } } else if (IsVar(Field, "nickname")) { c->SetStr(FIELD_NICK, Data); } else if (IsVar(Field, "photo")) { size_t B64Len = strlen(Data); ssize_t BinLen = BufferLen_64ToBin(B64Len); LAutoPtr Bin(new uint8_t[BinLen]); if (Bin) { ssize_t Bytes = ConvertBase64ToBinary(Bin.Get(), BinLen, Data, B64Len); LVariant v; if (v.SetBinary(Bytes, Bin.Release(), true)) { c->SetVar(FIELD_CONTACT_IMAGE, &v); } } } } } } ExitLoop: if (Emails.Length()) { if (PrefEmail < 0) PrefEmail = 0; c->SetStr(FIELD_EMAIL, Emails[PrefEmail]); Emails.DeleteAt(PrefEmail); if (Emails.Length()) { LStringPipe p; for (uint32_t i=0; iSetStr(FIELD_ALT_EMAIL, v); DeleteArray(v); } } } ClearFields(); return Status; } bool VIo::ReadField(LStreamI &s, LString &Name, ParamArray *Params, LString &Data) { bool Status = false; ParamArray LocalParams; Name.Empty(); Data.Empty(); if (Params) Params->Empty(); else Params = &LocalParams; char Temp[1024]; LArray p; bool Done = false; while (!Done) { bool EatNext = false; ReadNextLine: Temp[0] = 0; int64 r = d->Buf.Pop(Temp, sizeof(Temp)); if (r <= 0) { // Try reading more data... r = s.Read(Temp, sizeof(Temp)); if (r > 0) d->Buf.Write(Temp, r); else break; r = d->Buf.Pop(Temp, sizeof(Temp)); } else if (Temp[0] == '\r' || Temp[1] == '\n') { // Skip empty lines... goto ReadNextLine; } if (r <= 0) break; - if ((r == 1 && Temp[0] == '\n') || (r == 2 && Temp[0] == '\r' && Temp[1] == '\n')) // Blank line case... + if ((r == 1 && Temp[0] == '\n') || + (r == 2 && Temp[0] == '\r' && Temp[1] == '\n')) // Blank line case... continue; // Unfold for (char *c = Temp; *c; c++) { if (*c == '\r') { // do nothing } else if (*c == '\n') { char Next; r = d->Buf.Peek((uchar*) &Next, 1); if (r == 0) { r = s.Read(Temp, sizeof(Temp)); if (r <= 0) break; d->Buf.Write(Temp, (int)r); r = d->Buf.Peek((uchar*) &Next, 1); } if (r == 1) { if (Next == ' ' || Next == '\t') { // Wrapped, do nothing EatNext = true; goto ReadNextLine; } else { Done = true; break; } } else { break; } } else if (EatNext) { EatNext = false; } else { p.Add(*c); } } } p.Add(0); char *f = p.Length() > 1 ? &p[0] : 0; if (f) { char *e = strchr(f, ':'); if (e) { *e++ = 0; auto t = LString(f).SplitDelimit(";"); if (t.Length() > 0) { Name = t[0]; for (uint32_t i=1; iFind("encoding"); + auto QuotedPrintable = !Stricmp(Enc, "quoted-printable"); DeEscape(e, QuotedPrintable); - const char *Charset = Params->Find("charset"); + auto Charset = Params->Find("charset"); if (Charset) { LAutoString u((char*)LNewConvertCp("utf-8", e, Charset)); Data = u.Get(); } else { Data = e; } Status = Name.Length() > 0; } return Status; } void VIo::WriteField(LStreamI &s, const char *Name, ParamArray *Params, const char *Data) { if (!Name || !Data) { LAssert(!"Invalid params"); return; } int64 Size = s.GetSize(); LStreamPrint(&s, "%s", Name); const char *encoding = NULL; const char *charset = NULL; if (Params) { for (auto &p: *Params) LStreamPrint(&s, ";%s=%s", p.Field.Get(), p.Value.Get()); encoding = Params->Find("ENCODING"); charset = Params->Find("CHARSET"); } size_t Is7Bit = 0; size_t Is8Bit = 0; size_t HasEq = 0; for (uint8_t *c = (uint8_t*)Data; *c; c++) { if ((*c & 0x80) != 0) Is8Bit++; else if (*c == '=') HasEq++; else Is7Bit++; } size_t DataLen = Is7Bit + Is8Bit + HasEq; if (Is8Bit || HasEq) { if (!encoding) { encoding = "quoted-printable"; LStreamPrint(&s, ";ENCODING=%s", encoding); } if (Is8Bit && !charset) LStreamPrint(&s, ";charset=utf-8"); } s.Write((char*)":", 1); LString encoded; if (encoding && !Stricmp(encoding, "base64")) { encoded = LToBase64(Data, DataLen); Data = encoded.Get(); } Fold(s, Data, (int) (s.GetSize() - Size), encoding); s.Write((char*)"\r\n", 2); } bool VCard::Export(LDataPropI *c, LStreamI *o) { if (!c || !o) return false; bool Status = true; char s[512]; const char *Empty = ""; o->Push("begin:vcard\r\n"); o->Push("version:3.0\r\n"); - const char *First = 0, *Last = 0, *Title = 0; + const char *First = NULL, *Last = NULL, *Title = NULL; First = c->GetStr(FIELD_FIRST_NAME); - Last = c->GetStr(FIELD_LAST_NAME); + Last = c->GetStr(FIELD_LAST_NAME); Title = c->GetStr(FIELD_TITLE); if (First || Last) { sprintf_s(s, sizeof(s), "%s;%s;%s", Last?Last:Empty, First?First:Empty, Title?Title:Empty); WriteField(*o, "n", 0, s); } #define OutputTypedField(Field, Name, Type) \ - { const char *str = 0; \ - if ((str = c->GetStr(Name))) \ + { const char *str = 0; \ + if ((str = c->GetStr(Name))) \ { \ - WriteField(*o, Field, Type, str); \ + WriteField(*o, Field, Type, str); \ } } #define OutputField(Field, Name) \ - { const char *str = 0; \ - if ((str = c->GetStr(Name))) \ + { const char *str = 0; \ + if ((str = c->GetStr(Name))) \ { \ - WriteField(*o, Field, 0, str); \ + WriteField(*o, Field, 0, str); \ } } ParamArray Work("Work"), WorkCell("Work,Cell"), WorkFax("Work,Fax"); ParamArray Home("Home"), HomeCell("Home,Cell"), HomeFax("Home,Fax"); ParamArray InetPref("internet,pref"), Inet("internet"); OutputTypedField("tel", FIELD_WORK_PHONE, &Work); OutputTypedField("tel", FIELD_WORK_MOBILE, &WorkCell); OutputTypedField("tel", FIELD_WORK_FAX, &WorkFax); OutputTypedField("tel", FIELD_HOME_PHONE, &Home); OutputTypedField("tel", FIELD_HOME_MOBILE, &HomeCell); OutputTypedField("tel", FIELD_HOME_FAX, &HomeFax); OutputField("org", FIELD_COMPANY); OutputTypedField("email", FIELD_EMAIL, &InetPref); const char *Alt; if ((Alt = c->GetStr(FIELD_ALT_EMAIL))) { auto t = LString(Alt).SplitDelimit(","); for (unsigned i=0; iGetStr(FIELD_UID))) { LStreamPrint(o, "UID:%s\r\n", Uid); } const char *Street, *Suburb, *PostCode, *State, *Country; Street = Suburb = PostCode = State = Country = 0; Street = c->GetStr(FIELD_HOME_STREET); Suburb = c->GetStr(FIELD_HOME_SUBURB); PostCode = c->GetStr(FIELD_HOME_POSTCODE); State = c->GetStr(FIELD_HOME_STATE); Country = c->GetStr(FIELD_HOME_COUNTRY); if (Street || Suburb || PostCode || State || Country) { sprintf_s(s, sizeof(s), ";;%s;%s;%s;%s;%s", Street?Street:Empty, Suburb?Suburb:Empty, State?State:Empty, PostCode?PostCode:Empty, Country?Country:Empty); WriteField(*o, "adr", &Home, s); } Street = Suburb = PostCode = State = Country = 0; Street = c->GetStr(FIELD_WORK_STREET); Suburb = c->GetStr(FIELD_WORK_SUBURB); PostCode = c->GetStr(FIELD_WORK_POSTCODE); State = c->GetStr(FIELD_WORK_STATE); Country = c->GetStr(FIELD_WORK_COUNTRY); if (Street || Suburb || PostCode || State || Country) { sprintf_s(s, sizeof(s), ";;%s;%s;%s;%s;%s", Street?Street:Empty, Suburb?Suburb:Empty, State?State:Empty, PostCode?PostCode:Empty, Country?Country:Empty); WriteField(*o, "adr", &Work, s); } // OutputField("X-Perm", FIELD_PERMISSIONS); const char *Url; if ((Url = c->GetStr(FIELD_HOME_WEBPAGE))) { WriteField(*o, "url", &Home, Url); } if ((Url = c->GetStr(FIELD_WORK_WEBPAGE))) { WriteField(*o, "url", &Work, Url); } const char *Nick; if ((Nick = c->GetStr(FIELD_NICK))) { WriteField(*o, "nickname", 0, Nick); } const char *Note; if ((Note = c->GetStr(FIELD_NOTE))) { WriteField(*o, "note", 0, Note); } - const LVariant *Photo = c->GetVar(FIELD_CONTACT_IMAGE); + auto Photo = c->GetVar(FIELD_CONTACT_IMAGE); if (Photo && Photo->Type == GV_BINARY) { - ssize_t B64Len = BufferLen_BinTo64(Photo->Value.Binary.Length); + auto B64Len = BufferLen_BinTo64(Photo->Value.Binary.Length); LAutoPtr B64Buf(new char[B64Len]); if (B64Buf) { ssize_t Bytes = ConvertBinaryToBase64(B64Buf, B64Len, (uchar*)Photo->Value.Binary.Data, Photo->Value.Binary.Length); if (Bytes > 0) { LStreamPrint(o, "photo;type=jpeg;encoding=base64:\r\n"); int LineChar = 76; for (ssize_t i=0; i LineChar ? LineChar : Remain; o->Write(" ", 1); o->Write(B64Buf + i, Wr); o->Write("\r\n", 2); i += Wr; } } } } o->Push("end:vcard\r\n"); return Status; } ///////////////////////////////////////////////////////////// // VCal class int StringToWeekDay(const char *s) { const char *days[] = {"SU","MO","TU","WE","TH","FR","SA"}; for (unsigned i=0; i TzInfos; TimeZoneInfo *TzInfo = NULL; bool IsNormalTz = false, IsDaylightTz = false; LJson To; int Attendee = 0; LArray Alarms; int AlarmIdx = -1; while (ReadField(*In, Field, &Params, Data)) { if (Field.Equals("begin")) { if (!SectionType) SectionType = Field; - if (Data.Equals("vevent") - || - Data.Equals("vtodo")) + auto Type = ParseCalendarType(Data); + if (Type != CalTypeMax) { IsEvent = true; - int Type = Data.Equals("vtodo") ? 1 : 0; c->SetInt(FIELD_CAL_TYPE, Type); } else if (Data.Equals("vtimezone")) { IsTimeZone = true; TzInfo = &TzInfos.New(); } else if (Data.Equals("vcalendar")) IsCal = true; else if (Data.Equals("standard")) IsNormalTz = true; else if (Data.Equals("daylight")) IsDaylightTz = true; else if (Data.Equals("valarm")) { IsAlarm = true; AlarmIdx++; } } else if (Field.Equals("end")) { - LgiTrace("end:%s\n", Data.Get()); - if (Data.Equals("vcalendar")) - { - IsCal = false; - } - else if (Data.Equals("vevent") - || - Data.Equals("vtodo")) + auto Type = ParseCalendarType(Data); + if (Type != CalTypeMax) { Status = true; IsEvent = false; } + else if (Data.Equals("vcalendar")) + IsCal = false; else if (Data.Equals("vtimezone")) { IsTimeZone = false; TzInfo = NULL; } else if (Data.Equals("standard")) IsNormalTz = false; else if (Data.Equals("daylight")) IsDaylightTz = false; else if (Data.Equals("valarm")) IsAlarm = false; if (SectionType && SectionType.Equals(Data)) break; } else if (IsEvent) { if (IsAlarm) { auto &a = Alarms[AlarmIdx]; if (IsVar(Field, "ACTION")) a.Action = Data; else if (IsVar(Field, "DESCRIPTION")) a.Desc = Data; else if (IsVar(Field, "TRIGGER")) a.Trigger = Data; else if (IsVar(Field, "X-PARAM")) a.Param = Data; } else if (IsVar(Field, "dtstart")) { ParseDate(EventStart, Data); StartTz = Params.Find("TZID"); } else if (IsVar(Field, "dtend")) { ParseDate(EventEnd, Data); EndTz = Params.Find("TZID"); } else if (IsVar(Field, "summary")) { c->SetStr(FIELD_CAL_SUBJECT, Data); } else if (IsVar(Field, "description")) { - LAutoString Sum(UnMultiLine(Data)); - if (Sum) + if (auto Sum = UnMultiLine(Data)) c->SetStr(FIELD_CAL_NOTES, Sum); } else if (IsVar(Field, "location")) { c->SetStr(FIELD_CAL_LOCATION, Data); } else if (IsVar(Field, "uid")) { char *Uid = Data; c->SetStr(FIELD_UID, Uid); } else if (IsVar(Field, "x-showas")) { char *n = Data; if (Stricmp(n, "TENTATIVE") == 0) c->SetInt(FIELD_CAL_SHOW_TIME_AS, CalTentative); else if (Stricmp(n, "BUSY") == 0) c->SetInt(FIELD_CAL_SHOW_TIME_AS, CalBusy); else if (Stricmp(n, "OUT") == 0) c->SetInt(FIELD_CAL_SHOW_TIME_AS, CalOut); else c->SetInt(FIELD_CAL_SHOW_TIME_AS, CalFree); } else if (IsVar(Field, "attendee")) { auto Email = Data.SplitDelimit(":=").Last(); if (LIsValidEmail(Email)) { auto Name = Params.Find("CN"); auto Role = Params.Find("Role"); LString k; k.Printf("[%i].email", Attendee); To.Set(k, Email); if (Name) { k.Printf("[%i].name", Attendee); To.Set(k, Name); } if (Role) { k.Printf("[%i].role", Attendee); To.Set(k, Role); } Attendee++; } } else if (IsVar(Field, "status")) { c->SetStr(FIELD_CAL_STATUS, Data); } else if (IsVar(Field, "attach")) { // None of these error cases are fatal, the backend may not support // attachments... but log something for the user in that case. auto attachments = c->GetList(FIELD_CAL_ATTACHMENTS); if (!attachments) { LgiTrace("%s:%i - vCal import: No attachment list...\n", _FL); } else if (auto data = dynamic_cast(c)) { auto store = data->GetStore(); if (!store) { LgiTrace("%s:%i - vCal import: No store object.\n", _FL); } // Create a new attachment object to receive the file else if (auto file = attachments->Create(store)) { auto FileName = Params.Find("X-FILENAME"); auto MimeType = Params.Find("FMTTYPE"); auto Encoding = Params.Find("ENCODING"); auto Value = Params.Find("VALUE"); auto Now = LDateTime::Now(); if (FileName) file->SetStr(FIELD_NAME, FileName); if (MimeType) file->SetStr(FIELD_MIME_TYPE, MimeType); file->SetDate(FIELD_DATE_MODIFIED, &Now); if (!Stricmp(Encoding, "base64")) { auto bin = LToBinary(Data); file->SetStr(FIELD_ATTACHMENTS_DATA, bin); } else if (Data.Find("://") >= 0) { file->SetStr(FIELD_URI, Data); } else { LgiTrace("%s:%i - vCal import: Unknown data type?\n", _FL); LAssert(!"What is 'Data'?"); } if (auto fileData = dynamic_cast(file)) fileData->Save(data); } else { LgiTrace("%s:%i - vCal import: Couldn't create attachment object\n", _FL); } } else { LgiTrace("%s:%i - vCal import: no LDataI object\n", _FL); } } } else if (IsTimeZone && TzInfo) { /* e.g.: TZID:Pacific Standard Time BEGIN:STANDARD DTSTART:16010101T020000 TZOFFSETFROM:-0700 TZOFFSETTO:-0800 RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=1SU;BYMONTH=11 END:STANDARD BEGIN:DAYLIGHT DTSTART:16010101T020000 TZOFFSETFROM:-0800 TZOFFSETTO:-0700 RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=2SU;BYMONTH=3 END:DAYLIGHT */ if (IsNormalTz || IsDaylightTz) { TimeZoneSection &Sect = IsNormalTz ? TzInfo->Normal : TzInfo->Daylight; if (IsVar(Field, "DTSTART")) ParseDate(Sect.Start, Data); else if (IsVar(Field, "TZNAME")) Sect.Name = Data; else if (IsVar(Field, "TZOFFSETFROM")) Sect.From = (int)Data.Int(); else if (IsVar(Field, "TZOFFSETTO")) Sect.To = (int)Data.Int(); else if (IsVar(Field, "RRULE")) Sect.Rule = Data; } else if (IsVar(Field, "TZID")) { TzInfo->Name = Data; } } } if (Attendee > 0) { auto j = To.GetJson(); c->SetStr(FIELD_ATTENDEE_JSON, j); } if (StartTz || EndTz) { // Did we get a timezone defn? TimeZoneInfo *Match = NULL; for (unsigned i=0; iNormal, EventStart.Year()) && EvalRule(Dst, Match->Daylight, EventStart.Year())) { bool IsDst = false; if (Dst < Norm) { // DST in the middle of the year // |Jan----DST------Norm----Dec| if (EventStart >= Dst && EventStart <= Norm) IsDst = true; } else { // DST over the start and end of the year // |Jan----Norm------DST----Dec| if (EventStart >= Norm && EventStart <= Dst) IsDst = false; else IsDst = true; } #if DEBUG_LOGGING LgiTrace("Eval Start=%s, Norm=%s, Dst=%s, IsDst=%i\n", EventStart.Get().Get(), Norm.Get().Get(), Dst.Get().Get(), IsDst); #endif EffectiveTz = IsDst ? Match->Daylight.To : Match->Normal.To; LString sTz; sTz.Printf("%4.4i,%s", EffectiveTz, StartTz.Get()); c->SetStr(FIELD_CAL_TIMEZONE, sTz); } else goto StoreStringTz; } else { // Store whatever they gave us StoreStringTz: + + LString TzName; if (StartTz.Equals(EndTz)) - c->SetStr(FIELD_CAL_TIMEZONE, StartTz); + TzName = StartTz; else if (StartTz.Get() && EndTz.Get()) - { - LString s; - s.Printf("%s,%s", StartTz.Get(), EndTz.Get()); - c->SetStr(FIELD_CAL_TIMEZONE, s); - } + TzName.Printf("%s,%s", StartTz.Get(), EndTz.Get()); else if (StartTz) - c->SetStr(FIELD_CAL_TIMEZONE, StartTz); + TzName = StartTz; else if (EndTz) - c->SetStr(FIELD_CAL_TIMEZONE, EndTz); + TzName = EndTz; - /* This isn't going to work. - if (StartTz) - EffectiveTz = TimezoneToOffset(StartTz); - */ + c->SetStr(FIELD_CAL_TIMEZONE, LString::Fmt(",%s", TzName.Get())); } if (EffectiveTz) { // Convert the event to UTC int e = abs(EffectiveTz); int Mins = (((e / 100) * 60) + (e % 100)) * (EffectiveTz < 0 ? -1 : 1); #if DEBUG_LOGGING LgiTrace("%s:%i - EffectiveTz=%i, Mins=%i\n", _FL, EffectiveTz, Mins); #endif if (EventStart.IsValid()) { #if DEBUG_LOGGING LgiTrace("EventStart=%s\n", EventStart.Get().Get()); #endif EventStart.AddMinutes(-Mins); #if DEBUG_LOGGING LgiTrace("EventStart=%s\n", EventStart.Get().Get()); #endif } if (EventEnd.IsValid()) EventEnd.AddMinutes(-Mins); } } if (EventStart.IsValid()) c->SetDate(FIELD_CAL_START_UTC, &EventStart); if (EventEnd.IsValid()) c->SetDate(FIELD_CAL_END_UTC, &EventEnd); if (Alarms.Length()) { LString::Array s; for (LAlarm &a: Alarms) { LString r = a.GetStr(); if (r) s.Add(r); } if (s.Length()) { c->SetStr(FIELD_CAL_REMINDERS, LString("\n").Join(s)); } } ClearFields(); return Status; } LString ToString(LDateTime &dt) { LString s; s.Printf("%04.4i%02.2i%02.2iT%02.2i%02.2i%02.2i%s", dt.Year(), dt.Month(), dt.Day(), dt.Hours(), dt.Minutes(), dt.Seconds(), dt.GetTimeZone() ? "" : "Z"); return s; } bool VCal::Export(LDataPropI *c, LStreamI *o) { if (!c || !o) return false; - int64 Type = c->GetInt(FIELD_CAL_TYPE); - // auto *TimeZone = c->GetStr(FIELD_CAL_TIMEZONE); - char *TypeStr = Type == 1 ? (char*)"VTODO" : (char*)"VEVENT"; + auto Type = (CalendarType) c->GetInt(FIELD_CAL_TYPE); + LString TypeStr; + if (auto str = ToString(Type)) + { + TypeStr.Printf("V%s", str); + TypeStr = TypeStr.Upper(); + } + else + { + LAssert(!"Unknown calendar event type."); + return false; + } - o->Push((char*)"BEGIN:VCALENDAR\r\n" + o->Push("BEGIN:VCALENDAR\r\n" "VERSION:2.0\r\n" "CALSCALE:GREGORIAN\r\n" "PRODID:Memecode vcal filter\r\n"); if (c) { - LStreamPrint(o, "BEGIN:%s\r\n", TypeStr); + LStreamPrint(o, "BEGIN:%s\r\n", TypeStr.Get()); #define OutputStr(Field, Name) \ { \ const char *_s = 0; \ if ((_s = c->GetStr(Field))) \ { \ WriteField(*o, Name, 0, _s); \ } \ } LDateTime Now; Now.SetNow(); Now.ToUtc(); LStreamPrint(o, "DTSTAMP:%s\r\n", ToString(Now).Get()); OutputStr(FIELD_CAL_SUBJECT, "SUMMARY"); OutputStr(FIELD_CAL_LOCATION, "LOCATION"); OutputStr(FIELD_CAL_NOTES, "DESCRIPTION"); OutputStr(FIELD_CAL_STATUS, "STATUS"); int64 ShowAs; if ((ShowAs = c->GetInt(FIELD_CAL_SHOW_TIME_AS))) { switch (ShowAs) { default: case CalFree: - o->Push((char*)"X-SHOWAS:FREE\r\n"); + o->Push("X-SHOWAS:FREE\r\n"); break; case CalTentative: - o->Push((char*)"X-SHOWAS:TENTATIVE\r\n"); + o->Push("X-SHOWAS:TENTATIVE\r\n"); break; case CalBusy: - o->Push((char*)"X-SHOWAS:BUSY\r\n"); + o->Push("X-SHOWAS:BUSY\r\n"); break; case CalOut: - o->Push((char*)"X-SHOWAS:OUT\r\n"); + o->Push("X-SHOWAS:OUT\r\n"); break; } } const char *Uid; if ((Uid = c->GetStr(FIELD_UID))) { LStreamPrint(o, "UID:%s\r\n", Uid); } const LDateTime *Dt; if ((Dt = c->GetDate(FIELD_CAL_START_UTC)) && Dt->IsValid()) { LDateTime dt = *Dt; dt.ToUtc(); LStreamPrint(o, "DTSTART:%s\r\n", ToString(dt).Get()); } if ((Dt = c->GetDate(FIELD_CAL_END_UTC)) && Dt->IsValid()) { LDateTime dt = *Dt; dt.ToUtc(); LStreamPrint(o, "DTEND:%s\r\n", ToString(dt).Get()); } if (auto attachments = c->GetList(FIELD_CAL_ATTACHMENTS)) { // Save the attachments to 'ATTACH' fields for (auto file = attachments->First(); file; file = attachments->Next()) { auto fileData = dynamic_cast(file); auto uri = file->GetStr(FIELD_URI); auto fileName = file->GetStr(FIELD_NAME); auto mimeType = file->GetStr(FIELD_MIME_TYPE); ParamArray param; if (fileName) param.Set("X-FILENAME", fileName); if (mimeType) param.Set("FMTTYPE", mimeType); if (uri) { // Save as a URI WriteField(*o, "ATTACH", ¶m, uri); } else if (!fileData) { LAssert(!"Should be a LDataI object."); } else { auto stream = fileData->GetStream(_FL); if (!stream) LAssert(!"Should have either URI or Stream"); else { // Base64 encode the stream LString bin; if (!bin.Length(stream->GetSize())) LAssert(!"Failed to set string size"); else { stream->Read(bin.Get(), bin.Length()); auto b64 = LToBase64(bin); if (!b64) LAssert(!"Failed to convert to base64"); else { param.Set("ENCODING", "BASE64"); param.Set("VALUE", "BINARY"); WriteField(*o, "ATTACH", ¶m, b64); } } } } } } const char *Reminders; if ((Reminders = c->GetStr(FIELD_CAL_REMINDERS))) { auto Lines = LString(Reminders).SplitDelimit("\n"); for (auto Ln: Lines) { // Fields are: Number, CalendarReminderUnits, CalendarReminderType, Param auto p = Ln.SplitDelimit(","); if (p.Length() >= 3) { const char *Duration = NULL; int64 Count = p[0].Int(); switch (p[1].Int()) { case CalMinutes: Duration = "M"; break; - case CalHours: Duration = "H"; break; - case CalDays: Duration = "D"; break; - case CalWeeks: Duration = "D"; Count *= 7; break; + case CalHours: Duration = "H"; break; + case CalDays: Duration = "D"; break; + case CalWeeks: Duration = "D"; Count *= 7; break; } const char *Action = NULL; switch (p[2].Int()) { case CalEmail: Action = "EMAIL"; break; case CalPopup: Action = "DISPLAY"; break; case CalScriptCallback: Action = "X-SCRIPT"; break; } if (Action && Duration) { LString s; s.Printf( "BEGIN:VALARM\r\n" "ACTION:%s\r\n" "DESCRIPTION:REMINDER\r\n" "TRIGGER:-PT" LPrintfInt64 "%s\r\n", Action, - Count, Duration - ); + Count, Duration); if (p.Length() > 3) s += LString("X-PARAM: ") + p[3]; s += "END:VALARM\r\n"; - o->Write(s.Get(), s.Length()); + o->Write(s); } else LAssert(0); } } } LJson j(c->GetStr(FIELD_ATTENDEE_JSON)); for (auto g: j.GetArray(LString())) { // e.g.: ATTENDEE;CN="Matthew Allen";ROLE=REQ-PARTICIPANT;RSVP=TRUE:MAILTO:matthew@company.com.au auto Email = g.Get("email"); if (Email) { auto Name = g.Get("name"); auto Role = g.Get("role"); char s[400]; int ch = sprintf_s(s, sizeof(s), "ATTENDEE"); if (Name) ch += sprintf_s(s+ch, sizeof(s)-ch, ";CN=\"%s\"", Name.Get()); if (Role) ch += sprintf_s(s+ch, sizeof(s)-ch, ";ROLE=\"%s\"", Role.Get()); ch += sprintf_s(s+ch, sizeof(s)-ch, ":MAILTO=%s\r\n", Email.Get()); o->Write(s, ch); } } - LStreamPrint(o, "END:%s\r\n", TypeStr); + LStreamPrint(o, "END:%s\r\n", TypeStr.Get()); } - o->Push((char*)"END:VCALENDAR\r\n"); + o->Push("END:VCALENDAR\r\n"); return true; } diff --git a/src/common/Widgets/Panel.cpp b/src/common/Widgets/Panel.cpp --- a/src/common/Widgets/Panel.cpp +++ b/src/common/Widgets/Panel.cpp @@ -1,295 +1,295 @@ /* ** FILE: LPanel.cpp ** AUTHOR: Matthew Allen ** DATE: 29/8/99 ** DESCRIPTION: Scribe Mail Object and UI ** ** Copyright (C) 1999, Matthew Allen ** fret@memecode.com */ #include #include "lgi/common/Lgi.h" #include "lgi/common/Panel.h" #include "lgi/common/DisplayString.h" #include "lgi/common/LgiRes.h" #include "lgi/common/CssTools.h" ////////////////////////////////////////////////////////////////////////////// LPanel::LPanel(const char *name, int size, bool open) { Ds = 0; if (name) Name(name); IsOpen = open; _IsToolBar = !IsOpen; ClosedSize = LSysFont->GetHeight() + 3; OpenSize = size; Align = GV_EDGE_TOP; _BorderSize = 1; Raised(true); LResources::StyleElement(this); } LPanel::~LPanel() { DeleteObj(Ds); } int LPanel::CalcWidth() { if (!Ds) Ds = new LDisplayString(GetFont(), Name()); return 30 + (Ds ? Ds->X() : 0); } bool LPanel::Open() { return IsOpen; } void LPanel::Open(bool i) { if (i != IsOpen) { IsOpen = i; _IsToolBar = !IsOpen; SetChildrenVisibility(IsOpen); RePour(); /* int ExStyle = GetWindowLong(Handle(), GWL_EXSTYLE); SetWindowLong( Handle(), GWL_EXSTYLE, (ExStyle & ~WS_EX_CONTROLPARENT) | (IsOpen ? WS_EX_CONTROLPARENT : 0)); */ } } int LPanel::Alignment() { return Align; } void LPanel::Alignment(int i) { Align = i; } int LPanel::GetClosedSize() { return ClosedSize; } void LPanel::SetClosedSize(int i) { ClosedSize = i; } int LPanel::GetOpenSize() { return OpenSize; } void LPanel::SetOpenSize(int i) { OpenSize = i; } bool LPanel::Attach(LViewI *Wnd) { bool Status = LLayout::Attach(Wnd); SetChildrenVisibility(IsOpen); if (Status) AttachChildren(); return Status; } bool LPanel::Pour(LRegion &r) { int Sx = CalcWidth(); LRect *Best = 0; if (Open()) { Best = FindLargest(r); } else { Best = FindSmallestFit(r, Sx, ClosedSize); if (!Best) { Best = FindLargest(r); } } if (Best) { LRect r = *Best; if (OpenSize > 0) { int Size = ((Open()) ? OpenSize : ClosedSize); int Limit = 30; if (TestFlag(Align, GV_EDGE_RIGHT) || TestFlag(Align, GV_EDGE_LEFT)) { Limit = MIN(Size, r.X()-1); } else /* if (TestFlag(Align, GV_EDGE_BOTTOM) || TextFlag(Align, GV_EDGE_TOP)) */ { Limit = MIN(Size, r.Y()-1); } if (Align & GV_EDGE_RIGHT) { r.x1 = r.x2 - Limit; } else if (Align & GV_EDGE_BOTTOM) { r.y1 = r.y2 - Limit; } else if (Align & GV_EDGE_LEFT) { r.x2 = r.x1 + Limit; } else // if (Align & GV_EDGE_TOP) { r.y2 = r.y1 + Limit; } if (!Open()) { r.x2 = r.x1 + Sx - 1; } } else { r.y2 = r.y1 - OpenSize; } SetPos(r, true); if (IsOpen) { for (auto v: Children) { auto css = v->GetCss(); if (css && css->Width() == LCss::LenAuto && css->Height() == LCss::LenAuto) { LRect c = GetClient(); LCssTools tools(css, v->GetFont()); c = tools.ApplyMargin(c); v->SetPos(c); break; } } } return true; } return false; } int LPanel::OnNotify(LViewI *Ctrl, LNotification n) { if (GetParent()) { return GetParent()->OnNotify(Ctrl, n); } return 0; } void LPanel::OnPaint(LSurface *pDC) { auto r = GetClient(); LCssTools Tools(this); LColour cFore = Tools.GetFore(); LColour cBack = Tools.GetBack(); Tools.PaintContent(pDC, r); auto BackImg = Tools.GetBackImage(); auto Fnt = GetFont(); Fnt->Transparent(BackImg ? true : false); Fnt->Fore(cFore); Fnt->Back(cBack); if (!Open()) { // title if (!Ds) Ds = new LDisplayString(Fnt, Name()); if (Ds) Ds->Draw(pDC, r.x1 + 20, r.y1 + 1); } if (OpenSize > 0) { // Draw thumb auto scale = GetWindow()->GetDpiScale(); scale *= 1.2; int gapPx = (int)(scale.x * 2); - ThumbPos.ZOff(8 * scale.x, 8 * scale.y); + ThumbPos.ZOff((int)(8 * scale.x), (int)(8 * scale.y)); ThumbPos.Offset(r.x1 + 3, r.y1 + 3); pDC->Colour(L_LOW); pDC->Box(&ThumbPos); pDC->Colour(L_WHITE); pDC->Rectangle(ThumbPos.x1+1, ThumbPos.y1+1, ThumbPos.x2-1, ThumbPos.y2-1); pDC->Colour(L_SHADOW); auto center = ThumbPos.Center(); pDC->Line( ThumbPos.x1+gapPx, center.y, ThumbPos.x2-gapPx, center.y); if (!Open()) { pDC->Line( center.x, ThumbPos.y1+gapPx, center.x, ThumbPos.y2-gapPx); } } } void LPanel::OnMouseClick(LMouse &m) { if (OpenSize > 0 && m.Left() && m.Down()) { if (ThumbPos.Overlap(m.x, m.y)) { Open(!IsOpen); if (GetParent()) OnNotify(this, LNotifyItemClick); } else LgiTrace("%s:%i - Not over %i,%i - %s\n", _FL, m.x, m.y, ThumbPos.GetStr()); } } void LPanel::RePour() { if (auto Top = GetWindow()) Top->PourAll(); } void LPanel::SetChildrenVisibility(bool i) { for (auto w: Children) { if (i && !w->IsAttached()) { w->Attach(this); } w->Visible(i); } } diff --git a/src/win/General/File.cpp b/src/win/General/File.cpp --- a/src/win/General/File.cpp +++ b/src/win/General/File.cpp @@ -1,1929 +1,1929 @@ /*hdr ** FILE: File.cpp ** AUTHOR: Matthew Allen ** DATE: 11/7/95 ** DESCRIPTION: The new file subsystem ** ** Copyright (C) 1995, Matthew Allen ** fret@memecode.com */ /****************************** Includes ************************************************************************************/ #include #include #include #include #include #include #include #include #include #ifdef _MSC_VER #include #include #endif #include "lgi/common/Lgi.h" #include "lgi/common/Thread.h" /****************************** Defines *************************************************************************************/ #define FILEDEBUG #define CHUNK 0xFFF0 #define FLOPPY_360K 0x0001 #define FLOPPY_720K 0x0002 #define FLOPPY_1_2M 0x0004 #define FLOPPY_1_4M 0x0008 #define FLOPPY_5_25 (FLOPPY_360K | FLOPPY_1_2M) #define FLOPPY_3_5 (FLOPPY_720K | FLOPPY_1_4M) /****************************** Helper Functions ****************************************************************************/ LString LFile::Path::Sep(DIR_STR); bool LFile::Path::FixCase() { // Windows is case insensitive.. so meh... do nothing. return true; } int64 LFileSize(const char *FileName) { int64 Size = -1; LDirectory Dir; if (Dir.First(FileName, 0)) { Size = Dir.GetSize(); } return Size; } bool LFileExists(const char *Name, char *CorrectCase) { bool Status = false; if (Name) { HANDLE hFind = INVALID_HANDLE_VALUE; LAutoWString n(Utf8ToWide(Name)); if (n) { WIN32_FIND_DATAW Info; hFind = FindFirstFileW(n, &Info); if (hFind != INVALID_HANDLE_VALUE) Status = StrcmpW(Info.cFileName, L".") != 0; } if (hFind != INVALID_HANDLE_VALUE) { FindClose(hFind); return true; } } return false; } bool LDirExists(const char *Dir, char *CorrectCase) { LAutoWString n(Utf8ToWide(Dir)); DWORD e = GetFileAttributesW(n); return e != 0xFFFFFFFF && TestFlag(e, FILE_ATTRIBUTE_DIRECTORY); } const char *GetErrorName(int e) { static char Buf[256]; char *s = Buf; Buf[0] = 0; ::FormatMessageA( // FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM, NULL, e, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), (LPSTR)Buf, sizeof(Buf), NULL); s = Buf + strlen(Buf); while (s > Buf && strchr(" \t\r\n", s[-1])) { s--; *s = 0; } return Buf[0] ? Buf : 0; } template int _GetLongPathName ( T *Short, T *Long, int LongLen ) { bool Status = false; int Len = 0; if (Short && Long) { HMODULE hDll = 0; hDll = LoadLibrary(_T("kernel32.dll")); if (hDll) { // Win98/ME/2K... short->long conversion supported #ifdef UNICODE typedef DWORD (__stdcall *Proc_GetLongPathName)(LPCWSTR, LPWSTR, DWORD cchBuffer); Proc_GetLongPathName Proc = (Proc_GetLongPathName)GetProcAddress(hDll, "GetLongPathNameW"); #else typedef DWORD (__stdcall *Proc_GetLongPathName)(LPCSTR, LPSTR, DWORD); Proc_GetLongPathName Proc = (Proc_GetLongPathName)GetProcAddress(hDll, "GetLongPathNameA"); #endif if (Proc) { Len = Proc(Short, Long, LongLen); Status = true; } FreeLibrary(hDll); } if (!Status) { // Win95/NT4 doesn't support this function... so we do the conversion ourselves TCHAR *In = Short; TCHAR *Out = Long; *Out = 0; while (*In) { // find end of segment TCHAR *e = Strchr(In, DIR_CHAR); if (!e) e = In + Strlen(In); // process segment TCHAR Old = *e; *e = 0; if (Strchr(In, ':')) { // drive specification Strcat(Long, LongLen, In); // just copy over } else { TCHAR Sep[] = {DIR_CHAR, 0}; Strcat(Out, LongLen, Sep); if (Strlen(In) > 0) { // has segment to work on WIN32_FIND_DATA Info; HANDLE Search = FindFirstFile(Short, &Info); if (Search != INVALID_HANDLE_VALUE) { // have long name segment, copy over Strcat(Long, LongLen, Info.cFileName); FindClose(Search); } else { // no long name segment... copy over short segment :( Strcat(Long, LongLen, In); } } // else is a double path char... i.e. as in a M$ network path } In = (Old) ? e + 1 : e; Out += Strlen(Out); *e = Old; } } } return Len; } bool LResolveShortcut(const char *LinkFile, char *Path, ssize_t Len) { bool Status = false; HWND hwnd = NULL; HRESULT hres; IShellLink* psl; TCHAR szGotPath[DIR_PATH_SIZE] = {0}; WIN32_FIND_DATA wfd; CoInitialize(0); hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (void**) &psl); if (SUCCEEDED(hres)) { IPersistFile* ppf; hres = psl->QueryInterface(IID_IPersistFile, (void**) &ppf); if (SUCCEEDED(hres)) { char16 wsz[DIR_PATH_SIZE]; MultiByteToWideChar(CP_ACP, 0, LinkFile, -1, wsz, DIR_PATH_SIZE); char16 *l = StrrchrW(wsz, '.'); if (l && StricmpW(l, L".lnk")) { StrcatW(wsz, L".lnk"); } hres = ppf->Load(wsz, STGM_READ); if (SUCCEEDED(hres)) { hres = psl->Resolve(hwnd, SLR_ANY_MATCH); if (SUCCEEDED(hres)) { hres = psl->GetPath(szGotPath, DIR_PATH_SIZE, (WIN32_FIND_DATA *)&wfd, SLGP_SHORTPATH ); if (SUCCEEDED(hres) && Strlen(szGotPath) > 0) { #ifdef UNICODE TCHAR TmpPath[MAX_PATH_LEN]; ssize_t TpLen = _GetLongPathName(szGotPath, TmpPath, CountOf(TmpPath)) * sizeof(TmpPath[0]); const void *Tp = TmpPath; ssize_t OutLen = LBufConvertCp(Path, "utf-8", Len-1, Tp, LGI_WideCharset, TpLen); Path[OutLen] = 0; #else _GetLongPathName(szGotPath, Path, Len); #endif Status = true; } } } ppf->Release(); } psl->Release(); } CoUninitialize(); return Status; } void WriteStr(LFile &f, const char *s) { auto Len = (s) ? strlen(s) : 0; f << Len; if (Len > 0) { f.Write(s, Len); } } char *ReadStr(LFile &f DeclDebugArgs) { char *s = 0; // read the strings length... uint32_t Len; f >> Len; if (Len > 0) { // 16mb sanity check.... anything over this // is _probably_ an error if (Len >= (16 << 20)) { // LAssert(0); return 0; } // allocate the memory buffer #if defined MEMORY_DEBUG s = new(_file, _line) char[Len+1]; #else s = new char[Len+1]; #endif if (s) { // read the bytes from disk f.Read(s, Len); s[Len] = 0; } else { // memory allocation error, skip the data // on disk so the caller is where they think // they are in the file. f.Seek(Len, SEEK_CUR); } } return s; } ssize_t SizeofStr(const char *s) { return sizeof(uint32_t) + ((s) ? strlen(s) : 0); } bool LGetDriveInfo ( char *Path, uint64 *Free, uint64 *Size, uint64 *Available ) { bool Status = false; ULARGE_INTEGER available = {0, 0}; ULARGE_INTEGER total = {0, 0}; ULARGE_INTEGER free = {0, 0}; if (Path) { LAutoWString w(Utf8ToWide(Path)); if (w) { char16 *d = StrchrW(w, DIR_CHAR); if (d) *d = 0; if (GetDiskFreeSpaceExW(w, &available, &total, &free)) { if (Free) *Free = free.QuadPart; if (Size) *Size = total.QuadPart; if (Available) *Available = available.QuadPart; Status = true; } } } return Status; } /****************************** Classes ***************************************/ struct LVolumePriv { LString Name; LString Path; LVolumeTypes Type = VT_NONE; int Flags = 0; // VA_?? int64 Size = 0; int64 Free = 0; LSystemPath SysPath = LSP_ROOT; LAutoPtr Icon; LVolume *NextVol = NULL, *ChildVol = NULL; LVolumePriv(LSystemPath sysPath, const char *name) { SysPath = sysPath; Name = name; Type = VT_FOLDER; int Id = 0; switch (Type) { case LSP_HOME: { Id = CSIDL_PROFILE; break; } case LSP_DESKTOP: { Type = VT_DESKTOP; Id = CSIDL_DESKTOPDIRECTORY; break; } } if (Id) Path = WinGetSpecialFolderPath(Id); else Path = LGetSystemPath(SysPath); } LVolumePriv(const char *Drive) { int type = GetDriveTypeA(Drive); if (type != DRIVE_UNKNOWN && type != DRIVE_NO_ROOT_DIR) { char Buf[DIR_PATH_SIZE]; const char *Desc = NULL; switch (type) { case DRIVE_REMOVABLE: { Type = VT_USB_FLASH; if (GetVolumeInformationA(Drive, Buf, sizeof(Buf), 0, 0, 0, 0, 0) && ValidStr(Buf)) Desc = Buf; else Desc = "Usb Drive"; break; } case DRIVE_REMOTE: Desc = "Network"; Type = VT_NETWORK_SHARE; break; case DRIVE_CDROM: Desc = "Cdrom"; Type = VT_CDROM; break; case DRIVE_RAMDISK: Desc = "Ramdisk"; Type = VT_RAMDISK; break; case DRIVE_FIXED: default: { if (GetVolumeInformationA(Drive, Buf, sizeof(Buf), 0, 0, 0, 0, 0) && ValidStr(Buf)) Desc = Buf; else Desc = "Hard Disk"; Type = VT_HARDDISK; ULARGE_INTEGER Avail, TotalBytes, FreeBytes; if (GetDiskFreeSpaceExA(Drive, &Avail, &TotalBytes, &FreeBytes)) { Free = FreeBytes.QuadPart; Size = TotalBytes.QuadPart; } break; } } char s[DIR_PATH_SIZE]; if (Desc) sprintf_s(s, sizeof(s), "%s (%.2s)", Desc, Drive); else sprintf_s(s, sizeof(s), "%s", Drive); Name = s; Path = Drive; } } ~LVolumePriv() { DeleteObj(NextVol); DeleteObj(ChildVol); } void Insert(LVolume *newVol) { if (ChildVol) { for (auto v = ChildVol; v; v = v->d->NextVol) { if (!v->d->NextVol) { v->d->NextVol = newVol; break; } } } else ChildVol = newVol; } LVolume *First() { if (!ChildVol) { if (SysPath == LSP_DESKTOP) { // Add some basic shortcuts LSystemPath Paths[] = {LSP_HOME, LSP_USER_DOCUMENTS, LSP_USER_MUSIC, LSP_USER_VIDEO, LSP_USER_DOWNLOADS, LSP_USER_PICTURES}; const char *Names[] = {"Home", "Documents", "Music", "Video", "Downloads", "Pictures"}; for (unsigned i=0; i 0) for (char *p = Str; *p; p += strlen(p) + 1) Insert(new LVolume(p)); } } return ChildVol; } LVolume *Next() { if (SysPath == LSP_DESKTOP && !NextVol) { NextVol = new LVolume(LSP_MOUNT_POINT, "Drives"); } return NextVol; } }; LVolume::LVolume(const char *Path) { d = new LVolumePriv(Path); } LVolume::LVolume(LSystemPath SysPath, const char *Name) { d = new LVolumePriv(SysPath, Name); } LVolume::~LVolume() { DeleteObj(d); } const char *LVolume::Name() const { return d->Name; } const char *LVolume::Path() const { return d->Path; } LVolumeTypes LVolume::Type() const { return d->Type; } // VT_?? int LVolume::Flags() const { return d->Flags; } uint64 LVolume::Size() const { return d->Size; } uint64 LVolume::Free() const { return d->Free; } LSurface *LVolume::Icon() const { return d->Icon; } bool LVolume::IsMounted() const { return false; } bool LVolume::SetMounted(bool Mount) { return Mount; } void LVolume::Insert(LAutoPtr v) { d->Insert(v.Release()); } LVolume *LVolume::First() { return d->First(); } LVolume *LVolume::Next() { return d->Next(); } LDirectory *LVolume::GetContents() { LDirectory *Dir = 0; if (d->Path.Get()) { if (Dir = new LDirectory) { if (!Dir->First(d->Path)) { DeleteObj(Dir); } } } return Dir; } //////////////////////////////////////////////////////////////////////////////// class LFileSystemPrivate { public: LFileSystemPrivate() { } ~LFileSystemPrivate() { } }; LFileSystem::LFileSystem() { Instance = this; d = new LFileSystemPrivate; Root = 0; } LFileSystem::~LFileSystem() { DeleteObj(Root); DeleteObj(d); } bool LFileSystem::Copy(const char *From, const char *To, LError *ErrorCode, std::function callback) { if (!From || !To) { if (ErrorCode) *ErrorCode = ERROR_INVALID_PARAMETER; return false; } LFile In, Out; if (!In.Open(From, O_READ)) { if (ErrorCode) *ErrorCode = In.GetError(); return false; } if (!Out.Open(To, O_WRITE)) { if (ErrorCode) *ErrorCode = Out.GetError(); return false; } if (Out.SetSize(0)) { if (ErrorCode) *ErrorCode = ERROR_WRITE_FAULT; return false; } int64 Size = In.GetSize(); if (!Size) { return true; } int64 Block = min((1 << 20), Size); char *Buf = new char[Block]; if (!Buf) { if (ErrorCode) *ErrorCode = ERROR_NOT_ENOUGH_MEMORY; return false; } int64 i = 0; while (i < Size) { auto r = In.Read(Buf, Block); if (r > 0) { ssize_t Written = 0; while (Written < r) { auto w = Out.Write(Buf + Written, r - Written); if (w > 0) { Written += w; } else { if (ErrorCode) *ErrorCode = ERROR_WRITE_FAULT; goto ExitCopyLoop; } } i += Written; if (callback) { if (!callback(i, Size)) { break; } } } else break; } ExitCopyLoop: DeleteArray(Buf); if (i == Size) { if (ErrorCode) *ErrorCode = ERROR_SUCCESS; } else { Out.Close(); Delete(To, false); } return i == Size; } bool LFileSystem::Delete(LArray &Files, LArray *Status, bool ToTrash) { bool Ret = true; if (ToTrash) { LArray Name; for (int i=0; i w(Utf8ToWide(Files[i])); auto Chars = StrlenW(w); auto Len = Name.Length(); Name.Length(Len + Chars + 1); StrcpyW(Name.AddressOf(Len), w.Get()); } Name.Add(NULL); SHFILEOPSTRUCTW s; ZeroObj(s); s.hwnd = 0; s.wFunc = FO_DELETE; s.pFrom = Name.AddressOf(); s.fFlags = FOF_NOCONFIRMATION; // FOF_ALLOWUNDO; int e = SHFileOperationW(&s); Ret = e == 0; if (Status) { int val = ERROR_SUCCESS; if (s.fAnyOperationsAborted) val = ERROR_CANCELLED; else if (e) val = ERROR_CANT_DELETE_LAST_ITEM; // There isn't a better option here... for (int i=0; iSet(Code); if (CreateParentFoldersIfNeeded && Code == ERROR_PATH_NOT_FOUND) { char Base[DIR_PATH_SIZE]; strcpy_s(Base, sizeof(Base), PathName); do { char *Leaf = strrchr(Base, DIR_CHAR); if (!Leaf) return false; *Leaf = 0; } while (!LDirExists(Base)); LString::Array Parts = LString(PathName + strlen(Base)).SplitDelimit(DIR_STR); for (int i=0; iSet(GetLastError()); break; } } } } return Status; } bool LFileSystem::RemoveFolder(const char *PathName, bool Recurse) { if (Recurse) { LDirectory Dir; if (Dir.First(PathName)) { do { char Str[DIR_PATH_SIZE]; if (Dir.Path(Str, sizeof(Str))) { if (Dir.IsDir()) { RemoveFolder(Str, Recurse); } else { Delete(Str, false); } } } while (Dir.Next()); } } #ifdef UNICODE LAutoWString w(Utf8ToWide(PathName)); if (!::RemoveDirectory(w)) #else if (!::RemoveDirectory(PathName)) #endif { #ifdef _DEBUG DWORD e = GetLastError(); #endif return false; } return true; } bool LFileSystem::Move(const char *OldName, const char *NewName, LError *Err) { if (!OldName || !NewName) { if (Err) Err->Set(LErrorInvalidParam); return false; } LAutoWString New(Utf8ToWide(NewName)); LAutoWString Old(Utf8ToWide(OldName)); if (!New || !Old) { if (Err) Err->Set(LErrorNoMem); return false; } if (!::MoveFileW(Old, New)) { if (Err) Err->Set(GetLastError()); return false; } return true; } /* bool LFileSystem::GetVolumeInfomation(char Drive, VolumeInfo *pInfo) { bool Status = false; if (pInfo) { char Name[] = "A:\\"; uint32 Serial; Name[0] = Drive + 'A'; if (GetVolumeInformation( Name, pInfo->Name, sizeof(pInfo->Name), &Serial, &pInfo->MaxPath, &pInfo->Flags, pInfo->System, sizeof(pInfo->System))) { pInfo->Drive = Drive; Status = true; } } return Status; } */ void GetFlagsStr(char *flagstr, short flags) { if (flagstr) { flagstr[0] = 0; if (flags & O_WRONLY) { strcat(flagstr,"w"); } else if (flags & O_APPEND) { strcat(flagstr,"a"); } else { strcat(flagstr,"r"); } if ((flags & O_RDWR) == O_RDWR) { strcat(flagstr,"+"); } strcat(flagstr,"b"); } } bool Match(char *Name, char *Mask) { while (*Name && *Mask) { if (*Mask == '*') { if (toupper(*Name) == toupper(Mask[1])) { Mask++; } else { Name++; } } else if (*Mask == '?' || toupper(*Mask) == toupper(*Name)) { Mask++; Name++; } else { return false; } } while (*Mask && ((*Mask == '*') || (*Mask == '.'))) Mask++; return (*Name == 0 && *Mask == 0); } short DaysInMonth[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; int LeapYear(int year) { if (year & 3) { return 0; } if ((year % 100 == 0) && !(year % 400 == 0)) { return 0; } return 1; } // Disk Infomation typedef struct { uchar bsJump[3]; uchar bsOemName[8]; ushort bsBytePerSec; uchar bsSecPerCluster; ushort bsResSectores; uchar bsFAT; ushort bsRootDirEnts; ushort bsSectors; uchar bsMedia; ushort bsFATsecs; ushort bsSecPerTrack; ushort bsHeads; ulong bsHiddenSecs; ulong bsHugeSectoes; uchar bsDriveNumber; uchar bsReserved; uchar bsBootSig; ulong bsVolumeID; // serial number uchar bsVolumeLabel[11]; uchar bsFileSysType[8]; } BOOTSECTOR; typedef struct { ulong diStartSector; ushort diSectors; char *diBuffer; } DISKIO; ///////////////////////////////////////////////////////////////////////////////// #if 0 bool LDirectory::ConvertToTime(char *Str, int SLen, uint64 Time) const { if (Str) { FILETIME t; SYSTEMTIME s; t.dwHighDateTime = Time >> 32; t.dwLowDateTime = Time & 0xFFFFFFFF; if (FileTimeToSystemTime(&t, &s)) { int Hour = (s.wHour < 1) ? s.wHour + 23 : s.wHour - 1; char AP = (Hour >= 12) ? 'a' : 'p'; Hour %= 12; if (Hour == 0) Hour = 12; sprintf_s(Str, SLen, "%i:%02.2i:%02.2i %c", Hour-1, s.wMinute, s.wSecond, AP); } } return false; } bool LDirectory::ConvertToDate(char *Str, int SLen, uint64 Time) const { if (Str) { FILETIME t; SYSTEMTIME s; t.dwHighDateTime = Time >> 32; t.dwLowDateTime = Time & 0xFFFFFFFF; if (FileTimeToSystemTime(&t, &s)) { sprintf_s(Str, SLen, "%i/%i/%i", s.wDay, s.wMonth, s.wYear); } } return false; } #endif int64_t LDirectory::TsToUnix(uint64_t timeStamp) { #define WINDOWS_TICK 10000000 #define SEC_TO_UNIX_EPOCH 11644473600LL return timeStamp / WINDOWS_TICK - SEC_TO_UNIX_EPOCH; } LDateTime LDirectory::TsToDateTime(uint64_t timeStamp, bool convertToLocalTz) { LDateTime dt; dt.SetTimeZone(0, false); // UTC dt.Set(timeStamp); - LArray dst; + LArray dst; if (convertToLocalTz && - LDateTime::GetDaylightSavingsInfo(dst, dt)) + LTimeZone::GetDaylightSavingsInfo(dst, dt)) { // Convert to local using the timezone in effect at 'dt' - LDateTime::DstToLocal(dst, dt); + LTimeZone::DstToLocal(dst, dt); } else { // Without knowing the timezone at 'dt' just leave it as UTC... // The caller will have to figure it out. } return dt; } ///////////////////////////////////////////////////////////////////////////////// //////////////////////////// Directory ////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////// struct LDirectoryPriv { char BasePath[DIR_PATH_SIZE]; char *BaseEnd; int BaseRemaining; LString Utf; HANDLE Handle; WIN32_FIND_DATAW Data; LDirectoryPriv() { Handle = INVALID_HANDLE_VALUE; BasePath[0] = 0; BaseRemaining = 0; } ~LDirectoryPriv() { } }; LDirectory::LDirectory() { d = new LDirectoryPriv; } LDirectory::~LDirectory() { Close(); DeleteObj(d); } LDirectory *LDirectory::Clone() { return new LDirectory; } const char *LDirectory::FullPath() { auto n = GetName(); if (!n) return NULL; strcpy_s(d->BaseEnd, d->BaseRemaining, n); return d->BasePath; } bool LDirectory::Path(char *s, int BufLen) const { auto e = d->BasePath + strlen(d->BasePath); auto sep = e > d->BasePath && e[-1] == DIR_CHAR ? "" : DIR_STR; auto ch = sprintf_s(s, BufLen, "%s%s%s", d->BasePath, sep, GetName()); return ch > 0; } size_t Utf8To16Cpy(uint16_t *out, ssize_t outChar, uint8_t *in, ssize_t inChar = -1) { auto start = out; int32 u32; outChar <<= 1; // words to bytes if (inChar < 0) { while (*in && outChar >= 4) { ssize_t len = 6; u32 = LgiUtf8To32(in, len); LgiUtf32To16(u32, out, outChar); } } else { while (outChar >= 4 && (u32 = LgiUtf8To32(in, inChar))) { LgiUtf32To16(u32, out, outChar); } } outChar >>= 1; // bytes to words if (outChar > 0) *out = 0; return out - start; } size_t Utf16To8Cpy(uint8_t *out, ssize_t outChar, const uint16_t *in, ssize_t inChar = -1) { auto start = out; int32 u32; if (inChar < 0) { while (*in && outChar > 0) { ssize_t len = 4; u32 = LgiUtf16To32(in, len); LgiUtf32To8(u32, out, outChar); } } else { inChar <<= 1; // words to bytes while (outChar > 0 && (u32 = LgiUtf16To32(in, inChar))) { LgiUtf32To8(u32, out, outChar); } inChar >>= 1; // bytes to words } if (outChar > 0) *out = 0; return out - start; } template size_t UnicodeCpy(O *out, ssize_t outChar, I *in, ssize_t inChar = -1) { if (out == NULL || in == NULL) return 0; if (sizeof(O) == 2 && sizeof(I) == 1) return Utf8To16Cpy((uint16_t*)out, outChar, (uint8_t*)in, inChar); else if (sizeof(O) == 1 && sizeof(I) == 2) return Utf16To8Cpy((uint8_t*)out, outChar, (uint16_t*)in, inChar); else LAssert(0); return 0; } int LDirectory::First(const char *InName, const char *Pattern) { Close(); d->Utf.Empty(); if (!InName) return false; wchar_t wTmp[DIR_PATH_SIZE], wIn[DIR_PATH_SIZE]; UnicodeCpy(wTmp, CountOf(wTmp), InName); auto Chars = GetFullPathNameW(wTmp, CountOf(wTmp), wIn, NULL); UnicodeCpy(d->BasePath, sizeof(d->BasePath), wIn, Chars); d->BaseEnd = d->BasePath + strlen(d->BasePath); if (d->BaseEnd > d->BasePath && d->BaseEnd[-1] != DIR_CHAR) { *d->BaseEnd++ = DIR_CHAR; *d->BaseEnd = 0; } ssize_t Used = d->BaseEnd - d->BasePath; d->BaseRemaining = (int) (sizeof(d->BasePath) - Used); char Str[DIR_PATH_SIZE]; wchar_t *FindArg; if (Pattern) { if (!LMakePath(Str, sizeof(Str), d->BasePath, Pattern)) return false; UnicodeCpy(FindArg = wTmp, CountOf(wTmp), Str); } else { FindArg = wIn; } d->Handle = FindFirstFileW(FindArg, &d->Data); if (d->Handle != INVALID_HANDLE_VALUE) { const char *n; while ( (n = GetName()) && (stricmp(n, ".") == 0 || stricmp(n, "..") == 0)) { if (!FindNextFileW(d->Handle, &d->Data)) return false; d->Utf.Empty(); } } return d->Handle != INVALID_HANDLE_VALUE; } int LDirectory::Next() { if (d->Handle == INVALID_HANDLE_VALUE) return false; const char *n; do { d->Utf.Empty(); if (!FindNextFileW(d->Handle, &d->Data)) return false; if (!(n = GetName())) return false; } while (stricmp(n, ".") == 0 || stricmp(n, "..") == 0); return true; } int LDirectory::Close() { if (d->Handle != INVALID_HANDLE_VALUE) { FindClose(d->Handle); d->Handle = INVALID_HANDLE_VALUE; } d->Utf.Empty(); return true; } int LDirectory::GetUser(bool Group) const { return 0; } bool LDirectory::IsReadOnly() const { return (d->Data.dwFileAttributes & FA_READONLY) != 0; } bool LDirectory::IsDir() const { return (d->Data.dwFileAttributes & FA_DIRECTORY) != 0; } bool LDirectory::IsSymLink() const { return (d->Data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0; } bool LDirectory::IsHidden() const { return (d->Data.dwFileAttributes & FA_HIDDEN) != 0; } long LDirectory::GetAttributes() const { return d->Data.dwFileAttributes; } LVolumeTypes LDirectory::GetType() const { return IsDir() ? VT_FOLDER : VT_FILE; } const char *LDirectory::GetName() const { if (!d->Utf) d->Utf = d->Data.cFileName; return d->Utf; } LString LDirectory::FileName() const { if (!d->Utf) d->Utf = d->Data.cFileName; return d->Utf; } uint64 LDirectory::GetCreationTime() const { return ((uint64) d->Data.ftCreationTime.dwHighDateTime) << 32 | d->Data.ftCreationTime.dwLowDateTime; } uint64 LDirectory::GetLastAccessTime() const { return ((uint64) d->Data.ftLastAccessTime.dwHighDateTime) << 32 | d->Data.ftLastAccessTime.dwLowDateTime; } uint64 LDirectory::GetLastWriteTime() const { return ((uint64) d->Data.ftLastWriteTime.dwHighDateTime) << 32 | d->Data.ftLastWriteTime.dwLowDateTime; } uint64 LDirectory::GetSize() const { return ((uint64) d->Data.nFileSizeHigh) << 32 | d->Data.nFileSizeLow; } struct ClusterSizeMap { ClusterSizeMap() { ZeroObj(Sizes); } uint64 GetDriveCluserSize(char Letter) { uint64 Cs = 4096; auto letter = ToLower(Letter) - 'a'; Cs = Sizes[letter]; if (!Cs) { DWORD SectorsPerCluster, BytesPerSector; const char Drive[] = { Letter , ':', '\\', 0 }; BOOL b = GetDiskFreeSpaceA(Drive, &SectorsPerCluster, &BytesPerSector, NULL, NULL); if (b) Sizes[letter] = Cs = SectorsPerCluster * BytesPerSector; else LAssert(0); } return Cs; } private: uint64 Sizes[26]; } ClusterSizes; int64 LDirectory::GetSizeOnDisk() { auto Fp = FullPath(); if (!Fp || !IsAlpha(Fp[0])) return -1; auto ClusterSize = ClusterSizes.GetDriveCluserSize(Fp[0]); DWORD HighSize = 0; DWORD LoSize = GetCompressedFileSizeA(FullPath(), &HighSize); *d->BaseEnd = 0; auto Size = ((uint64)HighSize << 32) | LoSize; return ((Size + ClusterSize - 1) / ClusterSize) * ClusterSize; } ///////////////////////////////////////////////////////////////////////////////// //////////////////////////// File /////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////// class LFilePrivate { public: OsFile hFile; LString Name; int Attributes; bool Swap; bool Status; DWORD LastError; // Threading check stuff OsThreadId CreateId, UseId; bool ThreadWarn; LFilePrivate() { hFile = INVALID_HANDLE; Attributes = 0; Swap = false; Status = false; LastError = 0; ThreadWarn = true; CreateId = LThread::InvalidId; UseId = LThread::InvalidId; } bool UseThreadCheck() { auto Cur = LCurrentThreadId(); if (UseId == LThread::InvalidId) { UseId = Cur; } else if (Cur != UseId) { if (ThreadWarn) { ThreadWarn = false; LgiTrace("%s:%i - Warning: multi-threaded file access (me=%i/%s, user=%i/%s).\n", _FL, Cur, LThread::GetThreadName(Cur), UseId, LThread::GetThreadName(UseId)); } return false; } return true; } void OnError(const char *File, int Line, DWORD Code, const char *Ctx) { LgiTrace("%s:%i - LFile::%s(%s) Err=0x%x\n", File, Line, Ctx, Name.Get(), LastError = Code); } }; LFile::LFile(const char *Path, int Mode) { d = new LFilePrivate; if (Path) Open(Path, Mode); } LFile::~LFile() { if (ValidHandle(d->hFile)) { Close(); } DeleteObj(d); } OsFile LFile::Handle() { return d->hFile; } void LFile::ChangeThread() { d->UseId = LCurrentThreadId(); } uint64_t LFile::GetModifiedTime() { FILETIME create, access, write; if (!GetFileTime(Handle(), &create, &access, &write)) return 0; return (((uint64_t)write.dwHighDateTime) << 32) | write.dwLowDateTime; } bool LFile::SetModifiedTime(uint64_t dt) { FILETIME create, access, write; if (!GetFileTime(Handle(), &create, &access, &write)) return false; write.dwHighDateTime = dt >> 32; write.dwLowDateTime = dt & 0xffffffff; auto result = SetFileTime(Handle(), &create, &access, &write); return result != 0; } const char *LFile::GetName() { return d->Name; } void LFile::SetSwap(bool s) { d->Swap = s; } bool LFile::GetSwap() { return d->Swap; } int LFile::GetOpenMode() { return d->Attributes; } int LFile::GetBlockSize() { DWORD SectorsPerCluster; DWORD BytesPerSector = 0; DWORD NumberOfFreeClusters; DWORD TotalNumberOfClusters; #ifdef UNICODE LAutoWString n(Utf8ToWide(GetName())); #else LAutoString n(NewStr(GetName())); #endif if (n) { TCHAR *dir = n ? Strchr(n.Get(), '\\') : 0; if (dir) dir[1] = 0; GetDiskFreeSpace( n, &SectorsPerCluster, &BytesPerSector, &NumberOfFreeClusters, &TotalNumberOfClusters); } return BytesPerSector; } int LFile::Open(const char *File, int Mode) { int Status = false; bool NoCache = (Mode & O_NO_CACHE) != 0; Mode &= ~O_NO_CACHE; Close(); if (File) { bool SharedAccess = (Mode & O_SHARE) != 0; Mode &= ~O_SHARE; if (File[0] == '/' && File[1] == '/') { // This hangs the process, so just bail safely. return false; } LAutoWString n(Utf8ToWide(File)); if (n) { d->hFile = CreateFileW( n, Mode, FILE_SHARE_READ | (SharedAccess ? FILE_SHARE_WRITE : 0), 0, (Mode & O_WRITE) ? OPEN_ALWAYS : OPEN_EXISTING, NoCache ? FILE_FLAG_NO_BUFFERING : 0, NULL); } if (!ValidHandle(d->hFile)) { // d->OnError(_FL, GetLastError(), "Open"); switch (d->LastError = GetLastError()) { case ERROR_FILE_NOT_FOUND: case ERROR_PATH_NOT_FOUND: { // Path/File not found; int i=0; break; } case ERROR_ACCESS_DENIED: { // Access is denied: // Read-Only or another process has the file open int i=0; break; } } } else { d->Attributes = Mode; d->Name = File; Status = true; d->Status = true; d->CreateId = LCurrentThreadId(); } } return Status; } bool LFile::IsOpen() { return ValidHandle(d->hFile); } int LFile::GetError() { return d->LastError; } int LFile::Close() { int Status = false; d->UseThreadCheck(); if (ValidHandle(d->hFile)) { ::CloseHandle(d->hFile); d->hFile = INVALID_HANDLE; } d->Name.Empty(); return Status; } int64 LFile::GetSize() { LAssert(IsOpen()); d->UseThreadCheck(); DWORD High = -1; DWORD Low = GetFileSize(d->hFile, &High); return Low | ((int64)High<<32); } int64 LFile::SetSize(int64 Size) { LAssert(IsOpen()); d->UseThreadCheck(); LONG OldPosHigh = 0; DWORD OldPosLow = SetFilePointer(d->hFile, 0, &OldPosHigh, SEEK_CUR); LONG SizeHigh = Size >> 32; DWORD r = SetFilePointer(d->hFile, (LONG)Size, &SizeHigh, FILE_BEGIN); BOOL b = SetEndOfFile(d->hFile); if (!b) { DWORD err = GetLastError(); LgiTrace("%s:%i - SetSize(" LPrintfInt64 ") failed: 0x%x\n", _FL, Size, err); } SetFilePointer(d->hFile, OldPosLow, &OldPosHigh, FILE_BEGIN); return GetSize(); } int64 LFile::GetPos() { LAssert(IsOpen()); d->UseThreadCheck(); LONG PosHigh = 0; DWORD PosLow = SetFilePointer(d->hFile, 0, &PosHigh, FILE_CURRENT); return PosLow | ((int64)PosHigh<<32); } int64 LFile::SetPos(int64 Pos) { LAssert(IsOpen()); d->UseThreadCheck(); LONG PosHigh = Pos >> 32; DWORD PosLow = SetFilePointer(d->hFile, (LONG)Pos, &PosHigh, FILE_BEGIN); return PosLow | ((int64)PosHigh<<32); } ssize_t LFile::Read(void *Buffer, ssize_t Size, int Flags) { ssize_t Rd = 0; d->UseThreadCheck(); // This loop allows ReadFile (32bit) to read more than 4GB in one go. If need be. for (ssize_t Pos = 0; Pos < Size; ) { DWORD Bytes = 0; int BlockSz = (int) MIN( Size - Pos, 1 << 30 ); // 1 GiB blocks if (ReadFile(d->hFile, (char*)Buffer + Pos, BlockSz, &Bytes, NULL) && Bytes > 0) { Rd += Bytes; Pos += Bytes; d->Status &= Bytes > 0; } else { if (!Eof()) d->OnError(_FL, GetLastError(), "Read"); break; } } return Rd; } ssize_t LFile::Write(const void *Buffer, ssize_t Size, int Flags) { ssize_t Wr = 0; d->UseThreadCheck(); // This loop allows WriteFile (32bit) to read more than 4GB in one go. If need be. for (ssize_t Pos = 0; Pos < Size; ) { DWORD Bytes = 0; int BlockSz = (int) MIN( Size - Pos, 1 << 30 ); // 1 GiB blocks if (WriteFile(d->hFile, (const char*)Buffer + Pos, BlockSz, &Bytes, NULL)) { if (Bytes != BlockSz) { LAssert(!"Is this ok?"); } Wr += Bytes; Pos += Bytes; d->Status &= Bytes > 0; } else { d->OnError(_FL, GetLastError(), "Write"); break; } } return Wr; } int64 LFile::Seek(int64 To, int Whence) { LAssert(IsOpen()); d->UseThreadCheck(); int Mode; switch (Whence) { default: case SEEK_SET: Mode = FILE_BEGIN; break; case SEEK_CUR: Mode = FILE_CURRENT; break; case SEEK_END: Mode = FILE_END; break; } LONG ToHigh = To >> 32; DWORD ToLow = SetFilePointer(d->hFile, (LONG)To, &ToHigh, Mode); return ToLow | ((int64)ToHigh<<32); } bool LFile::Eof() { LAssert(IsOpen()); return GetPos() >= GetSize(); } ssize_t LFile::SwapRead(uchar *Buf, ssize_t Size) { DWORD r = 0; d->UseThreadCheck(); if (!ReadFile(d->hFile, Buf, (DWORD)Size, &r, NULL) || r != Size) { d->OnError(_FL, GetLastError(), "SwapRead"); return 0; } // Swap the bytes uchar *s = Buf, *e = Buf + Size - 1; while (s < e) { uchar t = *s; *s++ = *e; *e-- = t; } return r; } ssize_t LFile::SwapWrite(uchar *Buf, ssize_t Size) { int i = 0; DWORD w; Buf = &Buf[Size-1]; d->UseThreadCheck(); while (Size--) { if (!(d->Status &= WriteFile(d->hFile, Buf--, 1, &w, NULL) != 0 && w == 1)) break; i += w; } return i; } ssize_t LFile::ReadStr(char *Buf, ssize_t Size) { int i = 0; DWORD r; d->UseThreadCheck(); if (Buf && Size > 0) { char c; Size--; do { ReadFile(d->hFile, &c, 1, &r, NULL); if (Eof()) { break; } *Buf++ = c; i++; } while (i < Size - 1 && c != '\n'); *Buf = 0; } return i; } ssize_t LFile::WriteStr(char *Buf, ssize_t Size) { int i = 0; DWORD w; d->UseThreadCheck(); while (i <= Size) { WriteFile(d->hFile, Buf, 1, &w, NULL); Buf++; i++; if (*Buf == '\n') break; } return i; } void LFile::SetStatus(bool s) { d->Status = s; } bool LFile::GetStatus() { return d->Status; } #define GFileOp(type) LFile &LFile::operator >> (type &i) \ { \ if (d->Swap) SwapRead((uchar*) &i, sizeof(i)); \ else Read(&i, sizeof(i)); \ return *this; \ } GFileOps(); #undef GFileOp #define GFileOp(type) LFile &LFile::operator << (type i) \ { \ if (d->Swap) SwapWrite((uchar*) &i, sizeof(i)); \ else Write(&i, sizeof(i)); \ return *this; \ } GFileOps(); #undef GFileOp diff --git a/src/win/Lgi/Layout.cpp b/src/win/Lgi/Layout.cpp --- a/src/win/Lgi/Layout.cpp +++ b/src/win/Lgi/Layout.cpp @@ -1,206 +1,206 @@ /* ** FILE: GuiViews.cpp ** AUTHOR: Matthew Allen ** DATE: 18/7/98 ** DESCRIPTION: Standard Views ** ** Copyright (C) 1998, Matthew Allen ** fret@memecode.com */ #include #include "lgi/common/Lgi.h" #include "lgi/common/ScrollBar.h" #include "lgi/common/Css.h" #include "lgi/common/Layout.h" ////////////////////////////////////////////////////////////////////////////// LLayout::LLayout() : LView(0) { _SettingScrollBars = 0; _PourLargest = 0; VScroll = 0; HScroll = 0; #if WINNATIVE SetExStyle(GetExStyle() | WS_EX_CONTROLPARENT); #endif } LLayout::~LLayout() { DeleteObj(VScroll); DeleteObj(HScroll); } LViewI *LLayout::FindControl(int Id) { if (VScroll && VScroll->GetId() == Id) return VScroll; if (HScroll && HScroll->GetId() == Id) return HScroll; return LView::FindControl(Id); } LPoint LLayout::GetScrollPos() { int64 x, y; GetScrollPos(x, y); - return LPoint(x, y); + return LPoint((int)x, (int)y); } void LLayout::GetScrollPos(int64 &x, int64 &y) { x = HScroll ? HScroll->Value() : 0; y = VScroll ? VScroll->Value() : 0; } void LLayout::SetScrollPos(int64 x, int64 y) { if (HScroll) HScroll->Value(x); if (VScroll) VScroll->Value(y); } bool LLayout::SetScrollBars(bool x, bool y) { bool Status = false; if (!_SettingScrollBars) { _SettingScrollBars = true; if (y) { if (!VScroll) { VScroll = new LScrollBar; if (VScroll) { VScroll->SetVertical(true); VScroll->SetParent(this); VScroll->SetId(IDC_VSCROLL); Status = true; } } } else { if (VScroll) { DeleteObj(VScroll); Status = true; } } if (x) { if (!HScroll) { HScroll = new LScrollBar; if (HScroll) { HScroll->SetVertical(false); HScroll->SetParent(this); HScroll->SetId(IDC_HSCROLL); Status = true; } } } else { if (HScroll) { DeleteObj(HScroll); Status = true; } } _SettingScrollBars = false; } return Status; } bool LLayout::GetPourLargest() { return _PourLargest; } void LLayout::SetPourLargest(bool i) { _PourLargest = i; } LCss::Len &SelectValid(LCss::Len &a, LCss::Len &b, LCss::Len &c) { if (a.IsValid()) return a; if (b.IsValid()) return b; return c; } bool LLayout::Pour(LRegion &r) { if (!_PourLargest) return false; LRect *Largest = FindLargest(r); if (!Largest) return false; LRect p = *Largest; LCss *css = GetCss(); if (css) { LCss::Len margin = css->Margin(); LCss::Len s; LCss::Len zero; LFont *f = GetFont(); s = css->MarginTop(); p.x1 += SelectValid(s, margin, zero).ToPx(r.X(), f); s = css->MarginTop(); p.y1 += SelectValid(s, margin, zero).ToPx(r.Y(), f); s = css->MarginRight(); p.x2 -= SelectValid(s, margin, zero).ToPx(r.X(), f); s = css->MarginBottom(); p.y2 -= SelectValid(s, margin, zero).ToPx(r.Y(), f); if ((s = css->Width()).IsValid()) p.x2 = p.x1 + s.ToPx(r.X(), f) - 1; if ((s = css->Height()).IsValid()) p.y2 = p.y1 + s.ToPx(r.Y(), f) - 1; } if (p.Valid()) { SetPos(p, true); } else { LAssert(0); return false; } return true; } LMessage::Result LLayout::OnEvent(LMessage *Msg) { if (VScroll) VScroll->OnEvent(Msg); if (HScroll) HScroll->OnEvent(Msg); LMessage::Result Status = LView::OnEvent(Msg); if (Msg->Msg() == M_CHANGE && Status == -1 && GetParent()) { Status = GetParent()->OnEvent(Msg); } return Status; } diff --git a/win/LgiStatic_vs2019.vcxproj b/win/LgiStatic_vs2019.vcxproj --- a/win/LgiStatic_vs2019.vcxproj +++ b/win/LgiStatic_vs2019.vcxproj @@ -1,244 +1,245 @@  Debug Win32 Debug x64 Release Win32 Release x64 LgiStatic {DE6D97B9-A194-401B-A8A4-83110C56D572} LgiStatic_vc9 10.0.19041.0 StaticLibrary v142 false Unicode StaticLibrary v142 false Unicode StaticLibrary v142 false Unicode StaticLibrary v142 false Unicode <_ProjectFileVersion>12.0.30501.0 Static_$(Platform)$(Configuration)19\ Static_$(Platform)$(Configuration)19\ ..\Lib\ Static_$(Platform)$(Configuration)19\ $(ProjectName)19 Static_$(Platform)$(Configuration)19\ Static_$(Platform)$(Configuration)19\ ..\Lib\ Static_$(Platform)$(Configuration)19\ $(ProjectName)19d MinSpace OnlyExplicitInline ../include/lgi/win;..\include;%(AdditionalIncludeDirectories) NDEBUG;WIN32;_LIB;LGI_STATIC;WINDOWS;%(PreprocessorDefinitions) true MultiThreadedDLL true $(IntDir)LgiStatic.pch .\$(IntDir) .\$(IntDir) .\$(IntDir) true NDEBUG;%(PreprocessorDefinitions) 0x0c09 $(OutDir)$(ProjectName).lib true true .\LgiStaticRelease/LgiStatic.bsc X64 MinSpace OnlyExplicitInline ../include/lgi/win;..\include;%(AdditionalIncludeDirectories) NDEBUG;WIN32;_LIB;LGI_STATIC;WINDOWS;%(PreprocessorDefinitions) true MultiThreadedDLL true $(IntDir)LgiStatic.pch .\$(IntDir) .\$(IntDir) .\$(IntDir) true NDEBUG;%(PreprocessorDefinitions) 0x0c09 $(OutDir)$(TargetName).lib true true .\LgiStaticRelease/LgiStatic.bsc Disabled ../include/lgi/win;..\include;%(AdditionalIncludeDirectories) _DEBUG;WIN32;_LIB;LGI_STATIC;WINDOWS;%(PreprocessorDefinitions) EnableFastChecks MultiThreadedDebugDLL $(IntDir)LgiStatic.pch .\$(IntDir) .\$(IntDir) .\$(IntDir) true ProgramDatabase _DEBUG;%(PreprocessorDefinitions) 0x0c09 $(OutDir)$(ProjectName).lib true true .\LgiStaticDebug/LgiStatic.bsc X64 Disabled ../include/lgi/win;..\include;%(AdditionalIncludeDirectories) _DEBUG;WIN32;_LIB;LGI_STATIC;WINDOWS;%(PreprocessorDefinitions) EnableFastChecks MultiThreadedDebugDLL $(IntDir)LgiStatic.pch .\$(IntDir) .\$(IntDir) .\$(IntDir) true ProgramDatabase _DEBUG;%(PreprocessorDefinitions) 0x0c09 $(OutDir)$(TargetName).lib true true .\LgiStaticDebug/LgiStatic.bsc + \ No newline at end of file diff --git a/win/Lgi_vs2019.vcxproj b/win/Lgi_vs2019.vcxproj --- a/win/Lgi_vs2019.vcxproj +++ b/win/Lgi_vs2019.vcxproj @@ -1,628 +1,629 @@  Debug x64 ReleaseNoOptimize x64 Release x64 Lgi {95DF9CA4-6D37-4A85-A648-80C2712E0DA1} 10.0 DynamicLibrary v142 false Unicode DynamicLibrary v142 false Unicode DynamicLibrary v142 false Unicode <_ProjectFileVersion>12.0.30501.0 ..\Lib\ $(Platform)$(Configuration)19\ false $(VC_IncludePath);$(WindowsSDK_IncludePath);..\..\..\CodeLib\libiconv-1.14\include Lgi19x64 ..\Lib\ $(Platform)$(Configuration)19\ true $(VC_IncludePath);$(WindowsSDK_IncludePath);..\..\..\CodeLib\libiconv-1.14\include Lgi19x64d ..\Lib\ $(Platform)$(Configuration)19\ false $(VC_IncludePath);$(WindowsSDK_IncludePath);..\..\..\CodeLib\libiconv-1.14\include Lgi19x64nop NDEBUG;%(PreprocessorDefinitions) true true X64 .\Release/Lgi.tlb MinSpace OnlyExplicitInline ..\include;..\include\lgi\win;..\private\common;..\private\win;..\..\..\..\CodeLib\libiconv\include;..\..\..\scribe\libs\build-x64\libiconv-1.17\include;%(AdditionalIncludeDirectories) WIN64;NDEBUG;WINDOWS;LGI_RES;LGI_LIBRARY;%(PreprocessorDefinitions) true MultiThreadedDLL true true $(IntDir)$(TargetName).pch $(IntDir) $(IntDir) $(IntDir) Level2 true ProgramDatabase Default true NDEBUG;%(PreprocessorDefinitions) 0x0c09 ComCtl32.lib;Ws2_32.lib;UxTheme.lib;imm32.lib;%(AdditionalDependencies) $(OutDir)$(TargetFileName) true true $(OutDir)$(TargetName).pdb Windows true false $(OutDir)$(TargetName).lib MachineX64 _DEBUG;%(PreprocessorDefinitions) true true X64 .\Debug/Lgi.tlb Disabled ..\include;..\include\lgi\win;..\private\common;..\private\win;..\..\..\..\CodeLib\libiconv\include;..\..\..\scribe\libs\build-x64\libiconv-1.17\include;%(AdditionalIncludeDirectories) WIN64;LGI_LIBRARY;_DEBUG;WINDOWS;LGI_RES;LGI_UNIT_TESTS;%(PreprocessorDefinitions) EnableFastChecks MultiThreadedDebugDLL true $(IntDir)$(TargetName).pch $(IntDir) $(IntDir) $(IntDir) Level3 true ProgramDatabase Default false true _DEBUG;%(PreprocessorDefinitions) 0x0c09 ComCtl32.lib;Ws2_32.lib;UxTheme.lib;imm32.lib;%(AdditionalDependencies) NotSet $(OutDir)$(TargetFileName) true true $(OutDir)$(TargetName).pdb Windows false $(OutDir)$(TargetName).lib MachineX64 NDEBUG;%(PreprocessorDefinitions) true true X64 .\Release/Lgi.tlb MinSpace OnlyExplicitInline ..\include;..\include\lgi\win;..\private\common;..\private\win;..\..\..\..\CodeLib\libiconv\include;..\..\..\scribe\libs\build-x64\libiconv-1.17\include;%(AdditionalIncludeDirectories) WIN64;NDEBUG;WINDOWS;LGI_RES;LGI_LIBRARY;%(PreprocessorDefinitions) true MultiThreadedDLL true true $(IntDir)$(TargetName).pch $(IntDir) $(IntDir) $(IntDir) Level2 true ProgramDatabase Default false true NDEBUG;%(PreprocessorDefinitions) 0x0c09 ComCtl32.lib;Ws2_32.lib;UxTheme.lib;imm32.lib;%(AdditionalDependencies) $(OutDir)$(TargetFileName) true true $(OutDir)$(TargetName).pdb Windows true false $(OutDir)$(TargetName).lib MachineX64 + false true true false false false true true true true true true false false false true true true true true true \ No newline at end of file diff --git a/win/Lgi_vs2019.vcxproj.filters b/win/Lgi_vs2019.vcxproj.filters --- a/win/Lgi_vs2019.vcxproj.filters +++ b/win/Lgi_vs2019.vcxproj.filters @@ -1,1231 +1,1234 @@  {afe8cb77-9ad1-4536-bbdd-3c127e7ed08c} cpp;c;cxx;rc;def;r;odl;idl;hpj;bat {66a64573-871b-4499-ae26-c19e9e2a514a} {3fc23ef0-f144-4f1f-a9b4-18d3392bb63d} {c6cd6d73-d33c-4413-ade1-9dad78e2dc9c} {c8684fc7-2e3c-4f15-8284-9d44b044f6c6} {87b1c801-b9ce-4f6c-97ab-a8f89aee9594} {c06c25f2-2c07-4900-a517-4a6a324069e9} {2c01a737-36cf-4197-bfa1-20395060263f} {1e4cd802-8b94-4328-930e-37bfbfbedff5} {01075698-dde2-4ed0-808f-7dd54414b597} {4a6845a8-e6ec-47d5-8f6c-aa0cfdbc68df} {f567a76b-edd5-434d-b0d9-d51481f34845} {3dee0237-b01c-44e8-a730-08dc661df82f} {bbaaace6-0ac7-4e76-90ae-9e5d5a0cb899} {71e7b970-a070-40a7-a99f-88c215e14e44} {6e115ea1-09fb-492b-82b6-428afe17fed9} {719ef36f-419f-46f9-aef9-2f8158e4d106} {fb221556-3700-4dd8-ba9a-10b5d66bfe54} {8e9f0321-56ae-4485-a338-e87d714c7f50} {c6050f41-574b-4a92-9ce5-860be2719ecf} {a07cd969-801e-4ec9-9394-e34912a3271d} {e067fae0-ef98-4e7a-8434-6f36590ba0e6} {0a3e2151-b13a-407a-8cd9-ccb20f15cacf} {c72248a4-c2f0-43b9-950d-89d09bfddbb3} {dad60202-c763-4f32-9dfb-fe7def4637ee} {532dfa4a-58d3-4133-9ed6-a9fbf7f1556e} {5409aca4-2a55-4b2f-a719-d3db4e3cd7e4} {e3a3aadd-47ef-4723-9bcc-7db1f75a18b0} {c4327acf-78c3-4ef1-b8bc-3aac9ea52b41} {b3c906b8-595e-4641-8eec-8ad03ab13f6f} {baf0a65b-4a9c-4b11-850d-a957c19a22bf} {6e349f5b-36a8-4821-9574-4040a3784204} {ddfdebae-8bcf-4224-8938-2716aba03822} {a126c55a-edee-489f-a852-25cbd1b433b2} {ab5fd4a0-3862-42fd-b4ff-d5d8b0443f53} {048d5e0a-220f-4911-999d-96965eb53691} {258aef64-8cd0-4838-8131-147196656a99} {5fe450b8-5fa9-440e-9fb0-03860b3548d0} h;hpp;hxx;hm;inl Source Files\Core Source Files\Core Source Files\Dialogs Source Files\General Source Files\Widgets Source Files\Widgets Source Files\Widgets\Native Windows Source Files\Widgets\Native Windows Source Files\Widgets Source Files\Text Source Files\General\Clipboard Source Files\Graphics Source Files\Widgets\Native Windows Source Files\Core\Memory Subsystem Source Files\Widgets\Css Source Files\Widgets\Css Source Files\Core\DateTime Source Files\Widgets\Native Windows Source Files\Graphics\Font Source Files\Core\Drag and Drop Source Files\Core\Drag and Drop Source Files\Core Source Files\Core\Drag and Drop Source Files\Widgets\Native Windows Source Files\General Source Files\Core\File Source Files\Core\File Source Files\Dialogs Source Files\Graphics\Filters Source Files\Dialogs Source Files\Graphics\Font Source Files\Graphics\Font Source Files\Graphics\Font Source Files\Graphics\Font Source Files\Graphics Source Files\Graphics Source Files\Graphics\Gdi Leak Source Files\Core\Skin Source Files\General Source Files\General\Growl Source Files\Graphics Source Files\Dialogs Source Files\Core Source Files\General Source Files\General Source Files\Core\Resources Source Files\Core\Libraries Source Files\Dialogs Source Files\General\Hash Source Files\General\Hash Source Files\Core\Memory Subsystem Source Files\Graphics\Surfaces Source Files\Graphics\Applicators Source Files\Graphics\Applicators Source Files\Graphics\Applicators Source Files\Graphics\Applicators Source Files\Graphics\Applicators Source Files\Graphics\Applicators Source Files\Graphics\Applicators Source Files\Graphics\Applicators Source Files\Core\Memory Subsystem Source Files\Core\Menus Source Files\Core\Menus Source Files\Network Source Files\Network Source Files\General Source Files\General Source Files\Widgets Source Files\General Source Files\Graphics Source Files\Widgets Source Files\Graphics\Surfaces Source Files\Core Source Files\Widgets\Native Windows Source Files\Widgets Source Files\Widgets Source Files\Widgets\Native Windows Source Files\General Source Files\Core\Resources Source Files\Graphics\Surfaces Source Files\Widgets Source Files\General\Hash Source Files\Widgets\Native Windows Source Files\Widgets\Native Windows Source Files\Widgets Source Files\Text Source Files\Graphics\Font Source Files\Graphics\Surfaces Source Files\Widgets Source Files\Widgets Source Files\Widgets Source Files\Core\Threads Source Files\Core\Threads Source Files\Core\Threads Source Files\Text Source Files\Widgets Source Files\Widgets Source Files\Widgets Source Files\Graphics\Font Source Files\Text Source Files\Text Source Files\Network Source Files\Text Source Files\Core Source Files\Core Source Files\Core Source Files\Core Source Files\Text Source Files\Widgets\Container Source Files\Widgets\Container Source Files\Widgets\Layout Source Files\Widgets\Layout Source Files\Widgets\Layout Source Files\Widgets\Xp Source Files\Widgets\Xp Source Files\Widgets\Xp Source Files\Widgets\Layout Source Files\Widgets\Container Source Files\Core\Mutex Source Files\Core\Stream Source Files\Core\Variant Source Files\General\Mru Source Files\Graphics\Colour Reduction Source Files\Graphics\Rect/Region Source Files\General\Clipboard Source Files\Core\Process + + Source Files\Core\DateTime + Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Source Files\Widgets Source Files\Widgets Source Files\Widgets Source Files\Widgets Source Files\Widgets Source Files\Widgets Source Files\Widgets Source Files\Widgets Source Files\Widgets Source Files\Widgets Source Files\Widgets Source Files\Widgets Source Files\Widgets Source Files\Widgets Source Files\Widgets Source Files\Widgets Source Files\Widgets\Container Source Files\Widgets\Container Source Files\Widgets\Container Source Files\Widgets\Container Source Files\Widgets\Layout Source Files\Widgets\Layout Source Files\Widgets\Layout Source Files\Widgets\Xp Source Files\Widgets\Xp Source Files\Widgets\Xp Source Files\Widgets\Layout Source Files\Text Source Files\Text Source Files\Text Source Files\Text Source Files\Text Source Files\Text Source Files\Text Source Files\Network Source Files\Network Source Files\Network Source Files\Core Source Files\Core Source Files\Widgets\Container Source Files\Core Source Files\Core Source Files\Core Source Files\Core Source Files\Core\DateTime Source Files\Core\Drag and Drop Source Files\Core\Drag and Drop Source Files\Core\File Source Files\Core\Libraries Source Files\Core\Libraries Source Files\Core\Memory Subsystem Source Files\Core\Memory Subsystem Source Files\Core\Menus Source Files\Core\Menus Source Files\Core\Mutex Source Files\Core\Process Source Files\Core\Resources Source Files\Core\Resources Source Files\Core\Skin Source Files\Core\Skin Source Files\Core\Stream Source Files\Core\Threads Source Files\Core\Threads Source Files\Core\Variant Source Files\Core Source Files\Dialogs Source Files\Dialogs Source Files\Dialogs Source Files\General\Clipboard Source Files\General\Growl Source Files\General\Hash Source Files\General\Hash Source Files\General\Mru Source Files\General Source Files\General Source Files\General Source Files\Graphics\Filters Source Files\Graphics\Font Source Files\Graphics\Font Source Files\Graphics\Font Source Files\Graphics\Font Source Files\Graphics\Font Source Files\Graphics\Rect/Region Source Files\Graphics\Rect/Region Source Files\Graphics Source Files\Graphics Source Files\Graphics Source Files\Graphics Source Files\Graphics\Gdi Leak Source Files\Graphics Source Files\Core\Memory Subsystem Source Files \ No newline at end of file