001  (ns dog-and-duck.quack.picky.time
002    "Time, gentleman, please! Recognising and validating date time values."
003    (:require [dog-and-duck.quack.picky.utils :refer [cond-make-fault-object
004                                                      make-fault-object
005                                                      truthy?]]
006              [scot.weft.i18n.core :refer [get-message]]
007              [taoensso.timbre :refer [warn error]])
008    (:import [java.time LocalDateTime]
009             [java.time.format DateTimeFormatter DateTimeParseException]
010             [javax.xml.datatype DatatypeFactory]))
011  
012  ;;;     Copyright (C) Simon Brooke, 2023
013  
014  ;;;     This program is free software; you can redistribute it and/or
015  ;;;     modify it under the terms of the GNU General Public License
016  ;;;     as published by the Free Software Foundation; either version 2
017  ;;;     of the License, or (at your option) any later version.
018  
019  ;;;     This program is distributed in the hope that it will be useful,
020  ;;;     but WITHOUT ANY WARRANTY; without even the implied warranty of
021  ;;;     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
022  ;;;     GNU General Public License for more details.
023  
024  ;;;     You should have received a copy of the GNU General Public License
025  ;;;     along with this program; if not, write to the Free Software
026  ;;;     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
027  
028  (defn xsd-date-time?
029    "Return `true` if `value` matches the pattern for an 
030     [xsd:dateTime](https://www.w3.org/TR/xmlschema11-2/#dateTime), else `false`"
031    [^String value]
032    (try
033      (if (LocalDateTime/from (.parse DateTimeFormatter/ISO_DATE_TIME value)) true false)
034      (catch DateTimeParseException _
035        (warn (get-message :bad-date-time) ":" value)
036        false)
037      (catch Exception e
038        (error "Exception thrown while parsing date" value e)
039        false)))
040  
041  (defn xsd-duration?
042    "Return `true` if `value` matches the pattern for an 
043     [xsd:duration](https://www.w3.org/TR/xmlschema11-2/#duration), else `false`"
044    [value]
045    (truthy?
046     (and (string? value)
047          (try (.newDuration (DatatypeFactory/newInstance) value)
048               (catch IllegalArgumentException _
049                 (warn (get-message :bad-duration) ":" value)
050                 false)
051               (catch Exception e
052                 (error "Exception thrown while parsing duration" value e)
053                 false)))))
054  
055  (defn date-time-property-or-fault
056    "If the value of this `property` of object `x` is a valid xsd:dateTime 
057     value, return a fault object with this `token` and `severity`. 
058     
059     If `required?` is false and there is no such property, no fault will be
060     returned."
061    [x property severity token required?]
062    (let [value (property x)]
063      (if (and required? (not (x property)))
064        (make-fault-object severity token)
065        (cond-make-fault-object
066         (and value (xsd-date-time? value)) severity token))))