11package com .gu .sfl .model
22
33import java .io .IOException
4- import java .time .format .DateTimeFormatter
4+ import java .time .format .{DateTimeFormatter , DateTimeFormatterBuilder }
5+ import java .time .temporal .ChronoField
56import java .time .{Instant , LocalDateTime , ZoneOffset }
67
78import com .fasterxml .jackson .annotation .JsonIgnore
@@ -80,7 +81,13 @@ case class ErrorResponse(status: String = "error", errors: List[Error])
8081case class Error (message : String , description : String )
8182
8283object SavedArticleDateSerializer {
83- val formatter = DateTimeFormatter .ofPattern(" yyyy-MM-dd'T'HH:mm:ss'Z'" )
84+ /** Accept optional 3-digit milliseconds in input parsing since it is a valid ISO-8601 component
85+ * But drop them during output serialisation to ensure consistency in DB records and API responses
86+ */
87+ val inputFormatter = DateTimeFormatter .ofPattern(" yyyy-MM-dd'T'HH:mm:ss[.SSS]'Z'" )
88+ val outputFormatter = DateTimeFormatter .ofPattern(" yyyy-MM-dd'T'HH:mm:ss'Z'" )
89+
90+ def parse (value : String ): LocalDateTime = LocalDateTime .parse(value, inputFormatter)
8491}
8592
8693class DirtySavedArticleDeserializer (t : Class [DirtySavedArticle ]) extends StdDeserializer [DirtySavedArticle ](t) {
@@ -93,7 +100,7 @@ class DirtySavedArticleDeserializer(t: Class[DirtySavedArticle]) extends StdDese
93100 val id = Option (node.get(" id" )).filter(_.isTextual).map(_.asText())
94101 val shortUrl = Option (node.get(" shortUrl" )).filter(_.isTextual).map(_.asText())
95102 val read = Option (node.get(" read" )).filter(_.isBoolean).map(_.asBoolean())
96- val date = Option (node.get(" date" )).filter(_.isTextual).map(_.asText()).map(LocalDateTime .parse(_, SavedArticleDateSerializer .formatter) )
103+ val date = Option (node.get(" date" )).filter(_.isTextual).map(_.asText()).map(SavedArticleDateSerializer .parse )
97104 DirtySavedArticle (id, shortUrl, date, read.getOrElse(false ))
98105 }
99106}
@@ -109,7 +116,7 @@ class SavedArticleSerializer(t:Class[SavedArticle]) extends StdSerializer[SavedA
109116 gen.writeStartObject()
110117 gen.writeStringField(" id" , value.id)
111118 gen.writeStringField(" shortUrl" , value.shortUrl)
112- gen.writeStringField(" date" , SavedArticleDateSerializer .formatter .format(value.date))
119+ gen.writeStringField(" date" , SavedArticleDateSerializer .outputFormatter .format(value.date))
113120 gen.writeBooleanField(" read" , value.read)
114121 gen.writeEndObject()
115122 }
0 commit comments