Skip to content

Instantly share code, notes, and snippets.

@francoisdevlin
Created November 4, 2011 19:04
Show Gist options
  • Save francoisdevlin/93c7e40cc95a486cfc0f to your computer and use it in GitHub Desktop.
Save francoisdevlin/93c7e40cc95a486cfc0f to your computer and use it in GitHub Desktop.
Clojure Date
(ns demo.clojure-date
(:import [java.util Calendar GregorianCalendar TimeZone])
)
;1 - String to parse zulu time. Port from joda.
;2 - Reader Syntax
;3 - Timezone type. It's a sorted map.
(def millisecond 1)
(def seconds (* 1000 millisecond))
(def minute (* 60 seconds))
(def hour (* 60 minute))
(def day (* 24 hour))
(def year (* 365 day))
(def leap-year (* 366 day))
(def leap-year-zero 1968)
(def epoch-year-zero 1970)
(def time-regex
#"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}(Z|(\+|-)\d{2}:\d{2})")
(def normal-month-offset
(sorted-map
1 0,
2 31,
3 59,
4 90,
5 120,
6 151,
7 181,
8 212,
9 243,
10 273,
11 304,
12 334))
(def leap-month-offset
(sorted-map
1 0,
2 31,
3 60,
4 91,
5 121,
6 152,
7 182,
8 213,
9 244,
10 274,
11 305,
12 335))
(defn leap-year-count
"This is a convenience fn to figure out how many leap years have passed."
[y]
(int (/ (- y leap-year-zero) 4)))
(defn get-month-offset-map
[y]
(cond
(zero? (mod y 400)) leap-month-offset
(zero? (mod y 100)) normal-month-offset
(zero? (mod y 4)) leap-month-offset
true normal-month-offset))
(defn zulu
([y mo d h mi s ms] (zulu y mo d h mi s ms 0))
([y mo d h mi s ms offset]
(+
(* (- y epoch-year-zero) year)
(* (leap-year-count y) day)
(* ((cond
(zero? (mod y 400)) leap-month-offset
(zero? (mod y 100)) normal-month-offset
(zero? (mod y 4)) leap-month-offset
true normal-month-offset) mo) day)
(* (dec d) day)
(* h hour)
(* mi minute)
(* s seconds)
(* ms millisecond)
offset)))
(defn parse-zulu [input-s]
(let [[y mo d h mi s ms tzh tzm] (map #(Integer/parseInt %)
(.split input-s "[-:TZ\\.\\+]"))
offset (if (and tzh tzm)
(({\+ -,\- +} (.charAt input-s 23));Semantics are backwards...
(+
(* tzh hour)
(* tzm minute))) 0)]
(zulu y mo d h mi s ms offset)))
(defn long->map
[epoch]
(let [remaining epoch
delta-years (int (/ epoch year))
years (+ delta-years 1970)
remaining (- epoch (* delta-years year))
leap-days (leap-year-count years)
years (if (< remaining (* leap-days day))
(dec years)
years)
remaining (- remaining (* leap-days day))
remaining (if (neg? remaining)
(+ year remaining)
remaining)
month-offset (get-month-offset-map years)
months (first
(last
(take-while (fn [[k v]] (< (* v day) remaining))
month-offset)))
months (if months months 1)
remaining (- remaining (* (month-offset months) day))
day-of-month (int (/ remaining day))
remaining (- remaining (* day day-of-month))
]
{:year years
:month months
:day day-of-month
:ms remaining}))
(defn long->map
[t]
(let [base-cal (doto
(GregorianCalendar. (TimeZone/getTimeZone "UTC"))
(.setTimeInMillis t))]
{:year (.get base-cal Calendar/YEAR)
:month (inc (.get base-cal Calendar/MONTH))
:day (.get base-cal Calendar/DAY_OF_MONTH)
:hour (.get base-cal Calendar/HOUR_OF_DAY)
:minute (.get base-cal Calendar/MINUTE)
:second (.get base-cal Calendar/SECOND)
:millis (.get base-cal Calendar/MILLISECOND)
;:offset (.getRawOffset base-cal)
}))
(defprotocol Dateable
(clj-date* [t]))
(deftype CljDate
[epoch tz-offset]
Object
(toString [this]
(str epoch))
;clojure.lang.IMeta
;(meta [_] _meta)
java.lang.Comparable
(compareTo [this o]
(compare (.epoch this) (.epoch o)))
Dateable
(clj-date* [this] this)
)
(extend java.lang.String
Dateable
{:clj-date* #(CljDate. (parse-zulu %) 0)})
(extend clojure.lang.IPersistentMap
Dateable
{:clj-date* (fn [{y :year
mo :month
d :day
h :hour
mi :minute
s :second
ms :millis
os :offset}]
(CljDate. (zulu y mo d h mi s ms os) os))})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment