This is a simplified version of part of the code I've been using in answers based on chronos
, an attempt to create something somewhat like a substitute for the problematic chronosys
.
This code works as it should but it relies on a weird hack. How should this be done without the need for any weird hackery?
The relevant weird hack (i.e. the one which I know is weird) involves the lines
\tl_replace_all:Nnn \l_chronos_dateformat_tl { ~ } { @ }
and
{ @ } { ~ }
Essentially what this does is to substitute all spaces in the user-defined date format with @
and then substitute all @
s in that date format with spaces when typesetting the formatted date.
The idea is that a user can say something like
\tikzset{%
date format={ B d, Y},
}
in order for dates of timeline events to be printed as September 15, 2013
, for example, or
\tikzset{%
date format={b d Y},
}
for Sept 15 2013
, say. (The minimal code here won't do this because I've removed the code needed for b
, B
and d
to work, but I wanted to explain why I'm going about regurgitating the year the user inputs in such a roundabout way.)
The idea is that any character which is not recognised by the code as a date-format character will simply be passed through unaltered. However, spaces are a natural character to use in certain date formats and so the problem is to preserve those during the manipulation of the remaining characters.
It is also worth saying that the date is not immediately typeset as here but, is rather, stored and output as part of a TikZ node later. This explains some of the complexity (e.g. the use of #4
to create a tag to retrieve the date such as thing
) not explained by the formatting itself. (In fact, most of the apparatus is there for this reason and not because of the format-parsing requirement.)
I'm a bit worried that I may not have explained fully enough why I'm doing it this way, so if it seems there is some obvious, simple solution which involves demolishing most of the complexity I've introduced, then I'm certainly interested, but please ask for clarification before spending your time writing an answer!
\documentclass[border=10pt,multi,tikz]{standalone}
\usepackage{xparse}
\makeatletter
\ExplSyntaxOn
\tl_new:N \l_chronos_dateformat_tl
\tl_set:Nn \l_chronos_dateformat_tl { d/m/Y }
\cs_generate_variant:Nn \int_abs:n { c }
\cs_new_protected_nopar:Npn \chronos_show_date:n #1
{
\tl_map_inline:Nn \l_chronos_dateformat_tl
{
\str_case:nnF { ##1 }
{
{ Y } { \int_abs:c { chronos@#1year } } % for each Y in the date format, output the year in YYYY, omitting any leading minus sign
{ @ } { ~ } % un-hackery: for each @ s in the date format, output a space when showing a date
}
{
##1
}
}
}
\cs_new_protected_nopar:Npn \chronos_set_dateformat:n #1
{
\tl_set:Nn \l_chronos_dateformat_tl { #1 }
\tl_replace_all:Nnn \l_chronos_dateformat_tl { ~ } { @ } % hackery - substitute any spaces in the input with @
}
\NewDocumentCommand \chronos@setdateformat { m }
{
\chronos_set_dateformat:n { #1 }
}
\NewDocumentCommand \chronos@showdate { o m }
{
\group_begin:
\IfValueT { #1 }
{
\chronos_set_dateformat:n { #1 }
}
\chronos_show_date:n { #2 }
\group_end:
}
\ExplSyntaxOff
\newcounter{chronos@thingdate}
\tikzset{%
chronos set date/.code args={#1:#2:#3:#4}{%
\expandafter\def\csname chronos@#4year\endcsname{#1}%
\chronos@showdate{#4}% This doesn't usually happen here: instead, the date is saved with a tag such as 'thing' so that the date or parts of the date can be retrieved and formatted later, possibly more than once with different formatting. Multiple tags allow the storing of multiple sets of date/date part information. The information gets used in various ways to calculate, construct and annotate a timeline.
},
chronos date/.style args={#1-#2-#3}{%
/tikz/chronos set date/.expanded={#1:#2:#3:thing}% In the full code 'thing' is one of several tags used to tag dates for the timeline.
},
date format/.code={% Let the user change the format of the date without doing anything special to include spaces.
\chronos@setdateformat{#1}%
},
}
\makeatother
\begin{document}
\tikzset{%
date format={date: Y},
chronos date=2016-04-25,
}
\end{document}
EDIT
Using Manuel's suggestion (sort of) from the comments, I can do away with the @
-ery at the price of somewhat greater complexity. (Note, however, that I'm not at all sure this is what Manuel intended, so the fault is certainly mine!)
\documentclass[border=10pt,multi,tikz]{standalone}
\usepackage{xparse}
\makeatletter
\ExplSyntaxOn
\seq_new:N \l_chronos_date_seq
\seq_new:N \l_chronos_dateformat_seq
\seq_set_split:Nnn \l_chronos_dateformat_seq { ~ } { d/m/Y }
\cs_generate_variant:Nn \int_abs:n { c }
\cs_new_protected_nopar:Npn \chronos_show_date:n #1
{
\seq_clear:N \l_chronos_date_seq
\seq_map_inline:Nn \l_chronos_dateformat_seq
{
\seq_put_right:Nn \l_chronos_date_seq
{
\tl_map_inline:nn { ##1 }
{
\str_case:nnF { ####1 }
{
{ Y } { \int_abs:c { chronos@#1year } }
}
{
####1
}
}
}
}
\seq_use:Nn \l_chronos_date_seq { ~ }
}
\cs_new_protected_nopar:Npn \chronos_set_dateformat:n #1
{
\seq_set_split:Nnn \l_chronos_dateformat_seq { ~ } { #1 }
}
\NewDocumentCommand \chronos@setdateformat { m }
{
\chronos_set_dateformat:n { #1 }
}
\NewDocumentCommand \chronos@showdate { o m }
{
\group_begin:
\IfValueT { #1 }
{
\chronos_set_dateformat:n { #1 }
}
\chronos_show_date:n { #2 }
\group_end:
}
\ExplSyntaxOff
\newcounter{chronos@thingdate}
\tikzset{%
chronos set date/.code args={#1:#2:#3:#4}{%
\expandafter\def\csname chronos@#4year\endcsname{#1}%
\chronos@showdate{#4}%
},
chronos date/.style args={#1-#2-#3}{%
/tikz/chronos set date/.expanded={#1:#2:#3:thing}%
},
date format/.code={%
\chronos@setdateformat{#1}%
},
}
\makeatother
\begin{document}
\tikzset{%
chronos date=2016-04-25,
date format={date: Y},
chronos date=2016-04-25,
}
\end{document}
Again, this works. But it seems a very complicated way to do something very commonly needed i.e. preserving spaces in user input - surely this is not the best approach?!
EDIT 2
OK, this is simpler than EDIT and less hackish than the original. But I'm not sure how safe/robust/proper it is.
This basically replaces the original hackery with
\tl_replace_all:Nnn \l_chronos_dateformat_tl { ~ } { \c_space_token }
which seems to work for the minimal example, at least. It also seems to work in minimal testing with my full code and seems the 'least weird' of the approaches so far. But that is not to say that it is a good strategy ....
\documentclass[border=10pt,multi,tikz]{standalone}
\usepackage{xparse}
\makeatletter
\ExplSyntaxOn
\tl_new:N \l_chronos_dateformat_tl
\tl_set:Nn \l_chronos_dateformat_tl { d/m/Y }
\cs_generate_variant:Nn \int_abs:n { c }
\cs_new_protected_nopar:Npn \chronos_show_date:n #1
{
\tl_map_inline:Nn \l_chronos_dateformat_tl
{
\str_case:nnF { ##1 }
{
{ Y } { \int_abs:c { chronos@#1year } } % for each Y in the date format, output the year in YYYY, omitting any leading minus sign
}
{
##1
}
}
}
\cs_new_protected_nopar:Npn \chronos_set_dateformat:n #1
{
\tl_set:Nn \l_chronos_dateformat_tl { #1 }
\tl_replace_all:Nnn \l_chronos_dateformat_tl { ~ } { \c_space_token }
}
\NewDocumentCommand \chronos@setdateformat { m }
{
\chronos_set_dateformat:n { #1 }
}
\NewDocumentCommand \chronos@showdate { o m }
{
\group_begin:
\IfValueT { #1 }
{
\chronos_set_dateformat:n { #1 }
}
\chronos_show_date:n { #2 }
\group_end:
}
\ExplSyntaxOff
\newcounter{chronos@thingdate}
\tikzset{%
chronos set date/.code args={#1:#2:#3:#4}{%
\expandafter\def\csname chronos@#4year\endcsname{#1}%
\chronos@showdate{#4}% This doesn't usually happen here: instead, the date is saved with a tag such as 'thing' so that the date or parts of the date can be retrieved and formatted later, possibly more than once with different formatting. Multiple tags allow the storing of multiple sets of date/date part information. The information gets used in various ways to calculate, construct and annotate a timeline.
},
chronos date/.style args={#1-#2-#3}{%
/tikz/chronos set date/.expanded={#1:#2:#3:thing}% In the full code 'thing' is one of several tags used to tag dates for the timeline.
},
date format/.code={% Let the user change the format of the date without doing anything special to include spaces.
\chronos@setdateformat{#1}%
},
}
\makeatother
\begin{document}
\tikzset{%
date format={date: Y},
chronos date=2016-04-25,
}
\end{document}
\seq_set_split:Nnn \l_chronos_dateformat_seq { ~ } { #1 }
and then\seq_map_inline:Nn \l_chronos_dateformat_tl { \tl_map_inline:nn { ##1 } { .. } }
work for you?_seq
rather than_tl
?\seq_new:N \l_chronos_date_seq \seq_new:N \l_chronos_dateformat_seq \seq_set_split:Nnn \l_chronos_dateformat_seq { ~ } { d/m/Y } \cs_new_protected_nopar:Npn \chronos_show_date:n #1 { \seq_clear:N \l_chronos_date_seq \seq_map_inline:Nn \l_chronos_dateformat_seq { \seq_put_right:Nn \l_chronos_date_seq { \tl_map_inline:nn { ##1 } { \str_case:nnF { ####1 } { { Y } { \int_abs:c { chronos@#1year } } } { ####1 } } } } \seq_use:Nn \l_chronos_date_seq { ~ } }