![方法 1](https://linux22.com/image/386289/%E6%96%B9%E6%B3%95%201.png)
我正在使用以下代码创建每周讲座的时间表
\documentclass{article}
\usepackage{advdate}
%... Set the first lecture date
\ThisYear{2019}
\ThisMonth{3}
\ThisDay{1}
\newif\iffirst
\firsttrue
\newcommand{\nextlec}{%
\iffirst
\firstfalse
\else
\AdvanceDate[7]%
\fi
\section*{\today}
}
\begin{document}
\nextlec
Lecture 01.
\nextlec
Lecture 02.
\nextlec
Lecture 03.
\nextlec
Lecture 04.
\nextlec
Lecture 05.
\end{document}
代码运行正常。现在,假设3月8日, 3月29日, 4月18日, 5月17日,和6 月 18 日。
我的问题是:如何重新定义\nextlec
跳过假期并返回下一个非假期日期?
注意:这不是重复的这里,因为我想将计算出的日期与我想要定义的一组日期进行比较。
编辑:
根据 Jeffrey J Weimer 的建议(见评论),我尝试在原始代码中实现以下代码,但出现了很多错误。
\def\holidays#1{\def\@holidays{#1}}
\newcommand{\theholidays}{ {\@holidays} }
\newif\iffirst
\firsttrue
\newcommand{\jumpweek}{%
\iffirst
\firstfalse
\else
\AdvanceDate[7]%
\fi
}
\newcommand\nextlecbool{true}
\newcommand{\nextlec}{%
\jumpweek
\foeach \holiday in \theholidays
\while{\nextlecbool}
\ifnum (10000*\the\year + 100*\the\month + \the\day) = \holiday
\section*{\today}
Feriado.
\jumpweek
\else
\renewcommand{\nextlecbool}{false}
\fi
\EndWhile
;
\renewcommand{\nextlecbool}{true}
}
答案1
您可以列出假期列表并检查设置的当前日期是否\AdvDate
出现在列表中:
\documentclass[twocolumn]{article}
\usepackage{advdate}
\usepackage{xparse}
\ExplSyntaxOn
\clist_new:N \g_brasil_holiday_clist
\NewDocumentCommand{\holidays}{m}
{
\clist_gset:Nn \g_brasil_holiday_clist { #1 }
}
\NewDocumentCommand{\nextlec}{}
{
\brasil_check_holiday:
\section*{\today}
}
\cs_new_protected:Nn \brasil_check_holiday:
{
\AdvanceDate[7]
\clist_if_in:NxT \g_brasil_holiday_clist
{ \int_eval:n {\day} / \int_eval:n {\month} }
{ \brasil_check_holiday: } % redo the test
}
\cs_generate_variant:Nn \clist_if_in:NnT { Nx }
\ExplSyntaxOff
\holidays{8/3,29/3,18/4,17/5,18/6}
%... Set the first lecture date
\ThisYear{2019}
\ThisMonth{3}
\ThisDay{1}
\AdvanceDate[-7]
\begin{document}
\nextlec
Lecture 01.
\nextlec
Lecture 02.
\nextlec
Lecture 03.
\nextlec
Lecture 04.
\nextlec
Lecture 05.
\nextlec
Lecture 06.
\nextlec
Lecture 07.
\nextlec
Lecture 08.
\nextlec
Lecture 09.
\nextlec
Lecture 10.
\nextlec
Lecture 11.
\nextlec
Lecture 12.
\nextlec
Lecture 13.
\nextlec
Lecture 14.
\nextlec
Lecture 15.
\nextlec
Lecture 16.
\nextlec
Lecture 17.
\nextlec
Lecture 18.
\end{document}
这里我twocolumn
仅用来将输出减少到单页。
这是如何运作的?
该命令\holidays
将假期列表保存在 clist 变量中(只是用逗号分隔的一系列项目,可以稍后查询)。
\nextlec
首先\brasil_check_holiday:
调用执行检查的主命令。
这是真正棘手的部分。该包使用标准计数器、和advdate
来存储当前日期。然而,我们需要将它们转换为明确的数字,这可以通过 和来实现。\AdvanceDate
\day
\month
\year
\int_eval:n {\day}
\int_eval:n {\month}
该\clist_if_in:NnT
函数有三个参数:
- clist 变量
- 检查变量中的标记列表
- 测试成功时执行的代码
但是,我们需要做上面提到的扩展,所以我定义了一个变体
\clist_if_in:NxT
这将在执行检查之前扩展第二个参数。在检查之前,日期会提前一周。
如果测试成功,即当前日期是假日,会发生什么情况?该\brazil_check_holiday:
函数将再次执行,直到找到非假日为止。
至此\section*{\today}
已执行并且作业已完成。
答案2
也许这展示了一些关于递归迭代和使用分隔参数来分解模式字符串的技巧
<word for month><space><number of day>,<space><year>
\documentclass{article}
\usepackage{datenumber}
\usepackage{advdate}
\makeatletter
%-----------------------------------------------------------------
% Section: Code for comparing dates.
%-----------------------------------------------------------------
% \monthtonumber{<name of a month>} yields the number of that
% month (range 1..12; January -> 1, February -> 2 ,...,
% December -> 12)
% \monthfork just selects/"spits out" an undelimited argument that
% comes behind a delimited argument's delimiter which is formed by
% a list of all months.
% In any case there will be only one place where the list
% is correct and where the delimiter will match...
% ! is used both for making visibly distinguishing month-phrases
% from each other more easy and for denoting where the
% remainder which is to be removed by the !!!!-delimited argument
% ends.
%-----------------------------------------------------------------
\newcommand*\monthtonumber[1]{%
\monthfork
!#1!February!March!April!May!June!July!August!September!October!November!December!{1}%
!January!#1!March!April!May!June!July!August!September!October!November!December!{2}%
!January!February!#1!April!May!June!July!August!September!October!November!December!{3}%
!January!February!March!#1!May!June!July!August!September!October!November!December!{4}%
!January!February!March!April!#1!June!July!August!September!October!November!December!{5}%
!January!February!March!April!May!#1!July!August!September!October!November!December!{6}%
!January!February!March!April!May!June!#1!August!September!October!November!December!{7}%
!January!February!March!April!May!June!July!#1!September!October!November!December!{8}%
!January!February!March!April!May!June!July!August!#1!October!November!December!{9}%
!January!February!March!April!May!June!July!August!September!#1!November!December!{10}%
!January!February!March!April!May!June!July!August!September!October!#1!December!{11}%
!January!February!March!April!May!June!July!August!September!October!November!#1!{12}%
!!!!%
}%
%-----------------------------------------------------------------
% \monthfork grabs the undelimited argument behind that
% list of months that is correct/in correct order due to insertion
% of the name of the month by \monthtonumber.
% As \monthfork needs to be a macro that processes
% delimited arguments, it needs to be defined in terms of
% \def. Nonetheless provide a dummy-definition in terms
% of \newcommand before for triggering an error-message
% in case one is about to override a macro that already
% is defined, e.g., by some package.
%-----------------------------------------------------------------
\newcommand\monthfork{}%
\def\monthfork#1!January!February!March!April!May!June!%
July!August!September!October!November!%
December!#2#3!!!!{#2}%
%
%-----------------------------------------------------------------
% \comparedatetotoday{<month as word><space><day as number>,<space><year as number>}%
% {<tokens in case dates do not differ>}%
% {<tokens in case dates differ>}%
% converts <month as word> to a number and compares that to the value of \month,
% compares <day as number> to the value of \day,
% compares <year as number> to the value of \year
% In case a difference is detected delivers <tokens in case dates differ>
% In case no difference is detected delivers <tokens in case dates do not differ>
% The macro \splitdateandcomparecomponents is used for obtaining
% the components of the date.
% As the date is provided in the pattern
% <month as word><space><day as number>,<space><year as number>
% a space is attached at the end which yields the pattern
% <month as word><space><day as number>,<space><year as number><space>
% and then the components of that pattern are obtained by the macro
% \splitdateandcomparecomponents where the three arguments are delimited
% accordingly. Again first a \newcommand-dummy-definition for
% triggering error-message in case of overriding an already existing
% command.
%-----------------------------------------------------------------
\newcommand\comparedatetotoday[1]{\splitdateandcomparecomponents#1 }%
\newcommand\splitdateandcomparecomponents{}%
\def\splitdateandcomparecomponents#1 #2, #3 {%
\ifnum\the\month=\monthtonumber{#1} %
\expandafter\@firstofone\else\expandafter\@secondoftwo\fi
{%
\ifnum\the\day=#2 %
\expandafter\@firstofone\else\expandafter\@secondoftwo\fi
{%
\ifnum\the\year=#3 %
\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
{\@firstoftwo}%
}%
}%
\@secondoftwo
}%
%-----------------------------------------------------------------
% \comparedatestotoday{%
% {{<month 1 as word><space><day 1 as number>,<space><year 1 as number>}}%<-date1
% {{<month 2 as word><space><day 2 as number>,<space><year 2 as number>}}%<-date2
% ...
% {{<month k as word><space><day k as number>,<space><year k as number>}}%<-datek
% }%
% {<tokens in case at least one date does not differ from the date formed by
% \year, \month and \day>}%
% {<tokens in case all dates do differ from the date formed by
% \year, \month and \day>}%
%
% \comparedatestotoday attaches the token \relax to the list of
% holiday-dates and prepends an argument holding the token
% \@secondoftwo and calls \comparedatestotodayloop.
%
% \comparedatestotodayloop in a recursive loop compares with all
% elements of the list of holiday-dates compares the date-components to
% \month, \day, \year for finding out if \month, \day and \year
% denote one of these holiday-dates.
%
% \comparedatestotodayloop's recursive loop works as follows:
%
% \comparedatestotodayloop processes two arguments.
% The first argument denotes the action in case the end of the list is reached.
% The second argument either is an element of the list of dates or is the
% attached token \relax.
% Thus for finding out whether the end of the list is reached, one can
% check whether the second argument equals \relax.
% If so, just spit out the first argument which denotes the action in
% case the end of the list is reached.
% If not so, compare the date held in the second argument to
% \month, \day, \year (which are components of today) for finding out
% if \today is a holiday and therefore the first argument needs to be
% replaced by \@firstoftwo before doing the next iteration by
% calling \comparedatestotodayloop again...
%-----------------------------------------------------------------
\newcommand\comparedatestotoday[1]{%
\comparedatestotodayloop{\@secondoftwo}#1\relax
}%
% \comparedatestotodayloop{<token in case end of list is reached>}%
% {<either element of list of holiday-dates
% or \relax which was attached for marking
% the end of the list of holiday-dates>}%
\newcommand\comparedatestotodayloop[2]{%
\ifx\relax#2\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
{#1}{%
\comparedatetotoday{#2}%
{\comparedatestotodayloop{\@firstoftwo}}%
{\comparedatestotodayloop{#1}}%
}%
}%
%-----------------------------------------------------------------
% Section: Code for printing lists of dates.
%-----------------------------------------------------------------
% \PrintListOfHolidays[<macro for formatting a date>]%
% {<separator between dates>}%
% {<separator for the last date>}%
% {<macro holding the list of dates>}
\newcommand\exchange[2]{#2#1}
\newcommand\PrintListOfHolidays[4][\RemoveDaysLeadZero]{%
\expandafter\exchange\expandafter{#4}{\Holidayloop{#1}{}{}{#2}{#3}}\relax\relax
}%
%-----------------------------------------------------------------
% \Holidayloop{<macro for formatting a date>}%
% {<separator for this date in case it is not the last date of the list>}%
% {<separator for this date in case it is the last date of the list>}%
% {<separator between dates>}%
% {<separator for the last date>}%
% {<month as word><space><day as number>,>space><year as number>}
% At first glimpse the arguments <separator for this date in case it is not the last date of the list>
% and <separator between dates> respective
% <separator for this date in case it is the last date of the list> and
% <separator for the last date> seem to be the same.
% But we must consider the case of the list being empty or holding
% only one element: In these cases we don't want any separator, thus
% we need a possibility to pass empty arguments for the first
% iteration...
%
\newcommand\Holidayloop[7]{%
\ifx\relax#6\expandafter\@gobble\else\expandafter\@firstofone\fi
{%
\ifx\relax#7\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
{#3#1{#6}\Holidayloop{#1}{#4}{#5}{#4}{#5}{#7}}%<-case: last date of the list
{#2#1{#6}\Holidayloop{#1}{#4}{#5}{#4}{#5}{#7}}%<-case: not the last date of the list
}%
}%
% macros for formatting the date.
% They all take the date and use "inner" macros that by means of
% delimited arguments split it into components and do the desired
% things to the components.
\newcommand\RemoveDaysLeadZero[1]{\innerRemoveDaysLeadZero#1\relax}
\newcommand\innerRemoveDaysLeadZero{}%
\def\innerRemoveDaysLeadZero#1 #2,#3\relax{\mbox{#1 \number#2,#3}}%
%
\newcommand\RemoveYear[1]{\innerRemoveyear#1\relax}
\newcommand\innerRemoveyear{}%
\def\innerRemoveyear#1 #2,#3\relax{\mbox{#1 \number#2}}%
%
\newcommand\AddDayOfWeek[1]{\innerAddDayOfweek#1\relax}
\newcommand\innerAddDayOfweek{}%
\def\innerAddDayOfweek#1 #2, #3\relax{%
\setdatenumber{#3}{\monthtonumber{#1}}{#2}%
\mbox{\datedayname, #1 #2, #3}%
}%
%
% As we need to compare each \today/each combination of
% \year, \month and \day to the list of holidays for
% for finding out whether a \today is on a holiday, we can
% maintain a list of such \todays that are on holidays.
% Whenever such a \today is found, it will added to the macro
% \@DaysAffectedByHolidays. That macro in turn is used at the
% end of the document for writing to .aux-file a directive
% for defining a macro \DaysAffectedByHolidays that holds the
% list of only those holidays that "collide" with lecture-days.
%
\newcommand\DaysAffectedByHolidays{}%
\newcommand\@DaysAffectedByHolidays{}%
\AtEndDocument{%
\immediate\write\@auxout{%
\string\gdef\string\DaysAffectedByHolidays{\@DaysAffectedByHolidays}%
}%
}%
\newcounter{lecture}
\newlength\scratchlength
\newcommand{\nextlec}{%
% Compare the advanced \today to each element of the list of holidays:
\expandafter\comparedatestotoday
\expandafter{\Holidaylist}{%
%
% Tokens in case the advanced \today denotes one of the holidays:
%
%\setdatenumber{\year}{\month}{\day}%
%\section*{Holiday: \datedayname, \today} Holiday.%
\xdef\@DaysAffectedByHolidays{\@DaysAffectedByHolidays{\today}}%
\AdvanceDate[7]%
% calculate another lecture-day...
\nextlec
}{%
%
% Tokens in case the advanced \today does not denote one of the holidays:
%
\stepcounter{lecture}%
\setdatenumber{\year}{\month}{\day}%
\section*{Lecture~\arabic{lecture}: \datedayname, \today}%
\AdvanceDate[7]%
}%
}%
\makeatother
%... Set the first lecture date
\ThisYear{2019}
\ThisMonth{3}
\ThisDay{1}
\newcommand*\Holidaylist{%
{March 08, 2019}%
{March 29, 2019}%
{April 18, 2019}%
{May 17, 2019}%
{June 18, 2019}%
}%
\begin{document}
\settowidth\scratchlength{\hbox{\textbf{All holidays: }}}
\setlength\scratchlength{-\scratchlength}
\addtolength\scratchlength{\textwidth}
% The macros holding the lists of dates of holidays do not
% contain the token \relax. Therefore if they are not empty
% as there is at least one holiday, things will end up in
% the \else-branch of the \if-comparison.
%
\if\relax\Holidaylist\relax\else
\hbox{%
\hbox{\textbf{All holidays: }}%
\hbox to\scratchlength{\vtop{%
\hsize=\scratchlength\noindent
\PrintListOfHolidays{; }{; }{\Holidaylist}.\strut%
}}%
}%
\fi
\if\relax\Holidaylist\relax\else
\hbox{%
\hbox{\textbf{All holidays: }}%
\hbox to\scratchlength{\vtop{%
\hsize=\scratchlength\noindent
\PrintListOfHolidays[\RemoveYear]{, }{ and }{\Holidaylist}.\strut%
}}%
}%
\fi
\if\relax\Holidaylist\relax\else
\hbox{%
\hbox{\textbf{All holidays: }}%
\hbox to\scratchlength{\vtop{%
\hsize=\scratchlength\noindent
\PrintListOfHolidays[\AddDayOfWeek]{, }{ and }{\Holidaylist}.\strut%
}}%
}%
\fi
% These occur after the 2nd LaTeX-run:
\settowidth\scratchlength{\hbox{\textbf{Holidays that affect our lecture-plan: }}}
\setlength\scratchlength{-\scratchlength}
\addtolength\scratchlength{\textwidth}
\if\relax\DaysAffectedByHolidays\relax\else
\hbox{%
\hbox{\textbf{Holidays that affect our lecture-plan: }}%
\hbox to\scratchlength{\vtop{%
\hsize=\scratchlength\noindent
\PrintListOfHolidays{; }{; }{\DaysAffectedByHolidays}.\strut%
}}%
}%
\fi
\if\relax\DaysAffectedByHolidays\relax\else
\hbox{%
\hbox{\textbf{Holidays that affect our lecture-plan: }}%
\hbox to\scratchlength{\vtop{%
\hsize=\scratchlength\noindent
\PrintListOfHolidays[\RemoveYear]{, }{ and }{\DaysAffectedByHolidays}.\strut%
}}%
}%
\fi
\if\relax\DaysAffectedByHolidays\relax\else
\hbox{%
\hbox{\textbf{Holidays that affect our lecture-plan: }}%
\hbox to\scratchlength{\vtop{%
\hsize=\scratchlength\noindent
\PrintListOfHolidays[\AddDayOfWeek]{, }{ and }{\DaysAffectedByHolidays}.\strut%
}}%
}%
\fi
\newpage
\nextlec
Lecture 01.
\nextlec
Lecture 02.
\nextlec
Lecture 03.
\nextlec
Lecture 04.
\nextlec
Lecture 05.
\nextlec
Lecture 06.
\nextlec
Lecture 07.
\nextlec
Lecture 08.
\nextlec
Lecture 09.
\nextlec
Lecture 10.
\end{document}
答案3
方法 1
xifthen
这解决了包和的问题pgffor
。
\documentclass{article}
\usepackage{etoolbox, xifthen, pgffor, advdate}
%% counter for number of lecture
\newcounter{nlecture}\setcounter{nlecture}{1}
%% denote holiday when found
\newbool{holiday}
%% advance date
\newcommand{\DoLecture}{%
\global\boolfalse{holiday}%%%
\foreach \D in \holidaylist
{%
\ifthenelse{\equal{\today}{\D}}
{%
\global\booltrue{holiday}%%%
\breakforeach
}
{}%%%
}%%%
\ifbool{holiday}
{%
Holiday - \today
\AdvanceDate[7]%%%
\DoLecture
}
{%%%
Lecture \thenlecture{} - \today
\stepcounter{nlecture}%%%
\AdvanceDate[7]%%%
}%%%
}
%% start date for lectures
\ThisYear{2019}\ThisMonth{3}\ThisDay{1}
%% holiday list
\newcommand{\holidaylist}{{March 8, 2019}, {March 22, 2019}, {March 29, 2019}}
\renewcommand{\baselinestretch}{1.5}
\begin{document}
\DoLecture
\DoLecture
\DoLecture
\DoLecture
\DoLecture
\DoLecture
\end{document}
得出以下结果。
警告 1 - 如其他地方所述,进行字符串比较会\ifthenelse
耗尽 TeX 内存。
我留给读者一个练习,开发一种方法将 \DoLecture 命令放入循环中,也许可以使用\foreach
。一个问题是\AdvanceDate
不能直接在\foreach
循环中工作。
方法 2
这解决了包的问题datatool
。我更喜欢这种方法。
\documentclass{article}
\usepackage{etoolbox, datatool, advdate}
%% counter for number of lecture
\newcounter{nlecture}\setcounter{nlecture}{1}
%% denote holiday when found
\newbool{holiday}
\newcommand*{\theholiday}[1]{}
%% step through lectures
\newcommand{\StepThruLectures}[2]{%
\global\boolfalse{holiday}%%%
\DTLforeach*{holidays}
{\hdate=date, \event=event}
{%
\DTLifstringeq*{\today{}}{\hdate{}}
{%
\global\booltrue{holiday}%%%
\renewcommand{\theholiday}{\event}%%%
\dtlbreak
}
{}%%%
}%%%
\ifbool{holiday}
{%
\HolidayHeader{\today}{\theholiday}%%%
\AdvanceDate[7]%%%
\StepThruLectures{#1}{#2}%%%
}
{%
\LectureHeader{\today}{\thenlecture}{#1}{#2}%%%
\stepcounter{nlecture}%%%
\AdvanceDate[7]%%%
}%%%
}
%% generate lecture and holiday statements
\newcommand{\LectureHeader}[4]{#1: Lecture #2 - Topic: #3 / Reading: #4}
\newcommand{\HolidayHeader}[2]{#1: Holiday - #2}
%% start a date
\ThisYear{2019}\ThisMonth{3}\ThisDay{1}
% set the lecture topics
\begin{filecontents}{lectures.csv}
topic, reading
Introduction, none
Math is Fun, Chapter 1
Math is Hard, Chapter 2
\LaTeX{} is Preferred, Chapter 3
Conclusions, none
\end{filecontents}
% set the holidays
\begin{filecontents}{holidaylist.csv}
date, event
{March 8, 2019}, my birthday
{March 22, 2019}, a special day
{March 29, 2019}, tornados
\end{filecontents}
\DTLloaddb{holidays}{holidaylist.csv}
\DTLloaddb{lectures}{lectures.csv}
\renewcommand{\baselinestretch}{1.5}
\begin{document}
% uncomment next lines to show databases
%\DTLdisplaydb{lectures}
%\DTLdisplaydb{holidays}
\begin{DTLenvforeach}{lectures}%
{\topic=topic, \read=reading}
\StepThruLectures{\topic}{\read}
\end{DTLenvforeach}
\end{document}
这是输出。
这种方法的优点是,您可以生成讲座主题,并将它们准备好,每个学期都保存在一个不变的 CSV 文件中。您需要在任何给定学期中做的就是设置开始日期和假期的 CSV。编译将自动生成该学期的讲座时间表。
警告-如上所述datatool
,当数据库很大时,速度会很慢。
其他说明
我仍然相信日历该软件包很可能能够实现这些功能,甚至更多,或许还能更加优雅。