1 { Copyright (C) 2005 Bas Steendijk and Peter Green
\r
2 For conditions of distribution and use, see copyright notice in zlib_license.txt
\r
3 which is included in the package
\r
4 ----------------------------------------------------------------------------- }
\r
6 this unit has several functions for getting unix and monotonic time and UTC offset on both windows and linux/unix
\r
8 this unit aims to work on delphi 6 and later, both x86 and x64, on win95 and later
\r
9 delphi 5 may work (untested).
\r
10 as well as freepascal on linux x86 and x64 and freebsd x64 (tested), windows, and other unixes (untested)
\r
12 provided functions. all are available on both windows and linux/unix:
\r
13 - unix timestamp as a (double or extended) float or integer: unixtimefloat, unixtimeint
\r
14 - monotonic timestamp as float: monotimefloat, wintimefloat (both are equivalent)
\r
15 - _coarse versions of the floats, which can be faster, and precision in the milliseconds.
\r
16 - btime_gettime: a unified function like clock_gettime. less reliant on floats.
\r
17 - tzgetoffset returns the UTC offset (timezone) in seconds
\r
19 - on windows, it provides the legacy function gettimeofday (on unix, use the one provided by the OS units)
\r
21 other things in the interface are often more implementation specific, there for legacy reasons, and not guaranteed stable
\r
23 this unit should be 2038 safe:
\r
24 - seconds are handled as 64 bits
\r
25 - on 32 bits linux, it uses clock_gettime64 if available
\r
26 - for getting the UTC offset on unix, this unit does its own, 64 bits aware, parsing of the zoneinfo file
\r
27 - tested with clock set after 2038
\r
36 {$include lcoreconfig.inc}
\r
55 {$ifdef FPC_HAS_TYPE_EXTENDED}{$define has_extended}{$endif}
\r
56 {$ifndef fpc}{$ifdef cpu386}{$define has_extended}{$endif}{$endif}
\r
59 {$ifdef has_extended}
\r
67 colorburst=39375000/11; {3579545.4545....}
\r
70 CLOCK_MONOTONIC is the standard monotonic time offered by the OS, it may or may not include suspend time
\r
71 CLOCK_BOOTTIME includes suspend time.
\r
72 CLOCK_UPTIME excludes suspend time.
\r
78 CLOCK_REALTIME_COARSE=2;
\r
79 CLOCK_MONOTONIC_COARSE=3;
\r
80 CLOCK_BOOTTIME=CLOCK_MONOTONIC; //GetTickCount
\r
81 CLOCK_UPTIME=4; //QueryUnbiasedInterruptTime, fallback to QueryPerformanceCounter
\r
82 CLOCK_UPTIME_FAST=CLOCK_UPTIME;
\r
86 CLOCK_REALTIME=linux.CLOCK_REALTIME;
\r
87 CLOCK_MONOTONIC=linux.CLOCK_MONOTONIC;
\r
88 CLOCK_REALTIME_COARSE=linux.CLOCK_REALTIME_COARSE;
\r
89 CLOCK_MONOTONIC_COARSE=linux.CLOCK_MONOTONIC_COARSE;
\r
90 CLOCK_BOOTTIME=7; //linux.CLOCK_BOOTTIME - constant missing in freepascal
\r
91 CLOCK_UPTIME=CLOCK_MONOTONIC;
\r
92 CLOCK_UPTIME_FAST=CLOCK_MONOTONIC_COARSE;
\r
96 CLOCK_REALTIME=freebsd.CLOCK_REALTIME;
\r
97 CLOCK_MONOTONIC=freebsd.CLOCK_MONOTONIC;
\r
98 CLOCK_REALTIME_COARSE=freebsd.CLOCK_REALTIME_FAST;
\r
99 CLOCK_MONOTONIC_COARSE=freebsd.CLOCK_MONOTONIC_FAST;
\r
100 CLOCK_BOOTTIME=CLOCK_MONOTONIC;
\r
101 CLOCK_UPTIME=freebsd.CLOCK_UPTIME;
\r
102 CLOCK_UPTIME_FAST=freebsd.CLOCK_UPTIME_FAST;
\r
106 CLOCK_REALTIME=0; //values taken from darwin libc time.h
\r
108 CLOCK_REALTIME_COARSE=CLOCK_REALTIME; //darwin lacks these or equivalents
\r
109 CLOCK_MONOTONIC_COARSE=CLOCK_MONOTONIC;
\r
110 CLOCK_BOOTTIME=CLOCK_MONOTONIC;
\r
111 CLOCK_UPTIME_RAW=8;
\r
112 CLOCK_UPTIME=CLOCK_UPTIME_RAW;
\r
113 CLOCK_UPTIME_FAST=CLOCK_UPTIME_RAW;
\r
116 CLOCK_REALTIME_FAST=CLOCK_REALTIME_COARSE;
\r
117 CLOCK_MONOTONIC_FAST=CLOCK_MONOTONIC_COARSE;
\r
122 timezonestr:string;
\r
123 irctime,unixtime:tunixtimeint;
\r
125 settimebias:tunixtimeint;
\r
126 performancecountfreq:int64;
\r
127 performancecountstep:float;
\r
129 btimenowin8:boolean;
\r
131 function irctimefloat:float;
\r
132 function irctimeint:tunixtimeint;
\r
134 //unix timestamp (UTC) float seconds
\r
135 function unixtimefloat:float;
\r
136 function unixtimeint:tunixtimeint;
\r
138 //monotonic float seconds
\r
139 function monotimefloat:float;
\r
141 //coarse float seconds - usually faster, but a resolution in the milliseconds
\r
142 function unixtimefloat_coarse:float;
\r
143 function monotimefloat_coarse:float;
\r
145 //float versions of CLOCK_BOOTTIME and CLOCK_UPTIME
\r
146 function boottimefloat:float;
\r
147 function uptimefloat:float;
\r
149 //monotonic (alias, old function name)
\r
150 function wintimefloat:float;
\r
152 //get localtime vs UTC offset in seconds
\r
153 function tzgetoffset:integer;
\r
155 procedure settime(newtime:tunixtimeint);
\r
156 procedure gettimezone;
\r
157 procedure timehandler;
\r
160 function timestring(i:tunixtimeint):string; // Wednesday August 15 2012 -- 16:21:09 +02:00
\r
161 function timestrshort(i:tunixtimeint):string; // Wed Aug 15 16:21:09 2012
\r
162 function timestriso(i:tunixtimeint):string; // 2012-08-15 16:21:09
\r
163 function timestrisoutc(i:float):string; // 2012-08-15T14:21:09.255553Z
\r
165 procedure beginhightimerrate;
\r
166 procedure endhightimerrate;
\r
168 procedure tzinvalidate;
\r
171 function tzgetoffsetforts(ts:tunixtimeint):integer;
\r
175 function unixtimefloat_systemtime:float;
\r
178 function oletounixfloat(t:float):float;
\r
179 function oletounix(t:tdatetime):tunixtimeint;
\r
180 function unixtoole(i:float):tdatetime;
\r
183 function mmtimefloat:float;
\r
184 function mmtimeint64:int64;
\r
185 function qpctimefloat:float;
\r
189 function gettimeofday(var tv:ttimeval):integer;
\r
194 mmtime_driftavgsize=32;
\r
195 mmtime_warmupnum=4;
\r
196 mmtime_warmupcyclelength=15;
\r
198 //this flag is to be set when btime has been running long enough to stabilise
\r
199 warmup_finished:boolean;
\r
201 timefloatbias:float;
\r
202 ticks_freq:float=0;
\r
203 ticks_freq2:float=0;
\r
204 ticks_freq_known:boolean=false;
\r
205 lastunixtimefloat:float=0;
\r
206 lastsynctime:float=0;
\r
207 lastsyncbias:float=0;
\r
209 mmtime_last:integer=0;
\r
210 mmtime_wrapadd:int64;
\r
211 mmtime_lastsyncmm:float=0;
\r
212 mmtime_lastsyncqpc:float=0;
\r
213 mmtime_drift:float=1;
\r
214 mmtime_lastresult:float;
\r
215 mmtime_nextdriftcorrection:float;
\r
216 mmtime_driftavg:array[0..mmtime_driftavgsize] of float;
\r
217 mmtime_synchedqpc:boolean;
\r
219 mmtime_prev_drift:float;
\r
220 mmtime_prev_lastsyncmm:float;
\r
221 mmtime_prev_lastsyncqpc:float;
\r
223 gettime64_nosupport_cached:boolean;
\r
224 coarse_nosupport_cached:boolean;
\r
227 //i define my own "timespec" and "gettime" because that way, they can be 64 bits even if the real one is 32 bits, and avoid the wrong one being used.
\r
228 //tbtimespec can't be changed because it is passed as-is to clock_gettime64
\r
229 tbtimespec=packed record
\r
233 pbtimespec=^tbtimespec;
\r
236 //on unix, btime_gettime is the "inner" function that calls the actual OS/library functions, and other functions in btime call it
\r
237 //on windows, btime_gettime is an "outer" function that calls the other functions. so if not called by the app, it does not add to exe size.
\r
238 function btime_gettime(clockid:integer;tp:pbtimespec):integer;
\r
246 baseunix,unix,unixutil,sockets, {unixutil and sockets needed by unixstuff.inc on some compiler versions}
\r
249 windows,unitsettc,mmsystem,
\r
253 {$include unixstuff.inc}
\r
257 daysdifference=25569;
\r
259 function oletounixfloat(t:float):float;
\r
261 t := (t - daysdifference) * 86400;
\r
265 function oletounix(t:tdatetime):tunixtimeint;
\r
267 result := round(oletounixfloat(t));
\r
270 function unixtoole(i:float):tdatetime;
\r
272 result := ((i)/86400)+daysdifference;
\r
276 {-----------------------------------------*nix/freepascal code to read time }
\r
278 {$ifdef linux}{$define have_clock_gettime}{$endif}
\r
279 {$ifdef freebsd}{$define have_clock_gettime}{$endif}
\r
282 {$ifdef cpu386}{$define use_syscall_gettime64}{$endif}
\r
283 {$ifdef cpu32}{$define use_syscall_gettime64}{$endif}
\r
285 {$ifdef use_syscall_gettime64}
\r
287 clock_gettime64=403;
\r
293 {$ifdef darwin} {mac OS X}
\r
295 tmach_timebase_info = packed record
\r
299 pmach_timebase_info = ^tmach_timebase_info;
\r
301 function mach_absolute_time: int64; cdecl; external;
\r
302 function mach_timebase_info(info: pmach_timebase_info): integer; cdecl; external;
\r
305 timebase_info: tmach_timebase_info;
\r
309 function btime_gettime(clockid:integer;tp:pbtimespec):integer;
\r
311 {$ifdef have_clock_gettime}
\r
321 result := -1; //error
\r
324 if (clockid = CLOCK_MONOTONIC) or (clockid = CLOCK_MONOTONIC_COARSE) then begin
\r
325 if timebase_info.denom = 0 then begin
\r
326 mach_timebase_info(@timebase_info);
\r
328 if (timebase_info.denom > 0) then begin
\r
329 nanos := mach_absolute_time;
\r
330 if (nanos > 0) then begin
\r
331 result := 0; //indicate success
\r
332 if (timebase_info.denom > 10) then begin
\r
333 //on powerpc mac, numer and denom are large numbers such as 1000000000 and 33333335, and extended is available
\r
335 nanosf := (nanosf * timebase_info.numer) / timebase_info.denom;
\r
336 nanos := trunc(nanosf);
\r
338 //on intel mac, numer and denom are 1 and 1. on apple silicon they are typically 125 and 3, and extended is not available
\r
339 nanos := nanos div timebase_info.denom;
\r
340 nanos := nanos * timebase_info.numer;
\r
342 tp.tv_sec := nanos div 1000000000;
\r
343 tp.tv_nsec := nanos mod 1000000000;
\r
350 if (coarse_nosupport_cached) then begin
\r
351 if (clockid = CLOCK_REALTIME_COARSE) then clockid := CLOCK_REALTIME;
\r
352 if (clockid = CLOCK_MONOTONIC_COARSE) then clockid := CLOCK_MONOTONIC;
\r
355 {$ifdef use_syscall_gettime64}
\r
356 //don't do this for monotonic for performance reasons
\r
357 //the clock_gettime call below has the potential to be handled by libc, and then it is faster
\r
358 //also if it failed, don't call it again to avoid slowdown of doing two calls every time
\r
359 if (not ((clockid = CLOCK_MONOTONIC) or (clockid = CLOCK_MONOTONIC_COARSE) or (clockid = CLOCK_BOOTTIME) or (clockid = CLOCK_UPTIME))) and (not gettime64_nosupport_cached) then begin
\r
360 result := do_syscall(clock_gettime64,clockid,tsysparam(tp));
\r
362 if ((clockid = CLOCK_REALTIME) or (clockid = CLOCK_REALTIME_COARSE)) and (result <> 0) then gettime64_nosupport_cached := true;
\r
364 if (result = 0) then exit;
\r
368 {$ifdef have_clock_gettime}
\r
369 result := clock_gettime(clockid, @ts);
\r
370 if (result <> 0) then begin
\r
372 if (clockid = CLOCK_REALTIME_COARSE) then begin
\r
373 coarse_nosupport_cached := true;
\r
374 result := clock_gettime(CLOCK_REALTIME, @ts);
\r
376 if (clockid = CLOCK_MONOTONIC_COARSE) then begin
\r
377 coarse_nosupport_cached := true;
\r
378 result := clock_gettime(CLOCK_MONOTONIC, @ts);
\r
381 if (result = 0) then begin
\r
382 tp.tv_sec := ts.tv_sec;
\r
383 tp.tv_nsec := ts.tv_nsec;
\r
386 if (tp.tv_sec < -1) then inc(tp.tv_sec, $100000000);
\r
391 {$endif} //have_clock_gettime
\r
393 result := gettimeofday(tv);
\r
394 if (result = 0) then begin
\r
395 tp.tv_sec := tv.tv_sec;
\r
396 tp.tv_nsec := tv.tv_usec * 1000;
\r
399 if (tp.tv_sec < -1) then inc(tp.tv_sec, $100000000);
\r
405 function unixtimefloat:float;
\r
409 btime_gettime(CLOCK_REALTIME, @ts);
\r
410 //doing the below as a division causes a bug in fpc 3.2.0 x64, where the result becomes single precision
\r
411 result := ts.tv_sec + (ts.tv_nsec * 0.000000001);
\r
415 function monotimefloat:float;
\r
419 btime_gettime(CLOCK_MONOTONIC, @ts);
\r
420 result := ts.tv_sec + (ts.tv_nsec * 0.000000001);
\r
424 function unixtimefloat_coarse:float;
\r
428 btime_gettime(CLOCK_REALTIME_COARSE, @ts);
\r
429 result := ts.tv_sec + (ts.tv_nsec * 0.000000001);
\r
433 function monotimefloat_coarse:float;
\r
437 btime_gettime(CLOCK_MONOTONIC_COARSE, @ts);
\r
438 result := ts.tv_sec + (ts.tv_nsec * 0.000000001);
\r
442 function boottimefloat:float;
\r
446 btime_gettime(CLOCK_BOOTTIME, @ts);
\r
447 result := ts.tv_sec + (ts.tv_nsec * 0.000000001);
\r
451 function uptimefloat:float;
\r
455 btime_gettime(CLOCK_UPTIME, @ts);
\r
456 result := ts.tv_sec + (ts.tv_nsec * 0.000000001);
\r
460 function unixtimeint:tunixtimeint;
\r
464 btime_gettime(CLOCK_REALTIME,@ts);
\r
465 result := ts.tv_sec;
\r
468 {------------------------------ end of *nix/freepascal section}
\r
473 {------------------------------ windows/delphi code to read time}
\r
476 procedure tzinvalidate;
\r
483 GetTickCount64:function:int64; stdcall;
\r
484 gettickcount64_inited:boolean;
\r
486 procedure init_gettickcount64;
\r
490 gettickcount64_inited := true;
\r
491 dllhandle := loadlibrary('kernel32.dll');
\r
492 if (dllhandle <> 0) then begin
\r
493 GetTickCount64 := getprocaddress(dllhandle,'GetTickCount64');
\r
499 function mmtimeint64:int64;
\r
503 if not gettickcount64_inited then init_gettickcount64;
\r
504 if assigned(GetTickCount64) then begin
\r
505 result := GetTickCount64;
\r
508 if i < mmtime_last then begin
\r
509 mmtime_wrapadd := mmtime_wrapadd + $100000000;
\r
512 result := mmtime_wrapadd + i;
\r
518 time float: gettickcount
\r
519 resolution: 9x: ~55 ms NT: 1/64th of a second
\r
520 guarantees: continuous without any jumps
\r
521 frequency base: same as system clock.
\r
524 function mmtimefloat:float;
\r
528 result := mmtimeint64 * 0.001;
\r
530 if (ticks_freq <> 0) and ticks_freq_known then begin
\r
531 {the value we get is rounded to 1 ms, but the ticks are not a multiple of 1 ms
\r
532 this makes the value noisy. use the known ticks frequency to restore the original value}
\r
533 temp := int((result / ticks_freq)+0.5) * ticks_freq;
\r
535 {if the known ticks freq is wrong (can happen), disable the un-rounding behavior
\r
536 this will be a bit less accurate but it prevents problems}
\r
537 if abs(temp - result) > 0.002 then begin
\r
539 end else result := temp;
\r
544 win_version_known:boolean;
\r
546 win_ver_major:integer;
\r
547 win_ver_minor:integer;
\r
549 procedure init_win_version;
\r
553 if not win_version_known then begin
\r
554 win_version_known := true;
\r
555 fillchar(o,sizeof(o),0);
\r
556 o.dwOSVersionInfoSize := sizeof(o);
\r
558 win_isnt := o.dwPlatformId = VER_PLATFORM_WIN32_NT;
\r
559 win_ver_major := o.dwMajorVersion;
\r
560 win_ver_minor := o.dwMinorVersion;
\r
564 procedure measure_ticks_freq;
\r
568 adjust1,adjust2:cardinal;
\r
569 adjustbool:longbool;
\r
570 win8_or_later:boolean;
\r
572 if (performancecountfreq = 0) then qpctimefloat;
\r
573 ticks_freq_known := false;
\r
576 repeat g := mmtimefloat until g > f;
\r
583 mmtime_synchedqpc := false;
\r
585 if (win_isnt and (win_ver_major >= 5)) then begin
\r
586 {windows 2000 and later: query tick rate from OS in 100 ns units
\r
587 typical rates: XP: 156250 or 100144, windows 7: 156001}
\r
588 if GetSystemTimeAdjustment(adjust1,adjust2,adjustbool) then begin
\r
589 ticks_freq := adjust1 / 10000000.0;
\r
590 ticks_freq_known := true;
\r
591 mmtime_synchedqpc := false;
\r
593 //windows 8/10/11, 10 MHz time source is invariant TSC. add 64 Hz check for safety.
\r
594 //with manifest, windows 10/11 major.minor will be 10.0
\r
595 win8_or_later := ((win_ver_major = 6) and (win_ver_minor >= 2)) or (win_ver_major = 10);
\r
596 if (win8_or_later and (performancecountfreq = 10000000) and (adjust1 = 156250)) then begin
\r
597 mmtime_synchedqpc := true;
\r
603 if (performancecountfreq = 1193182) and (f >= 0.050) and (f <= 0.060) then begin
\r
604 ticks_freq_known := true;
\r
605 ticks_freq := 65536 / (colorburst / 3);
\r
606 mmtime_synchedqpc := true;
\r
608 ticks_freq_known := true;
\r
609 if ticks_freq <> 0 then ticks_freq2 := ticks_freq;
\r
610 // writeln(formatfloat('0.000000',ticks_freq));
\r
614 time float: QueryPerformanceCounter
\r
616 guarantees: can have forward jumps depending on hardware. can have forward and backwards jitter on dual core.
\r
617 frequency base: on NT, not the system clock, drifts compared to it.
\r
620 function qpctimefloat:float;
\r
624 if performancecountfreq = 0 then begin
\r
625 QueryPerformancefrequency(performancecountfreq);
\r
626 performancecountstep := 1.0 / performancecountfreq;
\r
628 queryperformancecounter(i64);
\r
629 result := i64 * performancecountstep;
\r
633 time float: QPC locked to gettickcount
\r
635 guarantees: continuous without any jumps
\r
636 frequency base: same as system clock.
\r
640 //function mmqpctimefloat:float;
\r
641 function monotimefloat:float;
\r
647 mm,f,qpc,newdrift:float;
\r
650 { retrycount:integer;}
\r
652 if not ticks_freq_known then measure_ticks_freq;
\r
653 { retrycount := maxretries;}
\r
655 qpc := qpctimefloat;
\r
657 f := (qpc - mmtime_lastsyncqpc) * mmtime_drift + mmtime_lastsyncmm;
\r
658 //writeln('XXXX ',formatfloat('0.000000',qpc-mm));
\r
659 qpcjumped := ((f-mm) > ticks_freq2+margin) or ((f-mm) < -margin);
\r
660 // if qpcjumped then writeln('qpc jumped ',(f-mm));
\r
661 if ((qpc > mmtime_nextdriftcorrection) and not mmtime_synchedqpc) or qpcjumped then begin
\r
663 mmtime_nextdriftcorrection := qpc + 1;
\r
665 mmtime_prev_drift := mmtime_drift;
\r
666 mmtime_prev_lastsyncmm := mmtime_lastsyncmm;
\r
667 mmtime_prev_lastsyncqpc := mmtime_lastsyncqpc;
\r
670 { dec(retrycount);}
\r
672 result := qpctimefloat;
\r
675 if f = mm then result := qpctimefloat;
\r
678 qpc := qpctimefloat;
\r
681 if (qpc > result + 0.0001) then begin
\r
686 if (mmtime_lastsyncqpc <> 0) and not qpcjumped then begin
\r
687 newdrift := (mm - mmtime_lastsyncmm) / (qpc - mmtime_lastsyncqpc);
\r
688 mmtime_drift := newdrift;
\r
689 { writeln('raw drift: ',formatfloat('0.00000000',mmtime_drift));}
\r
690 move(mmtime_driftavg[0],mmtime_driftavg[1],sizeof(mmtime_driftavg[0])*high(mmtime_driftavg));
\r
691 mmtime_driftavg[0] := mmtime_drift;
\r
693 { write('averaging drift ',formatfloat('0.00000000',mmtime_drift),' -> ');}
\r
694 { mmtime_drift := 0;}
\r
696 for a := 0 to high(mmtime_driftavg) do begin
\r
697 if mmtime_driftavg[a] <> 0 then inc(b);
\r
698 { mmtime_drift := mmtime_drift + mmtime_driftavg[a];}
\r
700 { mmtime_drift := mmtime_drift / b;}
\r
702 if (b = 1) then a := 5 else if (b = 2) then a := 15 else if (b = 3) then a := 30 else if (b = 4) then a := 60 else if (b = 5) then a := 120 else if (b >= 5) then a := 120;
\r
703 mmtime_nextdriftcorrection := qpc + a;
\r
704 if (b >= 2) then warmup_finished := true;
\r
705 { writeln(formatfloat('0.00000000',mmtime_drift));}
\r
706 if mmtime_synchedqpc then mmtime_drift := 1;
\r
709 mmtime_lastsyncqpc := qpc;
\r
710 mmtime_lastsyncmm := mm;
\r
711 { writeln(formatfloat('0.00000000',mmtime_drift));}
\r
716 qpc := qpctimefloat;
\r
718 result := (qpc - mmtime_lastsyncqpc) * mmtime_drift + mmtime_lastsyncmm;
\r
720 {f := (qpc - mmtime_prev_lastsyncqpc) * mmtime_prev_drift + mmtime_prev_lastsyncmm;
\r
722 writeln('jump ',formatfloat('0.000000',jump),' drift ',formatfloat('0.00000000',mmtime_drift),' duration ',formatfloat('0.000',(mmtime_lastsyncqpc-mmtime_prev_lastsyncqpc)),' ',formatfloat('0.00000000',jump/(mmtime_lastsyncqpc-mmtime_prev_lastsyncqpc)));}
\r
729 if (result < mmtime_lastresult) then result := mmtime_lastresult;
\r
730 mmtime_lastresult := result;
\r
734 function boottimefloat:float;
\r
736 result := monotimefloat;
\r
740 QueryUnbiasedInterruptTime:function(var i:int64):longbool; stdcall;
\r
741 unbiasedinterrupttime_inited:boolean;
\r
744 procedure initunbiasedinterrupttime;
\r
748 unbiasedinterrupttime_inited := true;
\r
749 dllhandle := loadlibrary('kernel32.dll');
\r
750 if (dllhandle <> 0) then begin
\r
751 QueryUnbiasedInterruptTime := getprocaddress(dllhandle,'QueryUnbiasedInterruptTime');
\r
755 function unbiasedtime_100ns:int64;
\r
758 if not unbiasedinterrupttime_inited then initunbiasedinterrupttime;
\r
759 if assigned(@QueryUnbiasedInterruptTime) then begin
\r
760 QueryUnbiasedInterruptTime(result);
\r
764 function uptimefloat:float;
\r
768 i := unbiasedtime_100ns;
\r
769 if (i > 0) then begin
\r
770 result := i * 0.0000001;
\r
773 result := qpctimefloat;
\r
779 GetSystemTimePreciseAsFileTime:procedure(var v:tfiletime); stdcall;
\r
780 win8inited:boolean;
\r
782 procedure initwin8;
\r
787 win8inited := true;
\r
788 dllhandle := loadlibrary('kernel32.dll');
\r
789 if (dllhandle <> 0) then begin
\r
790 GetSystemTimePreciseAsFileTime := getprocaddress(dllhandle,'GetSystemTimePreciseAsFileTime');
\r
795 function win8time_as_unix_100ns:int64;
\r
798 i:int64 absolute ft;
\r
801 if not win8inited then initwin8;
\r
802 if assigned(@GetSystemTimePreciseAsFileTime) then begin
\r
803 GetSystemTimePreciseAsFileTime(ft);
\r
804 //change from windows 1601-01-01 to unix 1970-01-01.
\r
805 dec(i, 116444736000000000);
\r
811 function unixtimefloat_systemtime:float;
\r
814 i:int64 absolute ft;
\r
816 //result := oletounixfloat(now_utc);
\r
818 //this method gives exactly the same result with extended precision, but is less sensitive to float rounding in theory}
\r
819 //result := oletounixfloat(int(date_utc+0.5))+time_utc*86400;
\r
821 //new method that is much faster (22 vs 190 ns)
\r
822 GetSystemTimeAsFileTime(ft);
\r
823 dec(i, 116444736000000000);
\r
824 result := i * 0.0000001;
\r
828 function unixtimefloat_coarse:float;
\r
830 //don't do the coarse method if worse than about 16 ms. on win9x it may be only a 55 ms resolution
\r
831 //but i have also seen it is less (like 1 ms)
\r
832 //don't call measure_ticks_freq on NT because it is expensive and unnecessary
\r
834 if not win_isnt then begin
\r
835 if not ticks_freq_known then measure_ticks_freq;
\r
837 if win_isnt or (ticks_freq < 0.017) then begin
\r
838 result := unixtimefloat_systemtime;
\r
842 result := unixtimefloat;
\r
845 function monotimefloat_coarse:float;
\r
848 if not win_isnt then begin
\r
849 if not ticks_freq_known then measure_ticks_freq;
\r
851 if win_isnt or (ticks_freq < 0.017) then begin
\r
852 result := mmtimeint64 * 0.001;
\r
855 result := monotimefloat;
\r
859 //simulate gettimeofday on windows so one can always use gettimeofday if preferred
\r
860 function gettimeofday(var tv:ttimeval):integer;
\r
867 if not btimenowin8 then begin
\r
868 i := win8time_as_unix_100ns;
\r
869 if (i > 0) then begin
\r
870 tv.tv_sec := i div 10000000;
\r
871 tv.tv_usec := (i mod 10000000) div 10;
\r
877 e := unixtimefloat;
\r
878 if (e > 0) then result := 0;
\r
879 tv.tv_sec := trunc(e);
\r
880 tv.tv_usec := trunc(frac(e)*1000000);
\r
884 function btime_gettime(clockid:integer;tp:pbtimespec):integer;
\r
889 {$ifdef cpu386}{$ifdef has_extended}{$define itotp_float}{$endif}{$endif}
\r
890 procedure i100ns_to_tp_and_success;
\r
891 {$ifdef itotp_float}
\r
896 //in 32 bits delphi, float is 10 times faster than int64 here (30 vs 300 ns for the conversion)
\r
897 //and with extended available, there is no loss of precision
\r
899 {$ifdef itotp_float}
\r
900 f := i / 10000000.0;
\r
901 tp.tv_sec := trunc(f);
\r
902 tp.tv_nsec := round(frac(f) * 1000000000.0);
\r
904 tp.tv_sec := i div 10000000;
\r
905 tp.tv_nsec := (i mod 10000000) * 100;
\r
908 result := 0; //success
\r
911 procedure f_to_tp_and_success;
\r
913 tp.tv_sec := trunc(f);
\r
914 tp.tv_nsec := round(frac(f) * 1000000000.0);
\r
915 result := 0; //success
\r
919 result := -1; //error
\r
922 CLOCK_REALTIME: begin
\r
923 //implement this case directly for full precision even without extended floats
\r
924 if not btimenowin8 then begin
\r
925 i := win8time_as_unix_100ns;
\r
926 if (i > 0) then begin
\r
927 i100ns_to_tp_and_success;
\r
931 f := unixtimefloat;
\r
932 f_to_tp_and_success;
\r
934 CLOCK_MONOTONIC: begin
\r
935 f := monotimefloat;
\r
936 f_to_tp_and_success;
\r
938 CLOCK_REALTIME_COARSE: begin
\r
939 f := unixtimefloat_coarse;
\r
940 f_to_tp_and_success;
\r
942 CLOCK_MONOTONIC_COARSE: begin
\r
943 f := monotimefloat_coarse;
\r
944 f_to_tp_and_success;
\r
946 CLOCK_UPTIME: begin
\r
947 i := unbiasedtime_100ns;
\r
948 if (i > 0) then begin
\r
949 i100ns_to_tp_and_success;
\r
953 f_to_tp_and_success;
\r
960 function unixtimefloat:float;
\r
966 if not btimenowin8 then begin
\r
967 result := win8time_as_unix_100ns * 0.0000001;
\r
968 if (result > 0) then exit;
\r
971 result := monotimefloat+timefloatbias;
\r
972 f := result-unixtimefloat_systemtime;
\r
973 if ((f > ticks_freq2+margin) or (f < -margin)) or (timefloatbias = 0) then begin
\r
974 f := unixtimefloat_systemtime;
\r
976 repeat g := unixtimefloat_systemtime; h := monotimefloat until g > f;
\r
978 timefloatbias := g-h;
\r
979 result := unixtimefloat;
\r
982 //for small changes backwards, guarantee no steps backwards
\r
983 if (result <= lastunixtimefloat) and (result > lastunixtimefloat-1.5) then result := lastunixtimefloat;
\r
984 lastunixtimefloat := result;
\r
987 function unixtimeint:tunixtimeint;
\r
989 result := trunc(unixtimefloat);
\r
992 {$endif} //mswindows
\r
993 {-----------------------------------------------end of platform specific}
\r
995 function wintimefloat:float;
\r
997 result := monotimefloat;
\r
1000 function irctimefloat:float;
\r
1002 result := unixtimefloat+settimebias;
\r
1005 function irctimeint:tunixtimeint;
\r
1007 result := unixtimeint+settimebias;
\r
1011 procedure settime(newtime:tunixtimeint);
\r
1015 a := irctimeint-settimebias;
\r
1016 if newtime = 0 then settimebias := 0 else settimebias := newtime-a;
\r
1018 irctime := irctimeint;
\r
1021 procedure timehandler;
\r
1023 if unixtime = 0 then init;
\r
1024 unixtime := unixtimeint;
\r
1025 irctime := unixtime+settimebias;
\r
1026 if unixtime and 63 = 0 then begin
\r
1027 {update everything, apply timezone changes, clock changes, etc}
\r
1029 timefloatbias := 0;
\r
1030 unixtime := unixtimeint;
\r
1031 irctime := irctimeint;
\r
1040 tzfile:ansistring;
\r
1042 function tzgetfilename:ansistring;
\r
1046 s,tz,tzdir:ansistring;
\r
1052 tz := getenv('TZ');
\r
1054 if (tz <> '') then begin
\r
1056 if (copy(tz,1,1) = ':') then begin
\r
1058 tz := copy(tz,2,99999);
\r
1061 if (copy(tz,1,1) <> '/') then begin
\r
1063 if (a > 1) and not ispath then begin
\r
1064 tz := copy(tz,1,a-1);
\r
1067 tzdir := getenv('TZDIR');
\r
1068 if (tzdir = '') then begin
\r
1069 tzdir := '/usr/share/zoneinfo/';
\r
1071 if (copy(tzdir,length(tzdir),1) <> '/') then tzdir := tzdir + '/';
\r
1077 {$i-}reset(t);{$i+}
\r
1078 if (ioresult = 0) then begin
\r
1086 assignfile(t,'/etc/localtime');
\r
1087 {$i-}reset(t);{$i+}
\r
1088 if (ioresult = 0) then begin
\r
1090 result := '/etc/localtime';
\r
1096 dvar=array[0..65535] of byte;
\r
1103 procedure tzinvalidate;
\r
1105 if assigned(tzcache) then freemem(tzcache);
\r
1113 function tzgetoffsetforts(ts:tunixtimeint):integer;
\r
1122 //tzstrofs:integer;
\r
1124 tzh_ttisgmtcnt:integer;
\r
1125 tzh_ttisstdcnt:integer;
\r
1126 tzh_leapcnt:integer;
\r
1127 tzh_timecnt:integer;
\r
1128 tzh_typecnt:integer;
\r
1129 tzh_charcnt:integer;
\r
1132 function getint:integer;
\r
1134 if (ofs < 0) or ((ofs + 4) > fs) then raise exception.create('getint');
\r
1135 result := (buf[ofs] shl 24) + (buf[ofs+1] shl 16) + (buf[ofs+2] shl 8) + buf[ofs+3];
\r
1139 function getint64:int64;
\r
1141 if (ofs < 0) or ((ofs + 8) > fs) then raise exception.create('getint64');
\r
1142 result := int64(getint) shl 32;
\r
1143 inc(result,cardinal(getint));
\r
1147 function getbyte:byte;
\r
1149 if (ofs < 0) or ((ofs + 1) > fs) then raise exception.create('getbyte');
\r
1150 result := buf[ofs];
\r
1158 if not assigned(tzcache) then begin
\r
1160 if (tzfile = '') then tzfile := tzgetfilename;
\r
1162 if (tzfile = '') then exit;
\r
1164 assignfile(f,tzfile);
\r
1166 {$i-}reset(f,1);{$i+}
\r
1167 if (ioresult <> 0) then begin
\r
1170 tzsize := filesize(f);
\r
1171 if (tzsize > 65536) then tzsize := 65536;
\r
1172 getmem(tzcache,tzsize);
\r
1173 blockread(f,tzcache^,tzsize);
\r
1183 if (getint <> $545a6966) then exit; // 'TZif'
\r
1184 has64 := getbyte >= $32; // '2'
\r
1188 tzh_ttisgmtcnt := getint;
\r
1189 tzh_ttisstdcnt := getint;
\r
1190 tzh_leapcnt := getint;
\r
1191 tzh_timecnt := getint;
\r
1192 tzh_typecnt := getint;
\r
1193 tzh_charcnt := getint;
\r
1195 if mode64 or (not has64) then break;
\r
1196 inc(ofs, 5 * tzh_timecnt + 6 * tzh_typecnt + 8 * tzh_leapcnt + tzh_ttisstdcnt + tzh_ttisgmtcnt + tzh_charcnt);
\r
1201 if (tzh_timecnt < 0) or (tzh_timecnt > fs) then raise exception.create('tzh_timecnt');
\r
1204 if (tzh_timecnt <> 0) then begin
\r
1205 for a := 0 to tzh_timecnt -1 do begin
\r
1206 if mode64 then t := getint64 else t := getint;
\r
1207 if (t > ts) then begin
\r
1211 if (a = tzh_timecnt -1) and (ts >= t) then index := a;
\r
1213 ofs := ofs2 + tzh_timecnt * (1 + ord(mode64)) * 4;
\r
1215 if (cardinal(ofs + index) >= fs) or (index < 0) then raise exception.create('index');
\r
1216 index := buf[ofs+index];
\r
1217 inc(ofs,tzh_timecnt);
\r
1222 if (index >= tzh_typecnt) then raise exception.create('type');
\r
1224 // writeln('ofs2 ',inttohex(ofs2,8));
\r
1225 inc(ofs,6 * index);
\r
1228 //tzisdst := getbyte;
\r
1230 //the abbreviation string
\r
1231 { tzstrofs := getbyte;
\r
1233 ofs := ofs2 + 6 * tzh_typecnt;
\r
1234 inc(ofs, tzstrofs);
\r
1238 if (a <> 0) then tzstr := tzstr + chr(a);
\r
1249 function tzgetoffset:integer;
\r
1251 {$ifndef ver1_9_4}
\r
1253 {$define above194}
\r
1256 {$ifndef above194}
\r
1262 TimeZoneInfo: TIME_ZONE_INFORMATION;
\r
1270 result := tzgetoffsetforts(unixtimeint);
\r
1271 //freepascal tzseconds is not 2038 safe
\r
1273 gettime(hh,mm,ss);
\r
1274 result := (integer(hh) * 3600 + mm * 60 + ss) - (unixtimeint mod 86400);
\r
1277 tztype := GetTimeZoneInformation(TimeZoneInfo);
\r
1278 min := TimeZoneInfo.Bias;
\r
1280 //TIME_ZONE_ID_UNKNOWN: don't add DST
\r
1281 TIME_ZONE_ID_STANDARD: inc(min, TimeZoneInfo.StandardBias);
\r
1282 TIME_ZONE_ID_DAYLIGHT: inc(min, TimeZoneInfo.DaylightBias);
\r
1284 result := min * -60;
\r
1287 while result > (14 * 3600) do dec(result,86400);
\r
1288 while result < -(14 * 3600) do inc(result,86400);
\r
1292 procedure gettimezone;
\r
1296 timezone := tzgetoffset;
\r
1298 if timezone >= 0 then timezonestr := '+' else timezonestr := '-';
\r
1299 l := abs(timezone) div 60;
\r
1300 timezonestr := timezonestr + char(l div 600 mod 10+48)+char(l div 60 mod 10+48)+':'+char(l div 10 mod 6+48)+char(l mod 10+48);
\r
1303 function timestrshort(i:tunixtimeint):string;
\r
1305 weekday:array[0..6] of string[4]=('Thu','Fri','Sat','Sun','Mon','Tue','Wed');
\r
1306 month:array[0..11] of string[4]=('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec');
\r
1308 y,m,d,h,min,sec,ms:word;
\r
1311 t := unixtoole(i+timezone);
\r
1312 decodedate(t,y,m,d);
\r
1313 decodetime(t,h,min,sec,ms);
\r
1314 result := weekday[(i+timezone) div 86400 mod 7]+' '+month[m-1]+' '+inttostr(d)+' '+
\r
1315 inttostr(h div 10)+inttostr(h mod 10)+':'+inttostr(min div 10)+inttostr(min mod 10)+':'+inttostr(sec div 10)+inttostr(sec mod 10)+' '+
\r
1319 function timestring(i:tunixtimeint):string;
\r
1321 weekday:array[0..6] of string[10]=('Thursday','Friday','Saturday','Sunday','Monday','Tuesday','Wednesday');
\r
1322 month:array[0..11] of string[10]=('January','February','March','April','May','June','July','August','September','October','November','December');
\r
1324 y,m,d,h,min,sec,ms:word;
\r
1327 t := unixtoole(i+timezone);
\r
1328 decodedate(t,y,m,d);
\r
1329 decodetime(t,h,min,sec,ms);
\r
1330 result := weekday[(i+timezone) div 86400 mod 7]+' '+month[m-1]+' '+inttostr(d)+' '+inttostr(y)+' -- '+
\r
1331 inttostr(h div 10)+inttostr(h mod 10)+':'+inttostr(min div 10)+inttostr(min mod 10)+':'+inttostr(sec div 10)+inttostr(sec mod 10)+' '+
\r
1335 function timestriso(i:tunixtimeint):string;
\r
1337 y,m,d,h,min,sec,ms:word;
\r
1340 t := unixtoole(i+timezone);
\r
1341 decodedate(t,y,m,d);
\r
1342 decodetime(t,h,min,sec,ms);
\r
1343 result := inttostr(y)+'-'+inttostr(m div 10)+inttostr(m mod 10)+'-'+inttostr(d div 10)+inttostr(d mod 10)+' '+inttostr(h div 10)+inttostr(h mod 10)+':'+inttostr(min div 10)+inttostr(min mod 10)+':'+inttostr(sec div 10)+inttostr(sec mod 10);
\r
1346 function timestrisoutc(i:float):string;
\r
1348 y,m,d,h,min,sec,ms:word;
\r
1352 t := unixtoole(i);
\r
1353 decodedate(t,y,m,d);
\r
1354 decodetime(t,h,min,sec,ms);
\r
1355 result := inttostr(y)+'-'+inttostr(m div 10)+inttostr(m mod 10)+'-'+inttostr(d div 10)+inttostr(d mod 10)+'T'+inttostr(h div 10)+inttostr(h mod 10)+':'+inttostr(min div 10)+inttostr(min mod 10)+':'+inttostr(sec div 10)+inttostr(sec mod 10);
\r
1358 result := result + '.'+
\r
1359 inttostr(trunc(fr*10) mod 10)+
\r
1360 inttostr(trunc(fr*100) mod 10)+
\r
1361 inttostr(trunc(fr*1000) mod 10)+
\r
1362 inttostr(trunc(fr*10000) mod 10)+
\r
1363 inttostr(trunc(fr*100000) mod 10)+
\r
1364 inttostr(trunc(fr*1000000) mod 10)+'Z';
\r
1368 procedure beginhightimerrate;
\r
1370 {$ifdef mswindows}timebeginperiod(1);{$endif}
\r
1373 procedure endhightimerrate;
\r
1375 {$ifdef mswindows}timeendperiod(1);{$endif}
\r
1380 {$ifdef btimehighrate}beginhightimerrate;{$endif}
\r
1381 fillchar(mmtime_driftavg,sizeof(mmtime_driftavg),0);
\r
1384 unixtime := unixtimeint;
\r
1385 irctime := irctimeint;
\r
1388 initialization init;
\r