tzutil.pas 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702
  1. //Unit with timezone support for some Freepascal platforms.
  2. //Tomas Hajny
  3. unit tzutil;
  4. interface
  5. type
  6. DSTSpecType = (DSTMonthWeekDay, DSTMonthDay, DSTJulian, DSTJulianX);
  7. (* Initialized to default values *)
  8. const
  9. TZName: string = '';
  10. TZDSTName: string = '';
  11. TZOffset: longint = 0;
  12. DSTOffset: longint = 0;
  13. DSTStartMonth: byte = 4;
  14. DSTStartWeek: shortint = 1;
  15. DSTStartDay: word = 0;
  16. DSTStartSec: cardinal = 7200;
  17. DSTEndMonth: byte = 10;
  18. DSTEndWeek: shortint = -1;
  19. DSTEndDay: word = 0;
  20. DSTEndSec: cardinal = 10800;
  21. DSTStartSpecType: DSTSpecType = DSTMonthWeekDay;
  22. DSTEndSpecType: DSTSpecType = DSTMonthWeekDay;
  23. function TZSeconds: longint;
  24. (* Return current offset from UTC in seconds while respecting DST *)
  25. implementation
  26. uses
  27. Dos;
  28. function TZSeconds: longint;
  29. const
  30. MonthDays: array [1..12] of byte =
  31. (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
  32. MonthEnds: array [1..12] of word =
  33. (31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365);
  34. var
  35. Y, Mo, D, WD, H, Mi, S, S100: word;
  36. MS, DS, ME, DE: byte;
  37. L: longint;
  38. Second: cardinal;
  39. AfterDSTStart, BeforeDSTEnd: boolean;
  40. function LeapDay: byte;
  41. begin
  42. if (Y mod 400 = 0) or (Y mod 100 <> 0) and (Y mod 4 = 0) then
  43. LeapDay := 1
  44. else
  45. LeapDay := 0;
  46. end;
  47. function FirstDay (MM: byte): byte;
  48. (* What day of week (0-6) is the first day of month MM? *)
  49. var
  50. DD: longint;
  51. begin
  52. if MM < Mo then
  53. begin
  54. DD := D + MonthEnds [Pred (Mo)];
  55. if MM > 1 then
  56. Dec (DD, MonthEnds [Pred (MM)]);
  57. if (MM <= 2) and (Mo > 2) then
  58. Inc (DD, LeapDay);
  59. end
  60. else
  61. if MM > Mo then
  62. begin
  63. DD := - MonthDays [Mo] + D - MonthEnds [Pred (MM)] + MonthEnds [Mo];
  64. if (Mo <= 2) and (MM > 2) then
  65. Dec (DD, LeapDay);
  66. end
  67. else
  68. (* M = MM *)
  69. DD := D;
  70. DD := WD - DD mod 7 + 1;
  71. if DD < 0 then
  72. FirstDay := DD + 7
  73. else
  74. FirstDay := DD mod 7;
  75. end;
  76. begin
  77. TZSeconds := TZOffset;
  78. if DSTOffset <> TZOffset then
  79. begin
  80. GetDate (Y, Mo, D, WD);
  81. GetTime (H, Mi, S, S100);
  82. Second := cardinal (H) * 3600 + Mi * 60 + S;
  83. if (DSTStartSpecType = DSTMonthWeekDay) or (DSTStartSpecType = DSTMonthDay)
  84. then
  85. begin
  86. MS := DSTStartMonth;
  87. if DSTStartSpecType = DSTMonthDay then
  88. DS := DSTStartDay
  89. else
  90. begin
  91. DS := FirstDay (DSTStartMonth);
  92. if (DSTStartWeek >= 1) and (DSTStartWeek <= 4) then
  93. if DSTStartDay < DS then
  94. DS := DSTStartWeek * 7 + DSTStartDay - DS + 1
  95. else
  96. DS := Pred (DSTStartWeek) * 7 + DSTStartDay - DS + 1
  97. else
  98. (* Last week in month *)
  99. begin
  100. DS := DS + MonthDays [MS] - 1;
  101. if MS = 2 then
  102. Inc (DS, LeapDay);
  103. DS := DS mod 7;
  104. if DS < DSTStartDay then
  105. DS := DS + 7 - DSTStartDay
  106. else
  107. DS := DS - DSTStartDay;
  108. DS := MonthDays [MS] - DS;
  109. end;
  110. end;
  111. end
  112. else
  113. begin
  114. (* Julian day *)
  115. L := DSTStartDay;
  116. if (DSTStartSpecType = DSTJulian) then
  117. (* 0-based *)
  118. if (L + LeapDay <= 59) then
  119. Inc (L)
  120. else
  121. L := L + 1 - LeapDay;
  122. if L <= 31 then
  123. begin
  124. MS := 1;
  125. DS := L;
  126. end
  127. else
  128. if (L <= 59) or
  129. (DSTStartSpecType = DSTJulian) and (L - LeapDay <= 59) then
  130. begin
  131. MS := 2;
  132. DS := DSTStartDay - 31;
  133. end
  134. else
  135. begin
  136. MS := 3;
  137. while (MS < 12) and (MonthEnds [MS] > L) do
  138. Inc (MS);
  139. DS := L - MonthEnds [Pred (MS)];
  140. end;
  141. end;
  142. if (DSTEndSpecType = DSTMonthWeekDay) or (DSTEndSpecType = DSTMonthDay) then
  143. begin
  144. ME := DSTEndMonth;
  145. if DSTEndSpecType = DSTMonthDay then
  146. DE := DSTEndDay
  147. else
  148. begin
  149. DE := FirstDay (DSTEndMonth);
  150. if (DSTEndWeek >= 1) and (DSTEndWeek <= 4) then
  151. if DSTEndDay < DE then
  152. DE := DSTEndWeek * 7 + DSTEndDay - DE + 1
  153. else
  154. DE := Pred (DSTEndWeek) * 7 + DSTEndDay - DE + 1
  155. else
  156. (* Last week in month *)
  157. begin
  158. DE := DE + MonthDays [ME] - 1;
  159. if ME = 2 then
  160. Inc (DE, LeapDay);
  161. DE := DE mod 7;
  162. if DE < DSTEndDay then
  163. DE := DE + 7 - DSTEndDay
  164. else
  165. DE := DE - DSTEndDay;
  166. DE := MonthDays [ME] - DE;
  167. end;
  168. end;
  169. end
  170. else
  171. begin
  172. (* Julian day *)
  173. L := DSTEndDay;
  174. if (DSTEndSpecType = DSTJulian) then
  175. (* 0-based *)
  176. if (L + LeapDay <= 59) then
  177. Inc (L)
  178. else
  179. L := L + 1 - LeapDay;
  180. if L <= 31 then
  181. begin
  182. ME := 1;
  183. DE := L;
  184. end
  185. else
  186. if (L <= 59) or
  187. (DSTEndSpecType = DSTJulian) and (L - LeapDay <= 59) then
  188. begin
  189. ME := 2;
  190. DE := DSTEndDay - 31;
  191. end
  192. else
  193. begin
  194. ME := 3;
  195. while (ME < 12) and (MonthEnds [ME] > L) do
  196. Inc (ME);
  197. DE := L - MonthEnds [Pred (ME)];
  198. end;
  199. end;
  200. if Mo < MS then
  201. AfterDSTStart := false
  202. else
  203. if Mo > MS then
  204. AfterDSTStart := true
  205. else
  206. if D < DS then
  207. AfterDSTStart := false
  208. else
  209. if D > DS then
  210. AfterDSTStart := true
  211. else
  212. AfterDSTStart := Second > DSTStartSec;
  213. if Mo > ME then
  214. BeforeDSTEnd := false
  215. else
  216. if Mo < ME then
  217. BeforeDSTEnd := true
  218. else
  219. if D > DE then
  220. BeforeDSTEnd := false
  221. else
  222. if D < DE then
  223. BeforeDSTEnd := true
  224. else
  225. BeforeDSTEnd := Second < DSTEndSec;
  226. if AfterDSTStart and BeforeDSTEnd then
  227. TZSeconds := DSTOffset;
  228. end;
  229. end;
  230. procedure InitTZ;
  231. const
  232. TZEnvName = 'TZ';
  233. EMXTZEnvName = 'EMXTZ';
  234. var
  235. TZ, S: string;
  236. I, J: byte;
  237. Err: longint;
  238. GnuFmt: boolean;
  239. ADSTStartMonth: byte;
  240. ADSTStartWeek: shortint;
  241. ADSTStartDay: word;
  242. ADSTStartSec: cardinal;
  243. ADSTEndMonth: byte;
  244. ADSTEndWeek: shortint;
  245. ADSTEndDay: word;
  246. ADSTEndSec: cardinal;
  247. ADSTStartSpecType: DSTSpecType;
  248. ADSTEndSpecType: DSTSpecType;
  249. ADSTChangeSec: cardinal;
  250. function ParseOffset (OffStr: string): longint;
  251. (* Parse time offset given as [-|+]HH[:MI[:SS]] and return in seconds *)
  252. var
  253. TZShiftHH, TZShiftDir: shortint;
  254. TZShiftMI, TZShiftSS: byte;
  255. N1, N2: byte;
  256. begin
  257. TZShiftHH := 0;
  258. TZShiftMI := 0;
  259. TZShiftSS := 0;
  260. TZShiftDir := 1;
  261. N1 := 1;
  262. while (N1 <= Length (OffStr)) and (OffStr [N1] <> ':') do
  263. Inc (N1);
  264. Val (Copy (OffStr, 1, Pred (N1)), TZShiftHH, Err);
  265. if (Err = 0) and (TZShiftHH >= -24) and (TZShiftHH <= 23) then
  266. begin
  267. (* Normalize the hour offset to -12..11 if necessary *)
  268. if TZShiftHH > 11 then
  269. Dec (TZShiftHH, 24) else
  270. if TZShiftHH < -12 then
  271. Inc (TZShiftHH, 24);
  272. if TZShiftHH < 0 then
  273. TZShiftDir := -1;
  274. if (N1 <= Length (OffStr)) then
  275. begin
  276. N2 := Succ (N1);
  277. while (N2 <= Length (OffStr)) and (OffStr [N2] <> ':') do
  278. Inc (N2);
  279. Val (Copy (OffStr, Succ (N1), N2 - N1), TZShiftMI, Err);
  280. if (Err = 0) and (TZShiftMI <= 59) then
  281. begin
  282. if (N2 <= Length (OffStr)) then
  283. begin
  284. Val (Copy (OffStr, Succ (N2), Length (OffStr) - N2), TZShiftSS, Err);
  285. if (Err <> 0) or (TZShiftSS > 59) then
  286. TZShiftSS := 0;
  287. end
  288. end
  289. else
  290. TZShiftMI := 0;
  291. end;
  292. end
  293. else
  294. TZShiftHH := 0;
  295. ParseOffset := longint (TZShiftHH) * 3600 +
  296. TZShiftDir * (longint (TZShiftMI) * 60 + TZShiftSS);
  297. end;
  298. begin
  299. TZ := GetEnv (TZEnvName);
  300. if TZ = '' then
  301. TZ := GetEnv (EMXTZEnvName);
  302. if TZ <> '' then
  303. begin
  304. TZ := Upcase (TZ);
  305. (* Timezone name *)
  306. I := 1;
  307. while (I <= Length (TZ)) and (TZ [I] in ['A'..'Z']) do
  308. Inc (I);
  309. TZName := Copy (TZ, 1, Pred (I));
  310. if I <= Length (TZ) then
  311. begin
  312. (* Timezone shift *)
  313. J := Succ (I);
  314. while (J <= Length (TZ)) and not (TZ [J] in ['A'..'Z']) do
  315. Inc (J);
  316. TZOffset := ParseOffset (Copy (TZ, I, J - I));
  317. (* DST timezone name *)
  318. I := J;
  319. while (J <= Length (TZ)) and (TZ [J] in ['A'..'Z']) do
  320. Inc (J);
  321. if J > I then
  322. begin
  323. TZDSTName := Copy (TZ, I, J - I);
  324. (* DST timezone name provided; if equal to the standard timezone *)
  325. (* name then DSTOffset is set to be equal to TZOffset by default, *)
  326. (* otherwise it is set to TZOffset - 3600 seconds. *)
  327. if TZDSTName <> TZName then
  328. DSTOffset := -3600 + TZOffset
  329. else
  330. DSTOffset := TZOffset;
  331. end
  332. else
  333. begin
  334. TZDSTName := TZName;
  335. (* No DST timezone name provided => DSTOffset is equal to TZOffset *)
  336. DSTOffset := TZOffset;
  337. end;
  338. if J <= Length (TZ) then
  339. begin
  340. (* Check if DST offset is specified here; *)
  341. (* if not, default value set above is used. *)
  342. if TZ [J] <> ',' then
  343. begin
  344. I := J;
  345. Inc (J);
  346. while (J <= Length (TZ)) and (TZ [J] <> ',') do
  347. Inc (J);
  348. DSTOffset := ParseOffset (Copy (TZ, I, J - I));
  349. end;
  350. if J < Length (TZ) then
  351. begin
  352. Inc (J);
  353. (* DST switching details *)
  354. case TZ [J] of
  355. 'M':
  356. begin
  357. (* Mmonth.week.dayofweek[/StartHour] *)
  358. ADSTStartSpecType := DSTMonthWeekDay;
  359. if J >= Length (TZ) then
  360. Exit;
  361. Inc (J);
  362. I := J;
  363. while (J <= Length (TZ)) and not (TZ [J] in ['.', ',', '/']) do
  364. Inc (J);
  365. if (J >= Length (TZ)) or (TZ [J] <> '.') then
  366. Exit;
  367. Val (Copy (TZ, I, J - I), ADSTStartMonth, Err);
  368. if (Err > 0) or (ADSTStartMonth > 12) then
  369. Exit;
  370. Inc (J);
  371. I := J;
  372. while (J <= Length (TZ)) and not (TZ [J] in ['.', ',', '/']) do
  373. Inc (J);
  374. if (J >= Length (TZ)) or (TZ [J] <> '.') then
  375. Exit;
  376. Val (Copy (TZ, I, J - I), ADSTStartWeek, Err);
  377. if (Err > 0) or (ADSTStartWeek < 1) or (ADSTStartWeek > 5) then
  378. Exit;
  379. Inc (J);
  380. I := J;
  381. while (J <= Length (TZ)) and not (TZ [J] in [',', '/']) do
  382. Inc (J);
  383. Val (Copy (TZ, I, J - I), ADSTStartDay, Err);
  384. if (Err > 0) or (ADSTStartDay < 0) or (ADSTStartDay > 6)
  385. or (J >= Length (TZ)) then
  386. Exit;
  387. if TZ [J] = '/' then
  388. begin
  389. Inc (J);
  390. I := J;
  391. while (J <= Length (TZ)) and (TZ [J] <> ',') do
  392. Inc (J);
  393. Val (Copy (TZ, I, J - I), ADSTStartSec, Err);
  394. if (Err > 0) or (ADSTStartSec > 86399) or (J >= Length (TZ))
  395. then
  396. Exit
  397. else
  398. ADSTStartSec := ADSTStartSec * 3600;
  399. end
  400. else
  401. (* Use the preset default *)
  402. ADSTStartSec := DSTStartSec;
  403. Inc (J);
  404. end;
  405. 'J':
  406. begin
  407. (* Jjulianday[/StartHour] *)
  408. ADSTStartSpecType := DSTJulianX;
  409. if J >= Length (TZ) then
  410. Exit;
  411. Inc (J);
  412. I := J;
  413. while (J <= Length (TZ)) and not (TZ [J] in [',', '/']) do
  414. Inc (J);
  415. Val (Copy (TZ, I, J - I), ADSTStartDay, Err);
  416. if (Err > 0) or (ADSTStartDay = 0) or (ADSTStartDay > 365)
  417. or (J >= Length (TZ)) then
  418. Exit;
  419. if TZ [J] = '/' then
  420. begin
  421. Inc (J);
  422. I := J;
  423. while (J <= Length (TZ)) and (TZ [J] <> ',') do
  424. Inc (J);
  425. Val (Copy (TZ, I, J - I), ADSTStartSec, Err);
  426. if (Err > 0) or (ADSTStartSec > 86399) or (J >= Length (TZ))
  427. then
  428. Exit
  429. else
  430. ADSTStartSec := ADSTStartSec * 3600;
  431. end
  432. else
  433. (* Use the preset default *)
  434. ADSTStartSec := DSTStartSec;
  435. Inc (J);
  436. end
  437. else
  438. begin
  439. (* Check the used format first - GNU libc / GCC / EMX expect *)
  440. (* "NameOffsetDstname[Dstoffset],Start[/StartHour],End[/EndHour]"; *)
  441. (* if more than one comma (',') is found, the following format is assumed: *)
  442. (* "NameOffsetDstname[Dstoffset],StartMonth,StartWeek,StartDay,StartSecond, *)
  443. (* EndMonth,EndWeek,EndDay,EndSecond,DSTDifference". *)
  444. I := J;
  445. while (J <= Length (TZ)) and (TZ [J] <> ',') do
  446. Inc (J);
  447. S := Copy (TZ, I, J - I);
  448. if J < Length (TZ) then
  449. begin
  450. Inc (J);
  451. I := J;
  452. while (J <= Length (TZ)) and (TZ [J] <> ',') do
  453. Inc (J);
  454. GnuFmt := J > Length (TZ);
  455. end
  456. else
  457. Exit;
  458. if GnuFmt then
  459. begin
  460. ADSTStartSpecType := DSTJulian;
  461. J := Pos ('/', S);
  462. if J = 0 then
  463. begin
  464. Val (S, ADSTStartDay, Err);
  465. if (Err > 0) or (ADSTStartDay > 365) then
  466. Exit;
  467. (* Use the preset default *)
  468. ADSTStartSec := DSTStartSec;
  469. end
  470. else
  471. begin
  472. if J = Length (S) then
  473. Exit;
  474. Val (Copy (S, 1, Pred (J)), ADSTStartDay, Err);
  475. if (Err > 0) or (ADSTStartDay > 365) then
  476. Exit;
  477. Val (Copy (S, Succ (J), Length (S) - J), ADSTStartSec, Err);
  478. if (Err > 0) or (ADSTStartSec > 86399) then
  479. Exit
  480. else
  481. ADSTStartSec := ADSTStartSec * 3600;
  482. end;
  483. J := I;
  484. end
  485. else
  486. begin
  487. Val (S, ADSTStartMonth, Err);
  488. if (Err > 0) or (ADSTStartMonth > 12) then
  489. Exit;
  490. Val (Copy (TZ, I, J - I), ADSTStartWeek, Err);
  491. if (Err > 0) or (ADSTStartWeek < -1) or (ADSTStartWeek > 5) or
  492. (J >= Length (TZ)) then
  493. Exit;
  494. Inc (J);
  495. I := J;
  496. while (J <= Length (TZ)) and (TZ [J] <> ',') do
  497. Inc (J);
  498. Val (Copy (TZ, I, J - I), ADSTStartDay, Err);
  499. if (DSTStartWeek = 0) then
  500. begin
  501. if (Err > 0) or (ADSTStartDay < 1) or (ADSTStartDay > 31)
  502. or (ADSTStartDay > 30) and (ADSTStartMonth in [4, 6, 9, 11])
  503. or (ADSTStartMonth = 2) and (ADSTStartDay > 29) then
  504. Exit;
  505. ADSTStartSpecType := DSTMonthDay;
  506. end
  507. else
  508. begin
  509. if (Err > 0) or (ADSTStartDay < 0) or (ADSTStartDay > 6) then
  510. Exit;
  511. ADSTStartSpecType := DSTMonthWeekDay;
  512. end;
  513. if J >= Length (TZ) then
  514. Exit;
  515. Inc (J);
  516. I := J;
  517. while (J <= Length (TZ)) and (TZ [J] <> ',') do
  518. Inc (J);
  519. Val (Copy (TZ, I, J - I), ADSTStartSec, Err);
  520. if (Err > 0) or (ADSTStartSec > 86399) or (J >= Length (TZ)) then
  521. Exit;
  522. Inc (J);
  523. I := J;
  524. while (J <= Length (TZ)) and (TZ [J] <> ',') do
  525. Inc (J);
  526. Val (Copy (TZ, I, J - I), ADSTEndMonth, Err);
  527. if (Err > 0) or (ADSTEndMonth > 12) or (J >= Length (TZ)) then
  528. Exit;
  529. Inc (J);
  530. I := J;
  531. while (J <= Length (TZ)) and (TZ [J] <> ',') do
  532. Inc (J);
  533. Val (Copy (TZ, I, J - I), ADSTEndWeek, Err);
  534. if (Err > 0) or (ADSTEndWeek < -1) or (ADSTEndWeek > 5)
  535. or (J >= Length (TZ)) then
  536. Exit;
  537. Inc (J);
  538. I := J;
  539. while (J <= Length (TZ)) and (TZ [J] <> ',') do
  540. Inc (J);
  541. Val (Copy (TZ, I, J - I), ADSTEndDay, Err);
  542. if (DSTEndWeek = 0) then
  543. begin
  544. if (Err > 0) or (ADSTEndDay < 1) or (ADSTEndDay > 31)
  545. or (ADSTEndDay > 30) and (ADSTEndMonth in [4, 6, 9, 11])
  546. or (ADSTEndMonth = 2) and (ADSTEndDay > 29) then
  547. Exit;
  548. ADSTEndSpecType := DSTMonthDay;
  549. end
  550. else
  551. begin
  552. if (Err > 0) or (ADSTEndDay < 0) or (ADSTEndDay > 6) then
  553. Exit;
  554. ADSTEndSpecType := DSTMonthWeekDay;
  555. end;
  556. if J >= Length (TZ) then
  557. Exit;
  558. Inc (J);
  559. I := J;
  560. while (J <= Length (TZ)) and (TZ [J] <> ',') do
  561. Inc (J);
  562. Val (Copy (TZ, I, J - I), ADSTEndSec, Err);
  563. if (Err > 0) or (ADSTEndSec > 86399) or (J >= Length (TZ)) then
  564. Exit;
  565. Val (Copy (TZ, Succ (J), Length (TZ) - J), ADSTChangeSec, Err);
  566. if (Err = 0) and (ADSTChangeSec < 86400) then
  567. begin
  568. (* Format complete, all checks successful => accept the parsed values. *)
  569. DSTStartMonth := ADSTStartMonth;
  570. DSTStartWeek := ADSTStartWeek;
  571. DSTStartDay := ADSTStartDay;
  572. DSTStartSec := ADSTStartSec;
  573. DSTEndMonth := ADSTEndMonth;
  574. DSTEndWeek := ADSTEndWeek;
  575. DSTEndDay := ADSTEndDay;
  576. DSTEndSec := ADSTEndSec;
  577. DSTStartSpecType := ADSTStartSpecType;
  578. DSTEndSpecType := ADSTEndSpecType;
  579. DSTOffset := TZOffset - ADSTChangeSec;
  580. end;
  581. (* Parsing finished *)
  582. Exit;
  583. end;
  584. end;
  585. end;
  586. (* GnuFmt - DST end specification *)
  587. if TZ [J] = 'M' then
  588. begin
  589. (* Mmonth.week.dayofweek *)
  590. ADSTEndSpecType := DSTMonthWeekDay;
  591. if J >= Length (TZ) then
  592. Exit;
  593. Inc (J);
  594. I := J;
  595. while (J <= Length (TZ)) and not (TZ [J] in ['.', ',', '/']) do
  596. Inc (J);
  597. if (J >= Length (TZ)) or (TZ [J] <> '.') then
  598. Exit;
  599. Val (Copy (TZ, I, J - I), ADSTEndMonth, Err);
  600. if (Err > 0) or (ADSTEndMonth > 12) then
  601. Exit;
  602. Inc (J);
  603. I := J;
  604. while (J <= Length (TZ)) and not (TZ [J] in ['.', ',', '/']) do
  605. Inc (J);
  606. if (J >= Length (TZ)) or (TZ [J] <> '.') then
  607. Exit;
  608. Val (Copy (TZ, I, J - I), ADSTEndWeek, Err);
  609. if (Err > 0) or (ADSTEndWeek < 1) or (ADSTEndWeek > 5) then
  610. Exit;
  611. Inc (J);
  612. I := J;
  613. while (J <= Length (TZ)) and (TZ [J] <> '/') do
  614. Inc (J);
  615. Val (Copy (TZ, I, J - I), ADSTEndDay, Err);
  616. if (Err > 0) or (ADSTEndDay < 0) or (ADSTEndDay > 6) then
  617. Exit;
  618. end
  619. else
  620. begin
  621. if TZ [J] = 'J' then
  622. begin
  623. (* Jjulianday *)
  624. if J = Length (TZ) then
  625. Exit;
  626. Inc (J);
  627. ADSTEndSpecType := DSTJulianX
  628. end
  629. else
  630. ADSTEndSpecType := DSTJulian;
  631. if J >= Length (TZ) then
  632. Exit;
  633. Inc (J);
  634. I := J;
  635. while (J <= Length (TZ)) and (TZ [J] <> '/') do
  636. Inc (J);
  637. Val (Copy (TZ, I, J - I), ADSTEndDay, Err);
  638. if (Err > 0) or (ADSTEndDay = 0) and (ADSTEndSpecType = DSTJulianX)
  639. or (ADSTEndDay > 365) then
  640. Exit;
  641. end;
  642. if (J <= Length (TZ)) and (TZ [J] = '/') then
  643. begin
  644. if J = Length (TZ) then
  645. Exit;
  646. Val (Copy (TZ, Succ (J), Length (TZ) - J), ADSTEndSec, Err);
  647. if (Err > 0) or (ADSTEndSec > 86399) then
  648. Exit
  649. else
  650. ADSTEndSec := ADSTEndSec * 3600;
  651. end
  652. else
  653. (* Use the preset default *)
  654. ADSTEndSec := DSTEndSec;
  655. (* Format complete, all checks successful => accept the parsed values. *)
  656. if ADSTStartSpecType = DSTMonthWeekDay then
  657. begin
  658. DSTStartMonth := ADSTStartMonth;
  659. DSTStartWeek := ADSTStartWeek;
  660. end;
  661. DSTStartDay := ADSTStartDay;
  662. DSTStartSec := ADSTStartSec;
  663. if ADSTStartSpecType = DSTMonthWeekDay then
  664. begin
  665. DSTEndMonth := ADSTEndMonth;
  666. DSTEndWeek := ADSTEndWeek;
  667. end;
  668. DSTEndDay := ADSTEndDay;
  669. DSTEndSec := ADSTEndSec;
  670. DSTStartSpecType := ADSTStartSpecType;
  671. DSTEndSpecType := ADSTEndSpecType;
  672. end;
  673. end
  674. else
  675. DSTOffset := -3600 + TZOffset;
  676. end;
  677. end;
  678. end;
  679. begin
  680. InitTZ;
  681. end.