Clojure java-time: getting instant with millis - clojure

I'm trying to convert a local-date into an instant with millis using java-time, but instant returns a timestamp without millis.
(def UTC (java-time/zone-id "UTC")
(defn local-date-to-instant [local-date]
(-> local-date
(.atStartOfDay UTC)
java-time/instant))
(local-date-to-instant (java-time/local-date))
=> #object[java.time.Instant 0xdb3a8c7 "2021-05-13T00:00:00Z"]
but
(java-time/instant)
=> #object[java.time.Instant 0x1d1c27c8 "2021-05-13T13:12:31.782Z"]
The service downstream expects a string in this format: yyyy-MM-ddTHH:mm:ss.SSSZ.

Create a DateTimeFormatter that prints an ISO instant with milliseconds (3 fractional digits) even if they are zeroes:
(ns steffan.overflow
(:require [java-time :as jt])
(:import (java.time.format DateTimeFormatterBuilder)))
(def iso-instant-ms-formatter
(-> (DateTimeFormatterBuilder.) (.appendInstant 3) .toFormatter))
Example of use:
(def today-inst (jt/truncate-to (jt/instant) :days))
(str today-inst) ; => "2021-05-13T00:00:00Z"
(jt/format iso-instant-ms-formatter today-inst) ; => "2021-05-13T00:00:00.000Z"

You need to use a DateTimeFormatter. Then you can write code like:
LocalDate date = LocalDate.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy MM dd");
String text = date.format(formatter);
In particular, look at these format pattern codes:
S fraction-of-second fraction 978
A milli-of-day number 1234
n nano-of-second number 987654321
I think the S code is the best one for your purposes. You'll have to experiment a bit as the docs don't have all the details.
Here is one example:
(ns tst.demo.core
(:use tupelo.core tupelo.test)
(:require
[schema.core :as s]
[tupelo.java-time :as tjt]
)
(:import
[java.time Instant LocalDate LocalDateTime ZonedDateTime]
[java.time.format DateTimeFormatter]
))
(dotest
(let [top-of-hour (tjt/trunc-to-hour (ZonedDateTime/now))
fmt (DateTimeFormatter/ofPattern "yyyy-MM-dd HH:mm:ss.SSS")
]
(spyx top-of-hour)
(spyx (.format top-of-hour fmt))
))
with result
-----------------------------------
Clojure 1.10.3 Java 15.0.2
-----------------------------------
Testing tst.demo.core
top-of-hour => #object[java.time.ZonedDateTime 0x9b64076 "2021-05-13T07:00-07:00[America/Los_Angeles]"]
(.format top-of-hour fmt) => "2021-05-13 07:00:00.000"
The above is based from this template project and the library tupelo.java-time.

LocalDate doesn't have a time component. .atStartOfDay puts zeroes in all time fields. A LocalDateTime can be converted to an Instant through .toInstant.

Related

Trying to read data from a text file and populating a list of integers

So, upfront, I'm super new at Clojure so this question may seem basic. I hava a txt file with 1 line that has a set number of intergers separated by a space. I need to read that data and populate a list so I can sort it later. I'm not asking how to do the sort, I need help populating the list with the string from the txt file.
My initial thought is to read the entire line of ints as one string, then split the string with a delimiter, and populate the list with the returned data, but I cant figure out how to do that in clojure. Any guidance is appreciated
Here is one way to do it, using some helper functions. Be sure to also bookmark:
The Clojure CheatSheet
Brave Clojure
Getting Clojure
(ns tst.demo.core
(:use demo.core tupelo.core tupelo.test)
(:require
[schema.core :as s]
[clojure.string :as str]))
(dotest
(let [filename "/tmp/dummy.txt"]
(spit filename "1 2 3 4 5")
(let-spy
[in-str (slurp filename)
nums-str (str/split in-str #"\W+")
nums (mapv #(Integer/parseInt %) nums-str)]
)))
with result:
-------------------------------
Clojure 1.10.0 Java 12
-------------------------------
Testing tst.demo.core
in-str => "1 2 3 4 5"
nums-str => ["1" "2" "3" "4" "5"]
nums => [1 2 3 4 5]

How can I destructure an instant and extract year, month, etc from java-time library

I'm trying to destructure an instant and get year, month, day.
I've tried with java-time/as function without success.
(ns myproject.time-test
(:require [java-time :as jt])
(:gen-class))
(def curr-time (jt/instant (System/currentTimeMillis)))
(jt/as curr-time :year)
Can anyone point me in the right direction?
I would do it without the .. to make it clear you are using Java interop (it appears that clojure.java-time has no wrapper to convert from an Instant to a ZonedDateTime:
(-> (jt/instant)
(.atZone (ZoneId/systemDefault)) ; => java ZonedDateTime obj
(.getYear))
=> 2018
There are other ways which may be useful:
(jt/zoned-date-time) => #object[java.time.ZonedDateTime 0x2585437a
"2018-07-19T11:42:37.093731-07:00[America/Los_Angeles]"]
(jt/year (jt/zoned-date-time)) => #object[java.time.Year 0x74694f06 "2018"]
(jt/year) => #object[java.time.Year 0x16c69c47 "2018"]
and also
(jt/as (jt/zoned-date-time) :year :month-of-year :day-of-month) => (2018 7 19)
Another way to convert an Instant to a ZonedDateTime:
(let [zdt (ZonedDateTime/ofInstant (jt/instant) (ZoneId/systemDefault))]
(.getYear zdt) => 2018
(.getMonth zdt) => #object[java.time.Month 0x403d9a5b "JULY"]
(.getDayOfMonth zdt) => 19
(ns mastering.stackoverflow
(:import
(java.time ZoneId)))
(.. (jt/instant)
(atZone (ZoneId/systemDefault))
(getYear))
Other methods like getMonthValue, getMinute are also available.
You could do "extraction" that way:
(let [i (.. (jt/instant)
(atZone (ZoneId/systemDefault)))
extract (juxt (memfn getYear) (memfn getMinute))]
(extract i))
; => [2018 37]

Generate from regular expression with Plumatic Schema generators

Regexes seem to be accepted schemas:
(require '[schema.core :as schema])
(schema/validate #"^..$" "hi") ; => "hi"
But schema-generators can't seem to generate from them:
(require '[schema-generators.generators :as gen])
(gen/generate #"^..$")
; 1. Unhandled java.lang.RuntimeException
; You must provide a leaf generator for
; schema.spec.leaf.LeafSpec#3d398cfd
Is it possible to work around this in some way?
If we use the miner/strgen library we can indeed work up a solution:
(require '[schema.core :as schema]
'[miner.strgen :as strgen]
'[schema-generators.generators :as gen])
(def two-char #"^..$")
(schema/validate two-char "hi") ; => "hi"
(gen/generate two-char {two-char (strgen/string-generator #"^..$")})
; => "x["
Though it should be noted that this only provides a generator for the regex #"^..$" in perticular and not regular expressions in general. We need a better solution, perhaps extend some protocol somewhere.

Convert Clojure schema input to lower case.

I can validate clojure schema inputs as string as shown below.
name :- s/Str,
place :- s/Str,
How can I write a defschema for Email to use like
email : - s/Email
which will convert/coerce it to lower case.
I happen to have exactly the one...
(ns myapp.utils.schema
(:import [org.apache.commons.validator.routines EmailValidator])
(:require [clojure.string :as str]
[schema.core :as s]
[schema.coerce :as sco]))
(def valid-email? "Checks that the value is a valid email string."
(let [ev (EmailValidator/getInstance)]
(fn [s]
(and
(string? s)
(.isValid ev, ^String s)
))))
(def Email "Email schema type, stricter than schema.core/Str."
(s/pred valid-email?))
(defn sanitize-email [s]
(if (string? s)
(-> s str/lower-case str/trim)
s))
(defn email-matcher [s]
(when (= Email s) sanitize-email))
(def matcher
"The Plumatic matcher used for Plumatic schema coercion in myapp."
(sco/first-matcher [email-matcher,,,]))
(defn coercer
[schema]
(sco/coercer schema matcher))
;; ...
(ns myapp.customer
(:require [schema.core :as s]
[myapp.utils.schema :as sc]))
(def Customer
{:customer/email sc/Email
:customer/name s/Str})
(def coerce-customer (sc/coercer Customer))
(coerce-customer {:customer/email "vAlENTin#gmaIl.com "
:customer/name "Valentin"})
=> {:customer/email "valentin#gmail.com"
:customer/name "Valentin"}

How to convert instant time to date in clojure

I have an instant time in clojure as "2016-08-03T18:45:00.000-00:00". I want to convert it to only date ("2016-08-03"). How do I do this.
My actual date is in the format "2016-08-03T18:45:00Z". I converted it to instant using (c/to-date (f/parse "2016-08-03T18:45:00Z")).How to get only date?
Thank you
clj-time is now deprecated, new projects should use java-time:
(:require [java-time :as jt]))
(defn format-date
"Format a Java instant"
[date]
(let [formatter (jt/format "dd-MM-yyyy HH:mm")
instant-with-zone (.atZone date (jt/zone-id))]
(jt/format formatter instant-with-zone)))
Note that (jt/zone-id) returns the current server zone, so you must use (jt/zone-id "America/Mexico_City") if you want a static zoned date.
If you want to get just the date part of the date and time string:
(require '[clojure.string :as str])
(def date-str "2016-08-03T18:45:00.000-00:00")
(first (str/split date-str #"T"))
;; => "2016-08-03"