diff --git a/lvc/src/BrowseUi.cpp b/lvc/src/BrowseUi.cpp --- a/lvc/src/BrowseUi.cpp +++ b/lvc/src/BrowseUi.cpp @@ -1,473 +1,459 @@ #include "lgi/common/Lgi.h" #include "lgi/common/TabView.h" #include "lgi/common/TableLayout.h" #include "lgi/common/TextLabel.h" #include "lgi/common/Edit.h" #include "lgi/common/List.h" #include "lgi/common/TextLog.h" #include "lgi/common/ClipBoard.h" #include "lgi/common/Button.h" #include "lgi/common/PopupNotification.h" #include "Lvc.h" #include "VcFolder.h" #define OPT_WND_STATE "BrowseUiState" #define USE_RELATIVE_TIMES 1 enum Ids { IDC_STATIC = -1, IDC_BLAME_FILTER = 100, IDC_BLAME_TABLE, IDC_BLAME_FILTER_CLEAR, IDC_BLAME_LST, IDC_LOG_TABLE, IDC_LOG_FILTER, IDC_LOG_FILTER_CLEAR, IDC_LOG_LST, IDM_COPY_USER, IDM_COPY_REF, IDM_GOTO_LINE }; struct BrowseUiPriv { LList *Blame = NULL; LList *Log = NULL; LTextLog *Raw = NULL; LTabView *Tabs = NULL; AppPriv *Priv = NULL; VcFolder *Folder = NULL; LTableLayout *BlameTbl = NULL; LTableLayout *LogTbl = NULL; BrowseUi::TMode Mode; LString Output; LString Path; LString UserHilight; LFont Mono; LArray Commits; LHashTbl, LColour*> Colours; int NextHue = 0; int NextLum = 140; LColour *NewColour() { LColour *hls; if (hls = new LColour) hls->SetHLS(NextHue, NextLum, 128); NextHue += 30; if (NextHue >= 360) { NextHue = 0; NextLum -= 32; } return hls; } BrowseUiPriv(BrowseUi::TMode mode, LString path) : Mode(mode), Path(path) { LCss css; css.FontFamily(LCss::FontFamilyMonospace); auto status = Mono.CreateFromCss(&css); LAssert(status); LDisplayString ds(&Mono, "1234"); Mono.TabSize(ds.X()); } ~BrowseUiPriv() { Colours.DeleteObjects(); Commits.DeleteObjects(); } void GotoRef(LString ref) { auto wnd = Blame->GetWindow(); Folder->SelectCommit(wnd, ref, Path); } void GotoLine(const char *line); }; enum LineIdx { TUser, TRef, TDate, TLine, TSrc }; struct BrowseItem : public LListItem { BrowseUiPriv *d = NULL; LColour *refColour = NULL; BrowseItem(BrowseUiPriv *priv, BlameLine &ln, LDateTime &now, LColour *refCol, int &lineNo) : refColour(refCol) { d = priv; SetText(ln.user, TUser); SetText(ln.ref, TRef); #if USE_RELATIVE_TIMES LDateTime dt; if (ln.date && dt.Set(ln.date)) SetText(dt.DescribePeriod(now), TDate); else #endif SetText(ln.date, TDate); if (ln.line) SetText(ln.line, TLine); else SetText(LString::Fmt("%i", lineNo), TLine); SetText(ln.src, TSrc); lineNo++; } void OnPaintColumn(LItem::ItemPaintCtx &Ctx, int i, LItemColumn *c) override { LColour old = Ctx.Fore; if (!Select()) { if (i == TUser) Ctx.Fore = LColour::Blue; else if (i == TRef && refColour) Ctx.Fore = *refColour; else if (i == TDate || i == TLine) Ctx.Fore.Rgb(192, 192, 192); } if (i == TLine) Ctx.Align = LCss::AlignRight; const char *src; if (i == TSrc && (src = GetText(TSrc)) ) { d->Mono.Transparent(false); d->Mono.Colour(Ctx.Fore, Ctx.Back); LDisplayString ds(&d->Mono, src); ds.Draw(Ctx.pDC, Ctx.x1, Ctx.y1, &Ctx); } else { LListItem::OnPaintColumn(Ctx, i, c); } Ctx.Fore = old; } void OnMouseClick(LMouse &m) override { int Col = -1; GetList()->GetColumnClickInfo(Col, m); if (m.IsContextMenu()) { LSubMenu sub; sub.AppendItem("Copy user", IDM_COPY_USER); sub.AppendItem("Copy ref", IDM_COPY_REF); sub.AppendItem("Goto line..", IDM_GOTO_LINE); switch (sub.Float(GetList(), m)) { case IDM_COPY_USER: { LClipBoard c(GetList()); c.Text(GetText(TUser)); break; } case IDM_COPY_REF: { LClipBoard c(GetList()); c.Text(GetText(TRef)); break; } case IDM_GOTO_LINE: { auto input = new LInput(GetList(), "", "Goto line:", AppName); input->DoModal([this, input](auto dlg, auto code) { if (code) d->GotoLine(input->GetStr()); delete dlg; }); break; } } } else if (m.Down() && m.Double()) { LAssert(d->Folder); switch (Col) { case TUser: { // Highlight all by that user? d->UserHilight = GetText(TUser); GetList()->UpdateAllItems(); break; } case TRef: { d->GotoRef(GetText(TRef)); break; } case TSrc: { d->GotoLine(GetText(TLine)); break; } } } LListItem::OnMouseClick(m); } }; void BrowseUiPriv::GotoLine(const char *line) { LArray all; if (Blame->GetAll(all)) { for (auto b: all) { auto ln = b->GetText(TLine); auto match = !Stricmp(ln, line); b->Select(match); if (match) b->ScrollTo(); } } } BrowseUi::BrowseUi(TMode mode, AppPriv *priv, VcFolder *folder, LString path) { d = new BrowseUiPriv(mode, path); d->Folder = folder; d->Priv = priv; Name("Lvc Browse"); if (!SerializeState(&d->Priv->Opts, OPT_WND_STATE, true)) { LRect r(0, 0, 800, 500); SetPos(r); MoveToCenter(); } if (Attach(0)) { AddView(d->Tabs = new LTabView(IDC_TABS)); auto BlameTab = d->Tabs->Append("Blame"); BlameTab->Append(d->BlameTbl = new LTableLayout(IDC_BLAME_TABLE)); auto c = d->BlameTbl->GetCell(0, 0); c->Add(new LTextLabel(IDC_STATIC, 0, 0, -1, -1, "Filter:")); c->VerticalAlign(LCss::VerticalMiddle); c = d->BlameTbl->GetCell(1, 0); c->Add(new LEdit(IDC_BLAME_FILTER, 0, 0, -1, -1)); c = d->BlameTbl->GetCell(2, 0); c->Add(new LButton(IDC_BLAME_FILTER_CLEAR, 0, 0, -1, -1, "x")); c = d->BlameTbl->GetCell(0, 1, true, 3); c->Add(d->Blame = new LList(IDC_BLAME_LST)); d->Blame->AddColumn("Ref", 100); d->Blame->AddColumn("User", 100); d->Blame->AddColumn("Date", 100); d->Blame->AddColumn("Line", 100); d->Blame->AddColumn("Src", 1000); d->Blame->SetPourLargest(true); auto LogTab = d->Tabs->Append("Log"); LogTab->Append(d->LogTbl = new LTableLayout(IDC_LOG_TABLE)); c = d->LogTbl->GetCell(0, 0); c->Add(new LTextLabel(IDC_STATIC, 0, 0, -1, -1, "Filter:")); c->VerticalAlign(LCss::VerticalMiddle); c = d->LogTbl->GetCell(1, 0); c->Add(new LEdit(IDC_LOG_FILTER, 0, 0, -1, -1)); c = d->LogTbl->GetCell(2, 0); c->Add(new LButton(IDC_LOG_FILTER_CLEAR, 0, 0, -1, -1, "x")); c = d->LogTbl->GetCell(0, 1, true, 3); c->Add(d->Log = new LList(IDC_LOG_LST)); folder->UpdateColumns(d->Log); d->Log->SetPourLargest(true); auto RawTab = d->Tabs->Append("Raw"); RawTab->Append(d->Raw = new LTextLog(IDC_RAW)); AttachChildren(); Visible(true); } } BrowseUi::~BrowseUi() { SerializeState(&d->Priv->Opts, OPT_WND_STATE, false); DeleteObj(d); } void BrowseUi::ParseBlame(LArray &lines, LString raw) { d->Colours.DeleteObjects(); d->Blame->Empty(); d->Raw->Name(d->Output = raw); LDateTime now; now.SetNow(); List items; int lineNo = 1; for (auto ln: lines) { auto col = d->Colours.Find(ln.ref); if (!col) d->Colours.Add(ln.ref, col = d->NewColour()); items.Insert(new BrowseItem(d, ln, now, col, lineNo)); } d->Blame->Insert(items); d->Blame->ResizeColumnsToContent(16); LView *e; if (GetViewById(IDC_BLAME_FILTER, e)) e->Focus(true); } void BrowseUi::ParseLog(LArray &commits, LString raw) { d->Commits.Swap(commits); d->Log->Empty(); d->Raw->Name(d->Output = raw); d->Tabs->Value(1); LDateTime now; now.SetNow(); for (auto commit: d->Commits) d->Log->Insert(commit); d->Log->ResizeColumnsToContent(); LView *e; if (GetViewById(IDC_LOG_FILTER, e)) e->Focus(true); } int BrowseUi::OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDC_BLAME_FILTER_CLEAR: { SetCtrlName(IDC_BLAME_FILTER, NULL); // Fall through } case IDC_BLAME_FILTER: { auto f = GetCtrlName(IDC_BLAME_FILTER); LArray items; if (!d->Blame->GetAll(items)) break; auto line = Atoi(f); if (line > 0 && line <= (ssize_t)items.Length()) { for (auto i: items) { auto lineTxt = i->GetText(TLine); auto n = Atoi(lineTxt); bool match = n > 0 && n == line; i->Select(match); if (match) i->ScrollTo(); } } else { bool first = true; size_t matches = 0; for (auto i: items) { auto ln = i->GetText(TLine); auto src = i->GetText(TSrc); auto match = Stristr(src, f) != NULL; i->Select(match); if (match) { matches++; if (first) { first = false; i->ScrollTo(); } } } if (matches == 0) LPopupNotification::Message(this, "No matches found."); } break; } case IDC_LOG_FILTER_CLEAR: { SetCtrlName(IDC_LOG_FILTER, NULL); // Fall through } case IDC_LOG_FILTER: { auto f = GetCtrlName(IDC_LOG_FILTER); LArray items; if (!d->Log->GetAll(items)) break; - /* - case LGraph: lst->AddColumn("---", 60); break; - case LIndex: lst->AddColumn("Index", 60); break; - case LBranch: lst->AddColumn("Branch", 60); break; - case LRevision: lst->AddColumn("Revision", 60); break; - case LAuthor: lst->AddColumn("Author", 240); break; - case LTime: lst->AddColumn("Date", 130); break; - case LMessageTxt: lst->AddColumn("Message", 700); break; - */ - auto revIdx = d->Folder->IndexOfCommitField(LRevision); auto authorIdx = d->Folder->IndexOfCommitField(LAuthor); auto msgIdx = d->Folder->IndexOfCommitField(LMessageTxt); for (auto i: items) { auto rev = i->GetText(revIdx); auto author = i->GetText(authorIdx); auto msg = i->GetText(msgIdx); auto match = Stristr(rev, f) || Stristr(author, f) || Stristr(msg, f); - - if (f) - i->GetCss(true)->Display(match ? LCss::DispBlock : LCss::DispNone); - else - i->GetCss(true)->Display(LCss::DispBlock); + i->GetCss(true)->Display(!f && match ? LCss::DispBlock : LCss::DispNone); } d->Log->UpdateAllItems(); break; } } return LWindow::OnNotify(Ctrl, n); } diff --git a/src/common/General/DateTime.cpp b/src/common/General/DateTime.cpp --- a/src/common/General/DateTime.cpp +++ b/src/common/General/DateTime.cpp @@ -1,2581 +1,2585 @@ /* ** FILE: LDateTime.cpp ** AUTHOR: Matthew Allen ** DATE: 11/11/98 ** DESCRIPTION: Scribe Date Time Object ** ** Copyright (C) 1998, Matthew Allen ** fret@memecode.com */ #define _DEFAULT_SOURCE #include #include #include #include #include #if defined(MAC) #include #endif #ifdef WINDOWS #include #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 = [next timeIntervalSince1970]; i.Utc = (nextTs + Offset1800) * Second64Bit; i.Offset = (int)([tz secondsFromGMTForDate:[next dateByAddingTimeInterval:60]]/60); #if DEBUG_DST_INFO { LDateTime dt; dt.Set(i.UtcTimeStamp); LgiTrace("%s:%i - Ts=%s Off=%i\n", _FL, dt.Get().Get(), i.Offset); } #endif if (nextTs >= ToUnix) break; [startDate release]; startDate = next; } if (startDate) [startDate release]; #elif USE_ZDUMP if (!Zdump.Length()) { static bool First = true; auto linkLoc = "/etc/localtime"; #if defined(LINUX) auto zoneLoc = "/usr/share/zoneinfo"; #elif defined(HAIKU) auto zoneLoc = "/boot/system/data/zoneinfo"; #else #error "Impl me" #endif if (!LFileExists(linkLoc)) { if (First) { LgiTrace("%s:%i - LDateTime::GetDaylightSavingsInfo error: '%s' doesn't exist.\n" " It should link to something in the '%s' tree.\n", _FL, linkLoc, zoneLoc); #ifdef HAIKU LgiTrace(" To fix that: pkgman install timezone_data and then create the '%s' link.\n", linkLoc); #endif } return First = false; } auto f = popen(LString::Fmt("zdump -v %s", linkLoc), "r"); if (f) { char s[1024]; size_t r; LStringPipe p(1024); while ((r = fread(s, 1, sizeof(s), f)) > 0) p.Write(s, (int)r); fclose(f); Zdump = p.NewLStr().Split("\n"); } else { if (First) { LgiTrace("%s:%i - LDateTime::GetDaylightSavingsInfo error: zdump didn't run.\n", _FL); #ifdef HAIKU LgiTrace("To fix that: pkgman install timezone_data\n"); #endif } return First = false; } } MonthHash Lut; LDateTime Prev; int PrevOff = 0; for (auto Line: Zdump) { auto l = Line.SplitDelimit(" \t"); if (l.Length() >= 16 && l[0].Equals("/etc/localtime")) { // /etc/localtime Sat Oct 3 15:59:59 2037 UTC = Sun Oct 4 01:59:59 2037 EST isdst=0 gmtoff=36000 // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 LDateTime Utc; Utc.Year(l[5].Int()); #if DEBUG_DST_INFO if (Utc.Year() < 2020) continue; // printf("DST: %s\n", Line.Get()); #endif auto Tm = l[4].SplitDelimit(":"); if (Tm.Length() != 3) { #if DEBUG_DST_INFO printf("%s:%i - Tm '%s' has wrong parts: %s\n", _FL, l[4].Get(), Line.Get()); #endif continue; } Utc.Hours(Tm[0].Int()); Utc.Minutes(Tm[1].Int()); Utc.Seconds(Tm[2].Int()); if (Utc.Minutes() < 0) { #if DEBUG_DST_INFO printf("%s:%i - Mins is zero: %s\n", _FL, l[4].Get()); #endif continue; } int m = Lut.Find(l[2]); if (!m) { #if DEBUG_DST_INFO printf("%s:%i - Unknown month '%s'\n", _FL, l[2].Get()); #endif continue; } Utc.Day(l[3].Int()); Utc.Month(m); LString Var, Val; if (!ParseValue(l[14], Var, Val) || Var != "isdst") { #if DEBUG_DST_INFO printf("%s:%i - Unknown value for isdst\n", _FL); #endif continue; } if (!ParseValue(l[15], Var, Val) || Var != "gmtoff") { #if DEBUG_DST_INFO printf("%s:%i - Unknown value for isdst\n", _FL); #endif continue; } int Off = atoi(Val) / 60; if (Utc.Ts().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()); 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 (days > 0) - s.Printf("%id %ih %im %is", days, hrs, mins, (int)seconds); + 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 }