2016-12-22 13 views
1

Меня озадачивают Aeson и Servant конверсий ZonedTime.ZonedTime fromJSON toJSON

К моему Servant app Я даю некоторое время в адресе: .../2016-12-18T07:51:00+03:00/....

Servant легко преобразует его в ZonedTime с ... :> Capture "zt" ZonedTime :> ....

Затем мое приложение выполняет некоторые вычисления и в json-response. Я хочу вернуть клиенту этот и некоторые другие ZonedTime s - в случае, если клиент хочет снова предоставить эти моменты моему приложению.

Если вход временной зоны не нулевой +0X:00 (Х/= 0), то на выходе я также получаю +0X:00, но если на входе я дам .../2016-12-18T07:51:00+00:00/..., то в ответ я получаю 2016-12-18T07:51:00Z. И если я попытаюсь подать эту строку на Servant снова с .../2016-12-18T07:51:00Z/..., то Servant не сможет преобразовать ее в ZonedTime. Фактически возвращается HTTP 400 (Bad Request).

Почему? Зачем?

ответ

0

ISO 8601 - стандартное текстовое представление времен, используемых в JSON. Когда часовой пояс UTC, «+00: 00» или «Z» является допустимым суффиксом часовой пояс. Похоже, что Aeson выводит время в ISO 8601, используя суффикс «Z», если это UTC и суффикс «+ xx: xx» в противном случае. К сожалению, Servant (на самом деле, Web.HttpApiData) использует наивный метод разбора ZonedTime из фрагментов URL, который не допускает суффикса «Z». Если вместо этого вы проанализируете UTCTime, тогда он использует (и требует) суффикс «Z».

Вы можете определить NewType псевдоним для ZonedTime следующим образом, который обрабатывает оба формата, пытаясь разобрать, как ZonedTime и - если это невозможно - в UTCTime с конверсией:

module ZonedTimeTest where 

import Data.Time.LocalTime 
import Servant.API 

newtype ZonedTime' = ZonedTime' { getZonedTime :: ZonedTime } 

instance FromHttpApiData ZonedTime' where 
    parseUrlPiece u =  ZonedTime' <$> parseUrlPiece u   -- "...+xx:xx" 
        <!> ZonedTime' . fromUTC <$> parseUrlPiece u -- "...Z" 
    where fromUTC = utcToZonedTime utc 
      infixl 3 <!> 
      Left _ <!> y = y 
      x  <!> _ = x 

, а затем Capture "zt" ZonedTime' следует обрабатывать как форматы для вас (хотя вам необходимо развернуть ZonedTime' до ZonedTime, если это необходимо).

+0

Aha. Очистить. Сначала я попытаюсь сделать новую строку захвата с 'utctime' вместо' zonedtime' и увидит, будет ли Servant управлять с диспетчеризацией между двумя строками. – Dahan

0

Ok.

Таким образом, как только ...Z нотации правильно разобран Слуги, как UTCTime, я просто сделал еще одну линию захвата:

:<|> "myendpoint" :> Capture "zt" ZonedTime :> ... 
:<|> "myendpoint" :> Capture "utct" UTCTime :> ... 

И сделал соответствующие обработчики. С помощью этих литералов с +xx:xx возьмите ZonedTime и перейдите к одному обработчику, а литералы с Z взяты второй строкой и перейдут во второй обработчик, что делает то же самое, что и первый, но преобразует UTC в Zoned на лету.


Update

я пришел к пониманию, что путь, как моя система работает приводит к тому, что в URL я могу иметь разные вариации времени строки кодирования.

  1. В часовом поясе может быть +03:00 или просто Z, если это для UTC.
  2. I может иметь дробные секунды как 2016-12-09T15:04:26.349857693845+05:00

Стандартный Capture для ZonedTime не понимает ни Z в часовом поясе, ни долей секунды.

Так что я сделал это как-то в направлении, как это предлагается в другом ответе.

я поставил только одну линию Захват

"daymonth" :> Capture "zt" ZonedTime' :> Capture "fl" Double :> Get '[JSON] Value 

и я делаю новый тип ZonedTime', что делает все для меня.

newtype ZonedTime' = ZonedTime' { unwrap :: ZonedTime } 

instance FromHttpApiData ZonedTime' where 
    parseUrlPiece text = Right zt 
    where 
     strRaw = unpack text 
     str = subRegex (mkRegex "Z$") strRaw "+00:00" 
     zt = ZonedTime' (parseTimeOrError False defaultTimeLocale "%Y-%m-%dT%H:%M:%S%Q%z" str) 
  1. Я имею дело с Z проблемы, заменив его +00:00
  2. Затем я пишу разбор строки вручную, давая строку формата, где %Q уловов дробной части секунд.