You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

btime.pas 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593
  1. { Copyright (C) 2005 Bas Steendijk and Peter Green
  2. For conditions of distribution and use, see copyright notice in zlib_license.txt
  3. which is included in the package
  4. ----------------------------------------------------------------------------- }
  5. {
  6. this unit returns unix timestamp with seconds and microseconds (as float)
  7. works on windows/delphi, and on freepascal on unix.
  8. }
  9. unit btime;
  10. interface
  11. type
  12. float=extended;
  13. const
  14. colorburst=39375000/11; {3579545.4545....}
  15. var
  16. timezone:integer;
  17. timezonestr:string;
  18. irctime,unixtime:integer;
  19. tickcount:integer;
  20. settimebias:integer;
  21. performancecountfreq:extended;
  22. function irctimefloat:float;
  23. function irctimeint:integer;
  24. function unixtimefloat:float;
  25. function unixtimeint:integer;
  26. function wintimefloat:float;
  27. procedure settime(newtime:integer);
  28. procedure gettimezone;
  29. procedure timehandler;
  30. procedure init;
  31. function timestring(i:integer):string;
  32. function timestrshort(i:integer):string;
  33. {$ifdef win32}
  34. function unixtimefloat_systemtime:float;
  35. {$endif}
  36. function oletounixfloat(t:float):float;
  37. function oletounix(t:tdatetime):integer;
  38. function unixtoole(i:integer):tdatetime;
  39. {$ifdef win32}
  40. function mmtimefloat:float;
  41. function qpctimefloat:float;
  42. {$endif}
  43. const
  44. mmtime_driftavgsize=32;
  45. mmtime_warmupnum=4;
  46. mmtime_warmupcyclelength=15;
  47. var
  48. //this flag is to be set when btime has been running long enough to stabilise
  49. warmup_finished:boolean;
  50. timefloatbias:float;
  51. ticks_freq:float=0;
  52. ticks_freq2:float=0;
  53. ticks_freq_known:boolean=false;
  54. lastunixtimefloat:float=0;
  55. lastsynctime:float=0;
  56. lastsyncbias:float=0;
  57. mmtime_last:integer=0;
  58. mmtime_wrapadd:float;
  59. mmtime_lastsyncmm:float=0;
  60. mmtime_lastsyncqpc:float=0;
  61. mmtime_drift:float=1;
  62. mmtime_lastresult:float;
  63. mmtime_nextdriftcorrection:float;
  64. mmtime_driftavg:array[0..mmtime_driftavgsize] of float;
  65. mmtime_synchedqpc:boolean;
  66. mmtime_prev_drift:float;
  67. mmtime_prev_lastsyncmm:float;
  68. mmtime_prev_lastsyncqpc:float;
  69. implementation
  70. {$ifdef fpc}
  71. {$mode delphi}
  72. {$endif}
  73. uses
  74. {$ifdef UNIX}
  75. {$ifdef VER1_0}
  76. linux,
  77. {$else}
  78. baseunix,unix,unixutil, {needed for 2.0.2}
  79. {$endif}
  80. {$else}
  81. windows,unitsettc,mmsystem,
  82. {$endif}
  83. sysutils;
  84. {$include unixstuff.inc}
  85. const
  86. daysdifference=25569;
  87. function oletounixfloat(t:float):float;
  88. begin
  89. t := (t - daysdifference) * 86400;
  90. result := t;
  91. end;
  92. function oletounix(t:tdatetime):integer;
  93. begin
  94. result := trunc(oletounixfloat(t));
  95. end;
  96. function unixtoole(i:integer):tdatetime;
  97. begin
  98. result := ((i)/86400)+daysdifference;
  99. end;
  100. const
  101. highdwordconst=65536.0 * 65536.0;
  102. function utrunc(f:float):integer;
  103. {converts float to integer, in 32 bits unsigned range}
  104. begin
  105. if f >= (highdwordconst/2) then f := f - highdwordconst;
  106. result := trunc(f);
  107. end;
  108. function uinttofloat(i:integer):float;
  109. {converts 32 bits unsigned integer to float}
  110. begin
  111. result := i;
  112. if result < 0 then result := result + highdwordconst;
  113. end;
  114. {$ifdef unix}
  115. {-----------------------------------------*nix/freepascal code to read time }
  116. function unixtimefloat:float;
  117. var
  118. tv:ttimeval;
  119. begin
  120. gettimeofday(tv);
  121. result := tv.tv_sec+(tv.tv_usec/1000000);
  122. end;
  123. function wintimefloat:extended;
  124. begin
  125. result := unixtimefloat;
  126. end;
  127. function unixtimeint:integer;
  128. var
  129. tv:ttimeval;
  130. begin
  131. gettimeofday(tv);
  132. result := tv.tv_sec;
  133. end;
  134. {$else} {delphi 3}
  135. {------------------------------ windows/delphi code to read time}
  136. {
  137. time float: gettickcount
  138. resolution: 9x: ~55 ms NT: 1/64th of a second
  139. guarantees: continuous without any jumps
  140. frequency base: same as system clock.
  141. epoch: system boot
  142. note: if called more than once per 49.7 days, 32 bits wrapping is compensated for and it keeps going on.
  143. note: i handle the timestamp as signed integer, but with the wrap compensation that works as well, and is faster
  144. }
  145. function mmtimefloat:float;
  146. const
  147. wrapduration=highdwordconst * 0.001;
  148. var
  149. i:integer;
  150. begin
  151. i := gettickcount; {timegettime}
  152. if i < mmtime_last then begin
  153. mmtime_wrapadd := mmtime_wrapadd + wrapduration;
  154. end;
  155. mmtime_last := i;
  156. result := mmtime_wrapadd + i * 0.001;
  157. if (ticks_freq <> 0) and ticks_freq_known then result := int((result / ticks_freq)+0.5) * ticks_freq; //turn the float into an exact multiple of 1/64th sec to improve accuracy of things using this
  158. end;
  159. procedure measure_ticks_freq;
  160. var
  161. f,g:float;
  162. o:tosversioninfo;
  163. isnt:boolean;
  164. is9x:boolean;
  165. begin
  166. if (performancecountfreq = 0) then qpctimefloat;
  167. ticks_freq_known := false;
  168. settc;
  169. f := mmtimefloat;
  170. repeat g := mmtimefloat until g > f;
  171. unsettc;
  172. f := g - f;
  173. fillchar(o,sizeof(o),0);
  174. o.dwOSVersionInfoSize := sizeof(o);
  175. getversionex(o);
  176. isnt := o.dwPlatformId = VER_PLATFORM_WIN32_NT;
  177. is9x := o.dwPlatformId = VER_PLATFORM_WIN32_WINDOWS;
  178. ticks_freq2 := f;
  179. mmtime_synchedqpc := false;
  180. {
  181. NT 64 Hz
  182. identify mode as: nt64
  183. QPC rate: either 3579545 or TSC freq
  184. QPC synched to gettickcount: no
  185. duration between 2 ticks is constant: yes
  186. gettickcount tick duration: 64 Hz
  187. }
  188. if (f >= 0.014) and (f <= 0.018) and isnt then begin
  189. ticks_freq_known := true;
  190. ticks_freq := 1/64;
  191. mmtime_synchedqpc := false;
  192. end;
  193. {
  194. NT 100 Hz
  195. identify mode as: nt100
  196. QPC rate: 1193182
  197. QPC synched to gettickcount: yes
  198. duration between 2 ticks is constant: no?
  199. gettickcount tick duration: ~99.85 Hz
  200. }
  201. if (performancecountfreq = 1193182) and (f >= 0.008) and (f <= 0.012) and isnt then begin
  202. ticks_freq_known := true;
  203. ticks_freq2 := 11949 / (colorburst / 3);
  204. // ticks_freq2 := 11949 / 1193182;
  205. ticks_freq := 0;
  206. {the ticks freq should be very close to the real one but if it's not exact, it will cause drift and correction jumps}
  207. mmtime_synchedqpc := true;
  208. end;
  209. {9x}
  210. if (performancecountfreq = 1193182) and (g >= 0.050) and (g <= 0.060) then begin
  211. ticks_freq_known := true;
  212. ticks_freq := 65536 / (colorburst / 3);
  213. mmtime_synchedqpc := true;
  214. end;
  215. ticks_freq_known := true;
  216. if ticks_freq <> 0 then ticks_freq2 := ticks_freq;
  217. // writeln(formatfloat('0.000000',ticks_freq));
  218. end;
  219. {
  220. time float: QueryPerformanceCounter
  221. resolution: <1us
  222. guarantees: can have forward jumps depending on hardware. can have forward and backwards jitter on dual core.
  223. frequency base: on NT, not the system clock, drifts compared to it.
  224. epoch: system boot
  225. }
  226. function qpctimefloat:extended;
  227. var
  228. p:packed record
  229. lowpart:longint;
  230. highpart:longint
  231. end;
  232. p2:tlargeinteger absolute p;
  233. e:extended;
  234. begin
  235. if performancecountfreq = 0 then begin
  236. QueryPerformancefrequency(p2);
  237. e := p.lowpart;
  238. if e < 0 then e := e + highdwordconst;
  239. performancecountfreq := ((p.highpart*highdwordconst)+e);
  240. end;
  241. queryperformancecounter(p2);
  242. e := p.lowpart;
  243. if e < 0 then e := e + highdwordconst;
  244. result := ((p.highpart*highdwordconst)+e)/performancecountfreq;
  245. end;
  246. {
  247. time float: QPC locked to gettickcount
  248. resolution: <1us
  249. guarantees: continuous without any jumps
  250. frequency base: same as system clock.
  251. epoch: system boot
  252. }
  253. function mmqpctimefloat:float;
  254. const
  255. maxretries=5;
  256. margin=0.002;
  257. var
  258. jump:float;
  259. mm,f,qpc,newdrift,f1,f2:float;
  260. qpcjumped:boolean;
  261. a,b,c:integer;
  262. retrycount:integer;
  263. begin
  264. if not ticks_freq_known then measure_ticks_freq;
  265. retrycount := maxretries;
  266. qpc := qpctimefloat;
  267. mm := mmtimefloat;
  268. f := (qpc - mmtime_lastsyncqpc) * mmtime_drift + mmtime_lastsyncmm;
  269. //writeln('XXXX ',formatfloat('0.000000',qpc-mm));
  270. qpcjumped := ((f-mm) > ticks_freq2+margin) or ((f-mm) < -margin);
  271. // if qpcjumped then writeln('qpc jumped ',(f-mm));
  272. if ((qpc > mmtime_nextdriftcorrection) and not mmtime_synchedqpc) or qpcjumped then begin
  273. mmtime_nextdriftcorrection := qpc + 1;
  274. repeat
  275. mmtime_prev_drift := mmtime_drift;
  276. mmtime_prev_lastsyncmm := mmtime_lastsyncmm;
  277. mmtime_prev_lastsyncqpc := mmtime_lastsyncqpc;
  278. mm := mmtimefloat;
  279. dec(retrycount);
  280. settc;
  281. result := qpctimefloat;
  282. f := mmtimefloat;
  283. repeat
  284. if f = mm then result := qpctimefloat;
  285. f := mmtimefloat
  286. until f > mm;
  287. qpc := qpctimefloat;
  288. unsettc;
  289. if (qpc > result + 0.0001) then begin
  290. continue;
  291. end;
  292. mm := f;
  293. if (mmtime_lastsyncqpc <> 0) and not qpcjumped then begin
  294. newdrift := (mm - mmtime_lastsyncmm) / (qpc - mmtime_lastsyncqpc);
  295. mmtime_drift := newdrift;
  296. { writeln('raw drift: ',formatfloat('0.00000000',mmtime_drift));}
  297. move(mmtime_driftavg[0],mmtime_driftavg[1],sizeof(mmtime_driftavg[0])*high(mmtime_driftavg));
  298. mmtime_driftavg[0] := mmtime_drift;
  299. { write('averaging drift ',formatfloat('0.00000000',mmtime_drift),' -> ');}
  300. { mmtime_drift := 0;}
  301. b := 0;
  302. for a := 0 to high(mmtime_driftavg) do begin
  303. if mmtime_driftavg[a] <> 0 then inc(b);
  304. { mmtime_drift := mmtime_drift + mmtime_driftavg[a];}
  305. end;
  306. { mmtime_drift := mmtime_drift / b;}
  307. 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;
  308. mmtime_nextdriftcorrection := qpc + a;
  309. if (b >= 2) then warmup_finished := true;
  310. { writeln(formatfloat('0.00000000',mmtime_drift));}
  311. if mmtime_synchedqpc then mmtime_drift := 1;
  312. end;
  313. mmtime_lastsyncqpc := qpc;
  314. mmtime_lastsyncmm := mm;
  315. { writeln(formatfloat('0.00000000',mmtime_drift));}
  316. break;
  317. until false;
  318. qpc := qpctimefloat;
  319. result := (qpc - mmtime_lastsyncqpc) * mmtime_drift + mmtime_lastsyncmm;
  320. f := (qpc - mmtime_prev_lastsyncqpc) * mmtime_prev_drift + mmtime_prev_lastsyncmm;
  321. jump := result-f;
  322. {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)));}
  323. f := result;
  324. end;
  325. result := f;
  326. if (result < mmtime_lastresult) then result := mmtime_lastresult + 0.000001;
  327. mmtime_lastresult := result;
  328. end;
  329. { free pascals tsystemtime is incomaptible with windows api calls
  330. so we declare it ourselves - plugwash
  331. }
  332. {$ifdef fpc}
  333. type
  334. TSystemTime = record
  335. wYear: Word;
  336. wMonth: Word;
  337. wDayOfWeek: Word;
  338. wDay: Word;
  339. wHour: Word;
  340. wMinute: Word;
  341. wSecond: Word;
  342. wMilliseconds: Word;
  343. end;
  344. {$endif}
  345. function Date_utc: extended;
  346. var
  347. SystemTime: TSystemTime;
  348. begin
  349. {$ifdef fpc}
  350. GetsystemTime(@SystemTime);
  351. {$else}
  352. GetsystemTime(SystemTime);
  353. {$endif}
  354. with SystemTime do Result := EncodeDate(wYear, wMonth, wDay);
  355. end;
  356. function Time_utc: extended;
  357. var
  358. SystemTime: TSystemTime;
  359. begin
  360. {$ifdef fpc}
  361. GetsystemTime(@SystemTime);
  362. {$else}
  363. GetsystemTime(SystemTime);
  364. {$endif}
  365. with SystemTime do
  366. Result := EncodeTime(wHour, wMinute, wSecond, wMilliSeconds);
  367. end;
  368. function Now_utc: extended;
  369. begin
  370. Result := round(Date_utc) + Time_utc;
  371. end;
  372. function unixtimefloat_systemtime:float;
  373. begin
  374. {result := oletounixfloat(now_utc);}
  375. {this method gives exactly the same result with extended precision, but is less sensitive to float rounding in theory}
  376. result := oletounixfloat(int(date_utc+0.5))+time_utc*86400;
  377. end;
  378. function wintimefloat:extended;
  379. begin
  380. result := mmqpctimefloat;
  381. end;
  382. function unixtimefloat:float;
  383. const
  384. margin = 0.0012;
  385. var
  386. f,g,h:float;
  387. begin
  388. result := wintimefloat+timefloatbias;
  389. f := result-unixtimefloat_systemtime;
  390. if ((f > ticks_freq2+margin) or (f < -margin)) or (timefloatbias = 0) then begin
  391. // writeln('unixtimefloat init');
  392. f := unixtimefloat_systemtime;
  393. settc;
  394. repeat g := unixtimefloat_systemtime; h := wintimefloat until g > f;
  395. unsettc;
  396. timefloatbias := g-h;
  397. result := unixtimefloat;
  398. end;
  399. {for small changes backwards, guarantee no steps backwards}
  400. if (result <= lastunixtimefloat) and (result > lastunixtimefloat-1.5) then result := lastunixtimefloat + 0.0000001;
  401. lastunixtimefloat := result;
  402. end;
  403. function unixtimeint:integer;
  404. begin
  405. result := trunc(unixtimefloat);
  406. end;
  407. {$endif}
  408. {-----------------------------------------------end of platform specific}
  409. function irctimefloat:float;
  410. begin
  411. result := unixtimefloat+settimebias;
  412. end;
  413. function irctimeint:integer;
  414. begin
  415. result := unixtimeint+settimebias;
  416. end;
  417. procedure settime(newtime:integer);
  418. var
  419. a:integer;
  420. begin
  421. a := irctimeint-settimebias;
  422. if newtime = 0 then settimebias := 0 else settimebias := newtime-a;
  423. irctime := irctimeint;
  424. end;
  425. procedure timehandler;
  426. begin
  427. if unixtime = 0 then init;
  428. unixtime := unixtimeint;
  429. irctime := irctimeint;
  430. if unixtime and 63 = 0 then begin
  431. {update everything, apply timezone changes, clock changes, etc}
  432. gettimezone;
  433. timefloatbias := 0;
  434. unixtime := unixtimeint;
  435. irctime := irctimeint;
  436. end;
  437. end;
  438. procedure gettimezone;
  439. var
  440. {$ifdef UNIX}
  441. {$ifndef ver1_9_4}
  442. {$ifndef ver1_0}
  443. {$define above194}
  444. {$endif}
  445. {$endif}
  446. {$ifndef above194}
  447. hh,mm,ss:word;
  448. {$endif}
  449. {$endif}
  450. l:integer;
  451. begin
  452. {$ifdef UNIX}
  453. {$ifdef above194}
  454. timezone := tzseconds;
  455. {$else}
  456. gettime(hh,mm,ss);
  457. timezone := (longint(hh) * 3600 + mm * 60 + ss) - (unixtimeint mod 86400);
  458. {$endif}
  459. {$else}
  460. timezone := round((now-now_utc)*86400);
  461. {$endif}
  462. while timezone > 43200 do dec(timezone,86400);
  463. while timezone < -43200 do inc(timezone,86400);
  464. if timezone >= 0 then timezonestr := '+' else timezonestr := '-';
  465. l := abs(timezone) div 60;
  466. 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);
  467. end;
  468. function timestrshort(i:integer):string;
  469. const
  470. weekday:array[0..6] of string[4]=('Thu','Fri','Sat','Sun','Mon','Tue','Wed');
  471. month:array[0..11] of string[4]=('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec');
  472. var
  473. y,m,d,h,min,sec,ms:word;
  474. t:tdatetime;
  475. begin
  476. t := unixtoole(i+timezone);
  477. decodedate(t,y,m,d);
  478. decodetime(t,h,min,sec,ms);
  479. result := weekday[(i+timezone) div 86400 mod 7]+' '+month[m-1]+' '+inttostr(d)+' '+
  480. inttostr(h div 10)+inttostr(h mod 10)+':'+inttostr(min div 10)+inttostr(min mod 10)+':'+inttostr(sec div 10)+inttostr(sec mod 10)+' '+
  481. inttostr(y);
  482. end;
  483. function timestring(i:integer):string;
  484. const
  485. weekday:array[0..6] of string[10]=('Thursday','Friday','Saturday','Sunday','Monday','Tuesday','Wednesday');
  486. month:array[0..11] of string[10]=('January','February','March','April','May','June','July','August','September','October','November','December');
  487. var
  488. y,m,d,h,min,sec,ms:word;
  489. t:tdatetime;
  490. begin
  491. t := unixtoole(i+timezone);
  492. decodedate(t,y,m,d);
  493. decodetime(t,h,min,sec,ms);
  494. result := weekday[(i+timezone) div 86400 mod 7]+' '+month[m-1]+' '+inttostr(d)+' '+inttostr(y)+' -- '+
  495. inttostr(h div 10)+inttostr(h mod 10)+':'+inttostr(min div 10)+inttostr(min mod 10)+':'+inttostr(sec div 10)+inttostr(sec mod 10)+' '+
  496. timezonestr;
  497. end;
  498. procedure init;
  499. begin
  500. {$ifdef win32}timebeginperiod(1);{$endif} //ensure stable unchanging clock
  501. fillchar(mmtime_driftavg,sizeof(mmtime_driftavg),0);
  502. settimebias := 0;
  503. gettimezone;
  504. unixtime := unixtimeint;
  505. irctime := irctimeint;
  506. end;
  507. initialization init;
  508. end.