Skip to content
Snippets Groups Projects
mkrules.pike 25.22 KiB
// this is a script to generate rules
// from timezone data files;
// ftp://elsie.nci.nih.gov/pub/ 
// (timezone mailing list: tz@elsie.nci.nih.gov)
//
// source datafile are usually found somewhere around zic(8),
// if they exist in the system.
//
// Most systems only have compiled files, just like pike,
// and zic(8) is the usual compiler.

// pike mkrules.pike ../data/{africa,antarctica,asia,australasia,backward,etcetera,europe,northamerica,pacificnew,southamerica,systemv}

#pike __REAL_VERSION__

object cal=Calendar.ISO->set_timezone("UTC");
function Year=cal->Year;
object nleapy=Year(1999);

object tzrules; // needed to make timezones, compiled below

mapping rules=([]);
mapping zones=([]);
mapping links=([]);
array arules=({});
array azones=({});

mapping(string:mapping(int:array(string))) abbr2zones = ([
  "DFT":([ 0x7fffffff:({ "Europe/Oslo", "Europe/Paris" }), ]),
  "NFT":([ 0x7fffffff:({ "Europe/Oslo", "Europe/Paris" }), ]),
]);

#define FIXED(D)   (yjd+((D)-1))
#define FIX_L(D)   (yjd+leap+((D)-1))
#define LDAY(D,W)  (yjd+((D)-1)-( (yjd+((D)+(8-W)-1)) % 7))
#define LDAYL(D,W) (yjd+((D)-1)+leap-( (yjd+leap+((D)+(8-W)-1)) % 7))

#define FIXID(id) replace(id,"/-+"/1,"__p"/1)

void add_abbr(string abbr, string zone, int until)
{
   mapping(int:array(string)) zones = abbr2zones[abbr];
   if (!zones) abbr2zones[abbr] = zones = ([]);
   zones[until] += ({ zone });
}

int parse_offset(string t)
{
   int h,m,s;
   string res;

   if (t=="0") return 0;

   res="";
   if (sscanf(t,"-%d:%d:%d%s",h,m,s,res)&&res=="")
      return -(h*3600+m*60+s);
   res="";
   if (sscanf(t,"-%d:%d%s",h,m,res)&&res=="")
      return -(h*3600+m*60);
   res="";
   if (sscanf(t,"%d:%d:%d%s",h,m,s,res)&&res=="")
      return h*3600+m*60+s;
   res="";
   if (sscanf(t,"-%d:%d%s",h,m,res)&&res=="")
      return h*3600+m*60;

   complain("failed to parse offset %O\n",t);
}

array parse_tod(string t)
{
   int h,m,s;
   string res;

   if (t=="0") return 0;

   if (sscanf(t,"%d:%d:%d%s",h,m,s,res)==4)
      return ({h*3600+m*60+s,res});
   res="";
   if (sscanf(t,"%d:%d%s",h,m,res)==3)
      return ({h*3600+m*60,res});
   if (sscanf(t,"%d%s",h,res)==2)
      return ({h*3600,res});

   complain("failed to parse time of day %O\n",t);
}

class Shift
{
   string dayrule;
   int time;
   string timetype;
   int offset;
   string s;
   string comment;

   void create(array a)
   {
      switch (sizeof(a))
      {
	 case 5:
	    dayrule=think_day(a[0],a[1]);
	    comment=a[0]+" "+a[1];
	    // NB: The Morocco rule for 2011-07-31 has 0 as AT,
	    //     while all others have 0:00.
	    [time,timetype] = parse_tod(a[2]) || ({ 0, "" });
	    switch (timetype)
	    {
	       case "": timetype="w"; break;
	       case "s": case "u": case "w": break;
	       default: complain("unknown time of day type %O\n",timetype);
	    }
	    offset=parse_offset(a[3]);
	    s=(a[4]=="-")?"":a[4];
	    break;
	 case 6:
	    [dayrule,comment,time,timetype,offset,s]=a;
	    break;
	 default:
	    error("illegal size of a\n");
      }
   }

   protected string _sprintf(int t)
   { 
      return t=='O' &&
	 sprintf("Shift(%s,%d%s,%+d,%O)",
		 dayrule,time,timetype,offset,s);
   }

