1

Can an English date be validated using the datetime2 package? If not, are there any other packages/ways to do this?

  • Valid arguments: 2023-06-14 , 2023-09-24 , ...

  • Invalid arguments: 2023-06-XX , 2023-09-32 , ...

Ideally I'd like to condition on an in-/valid argument in the following way:

\IfJulianDate{<date>}{<true code>}{<false code>}
Werner
  • 603,163
projetmbc
  • 13,315
  • 1
    This problem is best divides into two: is it even an integer (and not XX or 25.7) and if it is is it a proper date? For the first, I believe xstring has a test for that but I'm sure LaTeX3 has tests for that as well (at the very least a regexp test is possible). For the later, we could just feed it into PGFCalender and test whether we get the same date back (04-32 would return 05-01). Though, we could just test for 30 or 31 days and need a leap year test for February, without any calendar packages. – Qrrbrbirlbel Sep 19 '23 at 20:25
  • 1
    For the first: Q50111 and related. – Qrrbrbirlbel Sep 19 '23 at 20:27
  • Can you give the PGF calendar sequence? – projetmbc Sep 19 '23 at 20:28

2 Answers2

2

The PGFCalendar sequence is basically this:

\pgfcalendardatetojulian{<date>}{\<count>}
\pgfcalendarjuliantodate{\<count>}{\Year}{\Month}{\Day}

and then test \Year, \Month, \Day against your input (which you need to split somehow at the -s). Note that \<count> needs to be a single token count, not a LaTeX counter, not \count0 or even a macro. The L3 scratch “integer” \l_tmpa_int is one.

In the code below, I'm using L3Regex to set up a regular expresion for the typical Year-Month-Date format out of integers meaning that 0003-000012-000000 would match true against it.

However, negative years wouldn't match the regexp. Neither would 2023-09-last or 2023-09-19+3 which would all be valid PGFCalendar dates. The \A (beginning) and the \Z (end) make sure only a date and nothing else matches, so neither Foo 2023-09-13 Bar nor 2023-09-19 2023-09-20 (which would be two dates), both of which would break PGFCalendar.

The \regex_extract_once macro puts all matches into \l_tmpa_seq where the first element is the whole match and the other ones are the capturing groups denoted by the pair of ().

Code

