Iāve been thinking about this a lot on a long plane flight so here goes:
Calendar module responsibilities
This part is not about formatting but separation and encapsulation of concerns. But if there is agreement with this, and my proposal about augmenting the format flags, then it greatly simplifies the contract between strftime and a calendar.
I think a calendar should encapsulate everything relevant to calculating days, weeks, months, years etc. That is, any configuration should be baked into the module that implements the Calendar behaviour.
This seems obvious when we talk about the iSO calendar versus the Balinese, or Ancient Egyptian or the Islamic calendars. But its less obvious if we consider the myriad of calendars derived from the Gregorian.
For example, in the US, a company may have a fiscal calendar that starts on an arbitrary day of the year. All months, quarters, weeks and days are calculated from this date. Cisco Systems has a financial year calendar that ends on the last Saturday of July. There are a whole class of calendars called 445 calendars and their cousins the 454 and 544 which are common in retail and are designed to make it better for comparing this year to prior year periods.
Although I started thinking this would be best done as configuration passed around in each structurally compatible Date, Time or DateTime struct or map, the variations are too many. I now feel it is better that this detail be backed into a module that implements the Calendar behaviour. This doesnāt make implementing a calendar any more complex. It does potentially have the side effect of having to generate calendar modules at runtime for some use cases.
Calendar Behaviour
If you accept that a Calendar module encapsulates all the required configuration and implementation required for the behaviour then implementing strftime comes back to mostly calling the behaviour functions. And week_of_year should definitely stay - in all of those business calendars is used a lot and the notion of a week is, I think we would all agree, a very common case.
If you got this far then the missing pieces of the Calendar behaviour that would be required to support strftime would be day_name, month_name (short and long) and the am/pm functionality described by @josevalim above. Since these will need to be implemented anyway it feels as if they should be part of the Calendar behaviour.
Formatting
Formatting for strftime should not be the calendars concern (in my opinion). Thats a representational issue and other than names for days and months, formatting should be the strftime formats responsibility. In examining the Ruby and Python implementations I see that the Python version is quite sparse but the Ruby version includes an approach that I think is pragmatic.
A Proposal
@spec strftime(date_or_time_or_datetime, format :: String.t(), options :: Map.t()) ::
String.t | {:error, String.t}
format is a format string as described in the original proposal however it includes an optional width that is used to determine padding. This is similar to how printf and friends in C define padding.
Format encoding: %<flags><width><conversion>.
Flags (based upon the Ruby implementation)
| Flag |
Description |
| - |
donāt pad a numerical output |
| _ |
use spaces for padding |
| 0 |
use zeros for padding |
| ^ |
upcase the result string |
| # |
change case |
| : |
use colons for %z |
Using this approach allows a calendar implementor to use the %x directive to return a format string that uncludes the required padding. That is, %x is really just an interpolation of a format defined in the calendar. And that format can include the appropriate padding. Same would apply for %X and %c. The format for these āpreferred formatsā can also then vary based upon locale without too much difficulty in any locale-aware calendar.
Examples
| Format |
Output |
| %m |
1, 12 |
| %2m |
01, 12 |
| %A |
Monday |
| %^A |
MONDAY |
Summary
This proposal intents to:
-
Recommend reinforcing the Calendar behaviour and add to it those missing pieces that support formatting which are primarily the day_name and month_name (short and long) functions.
-
Simplify the interaction between strftime and a Calendar module by reverting to the original plan of simply calling the relevant functions in the calendar module.
-
Making formatting decisions part of the format string (ie flags and width modifiers)
Options
I have included an options argument to strftime above because for localisation a locale name needs to be specified. In Python a locale is part of the stdlib and runtime environment but not so Elixir. Therefore it should be passed in to the calendar. It could be argued that since there is no other part of Elixir that embodies the idea of localisation then this is not the place to start. I admit my bias given my work on Cldr, but I think the idea of a locale should be able to be specified.
A Calendar should feel free to return {:error, "unknown locale"} if a locale is requested that it doesnāt support. Of if a more loose contract is preferred then treat the locale as entirely optional.