//Unit with timezone support for some Freepascal platforms. //Tomas Hajny unit tzutil; interface type DSTSpecType = (DSTMonthWeekDay, DSTMonthDay, DSTJulian, DSTJulianX); (* Initialized to default values *) const TZName: string = ''; TZDSTName: string = ''; TZOffset: longint = 0; DSTOffset: longint = 0; DSTStartMonth: byte = 4; DSTStartWeek: shortint = 1; DSTStartDay: word = 0; DSTStartSec: cardinal = 7200; DSTEndMonth: byte = 10; DSTEndWeek: shortint = -1; DSTEndDay: word = 0; DSTEndSec: cardinal = 10800; DSTStartSpecType: DSTSpecType = DSTMonthWeekDay; DSTEndSpecType: DSTSpecType = DSTMonthWeekDay; function TZSeconds: longint; (* Return current offset from UTC in seconds while respecting DST *) implementation uses Dos; function TZSeconds: longint; const MonthDays: array [1..12] of byte = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31); MonthEnds: array [1..12] of word = (31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365); var Y, Mo, D, WD, H, Mi, S, S100: word; MS, DS, ME, DE: byte; L: longint; Second: cardinal; AfterDSTStart, BeforeDSTEnd: boolean; function LeapDay: byte; begin if (Y mod 400 = 0) or (Y mod 100 <> 0) and (Y mod 4 = 0) then LeapDay := 1 else LeapDay := 0; end; function FirstDay (MM: byte): byte; (* What day of week (0-6) is the first day of month MM? *) var DD: longint; begin if MM < Mo then begin DD := D + MonthEnds [Pred (Mo)]; if MM > 1 then Dec (DD, MonthEnds [Pred (MM)]); if (MM <= 2) and (Mo > 2) then Inc (DD, LeapDay); end else if MM > Mo then begin DD := - MonthDays [Mo] + D - MonthEnds [Pred (MM)] + MonthEnds [Mo]; if (Mo <= 2) and (MM > 2) then Dec (DD, LeapDay); end else (* M = MM *) DD := D; DD := WD - DD mod 7 + 1; if DD < 0 then FirstDay := DD + 7 else FirstDay := DD mod 7; end; begin TZSeconds := TZOffset; if DSTOffset <> TZOffset then begin GetDate (Y, Mo, D, WD); GetTime (H, Mi, S, S100); Second := cardinal (H) * 3600 + Mi * 60 + S; if (DSTStartSpecType = DSTMonthWeekDay) or (DSTStartSpecType = DSTMonthDay) then begin MS := DSTStartMonth; if DSTStartSpecType = DSTMonthDay then DS := DSTStartDay else begin DS := FirstDay (DSTStartMonth); if (DSTStartWeek >= 1) and (DSTStartWeek <= 4) then if DSTStartDay < DS then DS := DSTStartWeek * 7 + DSTStartDay - DS + 1 else DS := Pred (DSTStartWeek) * 7 + DSTStartDay - DS + 1 else (* Last week in month *) begin DS := DS + MonthDays [MS] - 1; if MS = 2 then Inc (DS, LeapDay); DS := DS mod 7; if DS < DSTStartDay then DS := DS + 7 - DSTStartDay else DS := DS - DSTStartDay; DS := MonthDays [MS] - DS; end; end; end else begin (* Julian day *) L := DSTStartDay; if (DSTStartSpecType = DSTJulian) then (* 0-based *) if (L + LeapDay <= 59) then Inc (L) else L := L + 1 - LeapDay; if L <= 31 then begin MS := 1; DS := L; end else if (L <= 59) or (DSTStartSpecType = DSTJulian) and (L - LeapDay <= 59) then begin MS := 2; DS := DSTStartDay - 31; end else begin MS := 3; while (MS < 12) and (MonthEnds [MS] > L) do Inc (MS); DS := L - MonthEnds [Pred (MS)]; end; end; if (DSTEndSpecType = DSTMonthWeekDay) or (DSTEndSpecType = DSTMonthDay) then begin ME := DSTEndMonth; if DSTEndSpecType = DSTMonthDay then DE := DSTEndDay else begin DE := FirstDay (DSTEndMonth); if (DSTEndWeek >= 1) and (DSTEndWeek <= 4) then if DSTEndDay < DE then DE := DSTEndWeek * 7 + DSTEndDay - DE + 1 else DE := Pred (DSTEndWeek) * 7 + DSTEndDay - DE + 1 else (* Last week in month *) begin DE := DE + MonthDays [ME] - 1; if ME = 2 then Inc (DE, LeapDay); DE := DE mod 7; if DE < DSTEndDay then DE := DE + 7 - DSTEndDay else DE := DE - DSTEndDay; DE := MonthDays [ME] - DE; end; end; end else begin (* Julian day *) L := DSTEndDay; if (DSTEndSpecType = DSTJulian) then (* 0-based *) if (L + LeapDay <= 59) then Inc (L) else L := L + 1 - LeapDay; if L <= 31 then begin ME := 1; DE := L; end else if (L <= 59) or (DSTEndSpecType = DSTJulian) and (L - LeapDay <= 59) then begin ME := 2; DE := DSTEndDay - 31; end else begin ME := 3; while (ME < 12) and (MonthEnds [ME] > L) do Inc (ME); DE := L - MonthEnds [Pred (ME)]; end; end; if Mo < MS then AfterDSTStart := false else if Mo > MS then AfterDSTStart := true else if D < DS then AfterDSTStart := false else if D > DS then AfterDSTStart := true else AfterDSTStart := Second > DSTStartSec; if Mo > ME then BeforeDSTEnd := false else if Mo < ME then BeforeDSTEnd := true else if D > DE then BeforeDSTEnd := false else if D < DE then BeforeDSTEnd := true else BeforeDSTEnd := Second < DSTEndSec; if AfterDSTStart and BeforeDSTEnd then TZSeconds := DSTOffset; end; end; procedure InitTZ; const TZEnvName = 'TZ'; EMXTZEnvName = 'EMXTZ'; var TZ, S: string; I, J: byte; Err: longint; GnuFmt: boolean; ADSTStartMonth: byte; ADSTStartWeek: shortint; ADSTStartDay: word; ADSTStartSec: cardinal; ADSTEndMonth: byte; ADSTEndWeek: shortint; ADSTEndDay: word; ADSTEndSec: cardinal; ADSTStartSpecType: DSTSpecType; ADSTEndSpecType: DSTSpecType; ADSTChangeSec: cardinal; function ParseOffset (OffStr: string): longint; (* Parse time offset given as [-|+]HH[:MI[:SS]] and return in seconds *) var TZShiftHH, TZShiftDir: shortint; TZShiftMI, TZShiftSS: byte; N1, N2: byte; begin TZShiftHH := 0; TZShiftMI := 0; TZShiftSS := 0; TZShiftDir := 1; N1 := 1; while (N1 <= Length (OffStr)) and (OffStr [N1] <> ':') do Inc (N1); Val (Copy (OffStr, 1, Pred (N1)), TZShiftHH, Err); if (Err = 0) and (TZShiftHH >= -24) and (TZShiftHH <= 23) then begin (* Normalize the hour offset to -12..11 if necessary *) if TZShiftHH > 11 then Dec (TZShiftHH, 24) else if TZShiftHH < -12 then Inc (TZShiftHH, 24); if TZShiftHH < 0 then TZShiftDir := -1; if (N1 <= Length (OffStr)) then begin N2 := Succ (N1); while (N2 <= Length (OffStr)) and (OffStr [N2] <> ':') do Inc (N2); Val (Copy (OffStr, Succ (N1), N2 - N1), TZShiftMI, Err); if (Err = 0) and (TZShiftMI <= 59) then begin if (N2 <= Length (OffStr)) then begin Val (Copy (OffStr, Succ (N2), Length (OffStr) - N2), TZShiftSS, Err); if (Err <> 0) or (TZShiftSS > 59) then TZShiftSS := 0; end end else TZShiftMI := 0; end; end else TZShiftHH := 0; ParseOffset := longint (TZShiftHH) * 3600 + TZShiftDir * (longint (TZShiftMI) * 60 + TZShiftSS); end; begin TZ := GetEnv (TZEnvName); if TZ = '' then TZ := GetEnv (EMXTZEnvName); if TZ <> '' then begin TZ := Upcase (TZ); (* Timezone name *) I := 1; while (I <= Length (TZ)) and (TZ [I] in ['A'..'Z']) do Inc (I); TZName := Copy (TZ, 1, Pred (I)); if I <= Length (TZ) then begin (* Timezone shift *) J := Succ (I); while (J <= Length (TZ)) and not (TZ [J] in ['A'..'Z']) do Inc (J); TZOffset := ParseOffset (Copy (TZ, I, J - I)); (* DST timezone name *) I := J; while (J <= Length (TZ)) and (TZ [J] in ['A'..'Z']) do Inc (J); if J > I then begin TZDSTName := Copy (TZ, I, J - I); (* DST timezone name provided; if equal to the standard timezone *) (* name then DSTOffset is set to be equal to TZOffset by default, *) (* otherwise it is set to TZOffset - 3600 seconds. *) if TZDSTName <> TZName then DSTOffset := -3600 + TZOffset else DSTOffset := TZOffset; end else begin TZDSTName := TZName; (* No DST timezone name provided => DSTOffset is equal to TZOffset *) DSTOffset := TZOffset; end; if J <= Length (TZ) then begin (* Check if DST offset is specified here; *) (* if not, default value set above is used. *) if TZ [J] <> ',' then begin I := J; Inc (J); while (J <= Length (TZ)) and (TZ [J] <> ',') do Inc (J); DSTOffset := ParseOffset (Copy (TZ, I, J - I)); end; if J < Length (TZ) then begin Inc (J); (* DST switching details *) case TZ [J] of 'M': begin (* Mmonth.week.dayofweek[/StartHour] *) ADSTStartSpecType := DSTMonthWeekDay; if J >= Length (TZ) then Exit; Inc (J); I := J; while (J <= Length (TZ)) and not (TZ [J] in ['.', ',', '/']) do Inc (J); if (J >= Length (TZ)) or (TZ [J] <> '.') then Exit; Val (Copy (TZ, I, J - I), ADSTStartMonth, Err); if (Err > 0) or (ADSTStartMonth > 12) then Exit; Inc (J); I := J; while (J <= Length (TZ)) and not (TZ [J] in ['.', ',', '/']) do Inc (J); if (J >= Length (TZ)) or (TZ [J] <> '.') then Exit; Val (Copy (TZ, I, J - I), ADSTStartWeek, Err); if (Err > 0) or (ADSTStartWeek < 1) or (ADSTStartWeek > 5) then Exit; Inc (J); I := J; while (J <= Length (TZ)) and not (TZ [J] in [',', '/']) do Inc (J); Val (Copy (TZ, I, J - I), ADSTStartDay, Err); if (Err > 0) or (ADSTStartDay < 0) or (ADSTStartDay > 6) or (J >= Length (TZ)) then Exit; if TZ [J] = '/' then begin Inc (J); I := J; while (J <= Length (TZ)) and (TZ [J] <> ',') do Inc (J); Val (Copy (TZ, I, J - I), ADSTStartSec, Err); if (Err > 0) or (ADSTStartSec > 86399) or (J >= Length (TZ)) then Exit else ADSTStartSec := ADSTStartSec * 3600; end else (* Use the preset default *) ADSTStartSec := DSTStartSec; Inc (J); end; 'J': begin (* Jjulianday[/StartHour] *) ADSTStartSpecType := DSTJulianX; if J >= Length (TZ) then Exit; Inc (J); I := J; while (J <= Length (TZ)) and not (TZ [J] in [',', '/']) do Inc (J); Val (Copy (TZ, I, J - I), ADSTStartDay, Err); if (Err > 0) or (ADSTStartDay = 0) or (ADSTStartDay > 365) or (J >= Length (TZ)) then Exit; if TZ [J] = '/' then begin Inc (J); I := J; while (J <= Length (TZ)) and (TZ [J] <> ',') do Inc (J); Val (Copy (TZ, I, J - I), ADSTStartSec, Err); if (Err > 0) or (ADSTStartSec > 86399) or (J >= Length (TZ)) then Exit else ADSTStartSec := ADSTStartSec * 3600; end else (* Use the preset default *) ADSTStartSec := DSTStartSec; Inc (J); end else begin (* Check the used format first - GNU libc / GCC / EMX expect *) (* "NameOffsetDstname[Dstoffset],Start[/StartHour],End[/EndHour]"; *) (* if more than one comma (',') is found, the following format is assumed: *) (* "NameOffsetDstname[Dstoffset],StartMonth,StartWeek,StartDay,StartSecond, *) (* EndMonth,EndWeek,EndDay,EndSecond,DSTDifference". *) I := J; while (J <= Length (TZ)) and (TZ [J] <> ',') do Inc (J); S := Copy (TZ, I, J - I); if J < Length (TZ) then begin Inc (J); I := J; while (J <= Length (TZ)) and (TZ [J] <> ',') do Inc (J); GnuFmt := J > Length (TZ); end else Exit; if GnuFmt then begin ADSTStartSpecType := DSTJulian; J := Pos ('/', S); if J = 0 then begin Val (S, ADSTStartDay, Err); if (Err > 0) or (ADSTStartDay > 365) then Exit; (* Use the preset default *) ADSTStartSec := DSTStartSec; end else begin if J = Length (S) then Exit; Val (Copy (S, 1, Pred (J)), ADSTStartDay, Err); if (Err > 0) or (ADSTStartDay > 365) then Exit; Val (Copy (S, Succ (J), Length (S) - J), ADSTStartSec, Err); if (Err > 0) or (ADSTStartSec > 86399) then Exit else ADSTStartSec := ADSTStartSec * 3600; end; J := I; end else begin Val (S, ADSTStartMonth, Err); if (Err > 0) or (ADSTStartMonth > 12) then Exit; Val (Copy (TZ, I, J - I), ADSTStartWeek, Err); if (Err > 0) or (ADSTStartWeek < -1) or (ADSTStartWeek > 5) or (J >= Length (TZ)) then Exit; Inc (J); I := J; while (J <= Length (TZ)) and (TZ [J] <> ',') do Inc (J); Val (Copy (TZ, I, J - I), ADSTStartDay, Err); if (DSTStartWeek = 0) then begin if (Err > 0) or (ADSTStartDay < 1) or (ADSTStartDay > 31) or (ADSTStartDay > 30) and (ADSTStartMonth in [4, 6, 9, 11]) or (ADSTStartMonth = 2) and (ADSTStartDay > 29) then Exit; ADSTStartSpecType := DSTMonthDay; end else begin if (Err > 0) or (ADSTStartDay < 0) or (ADSTStartDay > 6) then Exit; ADSTStartSpecType := DSTMonthWeekDay; end; if J >= Length (TZ) then Exit; Inc (J); I := J; while (J <= Length (TZ)) and (TZ [J] <> ',') do Inc (J); Val (Copy (TZ, I, J - I), ADSTStartSec, Err); if (Err > 0) or (ADSTStartSec > 86399) or (J >= Length (TZ)) then Exit; Inc (J); I := J; while (J <= Length (TZ)) and (TZ [J] <> ',') do Inc (J); Val (Copy (TZ, I, J - I), ADSTEndMonth, Err); if (Err > 0) or (ADSTEndMonth > 12) or (J >= Length (TZ)) then Exit; Inc (J); I := J; while (J <= Length (TZ)) and (TZ [J] <> ',') do Inc (J); Val (Copy (TZ, I, J - I), ADSTEndWeek, Err); if (Err > 0) or (ADSTEndWeek < -1) or (ADSTEndWeek > 5) or (J >= Length (TZ)) then Exit; Inc (J); I := J; while (J <= Length (TZ)) and (TZ [J] <> ',') do Inc (J); Val (Copy (TZ, I, J - I), ADSTEndDay, Err); if (DSTEndWeek = 0) then begin if (Err > 0) or (ADSTEndDay < 1) or (ADSTEndDay > 31) or (ADSTEndDay > 30) and (ADSTEndMonth in [4, 6, 9, 11]) or (ADSTEndMonth = 2) and (ADSTEndDay > 29) then Exit; ADSTEndSpecType := DSTMonthDay; end else begin if (Err > 0) or (ADSTEndDay < 0) or (ADSTEndDay > 6) then Exit; ADSTEndSpecType := DSTMonthWeekDay; end; if J >= Length (TZ) then Exit; Inc (J); I := J; while (J <= Length (TZ)) and (TZ [J] <> ',') do Inc (J); Val (Copy (TZ, I, J - I), ADSTEndSec, Err); if (Err > 0) or (ADSTEndSec > 86399) or (J >= Length (TZ)) then Exit; Val (Copy (TZ, Succ (J), Length (TZ) - J), ADSTChangeSec, Err); if (Err = 0) and (ADSTChangeSec < 86400) then begin (* Format complete, all checks successful => accept the parsed values. *) DSTStartMonth := ADSTStartMonth; DSTStartWeek := ADSTStartWeek; DSTStartDay := ADSTStartDay; DSTStartSec := ADSTStartSec; DSTEndMonth := ADSTEndMonth; DSTEndWeek := ADSTEndWeek; DSTEndDay := ADSTEndDay; DSTEndSec := ADSTEndSec; DSTStartSpecType := ADSTStartSpecType; DSTEndSpecType := ADSTEndSpecType; DSTOffset := TZOffset - ADSTChangeSec; end; (* Parsing finished *) Exit; end; end; end; (* GnuFmt - DST end specification *) if TZ [J] = 'M' then begin (* Mmonth.week.dayofweek *) ADSTEndSpecType := DSTMonthWeekDay; if J >= Length (TZ) then Exit; Inc (J); I := J; while (J <= Length (TZ)) and not (TZ [J] in ['.', ',', '/']) do Inc (J); if (J >= Length (TZ)) or (TZ [J] <> '.') then Exit; Val (Copy (TZ, I, J - I), ADSTEndMonth, Err); if (Err > 0) or (ADSTEndMonth > 12) then Exit; Inc (J); I := J; while (J <= Length (TZ)) and not (TZ [J] in ['.', ',', '/']) do Inc (J); if (J >= Length (TZ)) or (TZ [J] <> '.') then Exit; Val (Copy (TZ, I, J - I), ADSTEndWeek, Err); if (Err > 0) or (ADSTEndWeek < 1) or (ADSTEndWeek > 5) then Exit; Inc (J); I := J; while (J <= Length (TZ)) and (TZ [J] <> '/') do Inc (J); Val (Copy (TZ, I, J - I), ADSTEndDay, Err); if (Err > 0) or (ADSTEndDay < 0) or (ADSTEndDay > 6) then Exit; end else begin if TZ [J] = 'J' then begin (* Jjulianday *) if J = Length (TZ) then Exit; Inc (J); ADSTEndSpecType := DSTJulianX end else ADSTEndSpecType := DSTJulian; if J >= Length (TZ) then Exit; Inc (J); I := J; while (J <= Length (TZ)) and (TZ [J] <> '/') do Inc (J); Val (Copy (TZ, I, J - I), ADSTEndDay, Err); if (Err > 0) or (ADSTEndDay = 0) and (ADSTEndSpecType = DSTJulianX) or (ADSTEndDay > 365) then Exit; end; if (J <= Length (TZ)) and (TZ [J] = '/') then begin if J = Length (TZ) then Exit; Val (Copy (TZ, Succ (J), Length (TZ) - J), ADSTEndSec, Err); if (Err > 0) or (ADSTEndSec > 86399) then Exit else ADSTEndSec := ADSTEndSec * 3600; end else (* Use the preset default *) ADSTEndSec := DSTEndSec; (* Format complete, all checks successful => accept the parsed values. *) if ADSTStartSpecType = DSTMonthWeekDay then begin DSTStartMonth := ADSTStartMonth; DSTStartWeek := ADSTStartWeek; end; DSTStartDay := ADSTStartDay; DSTStartSec := ADSTStartSec; if ADSTStartSpecType = DSTMonthWeekDay then begin DSTEndMonth := ADSTEndMonth; DSTEndWeek := ADSTEndWeek; end; DSTEndDay := ADSTEndDay; DSTEndSec := ADSTEndSec; DSTStartSpecType := ADSTStartSpecType; DSTEndSpecType := ADSTEndSpecType; end; end else DSTOffset := -3600 + TZOffset; end; end; end; begin InitTZ; end.