   int `==(Shift other)
   {
      return ( dayrule==other->dayrule &&
	       time==other->time &&
	       timetype==other->timetype &&
	       offset==other->offset &&
	       s==other->s );
   }
   function(Shift:int) __equal=`==;
   constant wday=(["Mon":1,"Tue":2,"Wed":3,"Thu":4,"Fri":5,"Sat":6,"Sun":7]);
   constant vmonth=(<"Jan","Feb","Mar","Apr","May","Jun",
		     "Jul","Aug","Sep","Nov","Dec">);

   string think_day(string mon,string rule)
   {
      int d;
      string ds;

      if (mon=="") return "0";
      if (rule==(string)(d=(int)rule))
      {
	 if (mon=="Feb") return "FIXED("+(31+d)+")";
	 if (mon=="Jan") return "FIXED("+(d)+")";
	 return "FIX_L("+nleapy->month(mon)->day(d)->year_day()+")";
      }
      else if (sscanf(rule,"last%s",ds))
      {
	 int wd=wday[ds];
	 if (!wd) complain("unknown weekday %O (last%s)\n",ds,ds);

	 if (mon=="Jan") 
	    return "LDAY ("+31+","+wd+")";

	 return "LDAYL("+nleapy->month(mon)->day(-1)->year_day()+
	    ","+wd+")";
      }
      else if (sscanf(rule,"%s>=%d",ds,d))
      {
	 int wd=wday[ds];
	 if (!wd) complain("unknown weekday %O (last%s)\n",ds,ds);

	 if (d>24 && mon=="Feb")
	     complain("can't handle Feb %d in a >= rule\n",d);

	 if (mon=="Jan") 
	    return "LDAY ("+(nleapy->month(mon)->day(d)->year_day()+6)+
	       ","+wd+")";

	 return "LDAYL("+(nleapy->month(mon)->day(d)->year_day()+6)+
	    ","+wd+")";
      }
      else if (sscanf(rule,"%s<=%d",ds,d))
      {
	 int wd=wday[ds];
	 if (!wd) complain("unknown weekday %O (last%s)\n",ds,ds);

	 if (d>24 && mon=="Feb")
	     complain("can't handle Feb %d in a <= rule\n",d);

	 if (mon=="Jan" || mon=="Feb")
	    return "LDAY ("+(nleapy->month(mon)->day(d)->year_day())+
	       ","+wd+")";

	 return "LDAYL("+(nleapy->month(mon)->day(d)->year_day())+
	    ","+wd+")";
      }
      else
	 complain("unknown rule method %O\n",rule);
   }

   Shift|array ``+(array|Shift s)
   {
      if (!s) return this;
      if (!arrayp(s)) s=({s});
      return s+({this});
   }

   int ldayl_is_fix_l(int d1,int wd,int d2,int yn1,int yn2)
   {
      object y1=Year(yn1);
      object y2=Year(yn2);
      int yjd,leap;

      yjd=y1->julian_day();
      leap=y1->leap_year();
      d1=LDAYL(d1,wd);

      yjd=y2->julian_day();
      leap=y2->leap_year();
      d2=FIX_L(d2);

      return d1==d2;
   }

   Shift try_promote(Shift t,int y0,int y1)
   {
// this is year y0
// t is year y1
      if (t==this) return t; // same!
      if (t->time!=time ||
	  t->timetype!=timetype ||
	  t->offset!=offset ||
	  t->s!=s) return 0; // no chance

      int a,b,c;
      if (sscanf(dayrule,"LDAYL(%d,%d)",a,b)==2 &&
	  sscanf(t->dayrule,"FIX_L(%d)",c)==1)
	 if (ldayl_is_fix_l(a,b,c,y0,y1)) 
	    return this; // ldayl
	 else
	    return 0; // no
      if (sscanf(t->dayrule,"LDAYL(%d,%d)",a,b)==2 &&
	  sscanf(dayrule,"FIX_L(%d)",c)==1)
	 if (ldayl_is_fix_l(a,b,c,y1,y0)) 
	    return t; // ldayl
	 else
	    return 0; // no

      return 0;
   }

