(Android) Fehler in DateUtils behoben // TTimezone.Local.ID // Ich bitte um Prüfung
Wenn man
TTimezone.Local.ID innerhalb einer 64Bit Android App abruft, dann meldet die APP in Deutschland als Zeitzonen-ID "ET" . Das ist "Eastern Time" alias "Eastern Standard Time" alias "EST". Korrekt wäre es, wenn als Zeitzone "CET"(deprecated) oder "Europe/Berlin" gemeldet würde Selbst wenn ich die Zeitzone nach Portugal verlege, meldet die App dann nicht "WET" was korrekt wäre, sondern auch "ET" als Zeitzone. Sprich der erste Buchstabe geht verloren! *ARRRGH* Die App denkt also immer sie wäre in einer US-Zeitzone, weil es "ET" in meiner Datenbank tatsächlich gibt.... Ich habe also die Codestellen in der unit DateUtils.pas die diesen Code benutzen:
Delphi-Quellcode:
In folgenden Code geändert:
Einstring := Utf8ToString(LChars)
Delphi-Quellcode:
;
Einstring := Trim(TEncoding.UTF8.GetString(LChars))
Das Trim ist notwendig sonst funktionieren die Vergleiche nicht... Vermutlich wäre es besser gewesen Utf8ToString zu fixen, aber ich habe mich das nicht getraut und ich habe die Typen rawstring usw. nicht auf Anhieb verstanden. Bitte sagt mir ob die Anpassung in Ordnung ist. Soll ich das über meine Firma an die QC weitergeben oder ist das Problem schon von Emba gelöst? Listing der ganzen Funktion in der 3 Stellen geöndert wurden. (nach "Korrektur" ohne Gänsefüßchen suchen)
Delphi-Quellcode:
function TLocalTimeZone.GetChangesForYear(const AYear: Word): PYearlyChanges;
{$IF defined(MSWINDOWS)} function GetAbsoluteDateFromRule(const AYear, AMonth, ADoW, ADoWIndex: Word; AToD: TTime): TDateTime; const CReDoW: array [0 .. 6] of Integer = (7, 1, 2, 3, 4, 5, 6); var LExpDayOfWeek, LActualDayOfWeek: Word; begin { No actual rule } if (AMonth = 0) and (ADoW = 0) and (ADoWIndex = 0) then Exit(0); { Transform into ISO 8601 day of week } LExpDayOfWeek := CReDoW[ADoW]; { Generate a date in the form of: Year/Month/1st of month } Result := EncodeDate(AYear, AMonth, 1); { Get the day of week for this newly crafted date } LActualDayOfWeek := DayOfTheWeek(Result); { We're too far off now, let's decrease the number of days till we get to the desired one } if LActualDayOfWeek > LExpDayOfWeek then Result := IncDay(Result, DaysPerWeek - LActualDayOfWeek + LExpDayOfWeek) else if (LActualDayOfWeek < LExpDayOfWeek) Then Result := IncDay(Result, LExpDayOfWeek - LActualDayOfWeek); { Skip the required number of weeks } Result := IncDay(Result, DaysPerWeek * (ADoWIndex - 1)); { If we've skipped the day in this moth, go back a few weeks until we get it right again } while (MonthOf(Result) > AMonth) do Result := IncDay(Result, -DaysPerWeek); { Add the time part } Result := Result + AToD; end; var LTZ: TTimeZoneInformation; LResult: Cardinal; begin Result := AllocMem(SizeOf(TYearlyChanges)); LTZ := Default(TTimeZoneInformation); { Win32 can't handle dates outside this range } if (AYear <= 1601) or (AYear > 30827) then exit; { Use either Vista of lower APIs. If Vista one fails, also use the lower API. } if TOSVersion.Check(6, 1, 1) and GetTimeZoneInformationForYear(AYear, nil, LTZ) then LResult := TIME_ZONE_ID_STANDARD else LResult := GetTimeZoneInformation(LTZ); { Try with the old API } { Exit on error } if LResult = TIME_ZONE_ID_INVALID then exit; { Try to obtain the daylight adjustment for the specified year } if LResult <> TIME_ZONE_ID_UNKNOWN then begin with LTZ.StandardDate do Result.FEndOfDST := GetAbsoluteDateFromRule(AYear, wMonth, wDayOfWeek, wDay, EncodeTime(wHour, wMinute, wSecond, 0)); with LTZ.DaylightDate do Result.FStartOfDST := GetAbsoluteDateFromRule(AYear, wMonth, wDayOfWeek, wDay, EncodeTime(wHour, wMinute, wSecond, 0)); end; Result.FBias := -SecsPerMin * (LTZ.StandardBias + LTZ.Bias); Result.FBiasWithDST := -SecsPerMin * (LTZ.DaylightBias + LTZ.Bias); Result.FName := LTZ.StandardName; Result.FDSTName := LTZ.DaylightName; end; {$ELSEIF defined(MACOS)} function MacToDateTime(const AbsTime: CFAbsoluteTime; const ATZ: CFTimeZoneRef): TDateTime; var LDate: CFGregorianDate; begin { Decompose the object } LDate := CFAbsoluteTimeGetGregorianDate(AbsTime, ATZ); { Generate a TDateTime now } with LDate do Result := EncodeDateTime(year, month, day, hour, minute, Round(second), 0); end; procedure CopyInfo(const ATZ: CFTimeZoneRef; const AStartBT: CFAbsoluteTime; out LPt1DT, LPt2DT: TDateTime; out LPt1Off, LPt2Off: Int64); var L1stTrans, L2ndTrans: CFAbsoluteTime; begin { Calculate the first and the last transition times } L1stTrans := CFTimeZoneGetNextDaylightSavingTimeTransition(ATZ, AStartBT); L2ndTrans := CFTimeZoneGetNextDaylightSavingTimeTransition(ATZ, L1stTrans); { Obtain the GMT offset before first transition } LPt1Off := Round(CFTimeZoneGetSecondsFromGMT(ATZ, AStartBT)); if (L1stTrans <> 0) or (L2ndTrans > L1stTrans) then begin { Convert the first transition to TDateTime } LPt1DT := MacToDateTime(L1stTrans, ATZ); { Convert the second transition to TDateTime } LPt2DT := MacToDateTime(L2ndTrans, ATZ); { Obtain the GMT offset before second transition } LPt2Off := Round(CFTimeZoneGetSecondsFromGMT(ATZ, L1stTrans)); end else begin { There were no transitions. Use the base data } LPt2Off := LPt1Off; LPt1DT := MacToDateTime(AStartBT, ATZ); LPt2DT := LPt1DT; end; end; var LLocaleRef: CFLocaleRef; L1stJan: CFAbsoluteTime; LDate: CFGregorianDate; { What we expect } LStart, LEnd: TDateTime; begin { Create the changes block. Keep it clean so far. } Result := AllocMem(SizeOf(TYearlyChanges)); if FTZ <> nil then begin { Encode 1st Jan of the given year into Absolute time } with LDate do begin year := AYear; month := MonthJanuary; day := 1; hour := 0; minute := 0; second := 0; end; { Generate an absolute time (1st Jan Year) } L1stJan := CFGregorianDateGetAbsoluteTime(LDate, FTZ); { Use CopyInfo but reverse the variables depending on the Northern/Southern hemispheres } if not CFTimeZoneIsDaylightSavingTime(FTZ, L1stJan) then CopyInfo(FTZ, L1stJan, LStart, LEnd, Result.FBias, Result.FBiasWithDST) else CopyInfo(FTZ, L1stJan, LEnd, LStart, Result.FBiasWithDST, Result.FBias); { Fill the remaining parts of the structure } LLocaleRef := CFLocaleCopyCurrent(); if LLocaleRef <> nil then begin { Obtain localized names of the locale (normal and DST) } try Result.FName := TCFString(CFTimeZoneCopyLocalizedName(FTZ, kCFTimeZoneNameStyleStandard, LLocaleRef)).AsString(True); Result.FDSTName := TCFString(CFTimeZoneCopyLocalizedName(FTZ, kCFTimeZoneNameStyleDaylightSaving, LLocaleRef)).AsString(True); finally CFRelease(LLocaleRef); end; end else begin { Fall back to std info } Result.FName := TCFString(CFTimeZoneGetName(FTZ)); Result.FDSTName := Result.FName; end; Result.FStartOfDST := IncSecond(LStart, Result.FBias - Result.FBiasWithDST); // Remove the save time from result Result.FEndOfDST := IncSecond(LEnd, Result.FBiasWithDST - Result.FBias); // Add the save time to result end; end; {$ELSEIF defined(POSIX)} var LComp: tm; LTime: time_t; LLastOffset, LDay: Integer; LIsSecondCycle, LIsStandard: Boolean; LChars: TBytes; begin SetLength(LChars, 256); { Create the changes block. Keep it clean so far. } Result := AllocMem(SizeOf(TYearlyChanges)); if (SizeOf(LongInt) = 4) and ((AYear < 1970) or (AYear > 2037)) then Exit; // Not supported { Generate a value that starts with 1st of the current year } FillChar(LComp, SizeOf(LComp), 0); LComp.tm_mday := 1; LComp.tm_year := AYear - 1900; LTime := mktime(LComp); if LTime = -1 then Exit; // Some error occured! { Unknown DST information. Quit. } if LComp.tm_isdst < -1 then Exit; { Check if the DST or STD time was in effect at 1st Jan } LIsStandard := LComp.tm_isdst = 0; { Prepare to iterate over the year } LLastOffset := LComp.tm_gmtoff; LIsSecondCycle := false; { Initialize info, in some locales like Russia and in some years the clock is not changed for daylight saving } Result.FStartOfDST := IncSecond(FileDateToDateTime(LTime), LLastOffset - LComp.tm_gmtoff); Result.FEndOfDST := Result.FStartOfDST; Result.FBias := LLastOffset; Result.FDSTName := ''; Result.FBiasWithDST := LLastOffset; strftime(MarshaledAString(LChars), Length(LChars), '%Z', LComp); // DO NOT LOCALIZE Result.FName := Trim(TEncoding.UTF8.GetString(LChars));//<----- Korrektur Alter Code----> // Result.FName := Utf8ToString(LChars); for LDay := 0 to DaysInAYear(AYear) - 1 do begin { Skip to next day } Inc(LTime, SecsPerDay); { Decompose the time } if localtime_r(LTime, LComp) <> @LComp then Exit; if LComp.tm_gmtoff <> LLastOffset then begin { We found the day when the time change occured. Serach the hour now. } repeat Dec(LTime, SecsPerHour); { Decompose the time } if localtime_r(LTime, LComp) <> @LComp then Exit; until LComp.tm_gmtoff = LLastOffset; { Search for the minute } repeat Inc(LTime, SecsPerMin); { Decompose the time } if localtime_r(LTime, LComp) <> @LComp then Exit; until LComp.tm_gmtoff <> LLastOffset; { Generate the time zone abbreviation } strftime(MarshaledAString(LChars), Length(LChars), '%Z', LComp); // DO NOT LOCALIZE if LIsStandard then begin { We were in the standard period } Result.FStartOfDST := IncSecond(FileDateToDateTime(LTime), LLastOffset - LComp.tm_gmtoff); Result.FBias := LLastOffset; Result.FDSTName := Trim(TEncoding.UTF8.GetString(LChars));//<----- Korrektur Alter Code----> //Result.FDSTName := Utf8ToString(LChars); end else begin { We were in the DST period } Result.FEndOfDST := IncSecond(FileDateToDateTime(LTime), LLastOffset - LComp.tm_gmtoff); Result.FBiasWithDST := LLastOffset; Result.FName := Trim(TEncoding.UTF8.GetString(LChars));//<----- Korrektur Alter Code----> //Result.FName := Utf8ToString(LChars); end; { Set the last offset } LLastOffset := LComp.tm_gmtoff; LIsStandard := not LIsStandard; { Die if this is the second cycle } if LIsSecondCycle then Exit else LIsSecondCycle := true; end; end; end; {$ENDIF} |
AW: (Android) Fehler in DateUtils behoben // TTimezone.Local.ID // Ich bitte um Prüfu
Sorry, aber gibts grundsätzlich etwas gegen diese Lösung einzuwenden?
Ich will nicht wie ein Idiot dastehen wenn ich das bei QC posten sollte. |
AW: (Android) Fehler in DateUtils behoben // TTimezone.Local.ID // Ich bitte um Prüfu
Man kann das nur Aufwendig mit den Lokalisierungen Testen.
Du schreibst ja dass das Problem ursächlich beim UTF8 konvertieren liegt, gibt es dafür vielleicht einen kleinen Testcode der den Fehler, ohne das ganze Android/Language Gewusel ? Ich verstehe Dich so, dass
Delphi-Quellcode:
aus z.B. 'CET' nur 'ET' macht, und das erste Zeichen verschluckt.
Utf8ToString(LChars) // mit LChars: TBytes;
Es könnte sein das UTF8ToString wirklich zwingend ein BOM für UTF8 benötigt, und dadurch das erste Zeichen verschluckt. Kann ich mir aber eigentlich nicht wirklich vorstellen, das müsste ja an zig. Stellen auftauchen. Die Frage wäre was in LChars wirkliich drinsteht, ist das ein ANSI-String ? https://android.developreference.com...+in+Rad+Studio Ein Mini-Beispiel dazu wäre auf jeden Fall sinnvoll. |
AW: (Android) Fehler in DateUtils behoben // TTimezone.Local.ID // Ich bitte um Prüfu
Zitat:
und wenn sie das nicht tun, dann sollte man besser auch gleich mit prüfen was dort schief läuft. Ansonsten: Warum gibt die Funktion einen Pointer zurück, anstatt des Records? Gerade hier ist das eine Liebslingsstelle für Speicherlecks und dazumal es absolot sinnlos ist, weil immer was zurückgegeben wird im Delphi (XE) ist/war das auch noch ein Record. Und an ein paar gewissen Stellen würde ich noch eine Bereichsprüfung einfügen. Zitat:
Delphi-Quellcode:
aktiv,
{$RANGECHECKS ON}
oder mit Fehlermelduing
Delphi-Quellcode:
if ADoW > 6 then raise ...
oder zumindestens wildlaufende Speicherzugriffe abfangen
Delphi-Quellcode:
, was hier möglich ist, also einfach den Überstand in die nachfolgenden Wochen verschieben.
ExpDayOfWeek := CReDoW[ADoW mod 7];
Und sonst erstmal noch nicht weiter gesucht. (war so das "Schlimmste", was gleich auf den ersten Blick brutal ins Auge stach) |
AW: (Android) Fehler in DateUtils behoben // TTimezone.Local.ID // Ich bitte um Prüfu
@himitsu
Ich habe wirklich nur 3 Zeilen in dem Listing verändert. "Korrektur" steht neben dran. Der rest ist original code aus dateutils.pas. Ich habe quasi in der Funktion aus der Dateutils.pas einfach 3 stellen gepatcht. Ich wollte möglichst wenig EmbarcaderoCode anfassen um mein Problem zu lösen. @Rollo62 Ich habe jetzt mal die Funktion angesehen die den Fehler macht. Liegst in der "System.pas" Leider bin ich nicht gut bewandert in "codierung". Ich bin darauf angewiesen das die Convertierungsmethoden funktionieren. UTF8ToUnicode gibt es in etlichen überladenen methoden... unit System.pas
Delphi-Quellcode:
function UTF8ToString(const S: array of Byte): string; overload;
var Dest: array[0..511] of Char; begin SetString(Result, Dest, UTF8ToUnicode(Dest, Length(Dest), _PAnsiChr(@S[1]), S[0])-1); end; {$IFDEF USE_LIBICU} {$IFDEF LINUX} function Utf8ToUnicodeICU(Dest: PWideChar; MaxDestChars: Cardinal; Source: _PAnsiChr; SourceBytes: Cardinal): Cardinal; {$ELSE !LINUX} function Utf8ToUnicode(Dest: PWideChar; MaxDestChars: Cardinal; Source: _PAnsiChr; SourceBytes: Cardinal): Cardinal; {$ENDIF LINUX} var DestLen: Int32; ErrorConv: UErrorCode; begin Result := 0; if Source = nil then Exit; ErrorConv := 0; DestLen := 0; u_strFromUTF8(PUChar(Dest), MaxDestChars, DestLen, MarshaledAString(Source), SourceBytes, ErrorConv); Result := DestLen; if Dest <> nil then begin if (Result > 0) and (Result <= MaxDestChars) then begin if Result = MaxDestChars then begin if (Result > 1) and (Word(Dest[Result - 1]) >= $DC00) and (Word(Dest[Result - 1]) <= $DFFF) then Dec(Result); end else Inc(Result); Dest[Result - 1] := #0; end; end; end; Edit: !!!!!!!!!!!!!!HOLY COW!!!!!!!!!!
Delphi-Quellcode:
function UTF8ToString(const S: array of Byte): string; overload;
var Dest: array[0..511] of Char; begin SetString(Result, Dest, UTF8ToUnicode(Dest, Length(Dest), _PAnsiChr(@S[1]), S[0])-1); // _PAnsiChr(@S[1]) ist "ET" _PAnsiChr(@S[0]) müsste dann "CET" sein??? end; |
AW: (Android) Fehler in DateUtils behoben // TTimezone.Local.ID // Ich bitte um Prüfu
OK, Was nun???
|
AW: (Android) Fehler in DateUtils behoben // TTimezone.Local.ID // Ich bitte um Prüfu
Nun: Bugmeldung im Quality-Portal
Delphi-Quellcode:
statt
S[Low(string)]
Delphi-Quellcode:
S[1]
oder gleich den Mist mit dem @ lassen, also
Delphi-Quellcode:
statt
PAnsiChr(S)
Delphi-Quellcode:
.
PAnsiChr(@S[1])
Grund: $ZeroBasedStrings sind im NextGen (Android/iOS) aktiv. http://docwiki.embarcadero.com/RADSt...rings_(Delphi) |
AW: (Android) Fehler in DateUtils behoben // TTimezone.Local.ID // Ich bitte um Prüfu
Nur mal gerade in den Delphi-Sourcen gesucht, es gibt allein ca. 34-40 Units die das benutzen.
Das würde ja an allen möglichen Stellen abrauchen, kann ich gar nicht glauben :shock: Ganz zu schweigen von externen Libraries und Code. Macht Emba keine Unit-Tests ? Was ist jetzt besser, nur bei DateUtils zu fixen oder direkt das Übel an der Wurzel ? Gute Frage. Ich muss wohl mal bei mir checken ob und wo ich das nutze. |
AW: (Android) Fehler in DateUtils behoben // TTimezone.Local.ID // Ich bitte um Prüfu
Besser ist es immer das grundlegende Problem, anstatt nur die Symptome zu beheben.
Rein funktionell geht es auch den "Fehler" an beiden Stellen zu beheben, wobei TEncoding da einen größeren Overhead hat. Wenn du über den String-Helper gehst, dann ist es dort auch überall 0-basiered und in jeder Platform einheitlich. S.Chars(0) = S[1] {im Windows} ... abgesehn von dem nutzlosen Funktionsaufruf bei .Chars . Auch beim Copy-Befehl seh ich zu oft, dass es bei 0 beginnt (
Delphi-Quellcode:
), was "oft" funktioniert, da es nach unten abgefangen wird, aber mit höheren Indize wird dann natürlich auch falsch zugegriffen, wenn der Entwickler das immer falsch macht.
Copy(S, 0, ...)
|
AW: (Android) Fehler in DateUtils behoben // TTimezone.Local.ID // Ich bitte um Prüfu
Zitat:
Diese Funktion ist anscheinend von ShortString nach "array of bytes" adaptiert worden. Man beachte das S[0]. Das soll ja wohl die Länge sein.. |
Alle Zeitangaben in WEZ +1. Es ist jetzt 01:21 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz