%D \module %D [ file=core-con, %D version=1997.26.08, %D title=\CONTEXT\ Core Macros, %D subtitle=Conversion Macros, %D author=Hans Hagen, %D date=\currentdate, %D copyright={PRAGMA / Hans Hagen \& Ton Otten}] %C %C This module is part of the \CONTEXT\ macro||package and is %C therefore copyrighted by \PRAGMA. See mreadme.pdf for %C details. \writestatus{loading}{Context Core Macros / Conversion Macros} \unprotect %D This module deals with all kind of conversions from numbers %D and dates. I considered splitting this module in a support %D one and a core one, but to keep things simple as well as %D preserve the overview, I decided against splitting. %D \macros %D {numbers} %D %D First we deal with the dummy conversion of numbers using the %D \TEX\ primitive \type{\number}. The uppercase alternative is %D only there for compatibility with the other conversion %D macros. We could do without \type{#1} but this way we get %D rid of unwanted braces. For the savety we also define a %D non||sence uppercase alternative. %D %D \showsetup{\y!numbers} \def\numbers#1% {\number#1} \def\Numbers#1% {\number#1} %D \macros %D {romannumerals,Romannumerals} %D %D \TEX\ the program uses a rather tricky conversion from %D numbers to their roman counterparts. This conversion could %D of course be programmed in \TEX\ itself, but I guess Knuth %D found the programming trick worth presenting. %D %D \showsetup{\y!romannumerals} %D \showsetup{\y!Romannumerals} %D %D When upcasing the result, we just follow the text book rules %D of expansion. Later on we'll see some more uppercase tricks. \def\romannumerals#1% {\romannumeral#1} %D For some years we had \unknown %D %D \starttypen %D \def\Romannumerals#1% %D {\uppercase\expandafter{\romannumeral#1}} %D \stoptypen %D %D \unknown but we need to be fully expandable in order to get %D the utility output file right, so now we have th efollowing %D solution. It was Patrick Gundlach who first noticed this %D ommision. \def\Romannumerals#1% {\expandafter\doRomannumerals\number#1\relax} \def\doRomannumerals#1#2\relax % spaces after ifcase prevent \relax {\ifnum#1#2<10 \ifcase0#1#2 \or I\or II\or III\or IV\or V\or VI\or VII\or VIII\or IX\fi \else\ifnum#1#2<100 \ifcase0#1 \or X\or XX\or XXX\or XL\or L\or LX\or LXX\or LXXX\or XC\fi \doRomannumerals#2\relax \else\ifnum#1#2<1000 \ifcase0#1 \or C\or CC\or CCC\or CD\or D\or DC\or DCC\or DCCC\or CM\fi \doRomannumerals#2\relax \else\ifnum#1#2<4000 \ifcase0#1 \or M\or MM\or MMM\fi \doRomannumerals#2\relax \else \uppercase\expandafter{\romannumeral#1#2}% \fi\fi\fi\fi} %D \macros %D {character,Character} %D %D Converting a number into a character can of course only %D be done with numbers less or equal to~26. At the cost of %D much more macros a faster conversion is possible, using: %D %D \starttypen %D \setvalue{char1}{a} \def\character#1{\getvalue{char#1}} %D \stoptypen %D %D But we prefer a simpel \type{\case}. %D %D \showsetup{\y!character} %D \showsetup{\y!Character} \def\unknowncharacter{-} % else in lists \relax \def\character#1% {\ifcase#1\unknowncharacter \or a\or b\or c\or d\or e\or f\or g\or h\or i\or j\or k\or l\or m% \or n\or o\or p\or q\or r\or s\or t\or u\or v\or w\or x\or y\or z% \else \unknowncharacter \fi} \def\Character#1% {\ifcase#1\unknowncharacter \or A\or B\or C\or D\or E\or F\or G\or H\or I\or J\or K\or L\or M% \or N\or O\or P\or Q\or R\or S\or T\or U\or V\or W\or X\or Y\or Z% \else \unknowncharacter \fi} %D \macros %D {characters,Characters} %D %D Converting large numbers is supported by the next two %D macros. This time we just count on: $\cdots$~x, y, z, aa, %D ab, ac~$\cdots$. %D %D \showsetup{\y!characters} %D \showsetup{\y!Characters} \def\doconvertcharacters#1#2% {\ifnum#2>26 \bgroup \!!counta=#2 \ifnum\!!counta>0 \advance\!!counta by -1 \!!countb=\!!counta \divide\!!counta by 26 \!!countc=\!!counta \multiply\!!countc by 26 \advance\!!countb by -\!!countc \doconvertcharacters#1{\!!counta}% \advance\!!countb by 1 #1{\the\!!countb}% \fi \egroup \else #1{#2}% pure expansion, used in references \fi} \def\characters% {\doconvertcharacters\character} \def\Characters% {\doconvertcharacters\Character} %D \macros %D {greeknumerals,Greeknumerals} %D %D Why should we only honour the romans, and not the greek? \def\greeknumerals#1% watch the \normalxi {\mathematics {\ifcase#1\unknowncharacter\or \alpha\or\beta\or\gamma\or\delta\or\varepsilon\or \zeta\or\eta\or\theta\or\iota\or\kappa\or\lambda\or \mu\or\nu\or\normalxi\or o\or\pi\or\varrho\or\sigma\or \tau\or\upsilon\or\phi\or\chi\or\psi\or\omega \else \unknowncharacter \fi}} \def\Greeknumerals#1% {\mathematics {\ifcase#1\unknowncharacter \or A\or B\or\Gamma\or\Delta\or E\or Z\or H\or\Theta\or I\or K\or\Lambda\or M\or N\or\Xi\or O\or\Pi\or P\or \Sigma\or T\or\Upsilon\or\Phi\or X\or\Psi\or\Omega \else \unknowncharacter \fi}} %D \macros %D {oldstylenumerals,oldstyleromannumerals} %D %D These conversions are dedicated to Frans Goddijn. \unexpanded\def\oldstylenumerals#1% {{\os\number#1}} \unexpanded\def\oldstyleromannumerals#1% {{\leftrulefalse\rightrulefalse\ss\txx\boxrulewidth=.15ex \ruledhbox spread .15em{\hss\uppercased{\romannumerals{#1}}\hss}}} %D \macros %D {protectconversion} %D %D The previous two commands are not robust enough to be %D passed to \type{\write} en \type{\message}. That's why we %D introduce: \def\protectconversion% {\def\doconvertcharacters##1{##1}} % was \relax %{\def\doconvertcharacters##1{\ifcase0##1 0\else##1\fi}} more save %D \macros %D {normaltime,normalyear,normalmonth,normalday} %D %D The last part of this module is dedicated to converting %D dates. Because we want to use as meaningful commands as %D possible, and because \TEX\ already uses up some of those, %D we save the original meanings. \savenormalmeaning\time \savenormalmeaning\year \savenormalmeaning\month \savenormalmeaning\day %D \macros %D {month,MONTH} %D %D Converting the month number into a month name is done %D using a case statement, abstact values and the label %D mechanism. This way users can easily redefine a label from %D for instance german into austrian. %D %D \starttypen %D \setuplabeltext [de] [january=J\"anner] %D \stoptypen %D %D Anyhow, the conversion looks like: \def\doconvertmonth#1% {\labeltext {\ifcase#1% \or \v!january \or \v!february \or \v!march \or \v!april \or \v!may \or \v!june \or \v!july \or \v!august \or \v!september \or \v!october \or \v!november \or \v!december \fi}} %D We redefine the \TEX\ primitive \type{\month} as: %D %D \showsetup{\y!month} %D \showsetup{\y!MONTH} \def\month% {\doconvertmonth} \def\MONTH#1% {{\let\labeltext=\LABELTEXT\month{#1}}} %D We never explicitly needed this, but Tobias Burnus pointed %D out that it would be handy to convert to the day of the %D week. In doing so, we have to calculate the total number of %D days, taking leapyears into account. For those who are %D curious: %D %D \startopsomming[opelkaar] %D \som years that can be divided by 4 are leapyears %D \som exept years that can be divided by 100 %D \som unless years can be divided by 400 %D \stopopsomming %D %D This makes the year 1900 into a normal year and 1996 and %D 2000 into leap years, right? Well, converting to string %D looks familiar: \def\doconvertday#1% {\labeltext {\ifcase#1 \or \v!sunday \or \v!monday \or \v!tuesday \or \v!wednesday \or \v!thursday \or \v!friday \or \v!saturday \fi}} %D \macros %D {getdayoftheweek, dayoftheweek} %D %D The conversion algoritm is an old one and a translation from %D a procedure written in MODULA~2 back in the 80's. I finaly %D found the 4--100-400 rules in some enclopedia. Look at this %D messy low level routine that takes the day, month and year %D as arguments: \newcount\normalweekday \def\getdayoftheweek#1#2#3% {\bgroup \!!counta=#3\relax \advance\!!counta -1 \!!countb=\!!counta \multiply\!!countb 365 \advance\!!countb \ifcase#2\relax 0 \or 0 \or 31 \or 59 \or 90 \or120 \or151 \or 181 \or212 \or243 \or273 \or304 \or334 \or365 \fi \advance\!!countb #1\relax \ifnum#2>2 \doifleapyearelse{#3}{\advance\!!countb 1}{}\relax \fi \!!countc=\!!counta \DoDiv\!!countc by4to\!!countc \advance\!!countb \!!countc \!!countc=\!!counta \DoDiv\!!countc by100to\!!countc \advance\!!countb -\!!countc \!!countc=\!!counta \DoDiv\!!countc by400to\!!countc \advance\!!countb \!!countc \DoMod\!!countb by7to\!!countb \advance\!!countb 1 \@EA\egroup\@EA\normalweekday\the\!!countb\relax} \def\dayoftheweek#1#2#3% {\getdayoftheweek{#1}{#2}{#3}\doconvertday{\normalweekday}} %D Using this macro in %D %D \startbuffer %D monday: \dayoftheweek {4} {5} {1992} %D friday: \dayoftheweek {16} {6} {1995} %D monday: \dayoftheweek {25} {8} {1997} %D saturday: \dayoftheweek {30} {8} {1997} %D tuesday: \dayoftheweek {2} {1} {1996} %D tuesday: \dayoftheweek {7} {1} {1997} %D tuesday: \dayoftheweek {13} {1} {1998} %D friday: \dayoftheweek {1} {1} {2000} %D \stopbuffer %D %D \typebuffer %D %D gives %D %D \startvoorbeeld %D \startregels %D \haalbuffer %D \stopregels %D \stopvoorbeeld %D %D The macro \type {\getdayoftheweek} can be used to calculate %D the number \type {\normalweekday}. %D \macros %D {weekday,WEEKDAY} %D %D The first one is sort of redundant. It takes the day %D number argument. %D %D \showsetup{\y!weekday} %D \showsetup{\y!WEEKDAY} \def\weekday% {\doconvertday} \def\WEEKDAY#1% {{\let\labeltext=\LABELTEXT\doconvertday{#1}}} %D \macros %D {weekoftheday} %D %D {\em not yet implemented:} %D %D \starttypen %D \def\weekoftheday#1#2#3% %D {} %D \stoptypen %D \macros %D {doifleapyearelse, %D getdayspermonth} %D %D Sometimes we need to know if we're dealing with a %D leapyear, so here is a testmacro: %D %D \starttypen %D \doifleapyearelse{year}{yes}{no} %D \stoptypen %D %D An example of its use can be seen in the macro %D %D \starttypen %D \getdayspermonth{year}{month} %D \stoptypen %D %D The number of days is available in the macro \type %D {\numberofdays}. \def\doifleapyearelse#1#2#3% {\bgroup \!!doneafalse \!!counta=#1% \DoMod\!!counta by4to\!!countb \ifnum\!!countb=0 \DoMod\!!counta by100to\!!countb \ifnum\!!countb=0 \else \!!doneatrue \fi \DoMod\!!counta by400to\!!countb \ifnum\!!countb=0 \!!doneatrue \fi \fi \if!!donea \egroup\def\next{#2}% \else \egroup\def\next{#3}% \fi \next} \def\getdayspermonth#1#2% {\doifleapyearelse{#1} {\def\numberofdays{29}} {\def\numberofdays{28}}% \edef\numberofdays% {\ifcase#2 \or31\or\numberofdays\or31\or30\or 31\or30\or31\or31\or30\or31\or30\or31\fi}} %D \macros %D {currentdate, date} %D %D We use these conversion macros in the date formatting %D macro: %D %D \showsetup{\y!currentdate} %D %D This macro takes care of proper spacing and delivers for %D instance: %D %D \startbuffer %D \currentdate[weekdag,dag,maand,jaar] % still dutch example %D \currentdate[WEEKDAG,dag,MAAND,jaar] % still dutch example %D \stopbuffer %D %D \startvoorbeeld %D \startregels %D \haalbuffer %D \stopregels %D \stopvoorbeeld %D %D depending of course on the keywords. Here we gave: %D %D \typebuffer %D %D If needed one can also add non||keywords, like in %D %D \startbuffer %D \currentdate[dd,--,mm,--,yy] %D \stopbuffer %D %D \typebuffer %D %D or typeset: \haalbuffer. %D %D When no argument is passed, the current date is given as %D specified per language (using \type{\installlanguage}). %D %D \showsetup{\y!currentdate} %D %D \startbuffer %D \date %D \date[d=12,m=12,y=1998][weekdag] %D \date[d=12,m=12,y=1998] %D \stopbuffer %D %D We can also typeset arbitrary dates, using the previous %D command. %D %D \typebuffer %D %D The date is specified by one character keys. When no date %D is given, we get the current date. %D %D \startregels %D \haalbuffer %D \stopregels \def\kenmerkdatumpatroon{j,mm,dd} % jj,mm,dd changed at januari 1-1-2000 % \def\complexcurrentdate[#1]% % {\bgroup % \let\labellanguage\currentlanguage % \def\betweendates{\let\betweendates\space}% % \lowercase{\edef\!!stringa{#1}}% permits usage in \kap % \@EA\processallactionsinset\@EA % [\!!stringa] % [ \v!dag=>\betweendates\the\normalday, % \v!maand=>\betweendates\month\normalmonth, % \v!jaar=>\betweendates\the\normalyear, % \ =>\ , % optimization -) % d=>\the\normalday, % m=>\the\normalmonth, % j=>\the\normalyear, % y=>\the\normalyear, % w=>\betweendates\dayoftheweek\normalday\normalmonth\normalyear, % dd=>\ifnum\normalday >9 \else0\fi\the\normalday, % mm=>\ifnum\normalmonth>9 \else0\fi\the\normalmonth, % jj=>\expandafter\gobbletwoarguments\the\normalyear, % yy=>\expandafter\gobbletwoarguments\the\normalyear, % \v!weekdag=>\betweendates\dayoftheweek\normalday\normalmonth\normalyear, % % \v!MAAND=>\betweendates\MONTH\normalmonth, % % \v!WEEKDAG=>\betweendates % % \bgroup % % \let\labeltext=\LABELTEXT % % \dayoftheweek\normalday\normalmonth\normalyear % % \egroup, % \v!kenmerk=>\expanded{\complexcurrentdate[\kenmerkdatumpatroon]}, % \s!unknown=>\commalistelement % \def\betweendates{\let\betweendates\space}]% % \egroup} \newsignal\datesignal \def\dobetweendates% {\ifdim\lastskip=\datesignal\relax\else \unskip\space \hskip\datesignal\relax \fi} \def\complexcurrentdate[#1]% {\bgroup \let\labellanguage\currentlanguage \def\betweendates{\let\betweendates\dobetweendates}% \lowercase{\edef\!!stringa{#1}}% permits usage in \kap \@EA\processallactionsinset\@EA [\!!stringa] [ \v!dag=>\betweendates\the\normalday, \v!maand=>\betweendates\month\normalmonth, \v!jaar=>\betweendates\the\normalyear, \ =>\unskip\ \hskip\datesignal,% optimization -) d=>\the\normalday, m=>\the\normalmonth, j=>\the\normalyear, y=>\the\normalyear, w=>\betweendates\dayoftheweek\normalday\normalmonth\normalyear, dd=>\ifnum\normalday >9 \else0\fi\the\normalday, mm=>\ifnum\normalmonth>9 \else0\fi\the\normalmonth, jj=>\expandafter\gobbletwoarguments\the\normalyear, yy=>\expandafter\gobbletwoarguments\the\normalyear, \v!weekdag=>\betweendates\dayoftheweek\normalday\normalmonth\normalyear, \v!kenmerk=>\expanded{\complexcurrentdate[\kenmerkdatumpatroon]}, \s!unknown=>\unskip \commalistelement \hskip\datesignal \def\betweendates{\let\betweendates\dobetweendates}]% \ifdim\lastskip=\datesignal\relax \unskip \fi \egroup} \def\simplecurrentdate% {\expanded{\complexcurrentdate[\currentdatespecification]}} \definecomplexorsimple\currentdate \def\dodate[#1][#2]% {\bgroup \iffirstargument \getparameters[\??da][d=\normalday,m=\normalmonth,y=\normalyear,#1]% \normalday =\@@dad\relax \normalmonth=\@@dam\relax \normalyear =\@@day\relax \ifsecondargument \currentdate[#2]% \else \currentdate \fi \else \currentdate \fi \egroup} \def\date {\dodoubleempty\dodate} %D \macros %D {currenttime} %D %D The currenttime is actually the jobtime. You can specify %D a pattern similar to the previous date macro using the %D keys \type {h}, \type {m} and a separator. \def\calculatecurrenttime {\DoDiv\time by60to\scratchcounter\edef\currenthour {\the\scratchcounter}% \DoMod\time by60to\scratchcounter\edef\currentminute{\the\scratchcounter}} \appendtoks \calculatecurrenttime \to \everyjob \def\currenttimespecification{h,:,m} \def\complexcurrenttime[#1]% {\calculatecurrenttime \processallactionsinset[#1] [h=>\currenthour,m=>\currentminute,\s!unknown=>\commalistelement]} \def\simplecurrenttime {\expanded{\complexcurrenttime[\currenttimespecification]}} \definecomplexorsimple\currenttime %D Because we're dealing with dates, we also introduce a few %D day loops: %D %D \starttypen %D \processmonth{year}{month}{command} %D \processyear{year}{command}{before}{after} %D \stoptypen %D %D The counters \type {\normalyear}, \type {\normalmonth} and %D \type{\normalday} can be used for for date manipulations. \long\def\processmonth#1#2#3% year month command {\bgroup \getdayspermonth{#1}{#2}% \dostepwiserecurse{1}{\numberofdays}{1} {\normalyear =#1\relax \normalmonth=#2\relax \normalday =\recurselevel\relax #3}% \egroup} \def\lastmonth{12} % can be set to e.g. 1 when testing \long\def\processyear#1#2#3#4% year command before after {\bgroup \dorecurse{\lastmonth} {\normalyear =#1\relax \normalmonth=\recurselevel\relax #3\processmonth{\normalyear}{\normalmonth}{#2}#4}% \egroup} %D \macros %D {defineconversion, convertnumber} %D %D Conversion involves the macros that we implemented earlier %D in this module. %D %D \showsetup{\y!defineconversion} %D \showsetup{\y!convertnumber} %D %D We can feed this command with conversion macros as well as %D a set of conversion symbols. Both need a bit different %D treatment. %D %D \starttypen %D \defineconversion [roman] [\romannumerals] %D \defineconversion [set 1] [$\star$,$\bullet$,$\ast$] %D \stoptypen \def\dodefineconversion[#1][#2]% {\ConvertConstantAfter\doifinstringelse{,}{#2} {\scratchcounter=0 \def\docommando##1% {\advance\scratchcounter by 1 \setvalue{\??cv#1\the\scratchcounter}{##1}}% \processcommalist[#2]\docommando \setvalue{\??cv#1}##1{\csname\??cv#1##1\endcsname}} {\setvalue{\??cv#1}{#2}}} \def\defineconversion% {\dodoubleargument\dodefineconversion} \def\convertnumber#1% {\csname\??cv#1\endcsname} \def\doifconversiondefinedelse#1% {\ifundefined{\??cv#1}% \@EA\secondoftwoarguments \else \@EA\firstoftwoarguments \fi} %D As longs as symbols are linked to levels or numbers, we can %D also use the conversion mechanism, but in for instance the %D itemization macros, we prefer symbols because they can more %D easier be (partially) redefined. Symbols are implemented %D in another module. \defineconversion [] [\numbers] % the default conversion \defineconversion [a] [\characters] \defineconversion [A] [\Characters] \defineconversion [AK] [\kap\characters] \defineconversion [KA] [\kap\characters] \defineconversion [n] [\numbers] \defineconversion [N] [\Numbers] \defineconversion [m] [\mediaeval] \defineconversion [r] [\romannumerals] \defineconversion [R] [\Romannumerals] \defineconversion [KR] [\kap\romannumerals] \defineconversion [RK] [\kap\romannumerals] \defineconversion [g] [\greeknumerals] \defineconversion [G] [\Greeknumerals] \defineconversion [o] [\oldstylenumerals] \defineconversion [or] [\oldstyleromannumerals] \defineconversion [\v!letter] [\character] \defineconversion [\v!Letter] [\Character] \defineconversion [\v!letters] [\characters] \defineconversion [\v!Letters] [\Characters] \defineconversion [\v!cijfers] [\numbers] \defineconversion [\v!Cijfers] [\Numbers] \defineconversion [\v!mediaeval] [\mediaeval] \defineconversion [\v!romeins] [\romannumerals] \defineconversion [\v!Romeins] [\Romannumerals] \defineconversion [\v!grieks] [\greeknumerals] \defineconversion [\v!Grieks] [\Greeknumerals] % Some bonus ones: \defineconversion [\v!leeg] [\gobbleoneargument] \defineconversion [\v!geen] [\numbers] \defineconversion [set 0] [{\symbol[bullet]}, {\symbol[dash]}, {\symbol[star]}, {\symbol[triangle]}, {\symbol[circle]}, {\symbol[medcircle]}, {\symbol[bigcircle]}, {\symbol[square]}] \defineconversion [set 1] [\mathematics{\star}, \mathematics{\star\star}, \mathematics{\star\star\star}, \mathematics{\ddagger}, \mathematics{\ddagger\ddagger}, \mathematics{\ddagger\ddagger\ddagger}, \mathematics{\ast}, \mathematics{\ast\ast}, \mathematics{\ast\ast\ast}] \defineconversion [set 2] [\mathematics{*}, \mathematics{\dag}, \mathematics{\ddag}, \mathematics{**}, \mathematics{\dag\dag}, \mathematics{\ddag\ddag}, \mathematics{***}, \mathematics{\dag\dag\dag}, \mathematics{\ddag\ddag\ddag}, \mathematics{****}, \mathematics{\dag\dag\dag\dag}, \mathematics{\ddag\ddag\ddag\ddag}] \defineconversion [set 3] [\mathematics{\star}, \mathematics{\star\star}, \mathematics{\star\star\star}, \mathematics{\ddagger}, \mathematics{\ddagger\ddagger}, \mathematics{\ddagger\ddagger\ddagger}, \mathematics{\P}, \mathematics{\P\P}, \mathematics{\P\P\P}, \mathematics{\S}, \mathematics{\S\S}, \mathematics{\S\S\S}, \mathematics{\ast}, \mathematics{\ast\ast}, \mathematics{\ast\ast\ast}] \protect \endinput