   string dump(int lastoffset,multiset ys)
   {
      string t;
      array z=(array)ys;
      int l=Year(z[0])->leap_year();
      foreach (z[1..],int y) if (Year(y)->leap_year()!=l) { l=2; break; }
      switch (timetype)
      {
	 case "s": t=sprintf("UO%+d",time); break;
	 case "u": t=""+time; break;
	 case "w": t=sprintf("UO%+d",(time-lastoffset)); break;
	 default: error("illegal state\n");      
      }
      string r=dayrule;
      if (l!=2)
      {
	 int d,w;
	 if (sscanf(r,"FIX_L(%d)",d)) r=sprintf("FIXED(%d)",d+l);
	 else if (sscanf(r,"LDAYL(%d,%d)",d,w)==2) 
	    r=sprintf("LDAY (%d,%d)",d+l,w);
      }
      return sprintf("({%-12s,%-10s,%-5d,%-6O}),  %s",
		     r,t,offset,s,comment!=""?"// "+comment:"");
   }
}

class MyRule (string id)
{
   mapping rules=([]);

   multiset(string) symbols = (<>);

   int amt=0;

   void add(string line)
   {
      array a= array_sscanf(line, replace("%s %s %s %s %s %s %s %[^\t ]",
					  " ","%*[ \t]"));

      if (sizeof(a)<8) complain("illegal rule line format\n");

      a[7] = (a[7]/" #")[0];
      if (a[7] == "-") a[7] = "";
      symbols[a[7]] = 1;

      if (!(int)a[0] && a[0]!="min")
	 complain("unknown year %O\n",a[0]);

// ---

#define INF_YEAR 2050
#define NUL_YEAR 1850

      int y1=(int)a[0] || NUL_YEAR;
      int y2;
      if (a[1]=="max") y2=INF_YEAR; 
      else if (a[1]=="only") y2=y1; 
      else if (!(y2=(int)a[1]))
	 complain("unknown year %O\n",a[1]);
      else if (y2>=INF_YEAR)
	 complain("big year\n");

      Shift sh=Shift(a[3..]);

      switch (a[2])
      {
	 case "-": for (;y1<=y2;y1++) rules[y1]+=sh; break;
	 case "odd": 
	    if (!(y1&1)) y1++;
	    for (;y1<=y2;y1+=2) rules[y1]+=sh; 
	    break;
	 case "even": 
	    if ((y1&1)) y1++;
	    for (;y1<=y2;y1+=2) rules[y1]+=sh; 
	    break;
	 default:
	    complain("unknown year type %O\n",a[2]);
      }
   }

   string dump()
   {
      mapping r2=([]);
      Shift last=Shift(({"0","?",0,"u",0,"?"}));
      Shift first=last;
      string res="";

      if (!r2[NUL_YEAR]) r2[NUL_YEAR]=({last});

      for (int y=min(@indices(rules));y<=INF_YEAR; y++)
	 [r2[y],last]=mkperiods(rules[y],last,first);

      res+=("class "+
	    FIXID(id)+"\n"
	    "{\n"
	    "   inherit TZRules;\n"
	    "   protected array(array(string|int)) jd_year_periods(int jd)\n"
	    "   {\n"
	    "      [int y,int yjd,int leap]=gregorian_yjd(jd);\n"
	    "      switch (y)\n"
	    "      {\n");

      string s="",t;

      int mn=min(@indices(rules-(<NUL_YEAR>)));

      for (int y=INF_YEAR;sizeof(r2);y--)
	 if (r2[y])
	 {
	    array z=r2[y];
	    multiset my=(<y>);

	    foreach (indices(r2),int y2)
	       if (join_periods(z,r2[y2],y,y2))
		  my[y2]=1;

	    foreach ((array)my,int y2) m_delete(r2,y2);

	    string t="";

	    int y0=min(@(array)my);
	    int y2=max(@(array)my);
	    for (; y0<=y2; y0++)
	       if (my[y0])
	       {
		  int y1=y0;
		  while (my[++y1]);

		  y1--;
		  
		  if (y0==NUL_YEAR)
		  {
		     if (my[INF_YEAR])
			t+="         default: // .."+max(y1,mn-1)+
			   " and ..\n";
		     else
			t+="         default: // .."+max(y1,mn-1)+":\n";
		  }
		  else if (y0==y1)
		     t+="         case "+y0+":\n";
		  else if (y1==2050)
		     { 
			if (!my[NUL_YEAR]) t+="         case "+y0+"..:\n"; 
			else t=replace(t,"",(string)y0); 
		     }
		  else 		  
		     t+="         case "+y0+".."+y1+":\n";

		  y0=y1;
	       }

	    int lastoffset=0;
	    string res=" "*12+"return ({";
	    foreach (z,Shift s)
	    {
	       res+=s->dump(lastoffset,my)+("\n"+" "*21);
	       lastoffset=s->offset;
	    }
	    array resa=res/"\n";
	    resa[-2]=replace(resa[-2],",  ","});");
	 
	    t+=resa[..<1]*"\n"+"\n";
	    s=t+s;
	 }
      res+=(s+
	    "      }\n"
	    "   }\n"
	    "}\n\n");

      return res;
   }

