Skip to content

Commit 3095526

Browse files
committed
Remove %zone slot from <time>
There's no need for each `<time>` instance to contain a zone and since `<time>` objects may be created frequently we don't want unnecessary slots taking up space. Instead, the zone should always be specified when displaying times. `compose-time` and `time-components` now accept a `zone:` keyword argument with which to interpret the components. Also marked `test-us-eastern-sanity-check` as expected to fail for now.
1 parent 2021d6d commit 3095526

7 files changed

+143
-236
lines changed

sources/formatting-test.dylan

+9-9
Original file line numberDiff line numberDiff line change
@@ -113,20 +113,20 @@ define test test-rfc3339-format ()
113113
// Verify that negative zone offset overflow displays as previous day.
114114
assert-equal("1969-12-31T19:00:00.000000-05:00",
115115
with-output-to-string (s)
116-
let t = time-in-zone($epoch, make(<naive-zone>,
117-
offset-seconds: -5 * 60 * 60,
118-
name: "x"));
119-
format-time(s, $rfc3339-microseconds, t)
116+
format-time(s, $rfc3339-microseconds, $epoch,
117+
zone: make(<naive-zone>,
118+
offset-seconds: -5 * 60 * 60,
119+
name: "x"))
120120
end);
121121
// Verify that positive zone offset overflow displays as next day.
122122
// (Throw in a test for leap day Feb 29 because why not.)
123123
assert-equal("2020-02-29T00:00:00.000000+05:00",
124124
with-output-to-string (s)
125-
let t = time-in-zone(compose-time(2020, $february, 28, 19, 0, 0, 0, $utc),
126-
make(<naive-zone>,
127-
offset-seconds: 5 * 60 * 60,
128-
name: "x"));
129-
format-time(s, $rfc3339-microseconds, t)
125+
let t = compose-time(2020, $february, 28, 19, 0, 0, 0);
126+
format-time(s, $rfc3339-microseconds, t,
127+
zone: make(<naive-zone>,
128+
offset-seconds: 5 * 60 * 60,
129+
name: "x"))
130130
end);
131131
end test;
132132

sources/formatting.dylan

+55-47
Original file line numberDiff line numberDiff line change
@@ -72,54 +72,53 @@ define function parse-time-format (descriptor :: <string>) => (_ :: <sequence>)
7272
end function;
7373

