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))))