   int join_periods(array s,array t,int y0,int y1)
   {
      if (equal(s,t)) 
	 return 1;
      if (sizeof(s)!=sizeof(t)) return 0;
      if (s[0]!=t[0]) return 0;
// try promote
      array q=s[..0];
      int i;
      for (i=1; i<sizeof(s); i++)
      {
	 Shift u=s[i]->try_promote(t[i],y0,y1);
	 if (!u) return 0;
	 q+=({u});
      }
      for (i=1; i<sizeof(s); i++)
	 s[i]=q[i]; // destructive
      return 1;
   }

   array(array(Shift)|Shift) mkperiods(array|Shift s,Shift last,Shift first)
   {
      if (!s) s=({});
      if (!arrayp(s)) s=({s});
      
      sort(map(s,lambda(Shift s)
		 {
		    return array_sscanf(s->dayrule,"%*[^(](%d")[0];
		 }),s);

      if (first->s=="?")
	 foreach (s,Shift d) if (!d->offset) first->s=d->s;

      s=({last,@s});

      last=Shift( ({"0","",0,"u",
		    s[-1]->offset,s[-1]->s}) );

      return ({s, last});
   }
}

class Zone (string id)
{
   array rules=({});

   void add(string line)
   {
      array a= array_sscanf(line, replace("%s %s %s %s",
					  " ","%*[ \t]"));
      if (sizeof(a)<4)
	 complain("parse error\n");
      
      a=({parse_offset(a[0]), // offset
	  a[1], // rule or added offset
	  a[2], // string
	  a[3],
	  0, 0, "tz", 0}); // until
      a[5]=rule_shift(a);
      a[4]=clone_rule(a);

      if (sizeof(a[2])) {
	 int until = (a[5] == "forever")?0x7fffffff:(int)a[5];
	 foreach(a[2]/"/", string fmt) {
	    MyRule rule = global::rules[a[1]];
	    if (rule) {
	       foreach(indices(rule->symbols), string sym) {
		  if ((sizeof(sym) > 2) && (fmt != "%s")) continue;
		  add_abbr(sprintf(fmt, sym), id, until);
	       }
	    } else if (a[1] == "Romania") {
	       // Kludge for forward reference in tzdata2012c/europe
	       // for Europe/Chisinau to the Romania rule.
	       foreach(({ "", "S" }), string sym) {
		  add_abbr(sprintf(fmt, sym), id, until);
	       }
	    } else {
	       add_abbr(fmt, id, until);
	    }
	 }
      }

      rules+=({a});
   }

   string clone_rule(array a)
   {
      int h,m,s,roff=-17;
      if (a[1]=="-") roff=0;
      else if (sscanf(a[1],"-%d:%d:%d",h,m,s)==3) roff=h*3600+m*60+s;
      else if (sscanf(a[1],"%d:%d:%d",h,m,s)==3) roff=h*3600+m*60+s;
      else if (sscanf(a[1],"-%d:%d",h,m)==2) roff=h*3600+m*60;
      else if (sscanf(a[1],"%d:%d",h,m)==2) roff=h*3600+m*60;

      if (roff==-17) // based on DST rule 
	 return sprintf(
	    "TZrules.%s(%d,%O)",
	    FIXID(a[1]),-a[0],a[2]);
      else // simple timezone
	 return sprintf(
	    "Rule.Timezone(%d,%O)",
	    -(roff+a[0]),a[2]);
   }

