xref: /freebsd/contrib/tzdata/checktab.awk (revision 46226b0162ba03986b1de2c790804b37dd8e64d6)
1# Check tz tables for consistency.
2
3# Contributed by Paul Eggert.  This file is in the public domain.
4
5BEGIN {
6	FS = "\t"
7
8	if (!iso_table) iso_table = "iso3166.tab"
9	if (!zone_table) zone_table = "zone1970.tab"
10	if (!want_warnings) want_warnings = -1
11
12	monthabbr["Jan"] = 1
13	monthabbr["Feb"] = 1
14	monthabbr["Mar"] = 1
15	monthabbr["Apr"] = 1
16	monthabbr["May"] = 1
17	monthabbr["Jun"] = 1
18	monthabbr["Jul"] = 1
19	monthabbr["Aug"] = 1
20	monthabbr["Sep"] = 1
21	monthabbr["Oct"] = 1
22	monthabbr["Nov"] = 1
23	monthabbr["Dec"] = 1
24
25	while (getline <iso_table) {
26		iso_NR++
27		if ($0 ~ /^#/) continue
28		if (NF != 2) {
29			printf "%s:%d: wrong number of columns\n", \
30				iso_table, iso_NR >>"/dev/stderr"
31			status = 1
32		}
33		cc = $1
34		name = $2
35		if (cc !~ /^[A-Z][A-Z]$/) {
36			printf "%s:%d: invalid country code '%s'\n", \
37				iso_table, iso_NR, cc >>"/dev/stderr"
38			status = 1
39		}
40		if (cc <= cc0) {
41			if (cc == cc0) {
42				s = "duplicate";
43			} else {
44				s = "out of order";
45			}
46
47			printf "%s:%d: country code '%s' is %s\n", \
48				iso_table, iso_NR, cc, s \
49				>>"/dev/stderr"
50			status = 1
51		}
52		cc0 = cc
53		if (name2cc[name]) {
54			printf "%s:%d: '%s' and '%s' have the same name\n", \
55				iso_table, iso_NR, name2cc[name], cc \
56				>>"/dev/stderr"
57			status = 1
58		}
59		name2cc[name] = cc
60		cc2name[cc] = name
61		cc2NR[cc] = iso_NR
62	}
63
64	cc0 = ""
65
66	while (getline <zone_table) {
67		zone_NR++
68		if ($0 ~ /^#/) continue
69		if (NF != 3 && NF != 4) {
70			printf "%s:%d: wrong number of columns\n", \
71				zone_table, zone_NR >>"/dev/stderr"
72			status = 1
73		}
74		ccs = input_ccs[zone_NR] = $1
75		coordinates = $2
76		tz = $3
77		comments = input_comments[zone_NR] = $4
78		split(ccs, cca, /,/)
79		cc = cca[1]
80
81		if (tztab[tz]) {
82		  printf "%s:%d: %s: duplicate Zone from line %d\n", \
83		    zone_table, zone_NR, tz, tz2NR[tz]
84		  status = 1
85		}
86
87		# Don't complain about a special case for Crimea in zone.tab.
88		# FIXME: zone.tab should be removed, since it is obsolete.
89		# Or at least put just "XX" in its country-code column.
90		if (cc < cc0 \
91		    && !(zone_table == "zone.tab" \
92			 && tz0 == "Europe/Simferopol")) {
93			printf "%s:%d: country code '%s' is out of order\n", \
94				zone_table, zone_NR, cc >>"/dev/stderr"
95			status = 1
96		}
97		cc0 = cc
98		tz0 = tz
99		tztab[tz] = 1
100		tz2NR[tz] = zone_NR
101		for (i in cca) {
102		    cc = cca[i]
103		    if (cc2name[cc]) {
104			cc_used[cc]++
105		    } else if (! (cc == "XX" && zone_table == "zonenow.tab")) {
106			printf "%s:%d: %s: unknown country code\n", \
107				zone_table, zone_NR, cc >>"/dev/stderr"
108			status = 1
109		    }
110		}
111		if (coordinates !~ /^[-+][0-9][0-9][0-5][0-9][-+][01][0-9][0-9][0-5][0-9]$/ \
112		    && coordinates !~ /^[-+][0-9][0-9][0-5][0-9][0-5][0-9][-+][01][0-9][0-9][0-5][0-9][0-5][0-9]$/) {
113			printf "%s:%d: %s: invalid coordinates\n", \
114				zone_table, zone_NR, coordinates >>"/dev/stderr"
115			status = 1
116		}
117	}
118
119	for (i = 1; i <= zone_NR; i++) {
120	  ccs = input_ccs[i]
121	  if (!ccs) continue
122	  comments = input_comments[i]
123	  split(ccs, cca, /,/)
124	  used_max = 0
125          for (j in cca) {
126	    cc = cca[j]
127	    if (used_max < cc_used[cc]) {
128	      used_max = cc_used[cc]
129	      used_max_cc = cc
130	    }
131	  }
132	  if (used_max <= 1 && comments && zone_table != "zonenow.tab") {
133	    printf "%s:%d: unnecessary comment '%s'\n", \
134	      zone_table, i, comments \
135	      >>"/dev/stderr"
136	    status = 1
137	  } else if (1 < used_max && !comments) {
138	    printf "%s:%d: missing comment for %s\n", \
139	      zone_table, i, used_max_cc \
140	      >>"/dev/stderr"
141	    status = 1
142	  }
143	}
144	FS = " "
145}
146
147$1 ~ /^#/ { next }
148
149{
150	tz = rules = stdoff = ""
151	if ($1 == "Zone") {
152		tz = $2
153		stdoff = $3
154		ruleUsed[$4] = 1
155		if ($5 ~ /%/) rulePercentUsed[$4] = 1
156	} else if ($1 == "Link") {
157	    if (zone_table == "zone.tab") {
158		# Ignore Link commands if source and destination basenames
159		# are identical, e.g. Europe/Istanbul versus Asia/Istanbul.
160		src = $2
161		dst = $3
162		while ((i = index(src, "/"))) src = substr(src, i+1)
163		while ((i = index(dst, "/"))) dst = substr(dst, i+1)
164		if (src != dst) tz = $3
165	    }
166	} else if ($1 == "Rule") {
167		ruleDefined[$2] = 1
168		if ($10 != "-") ruleLetters[$2] = 1
169		if (!monthabbr[$6]) {
170		  printf "%s:%d: tricky month: %s\n", FILENAME, FNR, $6 \
171			  >>"/dev/stderr"
172		  status = 1
173		}
174	} else {
175		stdoff = $1
176		ruleUsed[$2] = 1
177		if ($3 ~ /%/) rulePercentUsed[$2] = 1
178	}
179
180	if (stdoff && stdoff !~ /^\-?1?[0-9](:[0-5][0-9](:[0-5][0-9])?)?$/) {
181		printf "%s:%d: unlikely STDOFF: %s\n", FILENAME, FNR, stdoff \
182			>>"/dev/stderr"
183		status = 1
184	}
185
186	if (tz && tz ~ /\// && tz !~ /^Etc\//) {
187		if (!tztab[tz] && FILENAME != "backward" \
188		    && zone_table != "zonenow.tab") {
189			printf "%s: no data for '%s'\n", zone_table, tz \
190				>>"/dev/stderr"
191			status = 1
192		}
193		zoneSeen[tz] = 1
194	}
195}
196
197END {
198	for (tz in ruleDefined) {
199		if (!ruleUsed[tz]) {
200			printf "%s: Rule never used\n", tz
201			status = 1
202		}
203	}
204	for (tz in ruleLetters) {
205		if (!rulePercentUsed[tz]) {
206			printf "%s: Rule contains letters never used\n", tz
207			status = 1
208		}
209	}
210	for (tz in tztab) {
211		if (!zoneSeen[tz] && tz !~ /^Etc\//) {
212			printf "%s:%d: no Zone table for '%s'\n", \
213				zone_table, tz2NR[tz], tz >>"/dev/stderr"
214			status = 1
215		}
216	}
217	if (0 < want_warnings) {
218		for (cc in cc2name) {
219			if (!cc_used[cc]) {
220				printf "%s:%d: warning: " \
221					"no Zone entries for %s (%s)\n", \
222					iso_table, cc2NR[cc], cc, cc2name[cc]
223			}
224		}
225	}
226
227	exit status
228}
229