1 |
(*
|
2 |
* Firebird Interface (fbintf). The fbintf components provide a set of
|
3 |
* Pascal language bindings for the Firebird API. Although predominantly
|
4 |
* a new development they include source code taken from IBX and may be
|
5 |
* considered a derived product. This software thus also includes the copyright
|
6 |
* notice and license conditions from IBX.
|
7 |
*
|
8 |
* Except for those parts dervied from IBX, contents of this file are subject
|
9 |
* to the Initial Developer's Public License Version 1.0 (the "License"); you
|
10 |
* may not use this file except in compliance with the License. You may obtain a
|
11 |
* copy of the License here:
|
12 |
*
|
13 |
* http://www.firebirdsql.org/index.php?op=doc&id=idpl
|
14 |
*
|
15 |
* Software distributed under the License is distributed on an "AS
|
16 |
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
17 |
* implied. See the License for the specific language governing rights
|
18 |
* and limitations under the License.
|
19 |
*
|
20 |
* The Initial Developer of the Original Code is Tony Whyman.
|
21 |
*
|
22 |
* The Original Code is (C) 2020 Tony Whyman, MWA Software
|
23 |
* (http://www.mwasoftware.co.uk).
|
24 |
*
|
25 |
* All Rights Reserved.
|
26 |
*
|
27 |
* Contributor(s): ______________________________________.
|
28 |
*
|
29 |
*)
|
30 |
unit FB30TimeZoneServices;
|
31 |
|
32 |
{$IFDEF MSWINDOWS}
|
33 |
{$DEFINE WINDOWS}
|
34 |
{$ENDIF}
|
35 |
|
36 |
{$IFDEF FPC}
|
37 |
{$mode delphi}
|
38 |
{$codepage UTF8}
|
39 |
{$ENDIF}
|
40 |
|
41 |
{Enable the following to use the current date for GMT/Local Time translations instead
|
42 |
of the the Firebird default of 2020/1/1}
|
43 |
|
44 |
{ $DEFINE USECURRENTDATEFORGMTTOLOCALTIME}
|
45 |
|
46 |
interface
|
47 |
|
48 |
uses
|
49 |
Classes, SysUtils, Firebird, IB, IBExternals, FBActivityMonitor, FBClientAPI,
|
50 |
FB30ClientAPI, FBAttachment, FB30Attachment, FBTransaction, FBSQLData,
|
51 |
{$IFDEF FPC} contnrs; {$ELSE} Generics.Collections; {$ENDIF}
|
52 |
|
53 |
type
|
54 |
{ TFB30TimeZoneServices }
|
55 |
|
56 |
TFB30TimeZoneServices = class(TFBInterfacedObject, ITimeZoneServices, IExTimeZoneServices, ITransactionUser)
|
57 |
private type
|
58 |
PTimeZoneInfo = ^TTimeZoneInfo;
|
59 |
TTimeZoneInfo = record
|
60 |
Starts: TDateTime;
|
61 |
Ends: TDateTime;
|
62 |
ZoneOffset: Smallint;
|
63 |
DstOffset: SmallInt;
|
64 |
EffectiveOffset: SmallInt;
|
65 |
Prev: PTimeZoneInfo;
|
66 |
Next: PTimeZoneInfo;
|
67 |
end;
|
68 |
|
69 |
private type
|
70 |
ITimeZone = interface
|
71 |
function GetTimeZoneID: TFBTimeZoneID;
|
72 |
function GetTimeZoneName: AnsiString;
|
73 |
function GetTimeZoneData(timestamp: TDateTime; isLocalTime: boolean): TFB30TimeZoneServices.PTimeZoneInfo;
|
74 |
function GetFirstTimeZoneInfo: TFB30TimeZoneServices.PTimeZoneInfo;
|
75 |
function GetLastTimeZoneInfo: TFB30TimeZoneServices.PTimeZoneInfo;
|
76 |
function AddTimeZoneInfo(Starts_, Ends_: TDateTime; ZoneOffset_: Smallint;
|
77 |
DstOffer_: SmallInt; EffectiveOffset_: SmallInt): TFB30TimeZoneServices.PTimeZoneInfo;
|
78 |
function CompareTimeRange(timeZoneInfo: TFB30TimeZoneServices.PTimeZoneInfo; timestamp: TDateTime; isLocalTime: boolean): integer;
|
79 |
end;
|
80 |
|
81 |
private type
|
82 |
ITimeZoneCache = interface
|
83 |
function GetTimeZone(aTimeZoneID: TFBTimeZoneID): TFB30TimeZoneServices.ITimeZone; overload;
|
84 |
function GetTimeZone(aTimeZone: AnsiString): TFB30TimeZoneServices.ITimeZone; overload;
|
85 |
function AddTimeZone(aTimeZoneID: TFBTimeZoneID; aTimeZone: AnsiString): TFB30TimeZoneServices.ITimeZone;
|
86 |
end;
|
87 |
|
88 |
private
|
89 |
FAttachment: TFB30Attachment;
|
90 |
FTransaction: ITransaction;
|
91 |
FFirebird30ClientAPI: TFB30ClientAPI;
|
92 |
FUsingRemoteTZDB: boolean;
|
93 |
FTimeZoneCache: ITimeZoneCache;
|
94 |
FInLoadTimeZoneData: boolean;
|
95 |
FTimeTZDate: TDateTime;
|
96 |
FTZTextOption: TTZTextOptions;
|
97 |
FServerTZName: AnsiString;
|
98 |
function ComputeDstOffset(localtime, gmtTimestamp: TDateTime): integer;
|
99 |
function GetTransaction: ITransaction;
|
100 |
function GetTimeZoneCache: ITimeZoneCache;
|
101 |
function GetTimeZoneData(aTimeZone: ITimeZone; timestamp: TDateTime;
|
102 |
isLocalTime: boolean): PTimeZoneInfo;
|
103 |
function GetDstOffset(timestamp: TDateTime; timezoneID: TFBTimeZoneID;
|
104 |
IsLocalTime: boolean): smallint; overload;
|
105 |
function GetDstOffset(timestamp: TDateTime; timezone: AnsiString;
|
106 |
IsLocalTime: boolean): smallint; overload;
|
107 |
function DecodeGMTTimestampTZ(bufptr: PISC_TIMESTAMP_TZ): TDateTime;
|
108 |
function LookupTimeZoneName(aTimeZoneID: TFBTimeZoneID): AnsiString;
|
109 |
function LookupTimeZoneID(aTimeZone: AnsiString): TFBTimeZoneID;
|
110 |
function LookupTimeZone(aTimeZoneID: TFBTimeZoneID): ITimeZone; overload;
|
111 |
function LookupTimeZone(aTimeZone: AnsiString): ITimeZone; overload;
|
112 |
function LookupTimeZoneInfo(aTimeZoneID: TFBTimeZoneID; timestamp: TDateTime;
|
113 |
isLocalTime: boolean): PTimeZoneInfo; overload;
|
114 |
function LookupTimeZoneInfo(aTimeZone: AnsiString; timestamp: TDateTime; isLocalTime: boolean): PTimeZoneInfo; overload;
|
115 |
public
|
116 |
constructor Create(attachment: TFB30Attachment);
|
117 |
destructor Destroy; override;
|
118 |
|
119 |
public
|
120 |
{ITransactionUser}
|
121 |
procedure TransactionEnding(aTransaction: ITransaction; Force: boolean);
|
122 |
|
123 |
public
|
124 |
{ITimeZoneServices}
|
125 |
function TimeZoneID2TimeZoneName(aTimeZoneID: TFBTimeZoneID): AnsiString;
|
126 |
function TimeZoneName2TimeZoneID(aTimeZone: AnsiString): TFBTimeZoneID;
|
127 |
function LocalTimeToGMT(aLocalTime: TDateTime; aTimeZone: AnsiString): TDateTime; overload;
|
128 |
function LocalTimeToGMT(aLocalTime: TDateTime; aTimeZoneID: TFBTimeZoneID): TDateTime; overload;
|
129 |
function GMTToLocalTime(aGMTTime: TDateTime; aTimeZone: AnsiString): TDateTime; overload;
|
130 |
function GMTToLocalTime(aGMTTime: TDateTime; aTimeZoneID: TFBTimeZoneID): TDateTime; overload;
|
131 |
function GetEffectiveOffsetMins(aLocalTime: TDateTime; aTimeZone: AnsiString): integer; overload;
|
132 |
function GetEffectiveOffsetMins(aLocalTime: TDateTime; aTimeZoneID: TFBTimeZoneID): integer; overload;
|
133 |
|
134 |
{Time Zone DB Information}
|
135 |
function UsingRemoteTZDB: boolean;
|
136 |
procedure SetUseLocalTZDB(useLocalTZDB: boolean);
|
137 |
function GetLocalTimeZoneName: AnsiString;
|
138 |
function GetLocalTimeZoneID: TFBTimeZoneID;
|
139 |
procedure GetTimeZoneInfo(aTimeZone: AnsiString; OnDate: TDateTime;
|
140 |
var ZoneOffset, DSTOffset, EffectiveOffset: integer);
|
141 |
function GetTimeTZDate: TDateTime;
|
142 |
procedure SetTimeTZDate(aDate: TDateTime);
|
143 |
function GetTZTextOption: TTZTextOptions;
|
144 |
procedure SetTZTextOption(aOptionValue: TTZTextOptions);
|
145 |
|
146 |
public
|
147 |
{IExTimeZoneServices}
|
148 |
procedure EncodeTimestampTZ(timestamp: TDateTime; timezoneID: TFBTimeZoneID;
|
149 |
bufptr: PByte); overload;
|
150 |
procedure EncodeTimestampTZ(timestamp: TDateTime; timezone: AnsiString;
|
151 |
bufptr: PByte); overload;
|
152 |
procedure EncodeTimeTZ(time: TDateTime; timezoneID: TFBTimeZoneID; OnDate: TDateTime;
|
153 |
bufptr: PByte); overload;
|
154 |
procedure EncodeTimeTZ(time: TDateTime; timezone: AnsiString; OnDate: TDateTime;
|
155 |
bufptr: PByte); overload;
|
156 |
procedure DecodeTimestampTZ(bufptr: PByte; var timestamp: TDateTime;
|
157 |
var dstOffset: smallint; var timezoneID: TFBTimeZoneID); overload;
|
158 |
procedure DecodeTimestampTZ(bufptr: PByte; var timestamp: TDateTime;
|
159 |
var dstOffset: smallint; var timezone: AnsiString); overload;
|
160 |
procedure DecodeTimestampTZEx(bufptr: PByte; var timestamp: TDateTime;
|
161 |
var dstOffset: smallint; var timezoneID: TFBTimeZoneID); overload;
|
162 |
procedure DecodeTimestampTZEx(bufptr: PByte; var timestamp: TDateTime;
|
163 |
var dstOffset: smallint; var timezone: AnsiString); overload;
|
164 |
procedure DecodeTimeTZ(bufptr: PByte; OnDate: TDateTime; var time: TDateTime;
|
165 |
var dstOffset: smallint; var timezoneID: TFBTimeZoneID); overload;
|
166 |
procedure DecodeTimeTZ(bufptr: PByte; OnDate: TDateTime; var time: TDateTime;
|
167 |
var dstOffset: smallint; var timezone: AnsiString); overload;
|
168 |
procedure DecodeTimeTZEx(bufptr: PByte; OnDate: TDateTime; var time: TDateTime;
|
169 |
var dstOffset: smallint; var timezoneID: TFBTimeZoneID); overload;
|
170 |
procedure DecodeTimeTZEx(bufptr: PByte; OnDate: TDateTime; var time: TDateTime;
|
171 |
var dstOffset: smallint; var timezone: AnsiString); overload;
|
172 |
end;
|
173 |
|
174 |
implementation
|
175 |
|
176 |
uses DateUtils, IBUtils, FBMessages;
|
177 |
|
178 |
type
|
179 |
{$IFDEF FPC}
|
180 |
TTZHashTable = TFPHashList;
|
181 |
{$ELSE}
|
182 |
TTZHashTable = TDictionary<AnsiString,integer>;
|
183 |
{$ENDIF}
|
184 |
|
185 |
|
186 |
{ TTimeZoneCache }
|
187 |
|
188 |
TTimeZoneCache = class(TFBInterfacedObject,TFB30TimeZoneServices.ITimeZoneCache)
|
189 |
private const
|
190 |
MaxZoneID = High(TFBTimeZoneID);
|
191 |
private
|
192 |
FTimeZoneIDIndex: array of TFB30TimeZoneServices.ITimeZone;
|
193 |
FTimeZoneNameIndex: TTZHashTable;
|
194 |
FLowValue: integer;
|
195 |
public
|
196 |
constructor Create(aLowValue: integer);
|
197 |
destructor Destroy; override;
|
198 |
public
|
199 |
{ITimeZoneCache}
|
200 |
function GetTimeZone(aTimeZoneID: TFBTimeZoneID): TFB30TimeZoneServices.ITimeZone; overload;
|
201 |
function GetTimeZone(aTimeZone: AnsiString): TFB30TimeZoneServices.ITimeZone; overload;
|
202 |
function AddTimeZone(aTimeZoneID: TFBTimeZoneID; aTimeZone: AnsiString): TFB30TimeZoneServices.ITimeZone;
|
203 |
end;
|
204 |
|
205 |
{The TTimeZone class provides information about a time zone, including its
|
206 |
ID, name and a cache of time zone records where each record provides the
|
207 |
offset in minutes to GMT and any applicable daylight savings time offset
|
208 |
for a timestamp range (in GMT). The time zone records are held as a
|
209 |
bidirectional linked list.}
|
210 |
|
211 |
TTimeZone = class(TFBInterfacedObject,TFB30TimeZoneServices.ITimeZone)
|
212 |
private
|
213 |
FTimeZoneID: TFBTimeZoneID;
|
214 |
FTimeZone: AnsiString;
|
215 |
FFirst: TFB30TimeZoneServices.PTimeZoneInfo;
|
216 |
FLast: TFB30TimeZoneServices.PTimeZoneInfo;
|
217 |
FCurrent: TFB30TimeZoneServices.PTimeZoneInfo;
|
218 |
public
|
219 |
constructor Create(aTimeZoneID: TFBTimeZoneID; aTimeZone: AnsiString);
|
220 |
destructor Destroy; override;
|
221 |
public
|
222 |
{ITimeZone}
|
223 |
function GetTimeZoneID: TFBTimeZoneID;
|
224 |
function GetTimeZoneName: AnsiString;
|
225 |
function GetTimeZoneData(timestamp: TDateTime; isLocalTime: boolean): TFB30TimeZoneServices.PTimeZoneInfo;
|
226 |
function GetFirstTimeZoneInfo: TFB30TimeZoneServices.PTimeZoneInfo;
|
227 |
function GetLastTimeZoneInfo: TFB30TimeZoneServices.PTimeZoneInfo;
|
228 |
function AddTimeZoneInfo(Starts_, Ends_: TDateTime; ZoneOffset_: Smallint;
|
229 |
DstOffset_: SmallInt; EffectiveOffset_: SmallInt): TFB30TimeZoneServices.PTimeZoneInfo;
|
230 |
function CompareTimeRange(timeZoneInfo: TFB30TimeZoneServices.PTimeZoneInfo; timestamp: TDateTime; isLocalTime: boolean): integer;
|
231 |
end;
|
232 |
|
233 |
constructor TTimeZone.Create(aTimeZoneID: TFBTimeZoneID; aTimeZone: AnsiString);
|
234 |
begin
|
235 |
inherited Create;
|
236 |
FTimeZoneID := aTimeZoneID;
|
237 |
FTimeZone := aTimeZone;
|
238 |
end;
|
239 |
|
240 |
destructor TTimeZone.Destroy;
|
241 |
var P: TFB30TimeZoneServices.PTimeZoneInfo;
|
242 |
tmp: TFB30TimeZoneServices.PTimeZoneInfo;
|
243 |
begin
|
244 |
P := FFirst;
|
245 |
while p <> nil do
|
246 |
begin
|
247 |
tmp := P^.Next;
|
248 |
dispose(P);
|
249 |
P := tmp;
|
250 |
end;
|
251 |
inherited Destroy;
|
252 |
end;
|
253 |
|
254 |
function TTimeZone.GetTimeZoneID: TFBTimeZoneID;
|
255 |
begin
|
256 |
Result := FTimeZoneID;
|
257 |
end;
|
258 |
|
259 |
function TTimeZone.GetTimeZoneName: AnsiString;
|
260 |
begin
|
261 |
Result := FTimeZone;
|
262 |
end;
|
263 |
|
264 |
{Walk the linked list from FCurrent to find the TimeZoneInfo record for the timestamp.
|
265 |
Returns a nil interface if not record present.}
|
266 |
|
267 |
function TTimeZone.GetTimeZoneData(timestamp: TDateTime; isLocalTime: boolean
|
268 |
): TFB30TimeZoneServices.PTimeZoneInfo;
|
269 |
var CompareFlag: integer;
|
270 |
begin
|
271 |
Result := nil;
|
272 |
if FCurrent = nil then
|
273 |
FCurrent := FFirst;
|
274 |
|
275 |
while FCurrent <> nil do
|
276 |
begin
|
277 |
CompareFlag := CompareTimeRange(FCurrent,timestamp,isLocalTime);
|
278 |
case CompareFlag of
|
279 |
-1:
|
280 |
if (FCurrent^.Prev <> nil) and (CompareTimeRange(FCurrent^.Prev,timestamp,isLocalTime) > 0) then
|
281 |
FCurrent := nil
|
282 |
else
|
283 |
FCurrent := FCurrent^.Prev;
|
284 |
0:
|
285 |
begin
|
286 |
Result := FCurrent;
|
287 |
Exit;
|
288 |
end;
|
289 |
1:
|
290 |
if (FCurrent^.Next <> nil) and (CompareTimeRange(FCurrent^.Next,timestamp,isLocalTime) < 0) then
|
291 |
FCurrent := nil
|
292 |
else
|
293 |
FCurrent := FCurrent^.Next;
|
294 |
end;
|
295 |
end;
|
296 |
Result := FCurrent;
|
297 |
end;
|
298 |
|
299 |
function TTimeZone.GetFirstTimeZoneInfo: TFB30TimeZoneServices.PTimeZoneInfo;
|
300 |
begin
|
301 |
Result := FFirst;
|
302 |
end;
|
303 |
|
304 |
function TTimeZone.GetLastTimeZoneInfo: TFB30TimeZoneServices.PTimeZoneInfo;
|
305 |
begin
|
306 |
Result := FLast;
|
307 |
end;
|
308 |
|
309 |
{Adds a new Time Zone Info record by inserting it into the list in start
|
310 |
time ascending order.}
|
311 |
|
312 |
function TTimeZone.AddTimeZoneInfo(Starts_, Ends_: TDateTime;
|
313 |
ZoneOffset_: Smallint; DstOffset_: SmallInt; EffectiveOffset_: SmallInt
|
314 |
): TFB30TimeZoneServices.PTimeZoneInfo;
|
315 |
var P: TFB30TimeZoneServices.PTimeZoneInfo;
|
316 |
P1: TFB30TimeZoneServices.PTimeZoneInfo;
|
317 |
begin
|
318 |
{create and initialise new entry}
|
319 |
new(Result);
|
320 |
with Result^ do
|
321 |
begin
|
322 |
Starts := Starts_;
|
323 |
Ends := Ends_;
|
324 |
ZoneOffset := ZoneOffset_;
|
325 |
DstOffset := DstOffset_;
|
326 |
EffectiveOffset := EffectiveOffset_;
|
327 |
end;
|
328 |
|
329 |
{empty list? then insert at front}
|
330 |
if FFirst = nil then
|
331 |
begin
|
332 |
FFirst := Result;
|
333 |
FCurrent := Result;
|
334 |
FLast := Result;
|
335 |
Result^.Prev := nil;
|
336 |
Result^.Next := nil;
|
337 |
end
|
338 |
else
|
339 |
{Before first entry in list? then insert before}
|
340 |
if Result^.Starts < FFirst^.Starts then
|
341 |
begin
|
342 |
Result^.Next := FFirst;
|
343 |
Result^.Prev := nil;
|
344 |
FFirst := Result;
|
345 |
end
|
346 |
else
|
347 |
{walk the list to find where we have to insert new entry}
|
348 |
begin
|
349 |
P := FFirst^.Next;
|
350 |
P1 := FFirst;
|
351 |
while (P <> nil) and (Result^.Starts > P^.Ends) do
|
352 |
begin
|
353 |
P1 := P;
|
354 |
P := P^.Next;
|
355 |
end;
|
356 |
|
357 |
{ignore duplicate entry}
|
358 |
if (P <> nil) and (Result^.Starts = P^.Starts) then
|
359 |
begin
|
360 |
dispose(Result);
|
361 |
Result := P;
|
362 |
end
|
363 |
else
|
364 |
{either at end of list (P=nil) or we insert after P1}
|
365 |
begin
|
366 |
Result^.Next := P;
|
367 |
Result^.Prev := P1;
|
368 |
P1^.Next := Result;
|
369 |
if P <> nil then {P=nil => at end of list}
|
370 |
P^.Prev := Result
|
371 |
else
|
372 |
FLast := Result;
|
373 |
end
|
374 |
end;
|
375 |
end;
|
376 |
|
377 |
function TTimeZone.CompareTimeRange(
|
378 |
timeZoneInfo: TFB30TimeZoneServices.PTimeZoneInfo; timestamp: TDateTime;
|
379 |
isLocalTime: boolean): integer;
|
380 |
begin
|
381 |
if isLocalTime then {adjust to GMT}
|
382 |
timestamp := IncMinute(timestamp,-timeZoneInfo^.EffectiveOffset);
|
383 |
|
384 |
if timestamp < timeZoneInfo^.Starts then
|
385 |
Result := -1
|
386 |
else
|
387 |
if timestamp > timeZoneInfo^.Ends then
|
388 |
Result := 1
|
389 |
else
|
390 |
Result := 0;
|
391 |
end;
|
392 |
|
393 |
{ TTimeZoneCache }
|
394 |
|
395 |
constructor TTimeZoneCache.Create(aLowValue: integer);
|
396 |
begin
|
397 |
inherited Create;
|
398 |
FTimeZoneNameIndex := TTZHashTable.Create;
|
399 |
SetLength(FTimeZoneIDIndex,MaxZoneID - aLowValue + 1);
|
400 |
FLowValue := aLowValue;
|
401 |
end;
|
402 |
|
403 |
destructor TTimeZoneCache.Destroy;
|
404 |
var i: integer;
|
405 |
begin
|
406 |
for i := Low(FTimeZoneIDIndex) to high(FTimeZoneIDIndex) do
|
407 |
FTimeZoneIDIndex[i] := nil;
|
408 |
if FTimeZoneNameIndex <> nil then FTimeZoneNameIndex.Free;
|
409 |
inherited Destroy;
|
410 |
end;
|
411 |
|
412 |
function TTimeZoneCache.GetTimeZone(aTimeZoneID: TFBTimeZoneID
|
413 |
): TFB30TimeZoneServices.ITimeZone;
|
414 |
begin
|
415 |
Result := FTimeZoneIDIndex[aTimeZoneID - FLowValue];
|
416 |
end;
|
417 |
|
418 |
function TTimeZoneCache.GetTimeZone(aTimeZone: AnsiString
|
419 |
): TFB30TimeZoneServices.ITimeZone;
|
420 |
{$IFDEF FPC}
|
421 |
var index: Pointer;
|
422 |
begin
|
423 |
index := FTimeZoneNameIndex.Find(aTimeZone);
|
424 |
if index = nil then
|
425 |
Result := nil
|
426 |
else
|
427 |
Result := FTimeZoneIDIndex[PtrUInt(index)];
|
428 |
end;
|
429 |
{$ELSE}
|
430 |
var index: integer;
|
431 |
begin
|
432 |
Result := nil;
|
433 |
if FTimeZoneNameIndex.TryGetValue(aTimeZone,index) then
|
434 |
Result := FTimeZoneIDIndex[index];
|
435 |
end;
|
436 |
{$ENDIF}
|
437 |
|
438 |
function TTimeZoneCache.AddTimeZone(aTimeZoneID: TFBTimeZoneID;
|
439 |
aTimeZone: AnsiString): TFB30TimeZoneServices.ITimeZone;
|
440 |
{$IFDEF FPC}
|
441 |
var index: PtrUInt;
|
442 |
{$ELSE}
|
443 |
var index: integer;
|
444 |
{$ENDIF}
|
445 |
begin
|
446 |
Result := nil;
|
447 |
if aTimeZoneID < FLowValue then
|
448 |
IBError(ibxeInvalidTimeZoneID,[aTimeZoneID]);
|
449 |
|
450 |
index := aTimeZoneID - FLowValue;
|
451 |
if FTimeZoneIDIndex[index] = nil then
|
452 |
begin
|
453 |
Result := TTimeZone.Create(aTimeZoneID,aTimeZone);
|
454 |
FTimeZoneIDIndex[index] := Result;
|
455 |
{$IFDEF FPC}
|
456 |
FTimeZoneNameIndex.Add(aTimeZone,Pointer(index));
|
457 |
{$ELSE}
|
458 |
FTimeZoneNameIndex.Add(aTimeZone,index);
|
459 |
{$ENDIF}
|
460 |
end;
|
461 |
end;
|
462 |
|
463 |
{ TFB30TimeZoneServices }
|
464 |
|
465 |
function TFB30TimeZoneServices.ComputeDstOffset(localtime,
|
466 |
gmtTimestamp: TDateTime): integer;
|
467 |
begin
|
468 |
Result := Round(MinuteSpan(localtime,gmtTimestamp));
|
469 |
if gmtTimestamp > localtime then
|
470 |
Result := -Result;
|
471 |
end;
|
472 |
|
473 |
function TFB30TimeZoneServices.GetTransaction: ITransaction;
|
474 |
begin
|
475 |
if FTransaction = nil then
|
476 |
begin
|
477 |
FTransaction := FAttachment.StartTransaction([isc_tpb_read,isc_tpb_wait,isc_tpb_concurrency],taCommit);
|
478 |
(FTransaction as TFBTransaction).AddObject(self);
|
479 |
end;
|
480 |
Result := FTransaction;
|
481 |
end;
|
482 |
|
483 |
function TFB30TimeZoneServices.GetTimeZoneCache: ITimeZoneCache;
|
484 |
var Data: IResultSet;
|
485 |
begin
|
486 |
if FTimeZoneCache = nil then
|
487 |
begin
|
488 |
Data := FAttachment.OpenCursorAtStart(GetTransaction,
|
489 |
'select min(RDB$TIME_ZONE_ID) as LowValue From RDB$TIME_ZONES');
|
490 |
FTimeZoneCache := TTimeZoneCache.Create(Data[0].AsInteger);
|
491 |
end;
|
492 |
Result := FTimeZoneCache;
|
493 |
end;
|
494 |
|
495 |
{This method returns the TimeZoneInfo record for the TimeZone and for which the
|
496 |
timestamp is between the start and end time for the record. If a matching record
|
497 |
does not exist in the cache then the remote TZ Database is queried for records
|
498 |
five years earlier than the timestamp and these are entered into the cache.
|
499 |
If one of the returned record is a match for the timestamp then it is returned
|
500 |
as the result of the function.
|
501 |
|
502 |
Note: the function may return nil if there is no matching record in either the
|
503 |
cache or remote database. For a localtime, this may be because it is an invalid
|
504 |
timestamp when taking daylight savings time adjusts into account.}
|
505 |
|
506 |
function TFB30TimeZoneServices.GetTimeZoneData(aTimeZone: ITimeZone;
|
507 |
timestamp: TDateTime; isLocalTime: boolean): PTimeZoneInfo;
|
508 |
var Data: IResultSet;
|
509 |
TimeZoneInfo: PTimeZoneInfo;
|
510 |
begin
|
511 |
Result := aTimeZone.GetTimeZoneData(timestamp,isLocalTime);
|
512 |
if Result = nil then
|
513 |
begin
|
514 |
FInLoadTimeZoneData := true;
|
515 |
// writeln('Looking up time zone data for ',aTimeZone.GetTimeZoneName,' at ',DateTimeToStr(timestamp));
|
516 |
try
|
517 |
{Lookup remote time zone database for ten year range and add result to cache}
|
518 |
with FAttachment.Prepare(GetTransaction,'Select * From rdb$time_zone_util.transitions(?,?,?)') do
|
519 |
begin
|
520 |
SQLParams[0].AsString := aTimeZone.GetTimeZoneName;
|
521 |
SQLParams[1].AsDateTime := timestamp;
|
522 |
SQLParams[2].AsDateTime := timestamp;
|
523 |
Data := OpenCursor;
|
524 |
while Data.FetchNext do
|
525 |
begin
|
526 |
TimeZoneInfo := aTimeZone.AddTimeZoneInfo(Data[0].AsDateTime,Data[1].AsDateTime,
|
527 |
Data[2].AsInteger,Data[3].AsInteger,
|
528 |
Data[4].AsInteger);
|
529 |
if aTimeZone.CompareTimeRange(TimeZoneInfo,timestamp,isLocalTime) = 0 then
|
530 |
Result := TimeZoneInfo;
|
531 |
end;
|
532 |
end;
|
533 |
finally
|
534 |
FInLoadTimeZoneData:= false;
|
535 |
end;
|
536 |
end
|
537 |
// else
|
538 |
// writeln('Cache hit for ',aTimeZone.GetTimeZoneName,' at ',DateTimeToStr(timestamp), ' Row Starts at ',DateTimeToStr(Result^.Starts));
|
539 |
end;
|
540 |
|
541 |
function TFB30TimeZoneServices.GetDstOffset(timestamp: TDateTime;
|
542 |
timezoneID: TFBTimeZoneID; IsLocalTime: boolean): smallint;
|
543 |
var TimeZoneInfo: PTimeZoneInfo;
|
544 |
begin
|
545 |
if DateOf(timestamp) = 0 then
|
546 |
timestamp := timestamp + GetTimeTZDate;
|
547 |
if FInLoadTimeZoneData then
|
548 |
Result := 0 {Assume GMT}
|
549 |
else
|
550 |
if timeZoneID < MaxOffsetTimeZoneID then
|
551 |
begin
|
552 |
if IsLocalTime then
|
553 |
Result := TimeZoneDisplacementDelta - timezoneID
|
554 |
else
|
555 |
Result := timezoneID - TimeZoneDisplacementDelta
|
556 |
end
|
557 |
else
|
558 |
if not FUsingRemoteTZDB then
|
559 |
Result := GetDstOffset(timestamp,TimeZoneID2TimeZoneName(timezoneID),isLocalTime)
|
560 |
else
|
561 |
begin
|
562 |
TimeZoneInfo := LookupTimeZoneInfo(timezoneID,timestamp,isLocalTime);
|
563 |
if TimeZoneInfo <> nil then
|
564 |
begin
|
565 |
Result := TimeZoneInfo^.EffectiveOffset;
|
566 |
if IsLocalTime then
|
567 |
Result := -Result;
|
568 |
end
|
569 |
else
|
570 |
IBError(ibxeBadTimestampOrNoTimeZoneDBEntry,[DateTimeToStr(timestamp),timezoneID]);
|
571 |
end;
|
572 |
end;
|
573 |
|
574 |
function TFB30TimeZoneServices.GetDstOffset(timestamp: TDateTime;
|
575 |
timezone: AnsiString; IsLocalTime: boolean): smallint;
|
576 |
var gmtTimeStamp: TDateTime;
|
577 |
Buffer: ISC_TIMESTAMP_TZ;
|
578 |
begin
|
579 |
if DateOf(timestamp) = 0 then
|
580 |
timestamp := timestamp + GetTimeTZDate;
|
581 |
if not FUsingRemoteTZDB then
|
582 |
begin
|
583 |
EncodeTimestampTZ(timestamp,timezone,@Buffer);
|
584 |
gmtTimeStamp := FFirebird30ClientAPI.SQLDecodeDateTime(@Buffer);
|
585 |
Result := -ComputeDstOffset(timestamp,gmtTimestamp);
|
586 |
end
|
587 |
else
|
588 |
Result := GetDstOffset(timestamp,TimeZoneName2TimeZoneID(timezone),isLocalTime);
|
589 |
end;
|
590 |
|
591 |
function TFB30TimeZoneServices.DecodeGMTTimestampTZ(bufptr: PISC_TIMESTAMP_TZ
|
592 |
): TDateTime;
|
593 |
var Yr, Mn, Dy: word;
|
594 |
Hr, Mt, S: word;
|
595 |
DMs: cardinal;
|
596 |
begin
|
597 |
with FFirebird30ClientAPI do
|
598 |
begin
|
599 |
UtilIntf.DecodeDate(bufptr^.utc_timestamp.timestamp_date,@Yr, @Mn, @Dy);
|
600 |
UtilIntf.DecodeTime(bufptr^.utc_timestamp.timestamp_time,@Hr, @Mt, @S, @DMs);
|
601 |
Result := EncodeDate(Yr, Mn, Dy) + FBEncodeTime(Hr,Mt,S,DMs);
|
602 |
end
|
603 |
end;
|
604 |
|
605 |
function TFB30TimeZoneServices.LookupTimeZoneName(aTimeZoneID: TFBTimeZoneID
|
606 |
): AnsiString;
|
607 |
begin
|
608 |
Result := LookupTimeZone(aTimeZoneID).GetTimeZoneName;
|
609 |
end;
|
610 |
|
611 |
function TFB30TimeZoneServices.LookupTimeZoneID(aTimeZone: AnsiString
|
612 |
): TFBTimeZoneID;
|
613 |
var dstOffset: integer;
|
614 |
begin
|
615 |
if DecodeTimeZoneOffset(aTimeZone,dstOffset) then
|
616 |
Result := dstOffset + TimeZoneDisplacementDelta
|
617 |
else
|
618 |
Result := LookupTimeZone(aTimeZone).GetTimeZoneID;
|
619 |
end;
|
620 |
|
621 |
function TFB30TimeZoneServices.LookupTimeZone(aTimeZoneID: TFBTimeZoneID
|
622 |
): ITimeZone;
|
623 |
var aTimeZone: Ansistring;
|
624 |
begin
|
625 |
Result := GetTimeZoneCache.GetTimeZone(aTimeZoneID);
|
626 |
if Result = nil then
|
627 |
begin
|
628 |
try
|
629 |
aTimeZone := FAttachment.OpenCursorAtStart(GetTransaction,
|
630 |
'Select Trim(RDB$TIME_ZONE_NAME) as RDB$TIME_ZONE_NAME From RDB$TIME_ZONES Where RDB$TIME_ZONE_ID = ?',3,
|
631 |
[aTimeZoneID])[0].AsString;
|
632 |
except
|
633 |
IBError(ibxeBadTimeZoneID,[aTimeZoneID,0]);
|
634 |
end;
|
635 |
Result := GetTimeZoneCache.AddTimeZone(aTimeZoneID,aTimeZone);
|
636 |
end;
|
637 |
end;
|
638 |
|
639 |
function TFB30TimeZoneServices.LookupTimeZone(aTimeZone: AnsiString): ITimeZone;
|
640 |
var aTimeZoneID: TFBTimeZoneID;
|
641 |
begin
|
642 |
Result := GetTimeZoneCache.GetTimeZone(aTimeZone);
|
643 |
if Result = nil then
|
644 |
begin
|
645 |
try
|
646 |
aTimeZoneID := FAttachment.OpenCursorAtStart(GetTransaction,
|
647 |
'Select RDB$TIME_ZONE_ID From RDB$TIME_ZONES Where RDB$TIME_ZONE_Name = ?',3,
|
648 |
[aTimeZone])[0].AsInteger;
|
649 |
except
|
650 |
IBError(ibxeBadTimeZoneName,[aTimeZone]);
|
651 |
end;
|
652 |
Result := GetTimeZoneCache.AddTimeZone(aTimeZoneID,aTimeZone);
|
653 |
end;
|
654 |
end;
|
655 |
|
656 |
function TFB30TimeZoneServices.LookupTimeZoneInfo(aTimeZoneID: TFBTimeZoneID;
|
657 |
timestamp: TDateTime; isLocalTime: boolean): PTimeZoneInfo;
|
658 |
var TimeZone: ITimeZone;
|
659 |
begin
|
660 |
TimeZone := LookupTimeZone(aTimeZoneID);
|
661 |
if TimeZone <> nil then
|
662 |
Result := GetTimeZoneData(TimeZone,timestamp,isLocalTime)
|
663 |
else
|
664 |
Result := nil;
|
665 |
end;
|
666 |
|
667 |
function TFB30TimeZoneServices.LookupTimeZoneInfo(aTimeZone: AnsiString;
|
668 |
timestamp: TDateTime; isLocalTime: boolean): PTimeZoneInfo;
|
669 |
var TimeZone: ITimeZone;
|
670 |
begin
|
671 |
TimeZone := LookupTimeZone(aTimeZone);
|
672 |
if TimeZone <> nil then
|
673 |
Result := GetTimeZoneData(TimeZone,timestamp,isLocalTime)
|
674 |
else
|
675 |
Result := nil;
|
676 |
end;
|
677 |
|
678 |
constructor TFB30TimeZoneServices.Create(attachment: TFB30Attachment);
|
679 |
begin
|
680 |
inherited Create;
|
681 |
FAttachment := attachment;
|
682 |
FFirebird30ClientAPI := attachment.Firebird30ClientAPI;
|
683 |
FUsingRemoteTZDB := true;
|
684 |
{$IFDEF USECURRENTDATEFORGMTTOLOCALTIME}
|
685 |
FTimeTZDate := Sysutils.Date;
|
686 |
{$ELSE}
|
687 |
FTimeTZDate := EncodeDate(2020,1,1);
|
688 |
{$ENDIF}
|
689 |
end;
|
690 |
|
691 |
destructor TFB30TimeZoneServices.Destroy;
|
692 |
begin
|
693 |
if FTransaction <> nil then
|
694 |
(FTransaction as TFBTransaction).Remove(self);
|
695 |
FTransaction := nil;
|
696 |
FTimeZoneCache := nil;
|
697 |
inherited Destroy;
|
698 |
end;
|
699 |
|
700 |
procedure TFB30TimeZoneServices.TransactionEnding(aTransaction: ITransaction;
|
701 |
Force: boolean);
|
702 |
begin
|
703 |
if (aTransaction as TObject) = (FTransaction as TObject) then
|
704 |
begin
|
705 |
(FTransaction as TFBTransaction).Remove(self);
|
706 |
FTransaction := nil;
|
707 |
end;
|
708 |
end;
|
709 |
|
710 |
procedure TFB30TimeZoneServices.EncodeTimestampTZ(timestamp: TDateTime;
|
711 |
timezoneID: TFBTimeZoneID; bufptr: PByte);
|
712 |
var
|
713 |
Yr, Mn, Dy: word;
|
714 |
Hr, Mt, S: word;
|
715 |
DMs: cardinal;
|
716 |
begin
|
717 |
if not FUsingRemoteTZDB then
|
718 |
EncodeTimestampTZ(timestamp,TimeZoneID2TimeZoneName(timezoneID),bufptr)
|
719 |
else
|
720 |
with FFirebird30ClientAPI do
|
721 |
begin
|
722 |
timestamp := LocalTimeToGMT(timestamp,timezoneID);
|
723 |
DecodeDate(timestamp, Yr, Mn, Dy);
|
724 |
FBDecodeTime(timestamp, Hr, Mt, S, DMs);
|
725 |
with PISC_TIMESTAMP_TZ(Bufptr)^ do
|
726 |
begin
|
727 |
utc_timestamp.timestamp_date := UtilIntf.encodeDate(Yr, Mn, Dy);
|
728 |
utc_timestamp.timestamp_time := UtilIntf.encodeTime(Hr, Mt, S, DMs);
|
729 |
time_zone := timezoneID;
|
730 |
end;
|
731 |
end
|
732 |
end;
|
733 |
|
734 |
procedure TFB30TimeZoneServices.EncodeTimestampTZ(timestamp: TDateTime; timezone: AnsiString;
|
735 |
bufptr: PByte);
|
736 |
var
|
737 |
Yr, Mn, Dy: word;
|
738 |
Hr, Mt, S: word;
|
739 |
DMs: cardinal;
|
740 |
begin
|
741 |
if FUsingRemoteTZDB then
|
742 |
EncodeTimestampTZ(timestamp,TimezoneName2TimeZoneID(timezone),bufptr)
|
743 |
else
|
744 |
with FFirebird30ClientAPI do
|
745 |
begin
|
746 |
DecodeDate(timestamp, Yr, Mn, Dy);
|
747 |
FBDecodeTime(timestamp, Hr, Mt, S, DMs);
|
748 |
if timezone = '' then
|
749 |
timezone := GetLocalTimeZoneName;
|
750 |
UtilIntf.encodeTimeStampTz(StatusIntf,ISC_TIMESTAMP_TZPtr(bufPtr),Yr, Mn, Dy, Hr, Mt, S, DMs,PAnsiChar(timezone));
|
751 |
Check4DataBaseError;
|
752 |
end;
|
753 |
end;
|
754 |
|
755 |
{When encoding a time it must first be converted to GMT on a given date.}
|
756 |
|
757 |
procedure TFB30TimeZoneServices.EncodeTimeTZ(time: TDateTime;
|
758 |
timezoneID: TFBTimeZoneID; OnDate: TDateTime; bufptr: PByte);
|
759 |
var localtime: TDateTime;
|
760 |
buffer: ISC_TIMESTAMP_TZ;
|
761 |
begin
|
762 |
localtime := DateOf(OnDate) + time;
|
763 |
EncodeTimestampTZ(localtime,timezoneID,@buffer);
|
764 |
PISC_TIME_TZ(bufptr)^.utc_time := buffer.utc_timestamp.timestamp_time;
|
765 |
PISC_TIME_TZ(bufptr)^.time_zone := buffer.time_zone;
|
766 |
end;
|
767 |
|
768 |
procedure TFB30TimeZoneServices.EncodeTimeTZ(time: TDateTime;
|
769 |
timezone: AnsiString; OnDate: TDateTime; bufptr: PByte);
|
770 |
var localtime: TDateTime;
|
771 |
buffer: ISC_TIMESTAMP_TZ;
|
772 |
begin
|
773 |
localtime := DateOf(OnDate) + time;
|
774 |
EncodeTimestampTZ(localtime,timezone,@buffer);
|
775 |
PISC_TIME_TZ(bufptr)^.utc_time := buffer.utc_timestamp.timestamp_time;
|
776 |
PISC_TIME_TZ(bufptr)^.time_zone := buffer.time_zone;
|
777 |
end;
|
778 |
|
779 |
procedure TFB30TimeZoneServices.DecodeTimestampTZ(bufptr: PByte;
|
780 |
var timestamp: TDateTime; var dstOffset: smallint;
|
781 |
var timezoneID: TFBTimeZoneID);
|
782 |
|
783 |
var aTimeZone: AnsiString;
|
784 |
gmtTimestamp: TDateTime;
|
785 |
begin
|
786 |
if not FUsingRemoteTZDB then
|
787 |
begin
|
788 |
DecodeTimestampTZ(bufptr,timestamp,dstOffset,aTimeZone);
|
789 |
timezoneID := PISC_TIMESTAMP_TZ(bufptr)^.time_zone;
|
790 |
end
|
791 |
else
|
792 |
with FFirebird30ClientAPI do
|
793 |
begin
|
794 |
gmtTimestamp := DecodeGMTTimestampTZ(PISC_TIMESTAMP_TZ(bufptr));
|
795 |
timezoneID := PISC_TIMESTAMP_TZ(bufptr)^.time_zone;
|
796 |
dstOffset := GetDstOffset(gmtTimestamp,timezoneID,false);
|
797 |
timestamp := IncMinute(gmtTimestamp, dstOffset);
|
798 |
end;
|
799 |
end;
|
800 |
|
801 |
procedure TFB30TimeZoneServices.DecodeTimestampTZ(bufptr: PByte;
|
802 |
var timestamp: TDateTime; var dstOffset: smallint; var timezone: AnsiString);
|
803 |
const
|
804 |
bufLength = 128;
|
805 |
|
806 |
var Yr, Mn, Dy: cardinal;
|
807 |
Hr, Mt, S: cardinal;
|
808 |
DMs: cardinal;
|
809 |
tzBuffer: array[ 0.. bufLength] of AnsiChar;
|
810 |
gmtTimestamp: TDateTime;
|
811 |
timezoneID: TFBTimeZoneID;
|
812 |
begin
|
813 |
if FUsingRemoteTZDB then
|
814 |
begin
|
815 |
DecodeTimestampTZ(bufptr,timestamp,dstOffset,timezoneID);
|
816 |
timezone := TimeZoneID2TimeZoneName(timeZoneID);
|
817 |
end
|
818 |
else
|
819 |
with FFirebird30ClientAPI do
|
820 |
begin
|
821 |
UtilIntf.decodeTimeStampTz(StatusIntf,ISC_TIMESTAMP_TZPtr(bufPtr),@Yr,@ Mn, @Dy, @Hr, @Mt, @S, @DMs,bufLength,PAnsiChar(@tzBuffer));
|
822 |
Check4DataBaseError;
|
823 |
timestamp := EncodeDate(Yr, Mn, Dy) + FBEncodeTime(Hr,Mt,S,DMs);
|
824 |
timezone := strpas(PAnsiChar(@tzBuffer));
|
825 |
gmtTimestamp := DecodeGMTTimestampTZ(PISC_TIMESTAMP_TZ(bufptr));
|
826 |
dstOffset := ComputeDstOffset(timestamp,gmtTimestamp);
|
827 |
end;
|
828 |
end;
|
829 |
|
830 |
procedure TFB30TimeZoneServices.DecodeTimestampTZEx(bufptr: PByte;
|
831 |
var timestamp: TDateTime; var dstOffset: smallint;
|
832 |
var timezoneID: TFBTimeZoneID);
|
833 |
var timezone: AnsiString;
|
834 |
gmtTimestamp: TDateTime;
|
835 |
begin
|
836 |
if not FUsingRemoteTZDB then
|
837 |
begin
|
838 |
DecodeTimestampTZEx(bufptr,timestamp,dstOffset,timezone);
|
839 |
timezoneID := PISC_TIMESTAMP_TZ(bufptr)^.time_zone;
|
840 |
end
|
841 |
else
|
842 |
with FFirebird30ClientAPI do
|
843 |
begin
|
844 |
gmtTimestamp := DecodeGMTTimestampTZ(PISC_TIMESTAMP_TZ(bufptr));
|
845 |
timezoneID := PISC_TIMESTAMP_TZ_EX(bufptr)^.time_zone;
|
846 |
dstOffset := PISC_TIMESTAMP_TZ_EX(bufptr)^.ext_offset;
|
847 |
timestamp := IncMinute(gmtTimestamp, dstOffset);
|
848 |
end;
|
849 |
end;
|
850 |
|
851 |
procedure TFB30TimeZoneServices.DecodeTimestampTZEx(bufptr: PByte;
|
852 |
var timestamp: TDateTime; var dstOffset: smallint; var timezone: AnsiString);
|
853 |
|
854 |
const
|
855 |
bufLength = 128;
|
856 |
var
|
857 |
Yr, Mn, Dy: cardinal;
|
858 |
Hr, Mt, S, DMs: cardinal;
|
859 |
tzBuffer: array[ 0.. bufLength] of AnsiChar;
|
860 |
timezoneID: TFBTimeZoneID;
|
861 |
begin
|
862 |
if FUsingRemoteTZDB then
|
863 |
begin
|
864 |
DecodeTimestampTZEx(bufptr,timestamp,dstOffset,timezoneID);
|
865 |
timezone := TimeZoneID2TimeZoneName(timezoneID);
|
866 |
end
|
867 |
else
|
868 |
with FFirebird30ClientAPI do
|
869 |
begin
|
870 |
if not HasExtendedTZSupport then
|
871 |
IBError(ibxeNotSupported,[]);
|
872 |
|
873 |
UtilIntf.decodeTimeStampTzEx(StatusIntf,ISC_TIMESTAMP_TZ_EXPtr(bufPtr),@Yr,@ Mn, @Dy, @Hr, @Mt, @S, @DMs,bufLength,PAnsiChar(@tzBuffer));
|
874 |
Check4DataBaseError;
|
875 |
timestamp := EncodeDate(Yr, Mn, Dy) + FBEncodeTime(Hr,Mt,S,DMs);
|
876 |
dstOffset := ISC_TIMESTAMP_TZ_EXPtr(bufPtr)^.ext_offset;
|
877 |
timezone := strpas(PAnsiChar(@tzBuffer));
|
878 |
end;
|
879 |
end;
|
880 |
|
881 |
procedure TFB30TimeZoneServices.DecodeTimeTZ(bufptr: PByte; OnDate: TDateTime;
|
882 |
var time: TDateTime; var dstOffset: smallint; var timezoneID: TFBTimeZoneID);
|
883 |
|
884 |
var
|
885 |
Hr, Mt, S, DMs: cardinal;
|
886 |
gmtTime: TDateTime;
|
887 |
gmtTimestamp: TDateTime;
|
888 |
aTimeZone: AnsiString;
|
889 |
begin
|
890 |
timezoneID := PISC_TIME_TZ(bufptr)^.time_zone;
|
891 |
if FUsingRemoteTZDB then
|
892 |
with FFirebird30ClientAPI do
|
893 |
begin
|
894 |
{decode the GMT time}
|
895 |
UtilIntf.decodeTime(PISC_TIME_TZ(bufptr)^.utc_time, @Hr, @Mt, @S, @DMs);
|
896 |
gmtTime := FBEncodeTime(Hr, Mt, S, DMs);
|
897 |
|
898 |
{expand to a timestamp}
|
899 |
gmtTimestamp := DateOf(OnDate) + gmtTime;
|
900 |
|
901 |
dstOffset := GetDstOffset(gmtTimestamp,timezoneID,false);
|
902 |
time := TimeOf(IncMinute(gmtTimestamp,dstOffset));
|
903 |
end
|
904 |
else
|
905 |
DecodeTimeTZ(bufptr,OnDate,time,dstOffset,aTimeZone);
|
906 |
end;
|
907 |
|
908 |
procedure TFB30TimeZoneServices.DecodeTimeTZ(bufptr: PByte; OnDate: TDateTime;
|
909 |
var time: TDateTime; var dstOffset: smallint; var timezone: AnsiString);
|
910 |
var aTimeZoneID: TFBTimeZoneID;
|
911 |
Hr, Mt, S, DMs: cardinal;
|
912 |
gmtTime: TDateTime;
|
913 |
gmtTimestamp: TDateTime;
|
914 |
localtimestamp: TDateTime;
|
915 |
tmptimestamp: ISC_TIMESTAMP_TZ;
|
916 |
begin
|
917 |
if FUsingRemoteTZDB then
|
918 |
begin
|
919 |
DecodeTimeTZ(bufptr,OnDate,time,dstOffset,aTimeZoneID);
|
920 |
timezone := TimeZoneID2TimeZoneName(aTimeZoneID);
|
921 |
end
|
922 |
else
|
923 |
with FFirebird30ClientAPI do
|
924 |
begin
|
925 |
{decode the GMT time}
|
926 |
UtilIntf.decodeTime(PISC_TIME_TZ(bufptr)^.utc_time, @Hr, @Mt, @S, @DMs);
|
927 |
gmtTime := FBEncodeTime(Hr, Mt, S, DMs);
|
928 |
|
929 |
{expand to a timestamp}
|
930 |
gmtTimestamp := DateOf(OnDate) + gmtTime;
|
931 |
|
932 |
{re-encode as a timestamp}
|
933 |
SQLEncodeDateTime(gmtTimeStamp,@(tmpTimestamp.utc_timestamp));
|
934 |
tmpTimestamp.time_zone := PISC_TIME_TZ(bufptr)^.time_zone;
|
935 |
|
936 |
{Decode to local time using local TZ data}
|
937 |
DecodeTimestampTZ(@tmpTimestamp,localtimestamp,dstOffset,timezone);
|
938 |
time := TimeOf(localtimestamp);
|
939 |
end;
|
940 |
end;
|
941 |
|
942 |
procedure TFB30TimeZoneServices.DecodeTimeTZEx(bufptr: PByte;
|
943 |
OnDate: TDateTime; var time: TDateTime; var dstOffset: smallint;
|
944 |
var timezone: AnsiString);
|
945 |
|
946 |
var timezoneID: TFBTimeZoneID;
|
947 |
begin
|
948 |
DecodeTimeTZEx(bufptr,OnDate,time,dstOffset,timezoneID);
|
949 |
timezone := TimeZoneID2TimeZoneName(timezoneID);
|
950 |
end;
|
951 |
|
952 |
procedure TFB30TimeZoneServices.DecodeTimeTZEx(bufptr: PByte;
|
953 |
OnDate: TDateTime; var time: TDateTime; var dstOffset: smallint;
|
954 |
var timezoneID: TFBTimeZoneID);
|
955 |
|
956 |
var
|
957 |
Hr, Mt, S, DMs: cardinal;
|
958 |
gmtTime: TDateTime;
|
959 |
gmtTimestamp: TDateTime;
|
960 |
begin
|
961 |
with FFirebird30ClientAPI do
|
962 |
begin
|
963 |
{decode the GMT time}
|
964 |
UtilIntf.decodeTime(PISC_TIME_TZ_EX(bufptr)^.utc_time, @Hr, @Mt, @S, @DMs);
|
965 |
gmtTime := FBEncodeTime(Hr, Mt, S, DMs);
|
966 |
|
967 |
{expand to a timestamp}
|
968 |
gmtTimestamp := DateOf(OnDate) + gmtTime;
|
969 |
timezoneID := PISC_TIME_TZ_EX(bufptr)^.time_zone;
|
970 |
dstOffset := GetDstOffset(gmtTimestamp,timezoneID,false);
|
971 |
{dstOffset := PISC_TIME_TZ_EX(bufptr)^.ext_offset; {--ignored see CORE6328}
|
972 |
|
973 |
time := TimeOf(IncMinute(gmtTimestamp,dstOffset));
|
974 |
end;
|
975 |
end;
|
976 |
|
977 |
function TFB30TimeZoneServices.TimeZoneID2TimeZoneName(
|
978 |
aTimeZoneID: TFBTimeZoneID): AnsiString;
|
979 |
const
|
980 |
bufLength = 128;
|
981 |
var Buffer: ISC_TIME_TZ;
|
982 |
Hr, Mt, S, DMs: cardinal;
|
983 |
tzBuffer: array[ 0.. bufLength] of AnsiChar;
|
984 |
begin
|
985 |
if aTimeZoneID < MaxOffsetTimeZoneID then {Time Zone ID is for an offset}
|
986 |
Result := FormatTimeZoneOffset(aTimeZoneID - TimeZoneDisplacementDelta)
|
987 |
else
|
988 |
with FFirebird30ClientAPI do
|
989 |
if not FUsingRemoteTZDB then
|
990 |
begin
|
991 |
Buffer.utc_time := 0;
|
992 |
Buffer.time_zone := aTimeZoneID;
|
993 |
UtilIntf.decodeTimeTz(StatusIntf, @Buffer,@Hr, @Mt, @S, @DMs,bufLength,PAnsiChar(@tzBuffer));
|
994 |
Check4DataBaseError;
|
995 |
Result := strpas(PAnsiChar(@tzBuffer));
|
996 |
end
|
997 |
else
|
998 |
Result := LookupTimeZoneName(aTimeZoneID);
|
999 |
end;
|
1000 |
|
1001 |
function TFB30TimeZoneServices.TimeZoneName2TimeZoneID(aTimeZone: AnsiString
|
1002 |
): TFBTimeZoneID;
|
1003 |
var Buffer: ISC_TIME_TZ;
|
1004 |
dstOffset: integer;
|
1005 |
begin
|
1006 |
with FFirebird30ClientAPI do
|
1007 |
if Trim(aTimeZone) = '' then
|
1008 |
begin
|
1009 |
aTimeZone := GetLocalTimeZoneName;
|
1010 |
Result := LookupTimeZoneID(aTimeZone);
|
1011 |
end
|
1012 |
else
|
1013 |
if DecodeTimeZoneOffset(aTimeZone,dstOffset) then
|
1014 |
Result := dstOffset + TimeZoneDisplacementDelta
|
1015 |
else
|
1016 |
if not FUsingRemoteTZDB then
|
1017 |
begin
|
1018 |
UtilIntf.EncodeTimeTZ(StatusIntf,@Buffer,0,0,0,0,PAnsiChar(aTimeZone));
|
1019 |
Result := Buffer.time_zone;
|
1020 |
end
|
1021 |
else
|
1022 |
Result := LookupTimeZoneID(aTimeZone);
|
1023 |
end;
|
1024 |
|
1025 |
function TFB30TimeZoneServices.LocalTimeToGMT(aLocalTime: TDateTime;
|
1026 |
aTimeZone: AnsiString): TDateTime;
|
1027 |
begin
|
1028 |
Result := IncMinute(aLocalTime,GetDSTOffset(aLocalTime,aTimeZone,true));
|
1029 |
end;
|
1030 |
|
1031 |
function TFB30TimeZoneServices.LocalTimeToGMT(aLocalTime: TDateTime;
|
1032 |
aTimeZoneID: TFBTimeZoneID): TDateTime;
|
1033 |
begin
|
1034 |
Result := IncMinute(aLocalTime,GetDSTOffset(aLocalTime,aTimeZoneID,true));
|
1035 |
end;
|
1036 |
|
1037 |
function TFB30TimeZoneServices.GMTToLocalTime(aGMTTime: TDateTime;
|
1038 |
aTimeZone: AnsiString): TDateTime;
|
1039 |
begin
|
1040 |
Result := IncMinute(aGMTTime,GetDSTOffset(aGMTTime,aTimeZone,false));
|
1041 |
end;
|
1042 |
|
1043 |
function TFB30TimeZoneServices.GMTToLocalTime(aGMTTime: TDateTime;
|
1044 |
aTimeZoneID: TFBTimeZoneID): TDateTime;
|
1045 |
begin
|
1046 |
Result := IncMinute(aGMTTime,GetDSTOffset(aGMTTime,aTimeZoneID,false));
|
1047 |
end;
|
1048 |
|
1049 |
function TFB30TimeZoneServices.GetEffectiveOffsetMins(aLocalTime: TDateTime;
|
1050 |
aTimeZone: AnsiString): integer;
|
1051 |
begin
|
1052 |
Result := -GetDSTOffset(aLocalTime,aTimeZone,true);
|
1053 |
end;
|
1054 |
|
1055 |
function TFB30TimeZoneServices.GetEffectiveOffsetMins(aLocalTime: TDateTime;
|
1056 |
aTimeZoneID: TFBTimeZoneID): integer;
|
1057 |
begin
|
1058 |
Result := -GetDSTOffset(aLocalTime,aTimeZoneID,true);
|
1059 |
end;
|
1060 |
|
1061 |
function TFB30TimeZoneServices.UsingRemoteTZDB: boolean;
|
1062 |
begin
|
1063 |
Result := FUsingRemoteTZDB;
|
1064 |
end;
|
1065 |
|
1066 |
procedure TFB30TimeZoneServices.SetUseLocalTZDB(useLocalTZDB: boolean);
|
1067 |
begin
|
1068 |
if FFirebird30ClientAPI.HasLocalTZDB then
|
1069 |
FUsingRemoteTZDB := not useLocalTZDB
|
1070 |
else
|
1071 |
FUsingRemoteTZDB := true;
|
1072 |
end;
|
1073 |
|
1074 |
function TFB30TimeZoneServices.GetLocalTimeZoneName: AnsiString;
|
1075 |
var aDateTime: TDateTime;
|
1076 |
dstOffset: SmallInt;
|
1077 |
begin
|
1078 |
with FFirebird30ClientAPI do
|
1079 |
if TZDataTimeZoneID <> '' then
|
1080 |
Result := TZDataTimeZoneID
|
1081 |
else
|
1082 |
{Use the Server TZ Data Name if possible}
|
1083 |
begin
|
1084 |
if FServerTZName = '' then
|
1085 |
FAttachment.OpenCursorAtStart('Select Current_Timestamp at local from RDB$Database')[0].
|
1086 |
GetAsDateTime(aDateTime,dstOffset,FServerTZName);
|
1087 |
if FServerTZName <> '' then
|
1088 |
Result := FServerTZName
|
1089 |
else
|
1090 |
{Otherwise use local time offset}
|
1091 |
Result := FormatTimeZoneOffset(-LocalTimeOffset);
|
1092 |
end;
|
1093 |
end;
|
1094 |
|
1095 |
function TFB30TimeZoneServices.GetLocalTimeZoneID: TFBTimeZoneID;
|
1096 |
begin
|
1097 |
Result := TimeZoneName2TimeZoneID(GetLocalTimeZoneName);
|
1098 |
end;
|
1099 |
|
1100 |
procedure TFB30TimeZoneServices.GetTimeZoneInfo(aTimeZone: AnsiString;
|
1101 |
OnDate: TDateTime; var ZoneOffset, DSTOffset, EffectiveOffset: integer);
|
1102 |
var Stmt: IStatement;
|
1103 |
TZInfo: IResultSet;
|
1104 |
begin
|
1105 |
with FAttachment do
|
1106 |
Stmt := Prepare(StartTransaction([isc_tpb_read,isc_tpb_wait,isc_tpb_concurrency],taCommit),
|
1107 |
'select * from rdb$time_zone_util.transitions(?,?,?)');
|
1108 |
Stmt.SQLParams[0].AsString := aTimeZone;
|
1109 |
Stmt.SQLParams[1].AsDateTime := OnDate;
|
1110 |
Stmt.SQLParams[2].AsDateTime := OnDate;
|
1111 |
TZInfo := Stmt.OpenCursor;
|
1112 |
if TZInfo.FetchNext then
|
1113 |
begin
|
1114 |
ZoneOffset := TZInfo.ByName('ZONE_OFFSET').AsInteger;
|
1115 |
DSTOffset := TZInfo.ByName('DST_OFFSET').AsInteger;
|
1116 |
EffectiveOffset := TZInfo.ByName('EFFECTIVE_OFFSET').AsInteger;
|
1117 |
end;
|
1118 |
end;
|
1119 |
|
1120 |
function TFB30TimeZoneServices.GetTimeTZDate: TDateTime;
|
1121 |
begin
|
1122 |
Result := FTimeTZDate;
|
1123 |
end;
|
1124 |
|
1125 |
procedure TFB30TimeZoneServices.SetTimeTZDate(aDate: TDateTime);
|
1126 |
begin
|
1127 |
FTimeTZDate := DateOf(aDate);
|
1128 |
end;
|
1129 |
|
1130 |
function TFB30TimeZoneServices.GetTZTextOption: TTZTextOptions;
|
1131 |
begin
|
1132 |
Result := FTZTextOption;
|
1133 |
end;
|
1134 |
|
1135 |
procedure TFB30TimeZoneServices.SetTZTextOption(aOptionValue: TTZTextOptions);
|
1136 |
begin
|
1137 |
FTZTextOption := aOptionValue;
|
1138 |
end;
|
1139 |
|
1140 |
end.
|
1141 |
|