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