\documentclass{article}
\usepackage{pgfcalendar, pgffor}% pgffor just for the .list handler
\ExplSyntaxOn
\regex_new:N \l_mbc_datetest
\regex_set:Nn\l_mbc_datetest { \A (\d+) \- (\d+) \- (\d+) \Z }
\NewDocumentCommand { \IfJulianDate }{ m m m }{
  \regex_extract_once:NnNTF \l_mbc_datetest { #1 } \l_tmpa_seq {
    \pgfcalendardatetojulian{ #1 } { \l_tmpa_int }
    \pgfcalendarjuliantodate { \l_tmpa_int }{ \l_tmp_year_int }
                         { \l_tmp_month_int }{ \l_tmp_day_int }
    \bool_lazy_all:nTF {
      { \int_compare_p:nNn { \seq_item:Nn\l_tmpa_seq{2} } = { \l_tmp_year_int  } }
      { \int_compare_p:nNn { \seq_item:Nn\l_tmpa_seq{3} } = { \l_tmp_month_int } }
      { \int_compare_p:nNn { \seq_item:Nn\l_tmpa_seq{4} } = { \l_tmp_day_int   } }
    }{ #2 }{ #3 }
  }{ % not even proper date format
    #3
  }
}
\ExplSyntaxOff
\begin{document}
\pgfkeys{tester/.code=#1 is \IfJulianDate{#1}{a real}{not a proper} date\par,
         tester/.list={
           2023-06-14, 2023-09-24, 2023-06-XX, 2023-09-32,
           2023-02-29, 2023-09-19 2023-09-20, -0001-12-24}}
\end{document}

Output

2023-06-14 is a real date
2023-09-24 is a real date
2023-06-XX is not a proper date
2023-09-32 is not a proper date
2023-02-29 is not a proper date
2023-09-19 2023-09-20 is not a proper date
-0001-12-24 is not a proper date

Qrrbrbirlbel
  • 119,821
  • 1
    I just realize there's a way to get the individual matches from l3regex … – Qrrbrbirlbel Sep 19 '23 at 21:00
  • It doesn't matter about the LaTeX3 extraction code because it's easy to do... I'll update your question to produce a cleaner code. Thanks for mentioning pgfcalendar. – projetmbc Sep 19 '23 at 21:21
  • 1
    @projetmbc My question? Eitherway, I've updated my answer. Don't know the efficience of \bool_lazy_all. Next step would be to do implement the versions that can generate variants for the full L3 expierence. – Qrrbrbirlbel Sep 19 '23 at 21:42
  • Answer and not question... You are right. Checking dates in LaTeX3 can be done, but I am lazy like some booleans. ;-) – projetmbc Sep 19 '23 at 21:47
  • I have posted a draft LaTeX3 solution. – projetmbc Sep 21 '23 at 10:19
1

Here is a first draft code in "pure" LaTeX3: error messages are not implemented because it is just a PoC. My code should be factorizable. Any advice is welcome...

EDIT: use of a conditional

\documentclass{article}

% Source for easy testing via pgffor: % + https://tex.stackexchange.com/a/696444/6880 \usepackage{pgffor}

\ExplSyntaxOn

\seq_new:N \g_mbc_month_size \seq_set_from_clist:Nn \g_mbc_month_size {% 0, % Not used. 31, % January 0, % February: this special value will help us to find bugs... 31, % March 30, % April 31, % May 30, % June 31, % July 31, % August 30, % September 31, % October 30, % November 31 % December }

% The rule defining a leap year A is as follows: % % + If A % 4 != 0, the year is not a leap year. % % + If A % 4 = 0 , the year is a leap year unless % A % 100 = 0 and A % 400 != 0. \prg_set_conditional:Npnn \if_leap_year:N #1 { p , T , TF } { \int_compare:nTF { \int_mod:nn #1 { 4 } = 0 }{ \int_compare:nTF { \int_mod:nn #1 { 100 } = 0 }{ \int_compare:nTF { \int_mod:nn #1 { 400 } = 0 }{ \prg_return_true: }{ \prg_return_false: } }{ \prg_return_true: } }{ \prg_return_false: } }

\regex_new:N \g_mbc_date_format_rgx \regex_set:Nn\g_mbc_date_format_rgx { \A (\d+) - (\d+) - (\d+) \Z }

\str_new:N \l_mbc_date_year_str \str_new:N \l_mbc_date_month_str \str_new:N \l_mbc_date_day_str

\int_new:N \l_mbc_date_year_int \int_new:N \l_mbc_date_month_int \int_new:N \l_mbc_date_day_int

\NewDocumentCommand { \ValidateJulianDate }{ m }{ \regex_extract_once:NnNTF \g_mbc_date_format_rgx { #1 } \l_tmpa_seq { % Integer values found. \seq_pop_right:NN \l_tmpa_seq \l_mbc_date_day_str \seq_pop_right:NN \l_tmpa_seq \l_mbc_date_month_str \seq_pop_right:NN \l_tmpa_seq \l_mbc_date_year_str

\int_set:Nn \l_mbc_date_day_int   \l_mbc_date_day_str
\int_set:Nn \l_mbc_date_month_int \l_mbc_date_month_str
\int_set:Nn \l_mbc_date_year_int  \l_mbc_date_year_str

% 1 <= month <= 12 \int_compare:nTF { 1 <= \l_mbc_date_month_int <= 12 }{ % February special setting. \if_int_compare:w \l_mbc_date_month_int = 2 \if_leap_year:NTF \l_mbc_date_year_int { \seq_set_item:Nnn \g_mbc_month_size 2 { 29 } }{ \seq_set_item:Nnn \g_mbc_month_size 2 { 28 } } \fi:

% Good day. \int_compare:nTF { 1 <= \l_mbc_date_day_int <= \seq_item:Nn \g_mbc_month_size { \int_use:N \l_mbc_date_month_int } }{ OK % Bad day. }{ KO (day) }

% NOT(1 <= month <= 12). }{ KO (month) }

% Syntax error }{ KO (syntax) } }

\ExplSyntaxOff

\begin{document}

\section{OK}

\pgfkeys{tester/.code=\ValidateJulianDate{#1}{:} #1\par\medskip, tester/.list={ 2023-06-14, 2023-09-24, 2023-02-28, 2024-02-29, 400-02-29 } }

\section{KO -- Invalid day}

\pgfkeys{tester/.code=\ValidateJulianDate{#1}{:} #1\par\medskip, tester/.list={ 300-02-29, 2023-02-29, 2024-02-30, 2023-09-00, 2023-09-32 } }

\section{KO -- Invalid month}

\pgfkeys{tester/.code=\ValidateJulianDate{#1}{:} #1\par\medskip, tester/.list={ 2023-19-32, 2023-00-29 } }

\section{KO -- Syntax error}

\pgfkeys{tester/.code=\ValidateJulianDate{#1}{:} #1\par\medskip, tester/.list={ 2023-06-XX,
2023-09-19 2023-09-20, -0001-12-24 } }

\end{document}

OUTPUT

1 OK
OK: 2023-06-14 
OK: 2023-09-24 
OK: 2023-02-28 
OK: 2024-02-29 
OK: 400-02-29

2 KO – Invalid day KO(day): 300-02-29 KO(day): 2023-02-29 KO(day): 2024-02-30 KO(day): 2023-09-00 KO(day): 2023-09-32

3 KO – Invalid month KO(month): 2023-19-32 KO(month): 2023-00-29

4 KO – Syntax error KO(syntax): 2023-06-XX KO(syntax): 2023-09-19 2023-09-20 KO(syntax): -0001-12-24

projetmbc
  • 13,315