-
Notifications
You must be signed in to change notification settings - Fork 0
/
calendar.d
298 lines (277 loc) · 8.42 KB
/
calendar.d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
/*
Folowwing code come from the article "Component programming with ranges"
By H. S. Teoh
http://wiki.dlang.org/Component_programming_with_ranges
https://github.com/quickfur/dcal
*/
/*
* D calendar
*
* An example of how using component-style programming with ranges simplifies a
* complex task into manageable pieces. The task is, given a year, to produce a
* range of lines representing a nicely laid-out calendar of that year.
*
* This example shows how something is complex as calendar layout can be
* written in a clear, readable way that allows individual components to be
* reused.
*/
int counterDateEq = 0;
int counterDateInc = 0;
int counterDateSave = 0;
import std.algorithm;
import std.conv;
import std.datetime;
import std.functional;
import std.range;
import std.stdio : writeln, writefln, stderr;
import std.string;
/**
* Returns: A string containing exactly n spaces.
*/
string spaces(size_t n) pure nothrow {
return std.array.replicate(" ", n);
}
/**
* Returns: A range of dates in a given year.
*/
auto datesInYear(int year) pure {
return Date(year, 1, 1)
.recurrence!((a,n) => a[n-1] + 1.days)
.until!(a => a.year > year);
}
auto datesInYear2(int year) {
static struct DatesYear {
private Date current_;
private Date end_;
this(Date b, Date e) {
current_ = b;
end_ = e;
}
@property bool empty() { counterDateEq++ ; return current_ == end_; }
@property auto front() { return current_; }
void popFront() { counterDateInc++; current_ = current_ + 1.days; }
@property DatesYear save() {
counterDateSave++;
DatesYear copy = this;
copy.current_ = current_;
copy.end_ = end_;
return copy;
}
}
return DatesYear( Date(year, 1, 1), Date(year + 1, 1, 1) );
}
/**
* Convenience template for verifying that a given range is an input range of
* Dates.
*/
template isDateRange(R) {
enum isDateRange = isInputRange!R && is(ElementType!R : Date);
}
static assert(isDateRange!(typeof(datesInYear(1))));
/**
* Chunks an input range by equivalent elements.
*
* This function takes an input range, and splits it up into subranges that
* contain equivalent adjacent elements, where equivalence is defined by having
* the same value of attrFun(e) for each element e.
*
* Note that equivalent elements separated by an intervening non-equivalent
* element will appear in separate subranges; this function only considers
* adjacent equivalence.
*
* This is similar to std.algorithm.group, but the latter can't be used when
* the individual elements in each group must be iterable in the result.
*
* Parameters:
* attrFun = The function for determining equivalence. The return value must
* be comparable using ==.
* r = The range to be chunked.
*
* Returns: A range of ranges in which all elements in a given subrange share
* the same attribute with each other.
*/
int counterChunkByAttrEq = 0;
auto chunkBy(alias attrFun, Range)(Range r)
if (isInputRange!Range && is(typeof(attrFun(ElementType!Range.init) == attrFun(ElementType!Range.init))))
{
alias attr = unaryFun!attrFun;
alias AttrType = typeof(attr(r.front));
static struct Chunk {
private Range r;
private AttrType curAttr;
@property bool empty() {
counterChunkByAttrEq++;
return r.empty || !(curAttr == attr(r.front));
}
@property ElementType!Range front() { return r.front; }
void popFront() {
assert(!r.empty);
r.popFront();
}
}
static struct ChunkBy {
private Range r;
private AttrType lastAttr;
this(Range _r) {
r = _r;
if (!empty)
lastAttr = attr(r.front);
}
@property bool empty() { return r.empty; }
@property auto front() {
assert(!r.empty);
return Chunk(r, lastAttr);
}
void popFront() {
assert(!r.empty);
while (!r.empty && attr(r.front) == lastAttr) {
counterChunkByAttrEq++;
r.popFront();
}
if (!r.empty)
lastAttr = attr(r.front);
}
static if (isForwardRange!Range) {
@property ChunkBy save() {
ChunkBy copy = this;
copy.r = r.save;
return copy;
}
}
}
return ChunkBy(r);
}
///
/**
* Chunks a given input range of dates by month.
* Returns: A range of ranges, each subrange of which contains dates for the
* same month.
*/
auto byMonth(InputRange)(InputRange dates)
if (isDateRange!InputRange)
{
return dates.chunkBy!(a => a.month());
}
/**
* Chunks a given input range of dates by week.
* Returns: A range of ranges, each subrange of which contains dates for the
* same week. Note that weeks begin on Sunday and end on Saturday.
*/
auto byWeek(InputRange)(InputRange dates) pure nothrow if (isDateRange!InputRange)
{
static struct ByWeek {
InputRange r;
@property bool empty() { return r.empty; }
@property auto front() {
return until!((Date a) => a.dayOfWeek == DayOfWeek.sat) (r, OpenRight.no);
}
void popFront() {
assert(!r.empty);
r.popFront();
while (!r.empty && r.front.dayOfWeek != DayOfWeek.sun)
r.popFront();
}
}
return ByWeek(dates);
}
/// The number of columns per day in the formatted output.
enum ColsPerDay = 3;
/// The number of columns per week in the formatted output.
enum ColsPerWeek = 7 * ColsPerDay;
/**
* Formats a range of weeks into a range of strings.
*
* Each day is formatted into the digit representation of the day of the month,
* padded with spaces to fill up 3 characters.
*
* Parameters:
* weeks = A range of ranges of Dates, each inner range representing
* consecutive dates in a week.
*/
auto formatWeek(Range)(Range weeks) pure nothrow
if (isInputRange!Range && isInputRange!(ElementType!Range) &&
is(ElementType!(ElementType!Range) == Date))
{
struct WeekStrings {
Range r;
@property bool empty() { return r.empty; }
string front()
out(s) { assert(s.length == ColsPerWeek); }
body
{
auto buf = appender!string();
// Insert enough filler to align the first day with its respective
// day-of-week.
assert(!r.front.empty);
auto startDay = r.front.front.dayOfWeek;
buf.put(spaces(ColsPerDay * startDay));
// Format each day into its own cell and append to target string.
string[] days = map!((Date d) => " %2d".format(d.day))(r.front)
.array;
assert(days.length <= 7 - startDay);
days.copy(buf);
// Insert more filler at the end to fill up the remainder of the
// week, if it's a short week (e.g. at the end of the month).
if (days.length < 7 - startDay)
buf.put(spaces(ColsPerDay * (7 - startDay - days.length)));
return buf.data;
}
void popFront() {
r.popFront();
}
}
return WeekStrings(weeks);
}
/**
* Formats the name of a month centered on ColsPerWeek.
*/
string monthTitle(Month month) pure nothrow {
static immutable string[] monthNames = [
"January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"];
static assert(monthNames.length == 12);
// Determine how many spaces before and after the month name we need to
// center it over the formatted weeks in the month
auto name = monthNames[month - 1];
assert(name.length < ColsPerWeek);
auto before = (ColsPerWeek - name.length) / 2;
auto after = ColsPerWeek - name.length - before;
return spaces(before) ~ name ~ spaces(after);
}
/**
* Formats a month.
* Parameters:
* monthDays = A range of Dates representing consecutive days in a month.
* Returns: A range of strings representing each line of the formatted month.
*/
auto formatMonth(Range)(Range monthDays)
if (isInputRange!Range && is(ElementType!Range == Date))
in {
assert(!monthDays.empty);
assert(monthDays.front.day == 1);
} body {
return chain( [ monthTitle(monthDays.front.month) ], monthDays.byWeek().formatWeek());
}
/**
* Formats a range of months.
* Parameters:
* months = A range of ranges, each inner range is a range of Dates in a
* month.
* Returns:
* A range of ranges of formatted lines for each month.
*/
auto formatMonths(Range)(Range months) pure nothrow
if (isInputRange!Range && is(ElementType!(ElementType!Range) == Date))
{
return months.map!formatMonth;
}
int main()
{
auto s = datesInYear2(2014).byMonth().formatMonths();
writeln(s);
writeln(counterDateEq);
writeln(counterDateInc);
writeln(counterDateSave);
writeln(counterChunkByAttrEq);
return 0;
}