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 |
|