Skip to content

Commit b63f94a

Browse files
committed
add rhombus/date
1 parent a244444 commit b63f94a

File tree

5 files changed

+928
-1
lines changed

5 files changed

+928
-1
lines changed

rhombus-lib/rhombus/date.rhm

Lines changed: 338 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,338 @@
1+
#lang rhombus/static
2+
import:
3+
lib("racket/base.rkt") as rkt
4+
lib("racket/date.rkt") as rkt_date
5+
6+
export:
7+
Time
8+
Date
9+
DateTime
10+
ZonedDateTime
11+
Format
12+
13+
// for referencing in annotatons with a `date.` prefix:
14+
namespace date:
15+
export:
16+
Time
17+
Date
18+
DateTime
19+
ZonedDateTime
20+
Format
21+
22+
fun rhm_to_string(s): to_string(s)
23+
alias 'rhombus_compare_to': 'compare_to'
24+
25+
fun str_2d(n) :~ String:
26+
str.d(n, ~width: 2, ~pad: Char"0", ~align: #'right)
27+
28+
fun date_string(year, month, day, ~iso = #false) :~ String:
29+
(if iso
30+
| str.d(year, ~min_width: 4, ~pad: Char"0", ~align: #'right, ~sign_align: #'left)
31+
| str.d(year))
32+
++ "-" ++ str_2d(month) ++ "-" ++ str_2d(day)
33+
34+
fun time_string(hour, minute, second, nanosecond):
35+
str_2d(hour) ++ ":" ++ str_2d(minute) ++ ":" ++ str_2d(second)
36+
++ (if nanosecond .= 0
37+
| ""
38+
| let s = str.d(nanosecond, ~width: 9, ~pad: Char"0", ~align: #'right)
39+
"." ++ (recur loop (i = s.length()):
40+
if s[i-1] == Char"0" && s[i-2] == Char"0" && s[i-3] == Char"0"
41+
| loop(i - 3)
42+
| s.substring(0, i)))
43+
44+
fun time_zone_string(time_zone_offset, sep :~ String):
45+
let offset = math.abs(time_zone_offset)
46+
let h = offset div 3600
47+
let m = (offset mod 3600) div 60
48+
(if time_zone_offset < 0 | "-" | "+")
49+
++ str_2d(h)
50+
++ sep ++ str_2d(m)
51+
52+
enum Format:
53+
rfc2822
54+
rfc3339
55+
iso8601
56+
american
57+
european
58+
julian
59+
60+
def default_format = #'rfc3339
61+
62+
class ZonedDateTime(~year: year :: Int,
63+
~month: month :: Int.in(1, 12),
64+
~day: day :: Int.in(1, 31),
65+
~hour: hour :: Int.in(0, 23) = 0,
66+
~minute: minute :: Int.in(0, 59) = 0,
67+
~second: second :: Int.in(0, 60) = 0,
68+
~nanosecond: nanosecond :: Int.in(0, 999_999_999) = 0,
69+
~week_day: week_day :: Int.in(0, 6) = 0,
70+
~year_day: year_day :: Int.in(0, 365) = 0,
71+
~is_dst: is_dst :: Boolean = #false,
72+
~time_zone_offset: time_zone_offset :: Int = 0,
73+
~time_zone_name: time_zone_name :: String = "UTC",
74+
private mutable _seconds = #false,
75+
private _handle = rkt.#{date*}(second, minute, hour, day, month, year,
76+
week_day, year_day, is_dst, time_zone_offset,
77+
nanosecond, time_zone_name)):
78+
internal _ZonedDateTime
79+
80+
property handle: _handle
81+
82+
private implements Printable
83+
private override method describe(mode, recur):
84+
if mode == #'text
85+
| to_string(~show_time: #true)
86+
| recur(this, ~as: #'super)
87+
88+
private implements Comparable
89+
private override method compare_to(other :~ ZonedDateTime):
90+
to_seconds() rhombus_compare_to other.to_seconds()
91+
92+
method to_string(~format: format :: date.Format = default_format,
93+
~show_time = #true,
94+
~show_nanosecond = #false,
95+
~show_time_zone = #true) :~ String:
96+
match format
97+
| #'iso8601 || #'rfc3339:
98+
if show_time
99+
| date_string(year, month, day, ~iso: format == #'iso8601)
100+
++ (if format == #'rfc3339 | " " | "T")
101+
++ time_string(hour, minute, second, if show_nanosecond | nanosecond | 0)
102+
++ (if show_time_zone
103+
| (if time_zone_offset == 0
104+
| "Z"
105+
| time_zone_string(time_zone_offset, ":"))
106+
| "")
107+
| date_string(year, month, day, ~iso: #true)
108+
| ~else:
109+
parameterize { rkt_date.#{date-display-format}: match format
110+
| #'european:
111+
#'irish
112+
| ~else:
113+
format }:
114+
rhm_to_string(rkt_date.#{date->string}(_handle, show_time))
115+
116+
method to_seconds() :~ Real:
117+
if _seconds
118+
| _seconds
119+
| let s = find_seconds(~second: second,
120+
~minute: minute,
121+
~hour: hour,
122+
~day: day,
123+
~month: month,
124+
~year: year,
125+
~local: #false)
126+
let s = s + time_zone_offset
127+
let s: if nanosecond .= 0
128+
| s
129+
| s + nanosecond / 1e9
130+
_seconds := s
131+
s
132+
133+
export:
134+
now
135+
from_seconds
136+
find_seconds
137+
138+
fun now(~local = #true) :~ ZonedDateTime:
139+
from_seconds(system.milliseconds() / 1000.0,
140+
~local: local)
141+
142+
fun from_seconds(secs :: Real,
143+
~local: local = #true) :~ ZonedDateTime:
144+
let d = rkt.#{seconds->date}(secs, local)
145+
_ZonedDateTime(~nanosecond: rkt.#{date*-nanosecond}(d),
146+
~second: rkt.#{date-second}(d),
147+
~minute: rkt.#{date-minute}(d),
148+
~hour: rkt.#{date-hour}(d),
149+
~day: rkt.#{date-day}(d),
150+
~month: rkt.#{date-month}(d),
151+
~year: rkt.#{date-year}(d),
152+
~week_day: rkt.#{date-week-day}(d),
153+
~year_day: rkt.#{date-year-day}(d),
154+
~is_dst: rkt.#{date-dst?}(d),
155+
~time_zone_offset: rkt.#{date-time-zone-offset}(d),
156+
~time_zone_name: rkt.#{date*-time-zone-name}(d),
157+
secs,
158+
d)
159+
160+
fun find_seconds(~second: second :: Int.in(0, 60) = 0,
161+
~minute: minute :: Int.in(0, 59) = 0,
162+
~hour: hour :: Int.in(0, 23) = 0,
163+
~day: day :: Int.in(1, 31),
164+
~month: month :: Int.in(1, 12),
165+
~year: year :: Int,
166+
~local: local = #true) :~ Int:
167+
rkt_date.#{find-seconds}(second, minute, hour, day, month, year, local)
168+
169+
class DateTime(~year: year :: Int,
170+
~month: month :: Int.in(1, 12),
171+
~day: day :: Int.in(1, 31),
172+
~hour: hour :: Int.in(0, 23) = 0,
173+
~minute: minute :: Int.in(0, 59) = 0,
174+
~second: second :: Int.in(0, 60) = 0,
175+
~nanosecond: nanosecond :: Int.in(0, 999_999_999) = 0):
176+
177+
private implements Printable
178+
private override method describe(mode, recur):
179+
if mode == #'text
180+
| to_zoned().to_string()
181+
| recur(this, ~as: #'super)
182+
183+
private implements Comparable
184+
private override method compare_to(other :~ DateTime):
185+
if year == other.year
186+
| if month == other.month
187+
| if day == other.day
188+
| if hour == other.hour
189+
| if minute == other.minute
190+
| if second == other.second
191+
| nanosecond rhombus_compare_to other.nanosecond
192+
| second - other.second
193+
| minute - other.minute
194+
| hour - other.hour
195+
| hour - other.hour
196+
| month - other.month
197+
| year - other.year
198+
199+
method to_seconds() :~ Real:
200+
let s = ZonedDateTime.find_seconds(~second: second,
201+
~minute: minute,
202+
~hour: hour,
203+
~day: day,
204+
~month: month,
205+
~year: year,
206+
~local: #false)
207+
if nanosecond .= 0
208+
| s
209+
| s + nanosecond / 1e9
210+
211+
method to_zoned() :~ ZonedDateTime:
212+
ZonedDateTime.from_seconds(to_seconds(), ~local: #false)
213+
214+
method to_string(~format: format :: date.Format = default_format,
215+
~show_time = #true,
216+
~show_nanosecond = #false) :~ String:
217+
match format
218+
| #'rfc3339 || #'iso8601:
219+
if show_time
220+
| date_string(year, month, day)
221+
++ (if format == #'iso8601 | "T" | " ")
222+
++ time_string(hour, minute, second, if show_nanosecond | nanosecond | 0)
223+
| date_string(year, month, day)
224+
| ~else:
225+
to_zoned().to_string(~format: format,
226+
~show_time: show_time,
227+
~show_nanosecond: show_nanosecond,
228+
~show_time_zone: #false)
229+
230+
export:
231+
now
232+
from_seconds
233+
234+
fun now(~local = #true) :~ DateTime:
235+
from_seconds(system.milliseconds() / 1000.0,
236+
~local: local)
237+
238+
fun from_seconds(secs :: Real,
239+
~local = #true) :~ DateTime:
240+
let d = rkt.#{seconds->date}(secs, local)
241+
DateTime(~nanosecond: rkt.#{date*-nanosecond}(d),
242+
~second: rkt.#{date-second}(d),
243+
~minute: rkt.#{date-minute}(d),
244+
~hour: rkt.#{date-hour}(d),
245+
~day: rkt.#{date-day}(d),
246+
~month: rkt.#{date-month}(d),
247+
~year: rkt.#{date-year}(d))
248+
249+
250+
class Date(~year: year :: Int,
251+
~month: month :: Int.in(1, 12),
252+
~day: day :: Int.in(1, 31)):
253+
254+
private implements Printable
255+
private override method describe(mode, recur):
256+
if mode == #'text
257+
| to_string()
258+
| recur(this, ~as: #'super)
259+
260+
private implements Comparable
261+
private override method compare_to(other :~ Date):
262+
if year == other.year
263+
| if month == other.month
264+
| day - other.day
265+
| month - other.month
266+
| year - other.year
267+
268+
method to_datetime(~time: time :: date.Time = Time()) :~ DateTime:
269+
DateTime(~day: day, ~month: month, ~year: year,
270+
~nanosecond: time.nanosecond,
271+
~second: time.second,
272+
~minute: time.minute,
273+
~hour: time.hour)
274+
275+
method to_seconds() :~ Real:
276+
to_datetime().to_seconds()
277+
278+
method to_string(~format: format :: date.Format = default_format) :~ String:
279+
match format
280+
| #'rfc3339 || #'iso8601:
281+
date_string(year, month, day)
282+
| ~else:
283+
to_datetime().to_string(~format: format, ~show_time: #false)
284+
285+
export:
286+
now
287+
from_seconds
288+
289+
fun now(~local = #true) :~ Date:
290+
from_seconds(system.seconds(),
291+
~local: local)
292+
293+
fun from_seconds(secs :: Real,
294+
~local = #true) :~ Date:
295+
let d = rkt.#{seconds->date}(secs, local)
296+
Date(~day: rkt.#{date-day}(d),
297+
~month: rkt.#{date-month}(d),
298+
~year: rkt.#{date-year}(d))
299+
300+
class Time(~hour: hour :: Int.in(0, 23) = 0,
301+
~minute: minute :: Int.in(0, 59) = 0,
302+
~second: second :: Int.in(0, 60) = 0,
303+
~nanosecond: nanosecond :: Int.in(0, 999_999_999) = 0):
304+
305+
private implements Printable
306+
private override method describe(mode, recur):
307+
if mode == #'text
308+
| to_string()
309+
| recur(this, ~as: #'super)
310+
311+
private implements Comparable
312+
private override method compare_to(other :~ Time):
313+
if hour == other.hour
314+
| if minute == other.minute
315+
| if second == other.second
316+
| nanosecond rhombus_compare_to other.nanosecond
317+
| second - other.second
318+
| minute - other.minute
319+
| hour - other.hour
320+
321+
method to_string(~show_nanosecond = #false) :~ String:
322+
time_string(hour, minute, second, if show_nanosecond | nanosecond | 0)
323+
324+
export:
325+
now
326+
from_seconds
327+
328+
fun now(~local = #true) :~ Time:
329+
from_seconds(system.milliseconds() / 1000.0,
330+
~local: local)
331+
332+
fun from_seconds(secs :: Real,
333+
~local = #true) :~ Time:
334+
let d = rkt.#{seconds->date}(secs, local)
335+
Time(~nanosecond: rkt.#{date*-nanosecond}(d),
336+
~second: rkt.#{date-second}(d),
337+
~minute: rkt.#{date-minute}(d),
338+
~hour: rkt.#{date-hour}(d))

0 commit comments

Comments
 (0)