   string rule_shift(array a)
   {
      if (a[3]=="" || a[3][0]=='#') return "forever";

      string in=a[3];
      sscanf(in,"until %s",in);
      sscanf(in,"%s#",in);

//        werror("%O\n",in);

      int y,d=1,t=0;
      string mn="Jan",ty="w";
      if (sscanf(in,"%d%*[ \t]%s%*[ \t]%d%*[ \t]%[^# \t]",
		 y,mn,d,string tod)==7 &&
	  tod!="")
	 [t,ty]=parse_tod(tod);
      else if (!(sscanf(in,"%d%*[ \t]%[A-Za-z]%*[ \t]%d",y,mn,d)==5 ||
		 (sscanf(in,"%d%*[ \t]%[A-Za-z]",y,mn)==3 && mn!="") ||
		 (mn="Jan",sscanf(in,"%d",y))))
      {
//  	 werror("%O\n",array_sscanf(in,"%d%*[ \t]%[A-Za-z]"));
	 complain("failed to understand UNTIL %O\n",in);
	 y=2000;
      }

      int utc0=Year(y)->month(mn)->day(d)->unix_time();
      switch (ty)
      {
	 case "u":     // utc time
//  	    a[3]=sprintf("[%d+%d=%d] %s\n",utc0,t,utc0+t,a[3]);
	    return (string)(utc0+t); break;  
	 case "s":     // local standard time
//  	    a[3]=sprintf("[%d+%d-%d=%d] %s\n",utc0,t,a[0],utc0+t-a[0],a[3]);
	    return (string)(utc0+t-a[0]); break; 
	 case "w": case "":       // with rule; check rule
	    int h,m,s,roff=-17;
	    if (a[1]=="-") roff=0;
	    else if (sscanf(a[1],"-%d:%d:%d",h,m,s)==3) roff=h*3600+m*60+s;
	    else if (sscanf(a[1],"%d:%d:%d",h,m,s)==3) roff=h*3600+m*60+s;
	    else if (sscanf(a[1],"-%d:%d",h,m)==2) roff=h*3600+m*60;
	    else if (sscanf(a[1],"%d:%d",h,m)==2) roff=h*3600+m*60;

	    if (roff==-17) // based on DST rule
	    {
	       if (!tzrules) return 0; // can't do that now
	       object|program rules=tzrules[FIXID(a[1])];
	       if (!rules)
	       {
		  werror("ERROR: Missing rule %O (used in Zone %O)\n",a[1],id);
		  return "[err]";
	       }
	       rules=rules(-a[0],"-");

	       roff=rules->tz_jd(Year(y)->month(mn)->day(d)->julian_day())[0];

//  	       werror("Using %O:%O\n",rules,roff);

	       return (string)(utc0+t-roff); 
	    }
	    return (string)(utc0+t-a[0]-roff); 

	 default:
	    complain("unknown time of day modifier %O\n",ty);
      }
   }

   string dump()
   {
      string cid=FIXID(id);
      string res="";

      if (!sizeof(rules))
      {
	 res+=("// skipped %O due to errors\n",id);
	 return res;
      }
	       
      if (sizeof(rules)==1) // simple zone
      {
	 res+=("Rule.Timezone "+cid+"="+
	       rules[0][4]+";\n");
	 return res;
      }

      mapping rname=([]);
      int n=1;
      
      foreach (rules,array a)
	 if (rname[a[4]]) a[6]=rname[a[4]];
	 else a[6]=rname[a[4]]="tz"+n++;
      
      res+=("class "+cid+"\n"
	    "{\n"
	    "   inherit TZHistory;\n"
	    "   Rule.Timezone "+
	    sort(values(rname))*","+";\n"
	    "   Rule.Timezone whatrule(int ux)\n"
	    "   {\n"
	    );

      foreach (rules,array a)
      {
	 if (!a[5]) a[5]=rule_shift(a);

	 string s="";
	 sscanf(a[3],"%s#%*[ \t]%s",a[3],s);
	 a[3]="from "+reverse(array_sscanf(reverse(a[3]),"%*[ \t]%s")[0]);
	 a[7]=s;
      }

      array last=rules[-1];
      n=sizeof(rules);
      foreach (reverse(rules)[1..],array a)
      {
	 res+=sprintf("      if (ux>=%s) // %s %s\n"
		      "         return %s || (%s=%s);\n",
		      a[5],a[3],last[7],last[6],last[6],last[4]);
	 n--;
	 last=a;
      }
      if (last[7]!="")
	 res+=sprintf("      // %s\n",last[7]);
      res+=sprintf("      return %s || (%s=%s);\n",
		   last[6],last[6],last[4]);

      res+=("   }\n"
	    "}\n");
      
      return res;
   }
}

