-
Notifications
You must be signed in to change notification settings - Fork 128
/
calendar.d
291 lines (232 loc) · 6.38 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
/++
OpenD could use automatic mixin to child class...
Extensions: color. exrule? trash day - if holiday occurred that week, move it forward a day
Standards: categories
UI idea for rrule: show a mini two year block with the day highlighted
-> also just let user click on a bunch of days so they can make a list
Want ability to add special info to a single item of a recurring event
Can use inotify to reload ui when sqlite db changes (or a trigger on postgres?)
https://datatracker.ietf.org/doc/html/rfc5545
https://icalendar.org/
+/
module arsd.calendar;
import arsd.core;
import std.datetime;
/++
History:
Added July 3, 2024
+/
SimplifiedUtcTimestamp parseTimestampString(string when, SysTime relativeTo) /*pure*/ {
import std.string;
int parsingWhat;
int bufferedNumber = int.max;
int secondsCount;
void addSeconds(string word, int bufferedNumber, int multiplier) {
if(parsingWhat == 0)
parsingWhat = 1;
if(parsingWhat != 1)
throw ArsdException!"unusable timestamp string"("you said 'at' but gave a relative time", when);
if(bufferedNumber == int.max)
throw ArsdException!"unusable timestamp string"("no number before unit", when, word);
secondsCount += bufferedNumber * multiplier;
bufferedNumber = int.max;
}
foreach(word; when.split(" ")) {
word = strip(word).toLower().replace(",", "");
if(word == "in")
parsingWhat = 1;
else if(word == "at")
parsingWhat = 2;
else if(word == "and") {
// intentionally blank
} else if(word.indexOf(":") != -1) {
if(secondsCount != 0)
throw ArsdException!"unusable timestamp string"("cannot mix time styles", when, word);
if(parsingWhat == 0)
parsingWhat = 2; // assume absolute time when this comes in
bool wasPm;
if(word.length > 2 && word[$-2 .. $] == "pm") {
word = word[0 .. $-2];
wasPm = true;
} else if(word.length > 2 && word[$-2 .. $] == "am") {
word = word[0 .. $-2];
}
// FIXME: what about midnight?
int multiplier = 3600;
foreach(part; word.split(":")) {
import std.conv;
secondsCount += multiplier * to!int(part);
multiplier /= 60;
}
if(wasPm)
secondsCount += 12 * 3600;
} else if(word.isNumeric()) {
import std.conv;
bufferedNumber = to!int(word);
} else if(word == "seconds" || word == "second") {
addSeconds(word, bufferedNumber, 1);
} else if(word == "minutes" || word == "minute") {
addSeconds(word, bufferedNumber, 60);
} else if(word == "hours" || word == "hour") {
addSeconds(word, bufferedNumber, 60 * 60);
} else
throw ArsdException!"unusable timestamp string"("i dont know what this word means", when, word);
}
if(parsingWhat == 0)
throw ArsdException!"unusable timestamp string"("couldn't figure out what to do with this input", when);
else if(parsingWhat == 1) // relative time
return SimplifiedUtcTimestamp((relativeTo + seconds(secondsCount)).stdTime);
else if(parsingWhat == 2) { // absolute time (assuming it is today in our time zone)
auto today = relativeTo;
today.hour = 0;
today.minute = 0;
today.second = 0;
return SimplifiedUtcTimestamp((today + seconds(secondsCount)).stdTime);
} else
assert(0);
}
unittest {
auto testTime = SysTime(DateTime(Date(2024, 07, 03), TimeOfDay(10, 0, 0)), UTC());
void test(string what, string expected) {
auto result = parseTimestampString(what, testTime).toString;
assert(result == expected, result);
}
test("in 5 minutes", "2024-07-03T10:05:00Z");
test("in 5 minutes and 5 seconds", "2024-07-03T10:05:05Z");
test("in 5 minutes, 45 seconds", "2024-07-03T10:05:45Z");
test("at 5:44", "2024-07-03T05:44:00Z");
test("at 5:44pm", "2024-07-03T17:44:00Z");
}
version(none)
void main() {
auto e = new CalendarEvent(
start: DateTime(2024, 4, 22),
end: Date(2024, 04, 22),
);
}
class Calendar {
CalendarEvent[] events;
}
/++
+/
class CalendarEvent {
DateWithOptionalTime start;
DateWithOptionalTime end;
Recurrence recurrence;
int color;
string title; // summary
string details;
string uid;
this(DateWithOptionalTime start, DateWithOptionalTime end, Recurrence recurrence = Recurrence.none) {
this.start = start;
this.end = end;
this.recurrence = recurrence;
}
}
/++
+/
struct DateWithOptionalTime {
string tzlocation;
DateTime dt;
bool hadTime;
@implicit
this(DateTime dt) {
this.dt = dt;
this.hadTime = true;
}
@implicit
this(Date d) {
this.dt = DateTime(d, TimeOfDay.init);
this.hadTime = false;
}
this(in char[] s) {
// FIXME
}
}
/++
+/
struct Recurrence {
static Recurrence none() {
return Recurrence.init;
}
}
/+
enum FREQ {
}
struct RRULE {
FREQ freq;
int interval;
int count;
DAY wkst;
// these can be negative too indicating the xth from the last...
DAYSET byday; // ubyte bitmask... except it can also have numbers atached wtf
// so like `BYDAY=-2MO` means second-to-last monday
MONTHDAYSET byMonthDay; // uint bitmask
HOURSET byHour; // uint bitmask
MONTHDSET byMonth; // ushort bitmask
WEEKSET byWeekNo; // ulong bitmask
int BYSETPOS;
}
+/
struct ICalParser {
// if the following line starts with whitespace, remove the cr/lf/ and that ONE ws char, then add to the previous line
// it is supposed to support this even if it is in the middle of a utf-8 sequence
// contentline = name *(";" param ) ":" value CRLF
// you're supposed to split lines longer than 75 octets when generating.
void feedEntireFile(in ubyte[] data) {
feed(data);
feed(null);
}
void feedEntireFile(in char[] data) {
feed(data);
feed(null);
}
/++
Feed it some data you have ready.
Feed it an empty array or `null` to indicate end of input.
+/
void feed(in char[] data) {
feed(cast(const(ubyte)[]) data);
}
/// ditto
void feed(in ubyte[] data) {
const(ubyte)[] toProcess;
if(unprocessedData.length) {
unprocessedData ~= data;
toProcess = unprocessedData;
} else {
toProcess = data;
}
auto eol = toProcess.indexOf("\n");
if(eol == -1) {
unprocessedData = cast(ubyte[]) toProcess;
} else {
// if it is \r\n, remove the \r FIXME
// if it is \r\n<space>, need to concat
// if it is \r\n\t, also need to concat
processLine(toProcess[0 .. eol]);
}
}
/// ditto
void feed(typeof(null)) {
feed(cast(const(ubyte)[]) null);
}
private ubyte[] unprocessedData;
private void processLine(in ubyte[] line) {
}
}
immutable monthNames = [
"",
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
];