寻找一种在 Clojure 中按月划分日期的方法
Looking for a way to partition date on a monthly basis in Clojure
想要将日期范围分成每月的块。
示例输入 - [10/20/2019 - 12/20/2019]
示例输出 - { [10/20/2019 10/31/2019] [11/01/2019 11/30/2019] [12/012019 12/20/2019] }
谢谢
这是您可以执行的操作的简单草稿(使用 java 互操作,没有外部库):
首先让我们按月进行迭代,从指定的月份开始:
(defn by-month [[mm yyyy]]
(iterate #(.plusMonths % 1)
(java.time.YearMonth/of yyyy mm)))
user> (take 4 (by-month [10 2019]))
;;=> (#object[java.time.YearMonth 0x62fc8302 "2019-10"]
;; #object[java.time.YearMonth 0x1a7bc211 "2019-11"]
;; #object[java.time.YearMonth 0x6a466e83 "2019-12"]
;; #object[java.time.YearMonth 0x652ac30f "2020-01"])
然后获取每个 YearMonth 的开始和结束日期:
(defn start-end [^java.time.YearMonth ym]
[(.atDay ym 1) (.atEndOfMonth ym)])
;;=> ([#object[java.time.LocalDate 0xe880abb "2019-10-01"]
;; #object[java.time.LocalDate 0x54aadf50 "2019-10-31"]]
;; [#object[java.time.LocalDate 0x14c1b42d "2019-11-01"]
;; #object[java.time.LocalDate 0x32d0a22c "2019-11-30"]])
现在,根据您输入的日期将其包装在范围函数中:
(defn day-range [[mm1 dd1 yyyy1] [mm2 dd2 yyyy2]]
(let [start (java.time.LocalDate/of yyyy1 mm1 dd1)
end (java.time.LocalDate/of yyyy2 mm2 dd2)
internal (->> [mm1 yyyy1]
by-month
(mapcat start-end)
(drop 1)
(take-while #(neg? (compare % end))))]
(partition 2 `(~start ~@internal ~end))))
user> (day-range [10 20 2019] [12 20 2019])
;;=> ((#object[java.time.LocalDate 0x6a8f92f2 "2019-10-20"]
;; #object[java.time.LocalDate 0x10135df3 "2019-10-31"])
;; (#object[java.time.LocalDate 0x576bcff7 "2019-11-01"]
;; #object[java.time.LocalDate 0x7b5ed908 "2019-11-30"])
;; (#object[java.time.LocalDate 0x6b2117a9 "2019-12-01"]
;; #object[java.time.LocalDate 0x57bf0864 "2019-12-20"]))
现在您可以根据需要对每个开始-结束对进行后处理:
(map (fn [[^java.time.LocalDate start ^java.time.LocalDate end]]
(let [fmt (java.time.format.DateTimeFormatter/ofPattern "MM/dd/yyyy")]
[(.format start fmt) (.format end fmt)]))
(day-range [10 20 2019] [12 20 2019]))
;;=> (["10/20/2019" "10/31/2019"]
;; ["11/01/2019" "11/30/2019"]
;; ["12/01/2019" "12/20/2019"])
另一种方法是按天迭代,然后按[月年]分区,然后收集first-last:
(defn ranges [[mm1 dd1 yyyy1] [mm2 dd2 yyyy2]]
(let [start (java.time.LocalDate/of yyyy1 mm1 dd1)
end (java.time.LocalDate/of yyyy2 mm2 dd2)]
(->> start
(iterate (fn [^java.time.LocalDate curr] (.plusDays curr 1)))
(take-while (fn [^java.time.LocalDate dt] (not (pos? (compare dt end)))))
(partition-by (fn [^java.time.LocalDate dt] [(.getMonthValue dt) (.getYear dt)]))
(map (juxt first last)))))
user> (ranges [10 20 2019] [12 20 2019])
;;=> ([#object[java.time.LocalDate 0x383f6a9e "2019-10-20"]
;; #object[java.time.LocalDate 0x2ca14c39 "2019-10-31"]]
;; [#object[java.time.LocalDate 0x74d1974 "2019-11-01"]
;; #object[java.time.LocalDate 0x5f6c16cc "2019-11-30"]]
;; [#object[java.time.LocalDate 0x74f63a42 "2019-12-01"]
;; #object[java.time.LocalDate 0x4b90c388 "2019-12-20"]])
这会产生一些不需要的中间值,但也为您提供了一种根据需要拆分范围的方法。
直 Java 互操作是这里的方式:
(let [start-ld (LocalDate/parse "2019-10-20")
start-bom (.with start-ld (TemporalAdjusters/firstDayOfMonth))
start-eom (.with start-ld (TemporalAdjusters/lastDayOfMonth))]
start-bom => #object[java.time.LocalDate 0x1a69aaa8 "2019-10-01"]
start-eom => #object[java.time.LocalDate 0x329970b5 "2019-10-31"]
您可以像这样递增月份:
next-bom (.plusMonths start-bom 1)
得到
next-bom => #object[java.time.LocalDate 0x21ced418 "2019-11-01"]
然后你就可以写一个循环了
您可以使用 java-time
:
(refer-clojure :exclude [range iterate format max min])
(use 'java-time)
(->> (iterate plus (local-date 2019 10 20) (days 1))
(take-while #(before? % (local-date 2019 12 21)))
(partition-by month)
(map (fn [dates] [(first dates) (last dates)])))
输出:
([#object[java.time.LocalDate 0x26c16faf "2019-10-20"]
#object[java.time.LocalDate 0x4113c834 "2019-10-31"]]
[#object[java.time.LocalDate 0x7d0a5212 "2019-11-01"]
#object[java.time.LocalDate 0x249fe02f "2019-11-30"]]
[#object[java.time.LocalDate 0x7345f070 "2019-12-01"]
#object[java.time.LocalDate 0x26d66577 "2019-12-20"]])
想要将日期范围分成每月的块。
示例输入 - [10/20/2019 - 12/20/2019]
示例输出 - { [10/20/2019 10/31/2019] [11/01/2019 11/30/2019] [12/012019 12/20/2019] }
谢谢
这是您可以执行的操作的简单草稿(使用 java 互操作,没有外部库):
首先让我们按月进行迭代,从指定的月份开始:
(defn by-month [[mm yyyy]]
(iterate #(.plusMonths % 1)
(java.time.YearMonth/of yyyy mm)))
user> (take 4 (by-month [10 2019]))
;;=> (#object[java.time.YearMonth 0x62fc8302 "2019-10"]
;; #object[java.time.YearMonth 0x1a7bc211 "2019-11"]
;; #object[java.time.YearMonth 0x6a466e83 "2019-12"]
;; #object[java.time.YearMonth 0x652ac30f "2020-01"])
然后获取每个 YearMonth 的开始和结束日期:
(defn start-end [^java.time.YearMonth ym]
[(.atDay ym 1) (.atEndOfMonth ym)])
;;=> ([#object[java.time.LocalDate 0xe880abb "2019-10-01"]
;; #object[java.time.LocalDate 0x54aadf50 "2019-10-31"]]
;; [#object[java.time.LocalDate 0x14c1b42d "2019-11-01"]
;; #object[java.time.LocalDate 0x32d0a22c "2019-11-30"]])
现在,根据您输入的日期将其包装在范围函数中:
(defn day-range [[mm1 dd1 yyyy1] [mm2 dd2 yyyy2]]
(let [start (java.time.LocalDate/of yyyy1 mm1 dd1)
end (java.time.LocalDate/of yyyy2 mm2 dd2)
internal (->> [mm1 yyyy1]
by-month
(mapcat start-end)
(drop 1)
(take-while #(neg? (compare % end))))]
(partition 2 `(~start ~@internal ~end))))
user> (day-range [10 20 2019] [12 20 2019])
;;=> ((#object[java.time.LocalDate 0x6a8f92f2 "2019-10-20"]
;; #object[java.time.LocalDate 0x10135df3 "2019-10-31"])
;; (#object[java.time.LocalDate 0x576bcff7 "2019-11-01"]
;; #object[java.time.LocalDate 0x7b5ed908 "2019-11-30"])
;; (#object[java.time.LocalDate 0x6b2117a9 "2019-12-01"]
;; #object[java.time.LocalDate 0x57bf0864 "2019-12-20"]))
现在您可以根据需要对每个开始-结束对进行后处理:
(map (fn [[^java.time.LocalDate start ^java.time.LocalDate end]]
(let [fmt (java.time.format.DateTimeFormatter/ofPattern "MM/dd/yyyy")]
[(.format start fmt) (.format end fmt)]))
(day-range [10 20 2019] [12 20 2019]))
;;=> (["10/20/2019" "10/31/2019"]
;; ["11/01/2019" "11/30/2019"]
;; ["12/01/2019" "12/20/2019"])
另一种方法是按天迭代,然后按[月年]分区,然后收集first-last:
(defn ranges [[mm1 dd1 yyyy1] [mm2 dd2 yyyy2]]
(let [start (java.time.LocalDate/of yyyy1 mm1 dd1)
end (java.time.LocalDate/of yyyy2 mm2 dd2)]
(->> start
(iterate (fn [^java.time.LocalDate curr] (.plusDays curr 1)))
(take-while (fn [^java.time.LocalDate dt] (not (pos? (compare dt end)))))
(partition-by (fn [^java.time.LocalDate dt] [(.getMonthValue dt) (.getYear dt)]))
(map (juxt first last)))))
user> (ranges [10 20 2019] [12 20 2019])
;;=> ([#object[java.time.LocalDate 0x383f6a9e "2019-10-20"]
;; #object[java.time.LocalDate 0x2ca14c39 "2019-10-31"]]
;; [#object[java.time.LocalDate 0x74d1974 "2019-11-01"]
;; #object[java.time.LocalDate 0x5f6c16cc "2019-11-30"]]
;; [#object[java.time.LocalDate 0x74f63a42 "2019-12-01"]
;; #object[java.time.LocalDate 0x4b90c388 "2019-12-20"]])
这会产生一些不需要的中间值,但也为您提供了一种根据需要拆分范围的方法。
直 Java 互操作是这里的方式:
(let [start-ld (LocalDate/parse "2019-10-20")
start-bom (.with start-ld (TemporalAdjusters/firstDayOfMonth))
start-eom (.with start-ld (TemporalAdjusters/lastDayOfMonth))]
start-bom => #object[java.time.LocalDate 0x1a69aaa8 "2019-10-01"]
start-eom => #object[java.time.LocalDate 0x329970b5 "2019-10-31"]
您可以像这样递增月份:
next-bom (.plusMonths start-bom 1)
得到
next-bom => #object[java.time.LocalDate 0x21ced418 "2019-11-01"]
然后你就可以写一个循环了
您可以使用 java-time
:
(refer-clojure :exclude [range iterate format max min])
(use 'java-time)
(->> (iterate plus (local-date 2019 10 20) (days 1))
(take-while #(before? % (local-date 2019 12 21)))
(partition-by month)
(map (fn [dates] [(first dates) (last dates)])))
输出:
([#object[java.time.LocalDate 0x26c16faf "2019-10-20"]
#object[java.time.LocalDate 0x4113c834 "2019-10-31"]]
[#object[java.time.LocalDate 0x7d0a5212 "2019-11-01"]
#object[java.time.LocalDate 0x249fe02f "2019-11-30"]]
[#object[java.time.LocalDate 0x7345f070 "2019-12-01"]
#object[java.time.LocalDate 0x26d66577 "2019-12-20"]])