7474
// Each value is a pair of #(time-component . formatter-function) where
75-
// time-component is the index into the return values list of the
76-
// time-components function. 0 = year, 1 = month, etc
75+
// time-component is a keyword that matches the select statement used in
76+
// format-time.
7777
//
7878
// TODO: BC/AD, BCE/CE (see ISO 8601)
79-
// TODO: make this extensible
8079
define table $time-format-map :: <string-table>
81-
= { "yyyy" => pair(0, curry(format-ndigit-int, 4)),
82-
"yy" => pair(0, curry(format-ndigit-int-mod, 2, 100)),
83-
"mm" => pair(1, method (stream, month)
84-
format-ndigit-int(2, stream, month.month-number)
85-
end),
86-
"mon" => pair(1, format-short-month-name),
87-
"month" => pair(1, format-long-month-name),
88-
"dd" => pair(2, curry(format-ndigit-int, 2)),
89-
"HH" => pair(3, curry(format-ndigit-int, 2)),
90-
"hh" => pair(3, format-hour-12),
91-
"am" => pair(3, format-lowercase-am-pm),
92-
"pm" => pair(3, format-lowercase-am-pm),
93-
"AM" => pair(3, format-uppercase-am-pm),
94-
"PM" => pair(3, format-uppercase-am-pm),
95-
"MM" => pair(4, curry(format-ndigit-int, 2)),
96-
"SS" => pair(5, curry(format-ndigit-int, 2)),
97-
"millis" => pair(6, curry(format-ndigit-int-mod, 3, 1000)),
98-
"micros" => pair(6, curry(format-ndigit-int-mod, 6, 1_000_000)),
99-
"nanos" => pair(6, curry(format-ndigit-int-mod, 9, 1_000_000_000)),
80+
= { "yyyy" => pair(#"year", curry(format-ndigit-int, 4)),
81+
"yy" => pair(#"year", curry(format-ndigit-int-mod, 2, 100)),
82+
"mm" => pair(#"month", method (stream, month)
83+
format-ndigit-int(2, stream, month.month-number)
84+
end),
85+
"mon" => pair(#"month", format-short-month-name),
86+
"month" => pair(#"month", format-long-month-name),
87+
"dd" => pair(#"day-of-month", curry(format-ndigit-int, 2)),
88+
"HH" => pair(#"hour", curry(format-ndigit-int, 2)),
89+
"hh" => pair(#"hour", format-hour-12),
90+
"am" => pair(#"hour", format-lowercase-am-pm),
91+
"pm" => pair(#"hour", format-lowercase-am-pm),
92+
"AM" => pair(#"hour", format-uppercase-am-pm),
93+
"PM" => pair(#"hour", format-uppercase-am-pm),
94+
"MM" => pair(#"minute", curry(format-ndigit-int, 2)),
95+
"SS" => pair(#"second", curry(format-ndigit-int, 2)),
96+
"millis" => pair(#"nanosecond", curry(format-ndigit-int-mod, 3, 1000)),
97+
"micros" => pair(#"nanosecond", curry(format-ndigit-int-mod, 6, 1_000_000)),
98+
"nanos" => pair(#"nanosecond", curry(format-ndigit-int-mod, 9, 1_000_000_000)),
10099
// f = fractional seconds with minimum digits. fN outputs exactly N digits.
101-
"f" => pair(6, format-nanos-with-minimum-digits),
100+
"f" => pair(#"nanosecond", format-nanos-with-minimum-digits),
102101
// Not sure if some of these will be used, but might as well be complete.
103-
"f1" => pair(6, curry(format-ndigit-int-mod, 1, 10)),
104-
"f2" => pair(6, curry(format-ndigit-int-mod, 2, 100)),
105-
"f3" => pair(6, curry(format-ndigit-int-mod, 3, 1000)),
106-
"f4" => pair(6, curry(format-ndigit-int-mod, 4, 10_000)),
107-
"f5" => pair(6, curry(format-ndigit-int-mod, 5, 100_000)),
108-
"f6" => pair(6, curry(format-ndigit-int-mod, 6, 1_000_000)),
109-
"f7" => pair(6, curry(format-ndigit-int-mod, 7, 10_000_000)),
110-
"f8" => pair(6, curry(format-ndigit-int-mod, 8, 100_000_000)),
111-
"f9" => pair(6, curry(format-ndigit-int-mod, 9, 1_000_000_000)),
112-
113-
"zone" => pair(7, format-zone-name), // UTC, PST, etc
102+
"f1" => pair(#"nanosecond", curry(format-ndigit-int-mod, 1, 10)),
103+
"f2" => pair(#"nanosecond", curry(format-ndigit-int-mod, 2, 100)),
104+
"f3" => pair(#"nanosecond", curry(format-ndigit-int-mod, 3, 1000)),
105+
"f4" => pair(#"nanosecond", curry(format-ndigit-int-mod, 4, 10_000)),
106+
"f5" => pair(#"nanosecond", curry(format-ndigit-int-mod, 5, 100_000)),
107+
"f6" => pair(#"nanosecond", curry(format-ndigit-int-mod, 6, 1_000_000)),
108+
"f7" => pair(#"nanosecond", curry(format-ndigit-int-mod, 7, 10_000_000)),
109+
"f8" => pair(#"nanosecond", curry(format-ndigit-int-mod, 8, 100_000_000)),
110+
"f9" => pair(#"nanosecond", curry(format-ndigit-int-mod, 9, 1_000_000_000)),
111+
112+
"zone" => pair(#"zone", format-zone-name), // UTC, PST, etc
114113

115114
// TODO: these fail for <aware-zone>s. Need some refactoring to make sure the
116115
// offset is found for the correct time.
117-
"offset" => pair(7, rcurry(format-zone-offset, colon?: #f, utc-name: #f)), // +0000
118-
"offset:" => pair(7, rcurry(format-zone-offset, colon?: #t, utc-name: #f)), // +00:00
119-
"offset:Z" => pair(7, rcurry(format-zone-offset, colon?: #t, utc-name: "Z")), // Z or +02:00
116+
"offset" => pair(#"zone", rcurry(format-zone-offset, colon?: #f, utc-name: #f)), // +0000
117+
"offset:" => pair(#"zone", rcurry(format-zone-offset, colon?: #t, utc-name: #f)), // +00:00
118+
"offset:Z" => pair(#"zone", rcurry(format-zone-offset, colon?: #t, utc-name: "Z")), // Z or +02:00
120119

121-
"day" => pair(8, format-short-weekday),
122-
"weekday" => pair(8, format-long-weekday),
120+
"day" => pair(#"day-of-week", format-short-weekday),
121+
"weekday" => pair(#"day-of-wook", format-long-weekday),
123122
};
124123

125124
define function format-nanos-with-minimum-digits
@@ -256,23 +255,32 @@ define /* inline */ method format-time
256255
format-time(stream, parse-time-format(fmt), time, zone: zone);
257256
end method;
258257

259-
define /* inline */ method format-time
258+
define method format-time
260259
(stream :: <stream>, fmt :: <sequence>, time :: <time>, #key zone :: <zone>?)
261260
=> ()
262-
// I'm assuming that v is stack allocated. Verify.
263-
let (#rest v) = time-components(time);
264-
if (zone)
265-
v[7] := zone;
266-
end;
261+
let zone :: <zone> = zone | $utc;
262+
let (year, month, day-of-month, hour, minute, second, nanosecond, day-of-week)
263+
= time-components(time, zone: zone);
267264
for (item in fmt)
268265
select (item by instance?)
269266
<string>
270267
=> write(stream, item);
271268
<pair>
272269
=> begin
273-
let index :: <integer> = item.head;
270+
let value
271+
= select (item.head)
272+
#"year" => year;
273+
#"month" => month;
274+
#"day-of-month" => day-of-month;
275+
#"hour" => hour;
276+
#"minute" => minute;
277+
#"second" => second;
278+
#"nanosecond" => nanosecond;
279+
#"day-of-week" => day-of-week;
280+
#"zone" => zone;
281+
end;
274282
let formatter :: <function> = item.tail;
275-
formatter(stream, v[index]);
283+
formatter(stream, value);
276284
end;
277285
otherwise => time-error("invalid time format element: %=", item);
278286
end;

sources/library.dylan

+1-11
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,7 @@ define module time
2525
// Time
2626
<time>,
2727
time-now,
28-
time-components, // returns the following nine values, in order
29-
time-year,
30-
time-month,
31-
time-day-of-month,
32-
time-hour,
33-
time-minute,
34-
time-second,
35-
time-nanosecond,
36-
time-zone,
37-
time-day-of-week,
28+
time-components, // => year, month, day, hour, minute, second, nanosecond
3829
$epoch,
3930
$minimum-time,
4031
$maximum-time,
@@ -68,7 +59,6 @@ define module time
6859
// Conversions
6960
compose-time, // make a <time> from its components
7061
time-components, // break a <time> into its components
71-
time-in-zone,
7262
parse-time, // TODO: $iso-8601-format etc?
7363
parse-duration,
7464
parse-day, // TODO: not sure about this

sources/specification.dylan

+4-13
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,13 @@ Module: time-test-suite
33
define interface-specification-suite time-specification-suite ()
44
// Time
55
sealed instantiable class <time> (<object>);
6-
function time-now(#"key", #"zone") => (<time>);
6+
function time-now() => (<time>);
77
sealed generic function compose-time
8-
(<integer>, <month>, <integer>, <integer>, <integer>, <integer>, <integer>, <zone>)
8+
(<integer>, <month>, <integer>, <integer>, <integer>, <integer>, <integer>, #"key", #"zone")
99
=> (<time>);
1010
sealed generic function time-components
11-
(<time>)
12-
=> (<integer>, <month>, <integer>, <integer>, <integer>, <integer>, <integer>, <zone>, <day>);
13-
sealed generic function time-year (<time>) => (<integer>);
14-
sealed generic function time-month (<time>) => (<month>);
15-
sealed generic function time-day-of-month (<time>) => (<integer>);
16-
sealed generic function time-hour (<time>) => (<integer>);
17-
sealed generic function time-minute (<time>) => (<integer>);
18-
sealed generic function time-second (<time>) => (<integer>);
19-
sealed generic function time-nanosecond (<time>) => (<integer>);
20-
sealed generic function time-zone (<time>) => (<zone>);
21-
sealed generic function time-day-of-week (<time>) => (<day>);
11+
(<time>, #"key", #"zone")
12+
=> (<integer>, <month>, <integer>, <integer>, <integer>, <integer>, <integer>, <day>);
2213
constant $epoch :: <time>;
2314

2415
// Durations

sources/time-test.dylan

+44-39
Original file line numberDiff line numberDiff line change
@@ -6,41 +6,50 @@ define test test-current-time ()
66
assert-true(t.%nanoseconds >= 0 & t.%nanoseconds < 1_000_000_000 * 60 * 60 * 24);
77
end test;
88

9-
define constant $components-test-cases
10-
= list(list(list(1970, $january, 1, 0, 0, 0, 0, $utc, $monday),
11-
list(0, 0)),
12-
list(list(1970, $january, 2, 1, 1, 1, 1, $utc, $monday),
13-
list(1, 3_661_000_000_001)),
14-
list(list(1969, $december, 31, 0, 0, 0, 1, $utc, $monday),
15-
list(-1, 1)),
16-
list(list(1969, $december, 31, 23, 59, 59, 999_999_999, $utc, $monday),
17-
list(-1, 86_399_999_999_999)),
18-
list(list(2020, $october, 18, 0, 0, 0, 0, $utc, $monday),
19-
list(18553, 0)));
20-
9+
// TODO: test non-UTC zone
2110
define test test-compose-time ()
22-
for (tc in $components-test-cases)
23-
let (args, want) = apply(values, tc);
24-
let args = copy-sequence(args, end: args.size - 1); // remove the day
25-
let t = apply(compose-time, args);
26-
let (want-days, want-nanos) = apply(values, want);
27-
assert-equal(t.%days, want-days,
28-
format-to-string("for %= got days %=, want %=",
29-
args, t.%days, want-days));
30-
assert-equal(t.%nanoseconds, want-nanos,
31-
format-to-string("for %= got nanoseconds %=, want %=",
32-
args, t.%nanoseconds, want-nanos));
33-
end;
11+
let t1 = compose-time(1970, $january, 1, 0, 0, 0, 0, zone: $utc);
12+
assert-equal(0, t1.%days);
13+
assert-equal(0, t1.%nanoseconds);
14+
15+
let t2 = compose-time(1970, $january, 2, 1, 1, 1, 1, zone: $utc);
16+
assert-equal(1, t2.%days);
17+
assert-equal(3_661_000_000_001, t2.%nanoseconds);
18+
19+
let t3 = compose-time(1969, $december, 31, 0, 0, 0, 1, zone: $utc);
20+
assert-equal(-1, t3.%days);
21+
assert-equal(1, t3.%nanoseconds);
22+
23+
let t4 = compose-time(1969, $december, 31, 23, 59, 59, 999_999_999, zone: $utc);
24+
assert-equal(-1, t4.%days);
25+
assert-equal(86_399_999_999_999, t4.%nanoseconds);
26+
27+
let t5 = compose-time(2020, $october, 18, 0, 0, 0, 0, zone: $utc);
28+
assert-equal(18553, t5.%days);
29+
assert-equal(0, t5.%nanoseconds);
3430
end test;
3531

32+
// TODO: test non-UTC zone
3633
define test test-time-components ()
37-
for (tc in $components-test-cases)
38-
let (args, want) = apply(values, reverse(tc));
39-
let t = make(<time>, days: args[0], nanoseconds: args[1]);
40-
let (#rest got) = time-components(t);
41-
assert-equal(got, want,
42-
format-to-string("for %= got %=, want %=", args, got, want));
43-
end;
34+
let t1 = make(<time>, days: 0, nanoseconds: 0);
35+
let (#rest c1) = time-components(t1, zone: $utc);
36+
assert-equal(vector(1970, $january, 1, 0, 0, 0, 0, $monday), c1);
37+
38+
let t2 = make(<time>, days: 1, nanoseconds: 3_661_000_000_001);
39+
let (#rest c2) = time-components(t2, zone: $utc);
40+
assert-equal(vector(1970, $january, 2, 1, 1, 1, 1, $monday), c2);
41+
42+
let t3 = make(<time>, days: -1, nanoseconds: 1);
43+
let (#rest c3) = time-components(t3, zone: $utc);
44+
assert-equal(vector(1969, $december, 31, 0, 0, 0, 1, $monday), c3);
45+
46+
let t4 = make(<time>, days: -1, nanoseconds: 86_399_999_999_999);
47+
let (#rest c4) = time-components(t4, zone: $utc);
48+
assert-equal(vector(1969, $december, 31, 23, 59, 59, 999_999_999, $monday), c4);
49+
50+
let t5 = make(<time>, days: 18553, nanoseconds: 0);
51+
let (#rest c5) = time-components(t5, zone: $utc);
52+
assert-equal(vector(2020, $october, 18, 0, 0, 0, 0, $monday), c5);
4453
end test;
4554

4655

@@ -66,14 +75,9 @@ define test test-time+duration ()
6675
end test;
6776

6877
define test test-time-= ()
78+
// Two times with the same UTC seconds and nanoseconds should be equal.
6979
let t1 = time-now();
7080
assert-equal(t1, make(<time>, days: t1.%days, nanoseconds: t1.%nanoseconds));
71-
72-
// Two times with the same UTC seconds and nanoseconds should be equal
73-
// regardless of zone.
74-
assert-equal(make(<time>, days: 1, nanoseconds: 1, zone: $utc),
75-
make(<time>, days: 1, nanoseconds: 1,
76-
zone: make(<naive-zone>, name: "x", offset-seconds: 5 * 60 * 60)));
7781
end test;
7882

7983
define test test-time-< ()
@@ -87,6 +91,7 @@ end test;
8791

8892
define test test-print-object ()
8993
assert-equal("1970-01-01T00:00:00.0Z", format-to-string("%s", $epoch));
90-
assert-true(regex-search(compile-regex("{<time> 0d 0ns \\+00:00 \\d+}"),
91-
format-to-string("%=", $epoch)));
94+
assert-true(regex-search(compile-regex("{<time> 0d 0ns UTC \\d+}"),
95+
format-to-string("%=", $epoch)),
96+
"%= didn't match the regular expression", $epoch);
9297
end test;

0 commit comments

Comments
 (0)