void complain(string fmt,mixed ... args)
{
   throw( sprintf(fmt,@args) );
}

void collect_rules(string file)
{
   int n=0;
   werror("reading %O...\n",file);
   string s=Stdio.read_bytes(file),t;
   if (!s) 
   {
      werror("%s:-: Failed to open file: %s\n",file,strerror(errno()));
      return;
   }

   Zone lastz;
   MyRule lastr;

   foreach (s/"\n",string line)
   {
      n++;
      mixed err=catch 
      {
	 if (line[..0]!="#") 
	    if (sscanf(line,"Zone%*[ \t]%[^ \t]%*[ \t]%s",s,t)==4)
	    {
	       if (zones[s]) lastz=zones[s]->add(t);
	       else (lastz=zones[s]=Zone(s))->add(t),azones+=({lastz});
	    }
	    else if (sscanf(line,"Rule%*[ \t]%[^ \t]%*[ \t]%s",s,t)==4)
	    {
	       if (rules[s]) rules[s]->add(t);
	       else (lastr=rules[s]=MyRule(s))->add(t),arules+=({lastr});
	       lastz=0;
	    }
	    else if (sscanf(line,"Link%*[ \t]%[^ \t]%*[ \t]%[^ \t]",s,t)==4)
	    {
	 // link t to s
	       if (links[s]) links[s]+=({t});
	       else links[s]=({t});
	    }
	    else if (sscanf(line,"%*[ \t]%[-0-9]%s",s,t)==3 && sizeof(s))
	    {
	       if (!lastz) complain("implicit zone line w/o zone\n");
	       lastz->add(s+t);
	    }
	    else if ((t="",sscanf(line,"%[ \t]",t),t==line))
	       ;
	    else if (sscanf(line,"%*[ \t]#%s",t)==2)
	       ;
	    else
	       complain("unknown keyword %O...\n",line[..10]);
      };
      if (err)
	 if (stringp(err))
	    werror("%s:%d: %s",file,n,err);
	 else
	    throw(err);
   }
}

