Closed mulderr closed 3 years ago
gfnLocalDateFormat :: Monad m => Function (Run p m h)
gfnLocalDateFormat args =
let extracted =
extractArgsDefL
[ ("date", def)
, ("format", def)
, ("locale", def)
]
args
in case extracted of
Right [gDate, gFormat, gLocale] ->
let fmtMay = Text.unpack <$> fromGVal gFormat
dateMay = convertToLocal <$> gvalToDate utc gDate
in case fmtMay of
Just fmt -> do
locale <- maybe
(getTimeLocale gLocale)
return
(fromGVal gLocale)
return . toGVal $ formatTime locale fmt <$> dateMay
Nothing -> do
return . toGVal $ dateMay
_ -> throwHere $ ArgumentsError (Just "localdate") "expected: (date, format, locale=null)"
where
convertToLocal :: ZonedTime -> ZonedTime
convertToLocal = unsafePerformIO . utcToLocalZonedTime . zonedTimeToUTC
Above gives the desired effect, alas at the cost of unsafePerformIO
, so pasting it here just for reference.
To keep things pure we would likely have to depend on timezone-series
and have a version of dateformat
that:
TimeZoneSeries
instead of TimeZone
utcToLocalTime' :: TimeZoneSeries -> UTCTime -> LocalTime
to run the conversion (likeley using zonedTimeToUTC
first)Another alternative could be tz
. Both rather niche. I don't see a "nice" solution here :/
IMO, the situation as it is now is already close to the best we can do. Ginger can, out of the box, deal with the de facto standard Haskell date/time library, it can format both UTC timestamps and local timestamps, provided the latter are pre-converted to the correct UTC offset. I don't think dateformat
is the problem here, the formatting is fine.
What you are running into is the fact that Ginger will not do timezone conversions for you. If you want that, then IMO your best options are:
ZonedTime
values to Ginger.IO
, but template expansion itself will not, and there's no need for unsafePerformIO
inside the date conversion function either.Options 2 and 3 could easily be provided as add-on libraries to Ginger, so I don't feel very compelled to include this in the core library, especially if it involves another dependency.
On a side note: if you look at how getlocale
is used in the current getTimeLocale
Ginger function (Builtins.hs
, line 724), you might imagine how the local UTC time and timezone data could be facilitated in a similar manner. Essentially, what this function does is it checks for a function named getlocale
in the global scope; if it exists, then it calls it to get the desired locale, and if not, it falls back to the default C locale. The same could be done for local time, except that instead of falling back on a default value, it would probably have to error out. And then you can choose: you can have your template run in IO, and write a currenttime
function that gets the current time on the fly, in IO, or you can get the current time in advance, close over it, and have a currenttime
function in the Identity monad that just returns the predetermined timestamp.
Thanks, option 2 seems plausible. I will try to figure out how to pass an IO based function inside the context then.
BTW. there is more to this then I originally thought. Neither timezone-olson
nor tz
can fully understand binary Olson files. They don't even attempt to parse POSIX-TZ strings so while historic dates will probably be as good as they can be, future dates (say 2038) may be converted differently to the unix date utility etc. So as it stands those would not provide a complete solution either.
Well, future dates are subject to speculation anyway; timezone data beyond 2 weeks into the future is pretty much "as far as we know, but might change on a politician's whim at any time".
On Thu, 11 Nov 2021, 17:55 mulderr, @.***> wrote:
Thanks, option 2 seems plausible. I will try to figure out how to pass an IO based function inside the context then.
BTW. there is more to this then I originally thought. Neither timezone-olson nor tz can fully understand binary Olson files. They don't event attempt to parse POSIX-TZ strings so while historic dates will probably be as good as they can be, future dates (say 2038) may be converted differently to the unix date utility etc. So as it stands those would not provide a complete solution either.
— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/tdammers/ginger/issues/70#issuecomment-966461394, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAK6HR7Q65FPZYQ3S65QWLTULPYOVANCNFSM5HYGHCSQ . Triage notifications on the go with GitHub Mobile for iOS https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675 or Android https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub.
I assume it is somewhat a best practice to store dates in UTC and only convert to local for presentation purposes which is what I ultimately decided to do. Therefore I'd like to pass UTC to ginger and have it properly convert and format it as local time. However, this leads to slight problems/inconveniences down the line stemming both from upstream and
ginger
itself.First, to start at the root,
time
library currently implements a simplistic notion of time zones as offsets from UTC. This is only correct in absence of DST. For example Europe/Berlin is a time zone with offset either +0100 (without) or +0200 (with DST) but fortime
there is no Europe/Berlin, there are just two distinct offsets which it both represents as separateTimeZone
(a misnomer really) values. Therefore to calculate local time from UTC usingutcToLocalTime
orutcToZonedTime
you need to know if DST was in effect for your chosen time zone and particular date combination.AFAIK the only exception to this is when you want to calculate local time according to your local system time zone settings. This is conveniently possible using functions like
getTimeZone :: UTCTime -> IO TimeZone
,utcToLocalZonedTime :: UTCTime -> IO ZonedTime
and would be enough for my purposes.Going back to
ginger
, currently my only choice is to convert everything toLocalTime
orZonedTime
before rendering or maybe attach an offset to each date so that I can pass it todateformat
. Both seem rather unsatisfactory because the dates, all represented asUTCTime
, may be embedded deeply inside the data types.To handle this once and for all I thought about adding a
localdate
builtin function toginger
for exactly this use case but it would requireIO
. I'm unsure if that would fit well intoginger
. The default scope only consist of pure functions.