getdate.y revision 116333
194630Sobrien%{ 294630Sobrien/* 394630Sobrien** Originally written by Steven M. Bellovin <smb@research.att.com> while 494630Sobrien** at the University of North Carolina at Chapel Hill. Later tweaked by 594630Sobrien** a couple of people on Usenet. Completely overhauled by Rich $alz 694630Sobrien** <rsalz@bbn.com> and Jim Berets <jberets@bbn.com> in August, 1990; 794630Sobrien** 894630Sobrien** This grammar has 10 shift/reduce conflicts. 994630Sobrien** 1094630Sobrien** This code is in the public domain and has no copyright. 1194630Sobrien*/ 1294630Sobrien/* SUPPRESS 287 on yaccpar_sccsid *//* Unused static variable */ 1394630Sobrien/* SUPPRESS 288 on yyerrlab *//* Label unused */ 1494630Sobrien 15116333Smarkm#include <sys/cdefs.h> 16116333Smarkm__FBSDID("$FreeBSD: head/usr.bin/find/getdate.y 116333 2003-06-14 13:00:21Z markm $"); 1794630Sobrien 1894630Sobrien#include <stdio.h> 1994630Sobrien#include <ctype.h> 2094630Sobrien 2194630Sobrien/* The code at the top of get_date which figures out the offset of the 2294630Sobrien current time zone checks various CPP symbols to see if special 2394630Sobrien tricks are need, but defaults to using the gettimeofday system call. 2494630Sobrien Include <sys/time.h> if that will be used. */ 2594630Sobrien 2694630Sobrien#if defined(vms) 2794630Sobrien# include <types.h> 2894630Sobrien#else /* defined(vms) */ 2994630Sobrien# include <sys/types.h> 3094630Sobrien# include <sys/time.h> 3194630Sobrien# include <sys/timeb.h> 3294630Sobrien#endif /* !defined(vms) */ 3394630Sobrien 3494792Sobrien#if defined (__STDC__) || defined (USG) 3594630Sobrien#include <string.h> 3694630Sobrien#endif 3794630Sobrien 3894630Sobrien/* Some old versions of bison generate parsers that use bcopy. 3994630Sobrien That loses on systems that don't provide the function, so we have 4094630Sobrien to redefine it here. */ 4194630Sobrien#if !defined (HAVE_BCOPY) && defined (HAVE_MEMCPY) && !defined (bcopy) 4294630Sobrien#define bcopy(from, to, len) memcpy ((to), (from), (len)) 4394630Sobrien#endif 4494630Sobrien 4594792Sobrien#if defined (__STDC__) 4694630Sobrien#include <stdlib.h> 4794630Sobrien#endif 4894630Sobrien 4994630Sobrien/* NOTES on rebuilding getdate.c (particularly for inclusion in CVS 5094630Sobrien releases): 5194630Sobrien 5294630Sobrien We don't want to mess with all the portability hassles of alloca. 5394630Sobrien In particular, most (all?) versions of bison will use alloca in 5494630Sobrien their parser. If bison works on your system (e.g. it should work 5594630Sobrien with gcc), then go ahead and use it, but the more general solution 5694630Sobrien is to use byacc instead of bison, which should generate a portable 5794630Sobrien parser. I played with adding "#define alloca dont_use_alloca", to 5894630Sobrien give an error if the parser generator uses alloca (and thus detect 5994630Sobrien unportable getdate.c's), but that seems to cause as many problems 6094630Sobrien as it solves. */ 6194630Sobrien 62116333Smarkm#include <time.h> 6394630Sobrien 6494630Sobrien#define yyparse getdate_yyparse 6594630Sobrien#define yylex getdate_yylex 6694630Sobrien#define yyerror getdate_yyerror 6794630Sobrien 68116333Smarkmstatic int yyparse(void); 69116333Smarkmstatic int yylex(void); 70116333Smarkmstatic int yyerror(const char *); 7194630Sobrien 72116333Smarkmtime_t get_date(char *, struct timeb *); 73116333Smarkm 7494630Sobrien#define EPOCH 1970 7594630Sobrien#define HOUR(x) ((time_t)(x) * 60) 7694630Sobrien#define SECSPERDAY (24L * 60L * 60L) 7794630Sobrien 7894630Sobrien 7994630Sobrien/* 8094630Sobrien** An entry in the lexical lookup table. 8194630Sobrien*/ 8294630Sobrientypedef struct _TABLE { 83116333Smarkm const char *name; 8494630Sobrien int type; 8594630Sobrien time_t value; 8694630Sobrien} TABLE; 8794630Sobrien 8894630Sobrien 8994630Sobrien/* 9094630Sobrien** Daylight-savings mode: on, off, or not yet known. 9194630Sobrien*/ 9294630Sobrientypedef enum _DSTMODE { 9394630Sobrien DSTon, DSToff, DSTmaybe 9494630Sobrien} DSTMODE; 9594630Sobrien 9694630Sobrien/* 9794630Sobrien** Meridian: am, pm, or 24-hour style. 9894630Sobrien*/ 9994630Sobrientypedef enum _MERIDIAN { 10094630Sobrien MERam, MERpm, MER24 10194630Sobrien} MERIDIAN; 10294630Sobrien 10394630Sobrien 10494630Sobrien/* 10594630Sobrien** Global variables. We could get rid of most of these by using a good 10694630Sobrien** union as the yacc stack. (This routine was originally written before 10794630Sobrien** yacc had the %union construct.) Maybe someday; right now we only use 10894630Sobrien** the %union very rarely. 10994630Sobrien*/ 11094630Sobrienstatic char *yyInput; 11194630Sobrienstatic DSTMODE yyDSTmode; 11294630Sobrienstatic time_t yyDayOrdinal; 11394630Sobrienstatic time_t yyDayNumber; 11494630Sobrienstatic int yyHaveDate; 11594630Sobrienstatic int yyHaveDay; 11694630Sobrienstatic int yyHaveRel; 11794630Sobrienstatic int yyHaveTime; 11894630Sobrienstatic int yyHaveZone; 11994630Sobrienstatic time_t yyTimezone; 12094630Sobrienstatic time_t yyDay; 12194630Sobrienstatic time_t yyHour; 12294630Sobrienstatic time_t yyMinutes; 12394630Sobrienstatic time_t yyMonth; 12494630Sobrienstatic time_t yySeconds; 12594630Sobrienstatic time_t yyYear; 12694630Sobrienstatic MERIDIAN yyMeridian; 12794630Sobrienstatic time_t yyRelMonth; 12894630Sobrienstatic time_t yyRelSeconds; 12994630Sobrien 13094630Sobrien%} 13194630Sobrien 13294630Sobrien%union { 13394630Sobrien time_t Number; 13494630Sobrien enum _MERIDIAN Meridian; 13594630Sobrien} 13694630Sobrien 13794630Sobrien%token tAGO tDAY tDAYZONE tID tMERIDIAN tMINUTE_UNIT tMONTH tMONTH_UNIT 13894630Sobrien%token tSEC_UNIT tSNUMBER tUNUMBER tZONE tDST 13994630Sobrien 14094630Sobrien%type <Number> tDAY tDAYZONE tMINUTE_UNIT tMONTH tMONTH_UNIT 14194630Sobrien%type <Number> tSEC_UNIT tSNUMBER tUNUMBER tZONE 14294630Sobrien%type <Meridian> tMERIDIAN o_merid 14394630Sobrien 14494630Sobrien%% 14594630Sobrien 14694630Sobrienspec : /* NULL */ 14794630Sobrien | spec item 14894630Sobrien ; 14994630Sobrien 15094630Sobrienitem : time { 15194630Sobrien yyHaveTime++; 15294630Sobrien } 15394630Sobrien | zone { 15494630Sobrien yyHaveZone++; 15594630Sobrien } 15694630Sobrien | date { 15794630Sobrien yyHaveDate++; 15894630Sobrien } 15994630Sobrien | day { 16094630Sobrien yyHaveDay++; 16194630Sobrien } 16294630Sobrien | rel { 16394630Sobrien yyHaveRel++; 16494630Sobrien } 16594630Sobrien | number 16694630Sobrien ; 16794630Sobrien 16894630Sobrientime : tUNUMBER tMERIDIAN { 16994630Sobrien yyHour = $1; 17094630Sobrien yyMinutes = 0; 17194630Sobrien yySeconds = 0; 17294630Sobrien yyMeridian = $2; 17394630Sobrien } 17494630Sobrien | tUNUMBER ':' tUNUMBER o_merid { 17594630Sobrien yyHour = $1; 17694630Sobrien yyMinutes = $3; 17794630Sobrien yySeconds = 0; 17894630Sobrien yyMeridian = $4; 17994630Sobrien } 18094630Sobrien | tUNUMBER ':' tUNUMBER tSNUMBER { 18194630Sobrien yyHour = $1; 18294630Sobrien yyMinutes = $3; 18394630Sobrien yyMeridian = MER24; 18494630Sobrien yyDSTmode = DSToff; 18594630Sobrien yyTimezone = - ($4 % 100 + ($4 / 100) * 60); 18694630Sobrien } 18794630Sobrien | tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid { 18894630Sobrien yyHour = $1; 18994630Sobrien yyMinutes = $3; 19094630Sobrien yySeconds = $5; 19194630Sobrien yyMeridian = $6; 19294630Sobrien } 19394630Sobrien | tUNUMBER ':' tUNUMBER ':' tUNUMBER tSNUMBER { 19494630Sobrien yyHour = $1; 19594630Sobrien yyMinutes = $3; 19694630Sobrien yySeconds = $5; 19794630Sobrien yyMeridian = MER24; 19894630Sobrien yyDSTmode = DSToff; 19994630Sobrien yyTimezone = - ($6 % 100 + ($6 / 100) * 60); 20094630Sobrien } 20194630Sobrien ; 20294630Sobrien 20394630Sobrienzone : tZONE { 20494630Sobrien yyTimezone = $1; 20594630Sobrien yyDSTmode = DSToff; 20694630Sobrien } 20794630Sobrien | tDAYZONE { 20894630Sobrien yyTimezone = $1; 20994630Sobrien yyDSTmode = DSTon; 21094630Sobrien } 21194630Sobrien | 21294630Sobrien tZONE tDST { 21394630Sobrien yyTimezone = $1; 21494630Sobrien yyDSTmode = DSTon; 21594630Sobrien } 21694630Sobrien ; 21794630Sobrien 21894630Sobrienday : tDAY { 21994630Sobrien yyDayOrdinal = 1; 22094630Sobrien yyDayNumber = $1; 22194630Sobrien } 22294630Sobrien | tDAY ',' { 22394630Sobrien yyDayOrdinal = 1; 22494630Sobrien yyDayNumber = $1; 22594630Sobrien } 22694630Sobrien | tUNUMBER tDAY { 22794630Sobrien yyDayOrdinal = $1; 22894630Sobrien yyDayNumber = $2; 22994630Sobrien } 23094630Sobrien ; 23194630Sobrien 23294630Sobriendate : tUNUMBER '/' tUNUMBER { 23394630Sobrien yyMonth = $1; 23494630Sobrien yyDay = $3; 23594630Sobrien } 23694630Sobrien | tUNUMBER '/' tUNUMBER '/' tUNUMBER { 23794630Sobrien if ($1 >= 100) { 23894630Sobrien yyYear = $1; 23994630Sobrien yyMonth = $3; 24094630Sobrien yyDay = $5; 24194630Sobrien } else { 24294630Sobrien yyMonth = $1; 24394630Sobrien yyDay = $3; 24494630Sobrien yyYear = $5; 24594630Sobrien } 24694630Sobrien } 24794630Sobrien | tUNUMBER tSNUMBER tSNUMBER { 24894630Sobrien /* ISO 8601 format. yyyy-mm-dd. */ 24994630Sobrien yyYear = $1; 25094630Sobrien yyMonth = -$2; 25194630Sobrien yyDay = -$3; 25294630Sobrien } 25394630Sobrien | tUNUMBER tMONTH tSNUMBER { 25494630Sobrien /* e.g. 17-JUN-1992. */ 25594630Sobrien yyDay = $1; 25694630Sobrien yyMonth = $2; 25794630Sobrien yyYear = -$3; 25894630Sobrien } 25994630Sobrien | tMONTH tUNUMBER { 26094630Sobrien yyMonth = $1; 26194630Sobrien yyDay = $2; 26294630Sobrien } 26394630Sobrien | tMONTH tUNUMBER ',' tUNUMBER { 26494630Sobrien yyMonth = $1; 26594630Sobrien yyDay = $2; 26694630Sobrien yyYear = $4; 26794630Sobrien } 26894630Sobrien | tUNUMBER tMONTH { 26994630Sobrien yyMonth = $2; 27094630Sobrien yyDay = $1; 27194630Sobrien } 27294630Sobrien | tUNUMBER tMONTH tUNUMBER { 27394630Sobrien yyMonth = $2; 27494630Sobrien yyDay = $1; 27594630Sobrien yyYear = $3; 27694630Sobrien } 27794630Sobrien ; 27894630Sobrien 27994630Sobrienrel : relunit tAGO { 28094630Sobrien yyRelSeconds = -yyRelSeconds; 28194630Sobrien yyRelMonth = -yyRelMonth; 28294630Sobrien } 28394630Sobrien | relunit 28494630Sobrien ; 28594630Sobrien 28694630Sobrienrelunit : tUNUMBER tMINUTE_UNIT { 28794630Sobrien yyRelSeconds += $1 * $2 * 60L; 28894630Sobrien } 28994630Sobrien | tSNUMBER tMINUTE_UNIT { 29094630Sobrien yyRelSeconds += $1 * $2 * 60L; 29194630Sobrien } 29294630Sobrien | tMINUTE_UNIT { 29394630Sobrien yyRelSeconds += $1 * 60L; 29494630Sobrien } 29594630Sobrien | tSNUMBER tSEC_UNIT { 29694630Sobrien yyRelSeconds += $1; 29794630Sobrien } 29894630Sobrien | tUNUMBER tSEC_UNIT { 29994630Sobrien yyRelSeconds += $1; 30094630Sobrien } 30194630Sobrien | tSEC_UNIT { 30294630Sobrien yyRelSeconds++; 30394630Sobrien } 30494630Sobrien | tSNUMBER tMONTH_UNIT { 30594630Sobrien yyRelMonth += $1 * $2; 30694630Sobrien } 30794630Sobrien | tUNUMBER tMONTH_UNIT { 30894630Sobrien yyRelMonth += $1 * $2; 30994630Sobrien } 31094630Sobrien | tMONTH_UNIT { 31194630Sobrien yyRelMonth += $1; 31294630Sobrien } 31394630Sobrien ; 31494630Sobrien 31594630Sobriennumber : tUNUMBER { 31694630Sobrien if (yyHaveTime && yyHaveDate && !yyHaveRel) 31794630Sobrien yyYear = $1; 31894630Sobrien else { 31994630Sobrien if($1>10000) { 32094630Sobrien yyHaveDate++; 32194630Sobrien yyDay= ($1)%100; 32294630Sobrien yyMonth= ($1/100)%100; 32394630Sobrien yyYear = $1/10000; 32494630Sobrien } 32594630Sobrien else { 32694630Sobrien yyHaveTime++; 32794630Sobrien if ($1 < 100) { 32894630Sobrien yyHour = $1; 32994630Sobrien yyMinutes = 0; 33094630Sobrien } 33194630Sobrien else { 33294630Sobrien yyHour = $1 / 100; 33394630Sobrien yyMinutes = $1 % 100; 33494630Sobrien } 33594630Sobrien yySeconds = 0; 33694630Sobrien yyMeridian = MER24; 33794630Sobrien } 33894630Sobrien } 33994630Sobrien } 34094630Sobrien ; 34194630Sobrien 34294630Sobrieno_merid : /* NULL */ { 34394630Sobrien $$ = MER24; 34494630Sobrien } 34594630Sobrien | tMERIDIAN { 34694630Sobrien $$ = $1; 34794630Sobrien } 34894630Sobrien ; 34994630Sobrien 35094630Sobrien%% 35194630Sobrien 35294630Sobrien/* Month and day table. */ 35394630Sobrienstatic TABLE const MonthDayTable[] = { 35494630Sobrien { "january", tMONTH, 1 }, 35594630Sobrien { "february", tMONTH, 2 }, 35694630Sobrien { "march", tMONTH, 3 }, 35794630Sobrien { "april", tMONTH, 4 }, 35894630Sobrien { "may", tMONTH, 5 }, 35994630Sobrien { "june", tMONTH, 6 }, 36094630Sobrien { "july", tMONTH, 7 }, 36194630Sobrien { "august", tMONTH, 8 }, 36294630Sobrien { "september", tMONTH, 9 }, 36394630Sobrien { "sept", tMONTH, 9 }, 36494630Sobrien { "october", tMONTH, 10 }, 36594630Sobrien { "november", tMONTH, 11 }, 36694630Sobrien { "december", tMONTH, 12 }, 36794630Sobrien { "sunday", tDAY, 0 }, 36894630Sobrien { "monday", tDAY, 1 }, 36994630Sobrien { "tuesday", tDAY, 2 }, 37094630Sobrien { "tues", tDAY, 2 }, 37194630Sobrien { "wednesday", tDAY, 3 }, 37294630Sobrien { "wednes", tDAY, 3 }, 37394630Sobrien { "thursday", tDAY, 4 }, 37494630Sobrien { "thur", tDAY, 4 }, 37594630Sobrien { "thurs", tDAY, 4 }, 37694630Sobrien { "friday", tDAY, 5 }, 37794630Sobrien { "saturday", tDAY, 6 }, 378116333Smarkm { NULL, 0, 0 } 37994630Sobrien}; 38094630Sobrien 38194630Sobrien/* Time units table. */ 38294630Sobrienstatic TABLE const UnitsTable[] = { 38394630Sobrien { "year", tMONTH_UNIT, 12 }, 38494630Sobrien { "month", tMONTH_UNIT, 1 }, 38594630Sobrien { "fortnight", tMINUTE_UNIT, 14 * 24 * 60 }, 38694630Sobrien { "week", tMINUTE_UNIT, 7 * 24 * 60 }, 38794630Sobrien { "day", tMINUTE_UNIT, 1 * 24 * 60 }, 38894630Sobrien { "hour", tMINUTE_UNIT, 60 }, 38994630Sobrien { "minute", tMINUTE_UNIT, 1 }, 39094630Sobrien { "min", tMINUTE_UNIT, 1 }, 39194630Sobrien { "second", tSEC_UNIT, 1 }, 39294630Sobrien { "sec", tSEC_UNIT, 1 }, 393116333Smarkm { NULL, 0, 0 } 39494630Sobrien}; 39594630Sobrien 39694630Sobrien/* Assorted relative-time words. */ 39794630Sobrienstatic TABLE const OtherTable[] = { 39894630Sobrien { "tomorrow", tMINUTE_UNIT, 1 * 24 * 60 }, 39994630Sobrien { "yesterday", tMINUTE_UNIT, -1 * 24 * 60 }, 40094630Sobrien { "today", tMINUTE_UNIT, 0 }, 40194630Sobrien { "now", tMINUTE_UNIT, 0 }, 40294630Sobrien { "last", tUNUMBER, -1 }, 40394630Sobrien { "this", tMINUTE_UNIT, 0 }, 40494630Sobrien { "next", tUNUMBER, 2 }, 40594630Sobrien { "first", tUNUMBER, 1 }, 40694630Sobrien/* { "second", tUNUMBER, 2 }, */ 40794630Sobrien { "third", tUNUMBER, 3 }, 40894630Sobrien { "fourth", tUNUMBER, 4 }, 40994630Sobrien { "fifth", tUNUMBER, 5 }, 41094630Sobrien { "sixth", tUNUMBER, 6 }, 41194630Sobrien { "seventh", tUNUMBER, 7 }, 41294630Sobrien { "eighth", tUNUMBER, 8 }, 41394630Sobrien { "ninth", tUNUMBER, 9 }, 41494630Sobrien { "tenth", tUNUMBER, 10 }, 41594630Sobrien { "eleventh", tUNUMBER, 11 }, 41694630Sobrien { "twelfth", tUNUMBER, 12 }, 417116333Smarkm { "ago", tAGO, 1 }, 418116333Smarkm { NULL, 0, 0 } 41994630Sobrien}; 42094630Sobrien 42194630Sobrien/* The timezone table. */ 42294630Sobrien/* Some of these are commented out because a time_t can't store a float. */ 42394630Sobrienstatic TABLE const TimezoneTable[] = { 42494630Sobrien { "gmt", tZONE, HOUR( 0) }, /* Greenwich Mean */ 42594630Sobrien { "ut", tZONE, HOUR( 0) }, /* Universal (Coordinated) */ 42694630Sobrien { "utc", tZONE, HOUR( 0) }, 42794630Sobrien { "wet", tZONE, HOUR( 0) }, /* Western European */ 42894630Sobrien { "bst", tDAYZONE, HOUR( 0) }, /* British Summer */ 42994630Sobrien { "wat", tZONE, HOUR( 1) }, /* West Africa */ 43094630Sobrien { "at", tZONE, HOUR( 2) }, /* Azores */ 43194630Sobrien#if 0 43294630Sobrien /* For completeness. BST is also British Summer, and GST is 43394630Sobrien * also Guam Standard. */ 43494630Sobrien { "bst", tZONE, HOUR( 3) }, /* Brazil Standard */ 43594630Sobrien { "gst", tZONE, HOUR( 3) }, /* Greenland Standard */ 43694630Sobrien#endif 43794630Sobrien#if 0 43894630Sobrien { "nft", tZONE, HOUR(3.5) }, /* Newfoundland */ 43994630Sobrien { "nst", tZONE, HOUR(3.5) }, /* Newfoundland Standard */ 44094630Sobrien { "ndt", tDAYZONE, HOUR(3.5) }, /* Newfoundland Daylight */ 44194630Sobrien#endif 44294630Sobrien { "ast", tZONE, HOUR( 4) }, /* Atlantic Standard */ 44394630Sobrien { "adt", tDAYZONE, HOUR( 4) }, /* Atlantic Daylight */ 44494630Sobrien { "est", tZONE, HOUR( 5) }, /* Eastern Standard */ 44594630Sobrien { "edt", tDAYZONE, HOUR( 5) }, /* Eastern Daylight */ 44694630Sobrien { "cst", tZONE, HOUR( 6) }, /* Central Standard */ 44794630Sobrien { "cdt", tDAYZONE, HOUR( 6) }, /* Central Daylight */ 44894630Sobrien { "mst", tZONE, HOUR( 7) }, /* Mountain Standard */ 44994630Sobrien { "mdt", tDAYZONE, HOUR( 7) }, /* Mountain Daylight */ 45094630Sobrien { "pst", tZONE, HOUR( 8) }, /* Pacific Standard */ 45194630Sobrien { "pdt", tDAYZONE, HOUR( 8) }, /* Pacific Daylight */ 45294630Sobrien { "yst", tZONE, HOUR( 9) }, /* Yukon Standard */ 45394630Sobrien { "ydt", tDAYZONE, HOUR( 9) }, /* Yukon Daylight */ 45494630Sobrien { "hst", tZONE, HOUR(10) }, /* Hawaii Standard */ 45594630Sobrien { "hdt", tDAYZONE, HOUR(10) }, /* Hawaii Daylight */ 45694630Sobrien { "cat", tZONE, HOUR(10) }, /* Central Alaska */ 45794630Sobrien { "ahst", tZONE, HOUR(10) }, /* Alaska-Hawaii Standard */ 45894630Sobrien { "nt", tZONE, HOUR(11) }, /* Nome */ 45994630Sobrien { "idlw", tZONE, HOUR(12) }, /* International Date Line West */ 46094630Sobrien { "cet", tZONE, -HOUR(1) }, /* Central European */ 46194630Sobrien { "met", tZONE, -HOUR(1) }, /* Middle European */ 46294630Sobrien { "mewt", tZONE, -HOUR(1) }, /* Middle European Winter */ 46394630Sobrien { "mest", tDAYZONE, -HOUR(1) }, /* Middle European Summer */ 46494630Sobrien { "swt", tZONE, -HOUR(1) }, /* Swedish Winter */ 46594630Sobrien { "sst", tDAYZONE, -HOUR(1) }, /* Swedish Summer */ 46694630Sobrien { "fwt", tZONE, -HOUR(1) }, /* French Winter */ 46794630Sobrien { "fst", tDAYZONE, -HOUR(1) }, /* French Summer */ 46894630Sobrien { "eet", tZONE, -HOUR(2) }, /* Eastern Europe, USSR Zone 1 */ 46994630Sobrien { "bt", tZONE, -HOUR(3) }, /* Baghdad, USSR Zone 2 */ 47094630Sobrien#if 0 47194630Sobrien { "it", tZONE, -HOUR(3.5) },/* Iran */ 47294630Sobrien#endif 47394630Sobrien { "zp4", tZONE, -HOUR(4) }, /* USSR Zone 3 */ 47494630Sobrien { "zp5", tZONE, -HOUR(5) }, /* USSR Zone 4 */ 47594630Sobrien#if 0 47694630Sobrien { "ist", tZONE, -HOUR(5.5) },/* Indian Standard */ 47794630Sobrien#endif 47894630Sobrien { "zp6", tZONE, -HOUR(6) }, /* USSR Zone 5 */ 47994630Sobrien#if 0 48094630Sobrien /* For completeness. NST is also Newfoundland Stanard, and SST is 48194630Sobrien * also Swedish Summer. */ 48294630Sobrien { "nst", tZONE, -HOUR(6.5) },/* North Sumatra */ 48394630Sobrien { "sst", tZONE, -HOUR(7) }, /* South Sumatra, USSR Zone 6 */ 48494630Sobrien#endif /* 0 */ 48594630Sobrien { "wast", tZONE, -HOUR(7) }, /* West Australian Standard */ 48694630Sobrien { "wadt", tDAYZONE, -HOUR(7) }, /* West Australian Daylight */ 48794630Sobrien#if 0 48894630Sobrien { "jt", tZONE, -HOUR(7.5) },/* Java (3pm in Cronusland!) */ 48994630Sobrien#endif 49094630Sobrien { "cct", tZONE, -HOUR(8) }, /* China Coast, USSR Zone 7 */ 49194630Sobrien { "jst", tZONE, -HOUR(9) }, /* Japan Standard, USSR Zone 8 */ 49294630Sobrien#if 0 49394630Sobrien { "cast", tZONE, -HOUR(9.5) },/* Central Australian Standard */ 49494630Sobrien { "cadt", tDAYZONE, -HOUR(9.5) },/* Central Australian Daylight */ 49594630Sobrien#endif 49694630Sobrien { "east", tZONE, -HOUR(10) }, /* Eastern Australian Standard */ 49794630Sobrien { "eadt", tDAYZONE, -HOUR(10) }, /* Eastern Australian Daylight */ 49894630Sobrien { "gst", tZONE, -HOUR(10) }, /* Guam Standard, USSR Zone 9 */ 49994630Sobrien { "nzt", tZONE, -HOUR(12) }, /* New Zealand */ 50094630Sobrien { "nzst", tZONE, -HOUR(12) }, /* New Zealand Standard */ 50194630Sobrien { "nzdt", tDAYZONE, -HOUR(12) }, /* New Zealand Daylight */ 50294630Sobrien { "idle", tZONE, -HOUR(12) }, /* International Date Line East */ 503116333Smarkm { NULL, 0, 0 } 50494630Sobrien}; 50594630Sobrien 50694630Sobrien/* Military timezone table. */ 50794630Sobrienstatic TABLE const MilitaryTable[] = { 50894630Sobrien { "a", tZONE, HOUR( 1) }, 50994630Sobrien { "b", tZONE, HOUR( 2) }, 51094630Sobrien { "c", tZONE, HOUR( 3) }, 51194630Sobrien { "d", tZONE, HOUR( 4) }, 51294630Sobrien { "e", tZONE, HOUR( 5) }, 51394630Sobrien { "f", tZONE, HOUR( 6) }, 51494630Sobrien { "g", tZONE, HOUR( 7) }, 51594630Sobrien { "h", tZONE, HOUR( 8) }, 51694630Sobrien { "i", tZONE, HOUR( 9) }, 51794630Sobrien { "k", tZONE, HOUR( 10) }, 51894630Sobrien { "l", tZONE, HOUR( 11) }, 51994630Sobrien { "m", tZONE, HOUR( 12) }, 52094630Sobrien { "n", tZONE, HOUR(- 1) }, 52194630Sobrien { "o", tZONE, HOUR(- 2) }, 52294630Sobrien { "p", tZONE, HOUR(- 3) }, 52394630Sobrien { "q", tZONE, HOUR(- 4) }, 52494630Sobrien { "r", tZONE, HOUR(- 5) }, 52594630Sobrien { "s", tZONE, HOUR(- 6) }, 52694630Sobrien { "t", tZONE, HOUR(- 7) }, 52794630Sobrien { "u", tZONE, HOUR(- 8) }, 52894630Sobrien { "v", tZONE, HOUR(- 9) }, 52994630Sobrien { "w", tZONE, HOUR(-10) }, 53094630Sobrien { "x", tZONE, HOUR(-11) }, 53194630Sobrien { "y", tZONE, HOUR(-12) }, 53294630Sobrien { "z", tZONE, HOUR( 0) }, 533116333Smarkm { NULL, 0, 0 } 53494630Sobrien}; 53594630Sobrien 53694630Sobrien 53794630Sobrien 53894630Sobrien 53994630Sobrien/* ARGSUSED */ 54094630Sobrienstatic int 541116333Smarkmyyerror(const char *s __unused) 54294630Sobrien{ 54394630Sobrien return 0; 54494630Sobrien} 54594630Sobrien 54694630Sobrien 54794630Sobrienstatic time_t 548116333SmarkmToSeconds(time_t Hours, time_t Minutes, time_t Seconds, MERIDIAN Meridian) 54994630Sobrien{ 55094630Sobrien if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 59) 55194630Sobrien return -1; 55294630Sobrien switch (Meridian) { 55394630Sobrien case MER24: 55494630Sobrien if (Hours < 0 || Hours > 23) 55594630Sobrien return -1; 55694630Sobrien return (Hours * 60L + Minutes) * 60L + Seconds; 55794630Sobrien case MERam: 55894630Sobrien if (Hours < 1 || Hours > 12) 55994630Sobrien return -1; 56094630Sobrien if (Hours == 12) 56194630Sobrien Hours = 0; 56294630Sobrien return (Hours * 60L + Minutes) * 60L + Seconds; 56394630Sobrien case MERpm: 56494630Sobrien if (Hours < 1 || Hours > 12) 56594630Sobrien return -1; 56694630Sobrien if (Hours == 12) 56794630Sobrien Hours = 0; 56894630Sobrien return ((Hours + 12) * 60L + Minutes) * 60L + Seconds; 56994630Sobrien default: 57094630Sobrien abort (); 57194630Sobrien } 57294630Sobrien /* NOTREACHED */ 57394630Sobrien} 57494630Sobrien 57594630Sobrien 57694630Sobrien/* Year is either 57794630Sobrien * A negative number, which means to use its absolute value (why?) 57894630Sobrien * A number from 0 to 99, which means a year from 1900 to 1999, or 57994630Sobrien * The actual year (>=100). */ 58094630Sobrienstatic time_t 581116333SmarkmConvert(time_t Month, time_t Day, time_t Year, 582116333Smarkm time_t Hours, time_t Minutes, time_t Seconds, 583116333Smarkm MERIDIAN Meridian, DSTMODE DSTmode) 58494630Sobrien{ 58594630Sobrien static int DaysInMonth[12] = { 58694630Sobrien 31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 58794630Sobrien }; 58894630Sobrien time_t tod; 58994630Sobrien time_t Julian; 59094630Sobrien int i; 59194630Sobrien 59294630Sobrien if (Year < 0) 59394630Sobrien Year = -Year; 59494630Sobrien if (Year < 69) 59594630Sobrien Year += 2000; 59694630Sobrien else if (Year < 100) 59794630Sobrien Year += 1900; 59894630Sobrien DaysInMonth[1] = Year % 4 == 0 && (Year % 100 != 0 || Year % 400 == 0) 59994630Sobrien ? 29 : 28; 60094630Sobrien /* Checking for 2038 bogusly assumes that time_t is 32 bits. But 60194630Sobrien I'm too lazy to try to check for time_t overflow in another way. */ 60294630Sobrien if (Year < EPOCH || Year > 2038 60394630Sobrien || Month < 1 || Month > 12 60494630Sobrien /* Lint fluff: "conversion from long may lose accuracy" */ 60594630Sobrien || Day < 1 || Day > DaysInMonth[(int)--Month]) 60694630Sobrien return -1; 60794630Sobrien 60894630Sobrien for (Julian = Day - 1, i = 0; i < Month; i++) 60994630Sobrien Julian += DaysInMonth[i]; 61094630Sobrien for (i = EPOCH; i < Year; i++) 61194630Sobrien Julian += 365 + (i % 4 == 0); 61294630Sobrien Julian *= SECSPERDAY; 61394630Sobrien Julian += yyTimezone * 60L; 61494630Sobrien if ((tod = ToSeconds(Hours, Minutes, Seconds, Meridian)) < 0) 61594630Sobrien return -1; 61694630Sobrien Julian += tod; 61794630Sobrien if (DSTmode == DSTon 61894630Sobrien || (DSTmode == DSTmaybe && localtime(&Julian)->tm_isdst)) 61994630Sobrien Julian -= 60 * 60; 62094630Sobrien return Julian; 62194630Sobrien} 62294630Sobrien 62394630Sobrien 62494630Sobrienstatic time_t 625116333SmarkmDSTcorrect(time_t Start, time_t Future) 62694630Sobrien{ 62794630Sobrien time_t StartDay; 62894630Sobrien time_t FutureDay; 62994630Sobrien 63094630Sobrien StartDay = (localtime(&Start)->tm_hour + 1) % 24; 63194630Sobrien FutureDay = (localtime(&Future)->tm_hour + 1) % 24; 63294630Sobrien return (Future - Start) + (StartDay - FutureDay) * 60L * 60L; 63394630Sobrien} 63494630Sobrien 63594630Sobrien 63694630Sobrienstatic time_t 637116333SmarkmRelativeDate(time_t Start, time_t DayOrdinal, time_t DayNumber) 63894630Sobrien{ 63994630Sobrien struct tm *tm; 64094630Sobrien time_t now; 64194630Sobrien 64294630Sobrien now = Start; 64394630Sobrien tm = localtime(&now); 64494630Sobrien now += SECSPERDAY * ((DayNumber - tm->tm_wday + 7) % 7); 64594630Sobrien now += 7 * SECSPERDAY * (DayOrdinal <= 0 ? DayOrdinal : DayOrdinal - 1); 64694630Sobrien return DSTcorrect(Start, now); 64794630Sobrien} 64894630Sobrien 64994630Sobrien 65094630Sobrienstatic time_t 651116333SmarkmRelativeMonth(time_t Start, time_t RelMonth) 65294630Sobrien{ 65394630Sobrien struct tm *tm; 65494630Sobrien time_t Month; 65594630Sobrien time_t Year; 65694630Sobrien 65794630Sobrien if (RelMonth == 0) 65894630Sobrien return 0; 65994630Sobrien tm = localtime(&Start); 66094630Sobrien Month = 12 * (tm->tm_year + 1900) + tm->tm_mon + RelMonth; 66194630Sobrien Year = Month / 12; 66294630Sobrien Month = Month % 12 + 1; 66394630Sobrien return DSTcorrect(Start, 66494630Sobrien Convert(Month, (time_t)tm->tm_mday, Year, 66594630Sobrien (time_t)tm->tm_hour, (time_t)tm->tm_min, (time_t)tm->tm_sec, 66694630Sobrien MER24, DSTmaybe)); 66794630Sobrien} 66894630Sobrien 66994630Sobrien 67094630Sobrienstatic int 671116333SmarkmLookupWord(char *buff) 67294630Sobrien{ 673116333Smarkm char *p; 674116333Smarkm char *q; 675116333Smarkm const TABLE *tp; 676116333Smarkm int i; 677116333Smarkm int abbrev; 67894630Sobrien 67994630Sobrien /* Make it lowercase. */ 68094630Sobrien for (p = buff; *p; p++) 68194630Sobrien if (isupper(*p)) 68294630Sobrien *p = tolower(*p); 68394630Sobrien 68494630Sobrien if (strcmp(buff, "am") == 0 || strcmp(buff, "a.m.") == 0) { 68594630Sobrien yylval.Meridian = MERam; 68694630Sobrien return tMERIDIAN; 68794630Sobrien } 68894630Sobrien if (strcmp(buff, "pm") == 0 || strcmp(buff, "p.m.") == 0) { 68994630Sobrien yylval.Meridian = MERpm; 69094630Sobrien return tMERIDIAN; 69194630Sobrien } 69294630Sobrien 69394630Sobrien /* See if we have an abbreviation for a month. */ 69494630Sobrien if (strlen(buff) == 3) 69594630Sobrien abbrev = 1; 69694630Sobrien else if (strlen(buff) == 4 && buff[3] == '.') { 69794630Sobrien abbrev = 1; 69894630Sobrien buff[3] = '\0'; 69994630Sobrien } 70094630Sobrien else 70194630Sobrien abbrev = 0; 70294630Sobrien 70394630Sobrien for (tp = MonthDayTable; tp->name; tp++) { 70494630Sobrien if (abbrev) { 70594630Sobrien if (strncmp(buff, tp->name, 3) == 0) { 70694630Sobrien yylval.Number = tp->value; 70794630Sobrien return tp->type; 70894630Sobrien } 70994630Sobrien } 71094630Sobrien else if (strcmp(buff, tp->name) == 0) { 71194630Sobrien yylval.Number = tp->value; 71294630Sobrien return tp->type; 71394630Sobrien } 71494630Sobrien } 71594630Sobrien 71694630Sobrien for (tp = TimezoneTable; tp->name; tp++) 71794630Sobrien if (strcmp(buff, tp->name) == 0) { 71894630Sobrien yylval.Number = tp->value; 71994630Sobrien return tp->type; 72094630Sobrien } 72194630Sobrien 72294630Sobrien if (strcmp(buff, "dst") == 0) 72394630Sobrien return tDST; 72494630Sobrien 72594630Sobrien for (tp = UnitsTable; tp->name; tp++) 72694630Sobrien if (strcmp(buff, tp->name) == 0) { 72794630Sobrien yylval.Number = tp->value; 72894630Sobrien return tp->type; 72994630Sobrien } 73094630Sobrien 73194630Sobrien /* Strip off any plural and try the units table again. */ 73294630Sobrien i = strlen(buff) - 1; 73394630Sobrien if (buff[i] == 's') { 73494630Sobrien buff[i] = '\0'; 73594630Sobrien for (tp = UnitsTable; tp->name; tp++) 73694630Sobrien if (strcmp(buff, tp->name) == 0) { 73794630Sobrien yylval.Number = tp->value; 73894630Sobrien return tp->type; 73994630Sobrien } 74094630Sobrien buff[i] = 's'; /* Put back for "this" in OtherTable. */ 74194630Sobrien } 74294630Sobrien 74394630Sobrien for (tp = OtherTable; tp->name; tp++) 74494630Sobrien if (strcmp(buff, tp->name) == 0) { 74594630Sobrien yylval.Number = tp->value; 74694630Sobrien return tp->type; 74794630Sobrien } 74894630Sobrien 74994630Sobrien /* Military timezones. */ 75094630Sobrien if (buff[1] == '\0' && isalpha(*buff)) { 75194630Sobrien for (tp = MilitaryTable; tp->name; tp++) 75294630Sobrien if (strcmp(buff, tp->name) == 0) { 75394630Sobrien yylval.Number = tp->value; 75494630Sobrien return tp->type; 75594630Sobrien } 75694630Sobrien } 75794630Sobrien 75894630Sobrien /* Drop out any periods and try the timezone table again. */ 75994630Sobrien for (i = 0, p = q = buff; *q; q++) 76094630Sobrien if (*q != '.') 76194630Sobrien *p++ = *q; 76294630Sobrien else 76394630Sobrien i++; 76494630Sobrien *p = '\0'; 76594630Sobrien if (i) 76694630Sobrien for (tp = TimezoneTable; tp->name; tp++) 76794630Sobrien if (strcmp(buff, tp->name) == 0) { 76894630Sobrien yylval.Number = tp->value; 76994630Sobrien return tp->type; 77094630Sobrien } 77194630Sobrien 77294630Sobrien return tID; 77394630Sobrien} 77494630Sobrien 77594630Sobrien 77694630Sobrienstatic int 777116333Smarkmyylex(void) 77894630Sobrien{ 779116333Smarkm char c; 780116333Smarkm char *p; 781116333Smarkm char buff[20]; 782116333Smarkm int Count; 783116333Smarkm int sign; 78494630Sobrien 78594630Sobrien for ( ; ; ) { 78694630Sobrien while (isspace(*yyInput)) 78794630Sobrien yyInput++; 78894630Sobrien 78994630Sobrien if (isdigit(c = *yyInput) || c == '-' || c == '+') { 79094630Sobrien if (c == '-' || c == '+') { 79194630Sobrien sign = c == '-' ? -1 : 1; 79294630Sobrien if (!isdigit(*++yyInput)) 79394630Sobrien /* skip the '-' sign */ 79494630Sobrien continue; 79594630Sobrien } 79694630Sobrien else 79794630Sobrien sign = 0; 79894630Sobrien for (yylval.Number = 0; isdigit(c = *yyInput++); ) 79994630Sobrien yylval.Number = 10 * yylval.Number + c - '0'; 80094630Sobrien yyInput--; 80194630Sobrien if (sign < 0) 80294630Sobrien yylval.Number = -yylval.Number; 80394630Sobrien return sign ? tSNUMBER : tUNUMBER; 80494630Sobrien } 80594630Sobrien if (isalpha(c)) { 80694630Sobrien for (p = buff; isalpha(c = *yyInput++) || c == '.'; ) 80794630Sobrien if (p < &buff[sizeof buff - 1]) 80894630Sobrien *p++ = c; 80994630Sobrien *p = '\0'; 81094630Sobrien yyInput--; 81194630Sobrien return LookupWord(buff); 81294630Sobrien } 81394630Sobrien if (c != '(') 81494630Sobrien return *yyInput++; 81594630Sobrien Count = 0; 81694630Sobrien do { 81794630Sobrien c = *yyInput++; 81894630Sobrien if (c == '\0') 81994630Sobrien return c; 82094630Sobrien if (c == '(') 82194630Sobrien Count++; 82294630Sobrien else if (c == ')') 82394630Sobrien Count--; 82494630Sobrien } while (Count > 0); 82594630Sobrien } 82694630Sobrien} 82794630Sobrien 82894630Sobrien#define TM_YEAR_ORIGIN 1900 82994630Sobrien 83094630Sobrien/* Yield A - B, measured in seconds. */ 83194630Sobrienstatic long 832116333Smarkmdifftm (struct tm *a, struct tm *b) 83394630Sobrien{ 83494630Sobrien int ay = a->tm_year + (TM_YEAR_ORIGIN - 1); 83594630Sobrien int by = b->tm_year + (TM_YEAR_ORIGIN - 1); 83694630Sobrien int days = ( 83794630Sobrien /* difference in day of year */ 83894630Sobrien a->tm_yday - b->tm_yday 83994630Sobrien /* + intervening leap days */ 84094630Sobrien + ((ay >> 2) - (by >> 2)) 84194630Sobrien - (ay/100 - by/100) 84294630Sobrien + ((ay/100 >> 2) - (by/100 >> 2)) 84394630Sobrien /* + difference in years * 365 */ 84494630Sobrien + (long)(ay-by) * 365 84594630Sobrien ); 84694630Sobrien return (60*(60*(24*days + (a->tm_hour - b->tm_hour)) 84794630Sobrien + (a->tm_min - b->tm_min)) 84894630Sobrien + (a->tm_sec - b->tm_sec)); 84994630Sobrien} 85094630Sobrien 85194630Sobrientime_t 852116333Smarkmget_date(char *p, struct timeb *now) 85394630Sobrien{ 85494630Sobrien struct tm *tm, gmt; 85594630Sobrien struct timeb ftz; 85694630Sobrien time_t Start; 85794630Sobrien time_t tod; 85894630Sobrien time_t nowtime; 85994630Sobrien 86094630Sobrien yyInput = p; 86194630Sobrien if (now == NULL) { 86294630Sobrien struct tm *gmt_ptr; 86394630Sobrien 86494630Sobrien now = &ftz; 86594630Sobrien (void)time (&nowtime); 86694630Sobrien 86794630Sobrien gmt_ptr = gmtime (&nowtime); 86894630Sobrien if (gmt_ptr != NULL) 86994630Sobrien { 87094630Sobrien /* Make a copy, in case localtime modifies *tm (I think 87194630Sobrien that comment now applies to *gmt_ptr, but I am too 87294630Sobrien lazy to dig into how gmtime and locatime allocate the 87394630Sobrien structures they return pointers to). */ 87494630Sobrien gmt = *gmt_ptr; 87594630Sobrien } 87694630Sobrien 87794630Sobrien if (! (tm = localtime (&nowtime))) 87894630Sobrien return -1; 87994630Sobrien 88094630Sobrien if (gmt_ptr != NULL) 88194630Sobrien ftz.timezone = difftm (&gmt, tm) / 60; 88294630Sobrien else 88394630Sobrien /* We are on a system like VMS, where the system clock is 88494630Sobrien in local time and the system has no concept of timezones. 88594630Sobrien Hopefully we can fake this out (for the case in which the 88694630Sobrien user specifies no timezone) by just saying the timezone 88794630Sobrien is zero. */ 88894630Sobrien ftz.timezone = 0; 88994630Sobrien 89094630Sobrien if(tm->tm_isdst) 89194630Sobrien ftz.timezone += 60; 89294630Sobrien } 89394630Sobrien else 89494630Sobrien { 89594630Sobrien nowtime = now->time; 89694630Sobrien } 89794630Sobrien 89894630Sobrien tm = localtime(&nowtime); 89994630Sobrien yyYear = tm->tm_year + 1900; 90094630Sobrien yyMonth = tm->tm_mon + 1; 90194630Sobrien yyDay = tm->tm_mday; 90294630Sobrien yyTimezone = now->timezone; 90394630Sobrien yyDSTmode = DSTmaybe; 90494630Sobrien yyHour = 0; 90594630Sobrien yyMinutes = 0; 90694630Sobrien yySeconds = 0; 90794630Sobrien yyMeridian = MER24; 90894630Sobrien yyRelSeconds = 0; 90994630Sobrien yyRelMonth = 0; 91094630Sobrien yyHaveDate = 0; 91194630Sobrien yyHaveDay = 0; 91294630Sobrien yyHaveRel = 0; 91394630Sobrien yyHaveTime = 0; 91494630Sobrien yyHaveZone = 0; 91594630Sobrien 91694630Sobrien if (yyparse() 91794630Sobrien || yyHaveTime > 1 || yyHaveZone > 1 || yyHaveDate > 1 || yyHaveDay > 1) 91894630Sobrien return -1; 91994630Sobrien 92094630Sobrien if (yyHaveDate || yyHaveTime || yyHaveDay) { 92194630Sobrien Start = Convert(yyMonth, yyDay, yyYear, yyHour, yyMinutes, yySeconds, 92294630Sobrien yyMeridian, yyDSTmode); 92394630Sobrien if (Start < 0) 92494630Sobrien return -1; 92594630Sobrien } 92694630Sobrien else { 92794630Sobrien Start = nowtime; 92894630Sobrien if (!yyHaveRel) 92994630Sobrien Start -= ((tm->tm_hour * 60L + tm->tm_min) * 60L) + tm->tm_sec; 93094630Sobrien } 93194630Sobrien 93294630Sobrien Start += yyRelSeconds; 93394630Sobrien Start += RelativeMonth(Start, yyRelMonth); 93494630Sobrien 93594630Sobrien if (yyHaveDay && !yyHaveDate) { 93694630Sobrien tod = RelativeDate(Start, yyDayOrdinal, yyDayNumber); 93794630Sobrien Start += tod; 93894630Sobrien } 93994630Sobrien 94094630Sobrien /* Have to do *something* with a legitimate -1 so it's distinguishable 94194630Sobrien * from the error return value. (Alternately could set errno on error.) */ 94294630Sobrien return Start == -1 ? 0 : Start; 94394630Sobrien} 94494630Sobrien 94594630Sobrien 94694630Sobrien#if defined(TEST) 94794630Sobrien 94894630Sobrien/* ARGSUSED */ 94994630Sobrienint 950116333Smarkmmain(int ac, char *av[]) 95194630Sobrien{ 95294630Sobrien char buff[128]; 95394630Sobrien time_t d; 95494630Sobrien 95594630Sobrien (void)printf("Enter date, or blank line to exit.\n\t> "); 95694630Sobrien (void)fflush(stdout); 95794630Sobrien while (gets(buff) && buff[0]) { 95894630Sobrien d = get_date(buff, (struct timeb *)NULL); 95994630Sobrien if (d == -1) 96094630Sobrien (void)printf("Bad format - couldn't convert.\n"); 96194630Sobrien else 96294630Sobrien (void)printf("%s", ctime(&d)); 96394630Sobrien (void)printf("\t> "); 96494630Sobrien (void)fflush(stdout); 96594630Sobrien } 96694630Sobrien exit(0); 96794630Sobrien /* NOTREACHED */ 96894630Sobrien} 96994630Sobrien#endif /* defined(TEST) */ 970