int main(int ac,array(string) am)
{
   array(string) files = am[1..];
   if (!sizeof(files))
   {
      werror("defaulting to reading zonefiles from %s...",
	     combine_path(__FILE__, "../tzdata"));
      files = get_dir(combine_path(__FILE__, "../tzdata"));
      files = map(sort(files),
		  lambda(string fname) {
		    if ((< ".gitignore", "Makefile", "Theory",
			   "factory", "leapseconds", >)[fname] ||
			(upper_case(fname) == fname) ||
			has_prefix(fname, "solar") ||
			has_suffix(fname, ".awk") ||
			has_suffix(fname, ".list") ||
			has_suffix(fname, ".pl") ||
			has_suffix(fname, ".sh") ||
			has_suffix(fname, ".tab")) return 0;
		    return combine_path(__FILE__, "../tzdata", fname);
		  }) - ({ 0 });
   }
   map(files, collect_rules);

   write("thinking...\n");

   string t="#pike "+__MAJOR__+"."+__MINOR__+"\n\n" + TZrules_base;

   foreach (arules,MyRule r)
      t+=r->dump();

   tzrules=compile_string(t)();

   mv("TZrules.pmod","TZrules.pmod~");
   werror("writing TZrules.pmod (%d bytes)...",sizeof(t));
   Stdio.File("TZrules.pmod","wtc")->write(t);
   werror("\n");

   t="// ----------------------------------------------------------------\n"
      "// Timezones\n"
      "//\n"
      "// NOTE: this file is generated by mkrules.pike;\n"
      "//       please do not edit manually /Mirar\n"
      "// ----------------------------------------------------------------\n"
      "\n"
      "import \".\";\n\n";

   t+=("// "+"-"*70+"\n"
       "// Timezones\n"
       "// "+"-"*70+"\n\n");

   mixed err=catch {
      foreach (azones,Zone z)
         if (sizeof(z->rules)==1) 
	 {
	    t+=z->dump();
	    if (links[z->id])
	       foreach(links[z->id],string s)
		  t+="Rule.Timezone "+FIXID(s)+"="+
		     FIXID(z->id)+";\n";
	 }
   };
   if (err) if (stringp(err)) error(err); else throw(err);

   t+=("\n"
       "// "+"-"*70+"\n"
       "// Timezones with an attitude\n"
       "// "+"-"*70+"\n"
       "\n");

   err=catch {
      foreach (azones,Zone z)
	 if (sizeof(z->rules)!=1) 
	 {
	    t+=z->dump();
	    if (links[z->id])
	       foreach(links[z->id],string s)
		  t+="constant "+FIXID(s)+"="+
		     FIXID(z->id)+";\n";
	    t+="\n";
	 }
   };
   if (err) if (stringp(err)) error(err); else throw(err);

   t+=("\n"
       "// "+"-"*70+"\n");

   mv("TZs.h","TZs.h~");
   werror("writing TZs.h (%d bytes)...",sizeof(t));
   Stdio.File("TZs.h","wtc")->write(t);
   werror("\n");

   mapping zs=([]);
   foreach (azones,Zone z)
      if (sscanf(z->id,"%s/%s",string s,string t)==2)
	 zs[s]=(zs[s]||({}))+({t});

   // Read and parse the original TZnames.pmod file.
   string orig_names = Stdio.read_bytes("TZnames.pmod");
   array(string) fragments = orig_names/" zones=";
   if (sizeof(fragments) == 1) fragments = orig_names/" zones =";
   if (sizeof(fragments) > 2)
     fragments = ({ fragments[0], fragments[1..] * " zones =" });
   fragments[1] = (fragments[1]/"]);\n\n")[1..]*"]);\n\n";

   t = fragments[0] + " zones =\n"
     "([\n";

   multiset(string) zone_names = (multiset)indices(zs);

   zone_names->SystemV = 0;
   zone_names->Etc = 0;

   // Historical ordering...
   foreach (({"America", "Pacific", "Antarctica", "Atlantic", "Indian",
	      "Europe", "Africa", "Asia", "Australia" }), string co) {
      t +=
	 sprintf("   %-13s({%-=60s\n",
		 sprintf("%O:",co),
		 map(zs[co],lambda(string s) { return sprintf("%O",s); })
		 *", "+"}),");
      zone_names[co] = 0;
   }
   // Take care of any remaining zones (probably none).
   foreach (sort(indices(zone_names)),string co) {
      t +=
	 sprintf("   %-13s({%-=60s\n",
		 sprintf("%O:",co),
		 map(zs[co],lambda(string s) { return sprintf("%O",s); })
		 *", "+"}),");
   }
   t += "]);\n\n" +
      fragments[1];

   // Update the abbreviation table as well.
   fragments = t/" abbr2zones=";
   if (sizeof(fragments) == 1) fragments = t/" abbr2zones =";
   if (sizeof(fragments) > 2)
      fragments = ({ fragments[0], fragments[1..] * " abbr2zones =" });
   fragments[1] = (fragments[1]/"]);\n\n")[1..]*"]);\n\n";

   t = fragments[0] + " abbr2zones =\n"
      "([\n";

   foreach(sort(indices(abbr2zones)), string abbr) {
      string line = sprintf("   %q: ({", abbr);

      mapping(int:array(string)) info = abbr2zones[abbr];
      array(string) zones = ({});
      array(int) until = ({});
      foreach(info; int us; array(string) zs) {
	 zones += zs;
	 until += allocate(sizeof(zs), -us);
      }
      // Sort so that the most recent use comes first,
      // and secondarily on the zone name.
      sort(zones, until);
      sort(until, zones);
      foreach(Array.uniq(zones); int i; string zone) {
	 string seg = sprintf("%s%q", i?", ":"", zone);
	 if (sizeof(line) + sizeof(seg) < 77) {
	    line += seg;
	    continue;
	 }
	 t += line + ",\n";
	 line = sprintf("       %q", zone);
      }
      t += line + "}),\n";
   }

   t += "]);\n\n" +
      fragments[1];

   // Cleanup white-space at end of line.
   string t2 = t;
   while ((t = replace(t2,
		       ({ "                        \n",
			  "                    \n",
			  "                \n",
			  "            \n",
			  "        \n",
			  "    \n",
			  "  \n",
			  " \n", }), ({ "\n" })*8)) != t2) {
     t2 = t;
   }
   
   mv("TZnames.pmod","TZnames.pmod~");
   werror("writing TZnames.pmod (%d bytes)...",sizeof(t));
   Stdio.File("TZnames.pmod","wtc")->write(t);
   werror("\n");

   return 0;
}


