tzenv.inc 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926
  1. {
  2. This file is part of the Free Pascal run time library.
  3. Copyright (c) 2020 by Tomas Hajny,
  4. member of the Free Pascal development team.
  5. Support routines for calculation of local timezone and DST time
  6. offset based on information provided in the environment variable TZ.
  7. There are various ways for specifying the timezone details using the
  8. TZ variable. The more information is provided, the better results.
  9. As an example, the following setting provides full information
  10. including details for DST on/off switching date and time:
  11. TZ=CET-1CEST,3,-1,0,7200,10,-1,0,10800,3600
  12. (CET timezone is 1 hour in advance from UTC, there is DST called CEST,
  13. DST starts on the last Sunday of March at 2am and finishes on the last
  14. Sunday of October at 3am, the DST difference is 1 hour).
  15. However, this is by no means the only supported syntax.
  16. See the file COPYING.FPC, included in this distribution,
  17. for details about the copyright.
  18. This program is distributed in the hope that it will be useful,
  19. but WITHOUT ANY WARRANTY; without even the implied warranty of
  20. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  21. **********************************************************************}
  22. type
  23. DSTSpecType = (DSTMonthWeekDay, DSTMonthDay, DSTJulian, DSTJulianX);
  24. const
  25. TZEnvName = 'TZ';
  26. {$IFDEF OS2}
  27. EMXTZEnvName = 'EMXTZ';
  28. {$ENDIF OS2}
  29. MaxSecond = 86399;
  30. (* The following values differing from the defaults *)
  31. (* below are not used at the moment. *)
  32. USDSTStartMonth = 3;
  33. USDSTStartWeek = 2;
  34. USDSTEndMonth = 11;
  35. USDSTEndWeek = 1;
  36. EUDSTStartMonth = 3;
  37. EUDSTStartWeek = -1;
  38. (* Initialized to default values, updated after a call to InitTZ *)
  39. TZName: string = '';
  40. TZDSTName: string = '';
  41. TZOffset: longint = 0;
  42. TZOffsetMin: longint = 0;
  43. DSTOffset: longint = 0;
  44. DSTOffsetMin: longint = 0;
  45. DSTStartMonth: byte = 4;
  46. DSTStartWeek: shortint = 1;
  47. DSTStartDay: word = 0;
  48. DSTStartSec: cardinal = 7200;
  49. DSTEndMonth: byte = 10;
  50. DSTEndWeek: shortint = -1;
  51. DSTEndDay: word = 0;
  52. DSTEndSec: cardinal = 10800;
  53. DSTStartSpecType: DSTSpecType = DSTMonthWeekDay;
  54. DSTEndSpecType: DSTSpecType = DSTMonthWeekDay;
  55. (* The following variables are initialized after a call to InitTZ. *)
  56. var
  57. RealDSTStartMonth, RealDSTStartDay, RealDSTEndMonth, RealDSTEndDay: byte;
  58. const
  59. MonthEnds: array [1..12] of word =
  60. (31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365);
  61. function LeapDay (Year: word): byte; inline;
  62. begin
  63. if IsLeapYear (Year) then
  64. LeapDay := 1
  65. else
  66. LeapDay := 0;
  67. end;
  68. function FirstDay (MM: byte; Y: word; Mo: word; D: word; WD: word): byte;
  69. inline;
  70. var
  71. DD: longint;
  72. begin
  73. if MM < Mo then
  74. begin
  75. DD := D + MonthEnds [Pred (Mo)];
  76. if MM > 1 then
  77. Dec (DD, MonthEnds [Pred (MM)]);
  78. if (MM <= 2) and (Mo > 2) then
  79. Inc (DD, LeapDay (Y));
  80. end
  81. else
  82. if MM > Mo then
  83. begin
  84. DD := - MonthDays [false, Mo] + D - MonthEnds [Pred (MM)]
  85. + MonthEnds [Mo];
  86. if (Mo <= 2) and (MM > 2) then
  87. Dec (DD, LeapDay (Y));
  88. end
  89. else
  90. (* M = MM *)
  91. DD := D;
  92. DD := WD - DD mod 7 + 1;
  93. if DD < 0 then
  94. FirstDay := DD + 7
  95. else
  96. FirstDay := DD mod 7;
  97. end;
  98. procedure UpdateTimeWithOffset (var SystemTime: TSystemTime; Offset: longint);
  99. inline;
  100. var
  101. Y: longint;
  102. Mo: longint;
  103. D: longint;
  104. WD: word;
  105. H: longint;
  106. Mi: longint;
  107. begin
  108. with SystemTime do
  109. begin
  110. Y := Year;
  111. Mo := Month;
  112. D := Day;
  113. WD := DayOfWeek;
  114. H := Hour;
  115. Mi := Minute;
  116. end;
  117. Mi := Mi + (Offset mod 60);
  118. H := H + (Offset div 60);
  119. if Mi < 0 then
  120. begin
  121. Inc (Mi, 60);
  122. Dec (H);
  123. end;
  124. if H < 0 then
  125. begin
  126. Inc (H, 24);
  127. if WD = 0 then
  128. WD := 6
  129. else
  130. Dec (WD);
  131. if D = 1 then
  132. begin
  133. if Mo = 1 then
  134. begin
  135. Dec (Y);
  136. Mo := 12;
  137. end
  138. else
  139. Dec (Mo);
  140. D := MonthDays [IsLeapYear (Y), Mo];
  141. end
  142. else
  143. Dec (D);
  144. end
  145. else
  146. begin
  147. if Mi > 59 then
  148. begin
  149. Dec (Mi, 60);
  150. Inc (H);
  151. end;
  152. if H > 23 then
  153. begin
  154. Dec (H, 24);
  155. if WD = 6 then
  156. WD := 0
  157. else
  158. Inc (WD);
  159. if D = MonthDays [IsLeapYear (Y), Mo] then
  160. begin
  161. D := 1;
  162. if Mo = 12 then
  163. begin
  164. Inc (Y);
  165. Mo := 1;
  166. end
  167. else
  168. Inc (Mo);
  169. end
  170. else
  171. Inc (D);
  172. end;
  173. end;
  174. with SystemTime do
  175. begin
  176. Year := Y;
  177. Month := Mo;
  178. Day := D;
  179. DayOfWeek := WD;
  180. Hour := H;
  181. Minute := Mi;
  182. end;
  183. end;
  184. function InDST (const Time: TSystemTime; const InputIsUTC: boolean): boolean;
  185. var
  186. AfterDSTStart, BeforeDSTEnd: boolean;
  187. Y: longint;
  188. Mo: longint;
  189. D: longint;
  190. WD: longint;
  191. Second: longint;
  192. begin
  193. InDST := false;
  194. if DSTOffset <> TZOffset then
  195. begin
  196. Second := longint (Time.Hour) * 3600 + Time.Minute * 60 + Time.Second;
  197. Y := Time.Year;
  198. Mo := Time.Month;
  199. D := Time.Day;
  200. if InputIsUTC and (TZOffset <> 0) then
  201. begin
  202. Second := Second - TZOffset;
  203. if Second < 0 then
  204. begin
  205. Second := Second + MaxSecond + 1;
  206. if D = 1 then
  207. begin
  208. if Mo = 1 then
  209. begin
  210. Dec (Y);
  211. Mo := 12;
  212. end
  213. else
  214. Dec (Mo);
  215. D := MonthDays [IsLeapYear (Y), Mo];
  216. end
  217. else
  218. Dec (D);
  219. end
  220. else
  221. if Second > MaxSecond then
  222. begin
  223. Second := Second - MaxSecond - 1;
  224. if D = MonthDays [IsLeapYear (Y), Mo] then
  225. begin
  226. D := 1;
  227. if Mo = 12 then
  228. begin
  229. Inc (Y);
  230. Mo := 1;
  231. end
  232. else
  233. Inc (Mo);
  234. end
  235. else
  236. Inc (D);
  237. end;
  238. end;
  239. if Mo < RealDSTStartMonth then
  240. AfterDSTStart := false
  241. else
  242. if Mo > RealDSTStartMonth then
  243. AfterDSTStart := true
  244. else
  245. if D < RealDSTStartDay then
  246. AfterDSTStart := false
  247. else
  248. if D > RealDSTStartDay then
  249. AfterDSTStart := true
  250. else
  251. AfterDSTStart := Second > DSTStartSec;
  252. if Mo > RealDSTEndMonth then
  253. BeforeDSTEnd := false
  254. else
  255. if Mo < RealDSTEndMonth then
  256. BeforeDSTEnd := true
  257. else
  258. if D > RealDSTEndDay then
  259. BeforeDSTEnd := false
  260. else
  261. if D < RealDSTEndDay then
  262. BeforeDSTEnd := true
  263. else
  264. BeforeDSTEnd := Second < DSTEndSec;
  265. InDST := AfterDSTStart and BeforeDSTEnd;
  266. end;
  267. end;
  268. function InDST: boolean; inline;
  269. var
  270. SystemTime: TSystemTime;
  271. begin
  272. InDST := false;
  273. if DSTOffset <> TZOffset then
  274. begin
  275. GetLocalTime (SystemTime);
  276. InDST := InDST (SystemTime, false);
  277. end;
  278. end;
  279. procedure InitTZ0; inline;
  280. var
  281. TZ, S: string;
  282. I, J: byte;
  283. Err: longint;
  284. GnuFmt: boolean;
  285. ADSTStartMonth: byte;
  286. ADSTStartWeek: shortint;
  287. ADSTStartDay: word;
  288. ADSTStartSec: cardinal;
  289. ADSTEndMonth: byte;
  290. ADSTEndWeek: shortint;
  291. ADSTEndDay: word;
  292. ADSTEndSec: cardinal;
  293. ADSTStartSpecType: DSTSpecType;
  294. ADSTEndSpecType: DSTSpecType;
  295. ADSTChangeSec: cardinal;
  296. function ParseOffset (OffStr: string): longint;
  297. (* Parse time offset given as [-|+]HH[:MI[:SS]] and return in seconds *)
  298. var
  299. TZShiftHH, TZShiftDir: shortint;
  300. TZShiftMI, TZShiftSS: byte;
  301. N1, N2: byte;
  302. begin
  303. TZShiftHH := 0;
  304. TZShiftMI := 0;
  305. TZShiftSS := 0;
  306. TZShiftDir := 1;
  307. N1 := 1;
  308. while (N1 <= Length (OffStr)) and (OffStr [N1] <> ':') do
  309. Inc (N1);
  310. Val (Copy (OffStr, 1, Pred (N1)), TZShiftHH, Err);
  311. if (Err = 0) and (TZShiftHH >= -24) and (TZShiftHH <= 23) then
  312. begin
  313. (* Normalize the hour offset to -12..11 if necessary *)
  314. if TZShiftHH > 11 then
  315. Dec (TZShiftHH, 24) else
  316. if TZShiftHH < -12 then
  317. Inc (TZShiftHH, 24);
  318. if TZShiftHH < 0 then
  319. TZShiftDir := -1;
  320. if (N1 <= Length (OffStr)) then
  321. begin
  322. N2 := Succ (N1);
  323. while (N2 <= Length (OffStr)) and (OffStr [N2] <> ':') do
  324. Inc (N2);
  325. Val (Copy (OffStr, Succ (N1), N2 - N1), TZShiftMI, Err);
  326. if (Err = 0) and (TZShiftMI <= 59) then
  327. begin
  328. if (N2 <= Length (OffStr)) then
  329. begin
  330. Val (Copy (OffStr, Succ (N2), Length (OffStr) - N2), TZShiftSS, Err);
  331. if (Err <> 0) or (TZShiftSS > 59) then
  332. TZShiftSS := 0;
  333. end
  334. end
  335. else
  336. TZShiftMI := 0;
  337. end;
  338. end
  339. else
  340. TZShiftHH := 0;
  341. ParseOffset := longint (TZShiftHH) * 3600 +
  342. TZShiftDir * (longint (TZShiftMI) * 60 + TZShiftSS);
  343. end;
  344. begin
  345. TZ := GetEnvironmentVariable (TZEnvName);
  346. {$IFDEF OS2}
  347. if TZ = '' then
  348. TZ := GetEnvironmentVariable (EMXTZEnvName);
  349. {$ENDIF OS2}
  350. if TZ <> '' then
  351. begin
  352. TZ := Upcase (TZ);
  353. (* Timezone name *)
  354. I := 1;
  355. while (I <= Length (TZ)) and (TZ [I] in ['A'..'Z']) do
  356. Inc (I);
  357. TZName := Copy (TZ, 1, Pred (I));
  358. if I <= Length (TZ) then
  359. begin
  360. (* Timezone shift *)
  361. J := Succ (I);
  362. while (J <= Length (TZ)) and not (TZ [J] in ['A'..'Z']) do
  363. Inc (J);
  364. TZOffset := ParseOffset (Copy (TZ, I, J - I));
  365. (* DST timezone name *)
  366. I := J;
  367. while (J <= Length (TZ)) and (TZ [J] in ['A'..'Z']) do
  368. Inc (J);
  369. if J > I then
  370. begin
  371. TZDSTName := Copy (TZ, I, J - I);
  372. (* DST timezone name provided; if equal to the standard timezone *)
  373. (* name then DSTOffset is set to be equal to TZOffset by default, *)
  374. (* otherwise it is set to TZOffset - 3600 seconds. *)
  375. if TZDSTName <> TZName then
  376. DSTOffset := -3600 + TZOffset
  377. else
  378. DSTOffset := TZOffset;
  379. end
  380. else
  381. begin
  382. TZDSTName := TZName;
  383. (* No DST timezone name provided => DSTOffset is equal to TZOffset *)
  384. DSTOffset := TZOffset;
  385. end;
  386. if J <= Length (TZ) then
  387. begin
  388. (* Check if DST offset is specified here; *)
  389. (* if not, default value set above is used. *)
  390. if TZ [J] <> ',' then
  391. begin
  392. I := J;
  393. Inc (J);
  394. while (J <= Length (TZ)) and (TZ [J] <> ',') do
  395. Inc (J);
  396. DSTOffset := ParseOffset (Copy (TZ, I, J - I));
  397. end;
  398. if J < Length (TZ) then
  399. begin
  400. Inc (J);
  401. (* DST switching details *)
  402. case TZ [J] of
  403. 'M':
  404. begin
  405. (* Mmonth.week.dayofweek[/StartHour] *)
  406. ADSTStartSpecType := DSTMonthWeekDay;
  407. if J >= Length (TZ) then
  408. Exit;
  409. Inc (J);
  410. I := J;
  411. while (J <= Length (TZ)) and not (TZ [J] in ['.', ',', '/']) do
  412. Inc (J);
  413. if (J >= Length (TZ)) or (TZ [J] <> '.') then
  414. Exit;
  415. Val (Copy (TZ, I, J - I), ADSTStartMonth, Err);
  416. if (Err > 0) or (ADSTStartMonth > 12) then
  417. Exit;
  418. Inc (J);
  419. I := J;
  420. while (J <= Length (TZ)) and not (TZ [J] in ['.', ',', '/']) do
  421. Inc (J);
  422. if (J >= Length (TZ)) or (TZ [J] <> '.') then
  423. Exit;
  424. Val (Copy (TZ, I, J - I), ADSTStartWeek, Err);
  425. if (Err > 0) or (ADSTStartWeek < 1) or (ADSTStartWeek > 5) then
  426. Exit;
  427. Inc (J);
  428. I := J;
  429. while (J <= Length (TZ)) and not (TZ [J] in [',', '/']) do
  430. Inc (J);
  431. Val (Copy (TZ, I, J - I), ADSTStartDay, Err);
  432. if (Err > 0) or (ADSTStartDay > 6) or (J >= Length (TZ)) then
  433. Exit;
  434. if TZ [J] = '/' then
  435. begin
  436. Inc (J);
  437. I := J;
  438. while (J <= Length (TZ)) and (TZ [J] <> ',') do
  439. Inc (J);
  440. Val (Copy (TZ, I, J - I), ADSTStartSec, Err);
  441. if (Err > 0) or (ADSTStartSec > MaxSecond) or (J >= Length (TZ))
  442. then
  443. Exit
  444. else
  445. ADSTStartSec := ADSTStartSec * 3600;
  446. end
  447. else
  448. (* Use the preset default *)
  449. ADSTStartSec := DSTStartSec;
  450. Inc (J);
  451. end;
  452. 'J':
  453. begin
  454. (* Jjulianday[/StartHour] *)
  455. ADSTStartSpecType := DSTJulianX;
  456. if J >= Length (TZ) then
  457. Exit;
  458. Inc (J);
  459. I := J;
  460. while (J <= Length (TZ)) and not (TZ [J] in [',', '/']) do
  461. Inc (J);
  462. Val (Copy (TZ, I, J - I), ADSTStartDay, Err);
  463. if (Err > 0) or (ADSTStartDay = 0) or (ADSTStartDay > 365)
  464. or (J >= Length (TZ)) then
  465. Exit;
  466. if TZ [J] = '/' then
  467. begin
  468. Inc (J);
  469. I := J;
  470. while (J <= Length (TZ)) and (TZ [J] <> ',') do
  471. Inc (J);
  472. Val (Copy (TZ, I, J - I), ADSTStartSec, Err);
  473. if (Err > 0) or (ADSTStartSec > MaxSecond) or (J >= Length (TZ))
  474. then
  475. Exit
  476. else
  477. ADSTStartSec := ADSTStartSec * 3600;
  478. end
  479. else
  480. (* Use the preset default *)
  481. ADSTStartSec := DSTStartSec;
  482. Inc (J);
  483. end
  484. else
  485. begin
  486. (* Check the used format first - GNU libc / GCC / EMX expect *)
  487. (* "NameOffsetDstname[Dstoffset],Start[/StartHour],End[/EndHour]"; *)
  488. (* if more than one comma (',') is found, the following format is assumed: *)
  489. (* "NameOffsetDstname[Dstoffset],StartMonth,StartWeek,StartDay,StartSecond, *)
  490. (* EndMonth,EndWeek,EndDay,EndSecond,DSTDifference". *)
  491. I := J;
  492. while (J <= Length (TZ)) and (TZ [J] <> ',') do
  493. Inc (J);
  494. S := Copy (TZ, I, J - I);
  495. if J < Length (TZ) then
  496. begin
  497. Inc (J);
  498. I := J;
  499. while (J <= Length (TZ)) and (TZ [J] <> ',') do
  500. Inc (J);
  501. GnuFmt := J > Length (TZ);
  502. end
  503. else
  504. Exit;
  505. if GnuFmt then
  506. begin
  507. ADSTStartSpecType := DSTJulian;
  508. J := Pos ('/', S);
  509. if J = 0 then
  510. begin
  511. Val (S, ADSTStartDay, Err);
  512. if (Err > 0) or (ADSTStartDay > 365) then
  513. Exit;
  514. (* Use the preset default *)
  515. ADSTStartSec := DSTStartSec;
  516. end
  517. else
  518. begin
  519. if J = Length (S) then
  520. Exit;
  521. Val (Copy (S, 1, Pred (J)), ADSTStartDay, Err);
  522. if (Err > 0) or (ADSTStartDay > 365) then
  523. Exit;
  524. Val (Copy (S, Succ (J), Length (S) - J), ADSTStartSec, Err);
  525. if (Err > 0) or (ADSTStartSec > MaxSecond) then
  526. Exit
  527. else
  528. ADSTStartSec := ADSTStartSec * 3600;
  529. end;
  530. J := I;
  531. end
  532. else
  533. begin
  534. Val (S, ADSTStartMonth, Err);
  535. if (Err > 0) or (ADSTStartMonth > 12) then
  536. Exit;
  537. Val (Copy (TZ, I, J - I), ADSTStartWeek, Err);
  538. if (Err > 0) or (ADSTStartWeek < -1) or (ADSTStartWeek > 5) or
  539. (J >= Length (TZ)) then
  540. Exit;
  541. Inc (J);
  542. I := J;
  543. while (J <= Length (TZ)) and (TZ [J] <> ',') do
  544. Inc (J);
  545. Val (Copy (TZ, I, J - I), ADSTStartDay, Err);
  546. if (DSTStartWeek = 0) then
  547. begin
  548. if (Err > 0) or (ADSTStartDay < 1) or (ADSTStartDay > 31)
  549. or (ADSTStartDay > 30) and (ADSTStartMonth in [4, 6, 9, 11])
  550. or (ADSTStartMonth = 2) and (ADSTStartDay > 29) then
  551. Exit;
  552. ADSTStartSpecType := DSTMonthDay;
  553. end
  554. else
  555. begin
  556. if (Err > 0) or (ADSTStartDay > 6) then
  557. Exit;
  558. ADSTStartSpecType := DSTMonthWeekDay;
  559. end;
  560. if J >= Length (TZ) then
  561. Exit;
  562. Inc (J);
  563. I := J;
  564. while (J <= Length (TZ)) and (TZ [J] <> ',') do
  565. Inc (J);
  566. Val (Copy (TZ, I, J - I), ADSTStartSec, Err);
  567. if (Err > 0) or (ADSTStartSec > MaxSecond) or
  568. (J >= Length (TZ)) then
  569. Exit;
  570. Inc (J);
  571. I := J;
  572. while (J <= Length (TZ)) and (TZ [J] <> ',') do
  573. Inc (J);
  574. Val (Copy (TZ, I, J - I), ADSTEndMonth, Err);
  575. if (Err > 0) or (ADSTEndMonth > 12) or (J >= Length (TZ)) then
  576. Exit;
  577. Inc (J);
  578. I := J;
  579. while (J <= Length (TZ)) and (TZ [J] <> ',') do
  580. Inc (J);
  581. Val (Copy (TZ, I, J - I), ADSTEndWeek, Err);
  582. if (Err > 0) or (ADSTEndWeek < -1) or (ADSTEndWeek > 5)
  583. or (J >= Length (TZ)) then
  584. Exit;
  585. Inc (J);
  586. I := J;
  587. while (J <= Length (TZ)) and (TZ [J] <> ',') do
  588. Inc (J);
  589. Val (Copy (TZ, I, J - I), ADSTEndDay, Err);
  590. if (DSTEndWeek = 0) then
  591. begin
  592. if (Err > 0) or (ADSTEndDay < 1) or (ADSTEndDay > 31)
  593. or (ADSTEndDay > 30) and (ADSTEndMonth in [4, 6, 9, 11])
  594. or (ADSTEndMonth = 2) and (ADSTEndDay > 29) then
  595. Exit;
  596. ADSTEndSpecType := DSTMonthDay;
  597. end
  598. else
  599. begin
  600. if (Err > 0) or (ADSTEndDay > 6) then
  601. Exit;
  602. ADSTEndSpecType := DSTMonthWeekDay;
  603. end;
  604. if J >= Length (TZ) then
  605. Exit;
  606. Inc (J);
  607. I := J;
  608. while (J <= Length (TZ)) and (TZ [J] <> ',') do
  609. Inc (J);
  610. Val (Copy (TZ, I, J - I), ADSTEndSec, Err);
  611. if (Err > 0) or (ADSTEndSec > MaxSecond) or
  612. (J >= Length (TZ)) then
  613. Exit;
  614. Val (Copy (TZ, Succ (J), Length (TZ) - J), ADSTChangeSec, Err);
  615. if (Err = 0) and (ADSTChangeSec < 86400) then
  616. begin
  617. (* Format complete, all checks successful => accept the parsed values. *)
  618. DSTStartMonth := ADSTStartMonth;
  619. DSTStartWeek := ADSTStartWeek;
  620. DSTStartDay := ADSTStartDay;
  621. DSTStartSec := ADSTStartSec;
  622. DSTEndMonth := ADSTEndMonth;
  623. DSTEndWeek := ADSTEndWeek;
  624. DSTEndDay := ADSTEndDay;
  625. DSTEndSec := ADSTEndSec;
  626. DSTStartSpecType := ADSTStartSpecType;
  627. DSTEndSpecType := ADSTEndSpecType;
  628. DSTOffset := TZOffset - ADSTChangeSec;
  629. end;
  630. (* Parsing finished *)
  631. Exit;
  632. end;
  633. end;
  634. end;
  635. (* GnuFmt - DST end specification *)
  636. if TZ [J] = 'M' then
  637. begin
  638. (* Mmonth.week.dayofweek *)
  639. ADSTEndSpecType := DSTMonthWeekDay;
  640. if J >= Length (TZ) then
  641. Exit;
  642. Inc (J);
  643. I := J;
  644. while (J <= Length (TZ)) and not (TZ [J] in ['.', ',', '/']) do
  645. Inc (J);
  646. if (J >= Length (TZ)) or (TZ [J] <> '.') then
  647. Exit;
  648. Val (Copy (TZ, I, J - I), ADSTEndMonth, Err);
  649. if (Err > 0) or (ADSTEndMonth > 12) then
  650. Exit;
  651. Inc (J);
  652. I := J;
  653. while (J <= Length (TZ)) and not (TZ [J] in ['.', ',', '/']) do
  654. Inc (J);
  655. if (J >= Length (TZ)) or (TZ [J] <> '.') then
  656. Exit;
  657. Val (Copy (TZ, I, J - I), ADSTEndWeek, Err);
  658. if (Err > 0) or (ADSTEndWeek < 1) or (ADSTEndWeek > 5) then
  659. Exit;
  660. Inc (J);
  661. I := J;
  662. while (J <= Length (TZ)) and (TZ [J] <> '/') do
  663. Inc (J);
  664. Val (Copy (TZ, I, J - I), ADSTEndDay, Err);
  665. if (Err > 0) or (ADSTEndDay > 6) then
  666. Exit;
  667. end
  668. else
  669. begin
  670. if TZ [J] = 'J' then
  671. begin
  672. (* Jjulianday *)
  673. if J = Length (TZ) then
  674. Exit;
  675. Inc (J);
  676. ADSTEndSpecType := DSTJulianX
  677. end
  678. else
  679. ADSTEndSpecType := DSTJulian;
  680. if J >= Length (TZ) then
  681. Exit;
  682. Inc (J);
  683. I := J;
  684. while (J <= Length (TZ)) and (TZ [J] <> '/') do
  685. Inc (J);
  686. Val (Copy (TZ, I, J - I), ADSTEndDay, Err);
  687. if (Err > 0) or (ADSTEndDay = 0) and (ADSTEndSpecType = DSTJulianX)
  688. or (ADSTEndDay > 365) then
  689. Exit;
  690. end;
  691. if (J <= Length (TZ)) and (TZ [J] = '/') then
  692. begin
  693. if J = Length (TZ) then
  694. Exit;
  695. Val (Copy (TZ, Succ (J), Length (TZ) - J), ADSTEndSec, Err);
  696. if (Err > 0) or (ADSTEndSec > MaxSecond) then
  697. Exit
  698. else
  699. ADSTEndSec := ADSTEndSec * 3600;
  700. end
  701. else
  702. (* Use the preset default *)
  703. ADSTEndSec := DSTEndSec;
  704. (* Format complete, all checks successful => accept the parsed values. *)
  705. if ADSTStartSpecType = DSTMonthWeekDay then
  706. begin
  707. DSTStartMonth := ADSTStartMonth;
  708. DSTStartWeek := ADSTStartWeek;
  709. end;
  710. DSTStartDay := ADSTStartDay;
  711. DSTStartSec := ADSTStartSec;
  712. if ADSTStartSpecType = DSTMonthWeekDay then
  713. begin
  714. DSTEndMonth := ADSTEndMonth;
  715. DSTEndWeek := ADSTEndWeek;
  716. end;
  717. DSTEndDay := ADSTEndDay;
  718. DSTEndSec := ADSTEndSec;
  719. DSTStartSpecType := ADSTStartSpecType;
  720. DSTEndSpecType := ADSTEndSpecType;
  721. end;
  722. end
  723. else
  724. DSTOffset := -3600 + TZOffset;
  725. end;
  726. end;
  727. end;
  728. procedure InitTZ;
  729. var
  730. L: longint;
  731. SystemTime: TSystemTime;
  732. Y: word absolute SystemTime.Year;
  733. Mo: word absolute SystemTime.Month;
  734. D: word absolute SystemTime.Day;
  735. WD: word absolute SystemTime.DayOfWeek;
  736. begin
  737. InitTZ0;
  738. TZOffsetMin := TZOffset div 60;
  739. DSTOffsetMin := DSTOffset div 60;
  740. if DSTOffset <> TZOffset then
  741. begin
  742. GetLocalTime (SystemTime);
  743. if (DSTStartSpecType = DSTMonthWeekDay) or (DSTStartSpecType = DSTMonthDay)
  744. then
  745. begin
  746. RealDSTStartMonth := DSTStartMonth;
  747. if DSTStartSpecType = DSTMonthDay then
  748. RealDSTStartDay := DSTStartDay
  749. else
  750. begin
  751. RealDSTStartDay := FirstDay (DSTStartMonth, Y, Mo, D, WD);
  752. if (DSTStartWeek >= 1) and (DSTStartWeek <= 4) then
  753. if DSTStartDay < RealDSTStartDay then
  754. RealDSTStartDay := DSTStartWeek * 7 + DSTStartDay - RealDSTStartDay
  755. + 1
  756. else
  757. RealDSTStartDay := Pred (DSTStartWeek) * 7 + DSTStartDay
  758. - RealDSTStartDay + 1
  759. else
  760. (* Last week in month *)
  761. begin
  762. RealDSTStartDay := RealDSTStartDay
  763. + MonthDays [false, RealDSTStartMonth] - 1;
  764. if RealDSTStartMonth = 2 then
  765. Inc (RealDSTStartDay, LeapDay (Y));
  766. RealDSTStartDay := RealDSTStartDay mod 7;
  767. if RealDSTStartDay < DSTStartDay then
  768. RealDSTStartDay := RealDSTStartDay + 7 - DSTStartDay
  769. else
  770. RealDSTStartDay := RealDSTStartDay - DSTStartDay;
  771. RealDSTStartDay := MonthDays [false, RealDSTStartMonth]
  772. - RealDSTStartDay;
  773. end;
  774. end;
  775. end
  776. else
  777. begin
  778. (* Julian day *)
  779. L := DSTStartDay;
  780. if (DSTStartSpecType = DSTJulian) then
  781. (* 0-based *)
  782. if (L + LeapDay (Y) <= 59) then
  783. Inc (L)
  784. else
  785. L := L + 1 - LeapDay (Y);
  786. if L <= 31 then
  787. begin
  788. RealDSTStartMonth := 1;
  789. RealDSTStartDay := L;
  790. end
  791. else
  792. if (L <= 59) or
  793. (DSTStartSpecType = DSTJulian) and (L - LeapDay (Y) <= 59) then
  794. begin
  795. RealDSTStartMonth := 2;
  796. RealDSTStartDay := DSTStartDay - 31;
  797. end
  798. else
  799. begin
  800. RealDSTStartMonth := 3;
  801. while (RealDSTStartMonth < 12) and (MonthEnds [RealDSTStartMonth] > L)
  802. do
  803. Inc (RealDSTStartMonth);
  804. RealDSTStartDay := L - MonthEnds [Pred (RealDSTStartMonth)];
  805. end;
  806. end;
  807. if (DSTEndSpecType = DSTMonthWeekDay) or (DSTEndSpecType = DSTMonthDay) then
  808. begin
  809. RealDSTEndMonth := DSTEndMonth;
  810. if DSTEndSpecType = DSTMonthDay then
  811. RealDSTEndDay := DSTEndDay
  812. else
  813. begin
  814. RealDSTEndDay := FirstDay (DSTEndMonth, Y, Mo, D, WD);
  815. if (DSTEndWeek >= 1) and (DSTEndWeek <= 4) then
  816. if DSTEndDay < RealDSTEndDay then
  817. RealDSTEndDay := DSTEndWeek * 7 + DSTEndDay - RealDSTEndDay + 1
  818. else
  819. RealDSTEndDay := Pred (DSTEndWeek) * 7 + DSTEndDay - RealDSTEndDay
  820. + 1
  821. else
  822. (* Last week in month *)
  823. begin
  824. RealDSTEndDay := RealDSTEndDay + MonthDays [false, RealDSTEndMonth]
  825. - 1;
  826. if RealDSTEndMonth = 2 then
  827. Inc (RealDSTEndDay, LeapDay (Y));
  828. RealDSTEndDay := RealDSTEndDay mod 7;
  829. if RealDSTEndDay < DSTEndDay then
  830. RealDSTEndDay := RealDSTEndDay + 7 - DSTEndDay
  831. else
  832. RealDSTEndDay := RealDSTEndDay - DSTEndDay;
  833. RealDSTEndDay := MonthDays [false, RealDSTEndMonth] - RealDSTEndDay;
  834. end;
  835. end;
  836. end
  837. else
  838. begin
  839. (* Julian day *)
  840. L := DSTEndDay;
  841. if (DSTEndSpecType = DSTJulian) then
  842. (* 0-based *)
  843. if (L + LeapDay (Y) <= 59) then
  844. Inc (L)
  845. else
  846. L := L + 1 - LeapDay (Y);
  847. if L <= 31 then
  848. begin
  849. RealDSTEndMonth := 1;
  850. RealDSTEndDay := L;
  851. end
  852. else
  853. if (L <= 59) or
  854. (DSTEndSpecType = DSTJulian) and (L - LeapDay (Y) <= 59) then
  855. begin
  856. RealDSTEndMonth := 2;
  857. RealDSTEndDay := DSTEndDay - 31;
  858. end
  859. else
  860. begin
  861. RealDSTEndMonth := 3;
  862. while (RealDSTEndMonth < 12) and (MonthEnds [RealDSTEndMonth] > L) do
  863. Inc (RealDSTEndMonth);
  864. RealDSTEndDay := L - MonthEnds [Pred (RealDSTEndMonth)];
  865. end;
  866. end;
  867. end;
  868. end;
  869. {$IFNDEF HAS_DUAL_TZHANDLING}
  870. function GetUniversalTime (var SystemTime: TSystemTime): boolean;
  871. begin
  872. GetLocalTime (SystemTime);
  873. UpdateTimeWithOffset (SystemTime, GetLocalTimeOffset);
  874. GetUniversalTime := true;
  875. end;
  876. function GetLocalTimeOffset: integer;
  877. begin
  878. if InDST then
  879. GetLocalTimeOffset := DSTOffsetMin
  880. else
  881. GetLocalTimeOffset := TZOffsetMin;
  882. end;
  883. {$ENDIF HAS_DUAL_TZHANDLING}
  884. function GetLocalTimeOffset(const DateTime: TDateTime; const InputIsUTC: boolean; out Offset: integer): boolean;
  885. var
  886. SystemTime: TSystemTime;
  887. begin
  888. DateTimeToSystemTime (DateTime, SystemTime);
  889. if InDST (SystemTime, InputIsUTC) then
  890. Offset := DSTOffsetMin
  891. else
  892. Offset := TZOffsetMin;
  893. GetLocalTimeOffset := true;
  894. end;