string TZrules_base=
#"// ----------------------------------------------------------------
// Daylight savings and war time rules
//
// NOTE: this file is generated by mkrules.pike;
//       please do not edit manually /Mirar
// ----------------------------------------------------------------

// ----------------------------------------------------------------
// all rules are based on the gregorian calendar, so 
// this is the gregorian rule:
// ----------------------------------------------------------------

protected array gregorian_yjd(int jd)
{
   int d=jd-1721426;

   int century=(4*d+3)/146097;
   int century_jd=(century*146097)/4;
   int century_day=d-century_jd;
   int century_year=(100*century_day+75)/36525;

   int y=century*100+century_year+1;

   return 
   ({
      y,
      1721426+century_year*365+century_year/4+century_jd,
      (!(((y)%4) || (!((y)%100) && ((y)%400))))
   });
}

// ----------------------------------------------------------------
// Base \"Timezone with rules\" class
// ----------------------------------------------------------------

class TZRules
{
   constant is_timezone=1;
   constant is_dst_timezone=1;
   protected int offset_to_utc;  
   string name;

   protected function(string:string) tzformat;
   protected array names;

   protected void create(int offset,string _name) 
   { 
      offset_to_utc=offset; 
      name=_name;
      if (has_value(name, \"/\"))
      {
	 names=name/\"/\";
	 tzformat=lambda(string s)
		  {
		     if (s==\"\") return names[0]; else return names[1];
		  };
      }
      else
	 tzformat=lambda(string s) { return sprintf(name,s); };
   }
// the Rule:
// which julian day does dst start and end this year?
   protected array(array(string|int)) jd_year_periods(int jd);

// is (midnight) this julian day dst?
   array tz_jd(int jd)
   {
      array(array(string|int)) a=jd_year_periods(jd);

      int i=0,n=sizeof(a)-1;
      while (i<n)
      {
	 array b=a[i+1];
	 if (jd<b[0]) break;
	 if (jd==b[0] && -offset_to_utc+b[1]>=0) break;
	 i++;
      }

      return ({offset_to_utc-a[i][2],tzformat(a[i][3])});
   }

// is this unixtime (utc) dst?
   array tz_ux(int ux)
   {
      int jd=2440588+ux/86400;
      array(array(string|int)) a=jd_year_periods(jd);

      int i=0,n=sizeof(a)-1;
      while (i<n)
      {
	 array b=a[i+1];
	 if (jd<b[0]-1) break;
	 if (jd<b[0]+1 &&
	     ux<(b[0]-2440588)*86400+b[1]) break;
	 i++;
      }

      return ({offset_to_utc-a[i][2],tzformat(a[i][3])});
   }

   protected string _sprintf(int t)
  {
    return t=='O' && \"Timezone(\"+name+\")\";
  }

   int raw_utc_offset() { return offset_to_utc; }
}

// ----------------------------------------------------------------------
// DST Rules
// ----------------------------------------------------------------------

// useful macros
#define FIXED(D)   (yjd+((D)-1))
#define FIX_L(D)   (yjd+leap+((D)-1))
#define LDAY(D,W)  (yjd+((D)-1)-( (yjd+((D)+(8-W)-1)) % 7))
#define LDAYL(D,W) (yjd+((D)-1)+leap-( (yjd+leap+((D)+(8-W)-1)) % 7))
#define UO offset_to_utc

// ----------------------------------------------------------------------
";