1 // SDLang-D
2 // Written in the D programming language.
3 
4 module gfx.decl.sdlang.token;
5 
6 import std.array;
7 import std.base64;
8 import std.conv;
9 import std.datetime;
10 import std.meta;
11 import std.range;
12 import std.string;
13 import std.traits;
14 import std.typetuple;
15 import std.variant;
16 
17 import gfx.decl.sdlang.exception;
18 import gfx.decl.sdlang.symbol;
19 import gfx.decl.sdlang.util;
20 
21 /// DateTime doesn't support milliseconds, but SDLang's "Date Time" type does.
22 /// So this is needed for any SDL "Date Time" that doesn't include a time zone.
23 struct DateTimeFrac
24 {
25 	DateTime dateTime;
26 	Duration fracSecs;
27 }
28 
29 /++
30 If a "Date Time" literal in the SDL file has a time zone that's not found in
31 your system, you get one of these instead of a SysTime. (Because it's
32 impossible to indicate "unknown time zone" with `std.datetime.TimeZone`.)
33 
34 The difference between this and `DateTimeFrac` is that `DateTimeFrac`
35 indicates that no time zone was specified in the SDL at all, whereas
36 `DateTimeFracUnknownZone` indicates that a time zone was specified but
37 data for it could not be found on your system.
38 +/
39 struct DateTimeFracUnknownZone
40 {
41 	DateTime dateTime;
42 	Duration fracSecs;
43 	string timeZone;
44 
45 	bool opEquals(const DateTimeFracUnknownZone b) const
46 	{
47 		return opEquals(b);
48 	}
49 	bool opEquals(ref const DateTimeFracUnknownZone b) const
50 	{
51 		return
52 			this.dateTime == b.dateTime &&
53 			this.fracSecs  == b.fracSecs  &&
54 			this.timeZone == b.timeZone;
55 	}
56 }
57 
58 /++
59 SDLang's datatypes map to D's datatypes as described below.
60 Most are straightforward, but take special note of the date/time-related types.
61 
62 ---------------------------------------------------------------
63 Boolean:                       bool
64 Null:                          typeof(null)
65 Unicode Character:             dchar
66 Double-Quote Unicode String:   string
67 Raw Backtick Unicode String:   string
68 Integer (32 bits signed):      int
69 Long Integer (64 bits signed): long
70 Float (32 bits signed):        float
71 Double Float (64 bits signed): double
72 Decimal (128+ bits signed):    real
73 Binary (standard Base64):      ubyte[]
74 Time Span:                     Duration
75 
76 Date (with no time at all):           Date
77 Date Time (no timezone):              DateTimeFrac
78 Date Time (with a known timezone):    SysTime
79 Date Time (with an unknown timezone): DateTimeFracUnknownZone
80 ---------------------------------------------------------------
81 +/
82 alias ValueTypes = TypeTuple!(
83 	bool,
84 	string, dchar,
85 	int, long,
86 	float, double, real,
87 	Date, DateTimeFrac, SysTime, DateTimeFracUnknownZone, Duration,
88 	ubyte[],
89 	typeof(null),
90 );
91 
92 alias Value = Algebraic!( ValueTypes ); ///ditto
93 enum isValueType(T) = staticIndexOf!(T, ValueTypes) != -1;
94 
95 enum isSink(T) =
96 	isOutputRange!T &&
97 	is(ElementType!(T)[] == string);
98 
99 string toSDLString(T)(T value) if(is(T==Value) || isValueType!T)
100 {
101 	Appender!string sink;
102 	toSDLString(value, sink);
103 	return sink.data;
104 }
105 
106 /// Throws SDLangException if value is infinity, -infinity or NaN, because
107 /// those are not currently supported by the SDLang spec.
108 void toSDLString(Sink)(Value value, ref Sink sink) if(isOutputRange!(Sink,char))
109 {
110 	foreach(T; ValueTypes)
111 	{
112 		if(value.type == typeid(T))
113 		{
114 			toSDLString( value.get!T(), sink );
115 			return;
116 		}
117 	}
118 
119 	throw new Exception("Internal SDLang-D error: Unhandled type of Value. Contains: "~value.toString());
120 }
121 
122 @("toSDLString on infinity and NaN")
123 unittest
124 {
125 	import std.exception;
126 
127 	auto floatInf    = float.infinity;
128 	auto floatNegInf = -float.infinity;
129 	auto floatNaN    = float.nan;
130 
131 	auto doubleInf    = double.infinity;
132 	auto doubleNegInf = -double.infinity;
133 	auto doubleNaN    = double.nan;
134 
135 	auto realInf    = real.infinity;
136 	auto realNegInf = -real.infinity;
137 	auto realNaN    = real.nan;
138 
139 	assertNotThrown( toSDLString(0.0F) );
140 	assertNotThrown( toSDLString(0.0)  );
141 	assertNotThrown( toSDLString(0.0L) );
142 
143 	assertThrown!ValidationException( toSDLString(floatInf) );
144 	assertThrown!ValidationException( toSDLString(floatNegInf) );
145 	assertThrown!ValidationException( toSDLString(floatNaN) );
146 
147 	assertThrown!ValidationException( toSDLString(doubleInf) );
148 	assertThrown!ValidationException( toSDLString(doubleNegInf) );
149 	assertThrown!ValidationException( toSDLString(doubleNaN) );
150 
151 	assertThrown!ValidationException( toSDLString(realInf) );
152 	assertThrown!ValidationException( toSDLString(realNegInf) );
153 	assertThrown!ValidationException( toSDLString(realNaN) );
154 
155 	assertThrown!ValidationException( toSDLString(Value(floatInf)) );
156 	assertThrown!ValidationException( toSDLString(Value(floatNegInf)) );
157 	assertThrown!ValidationException( toSDLString(Value(floatNaN)) );
158 
159 	assertThrown!ValidationException( toSDLString(Value(doubleInf)) );
160 	assertThrown!ValidationException( toSDLString(Value(doubleNegInf)) );
161 	assertThrown!ValidationException( toSDLString(Value(doubleNaN)) );
162 
163 	assertThrown!ValidationException( toSDLString(Value(realInf)) );
164 	assertThrown!ValidationException( toSDLString(Value(realNegInf)) );
165 	assertThrown!ValidationException( toSDLString(Value(realNaN)) );
166 }
167 
168 void toSDLString(Sink)(typeof(null) value, ref Sink sink) if(isOutputRange!(Sink,char))
169 {
170 	sink.put("null");
171 }
172 
173 void toSDLString(Sink)(bool value, ref Sink sink) if(isOutputRange!(Sink,char))
174 {
175 	sink.put(value? "true" : "false");
176 }
177 
178 //TODO: Figure out how to properly handle strings/chars containing lineSep or paraSep
179 void toSDLString(Sink)(string value, ref Sink sink) if(isOutputRange!(Sink,char))
180 {
181 	sink.put('"');
182 
183 	// This loop is UTF-safe
184 	foreach(char ch; value)
185 	{
186 		if     (ch == '\n') sink.put(`\n`);
187 		else if(ch == '\r') sink.put(`\r`);
188 		else if(ch == '\t') sink.put(`\t`);
189 		else if(ch == '\"') sink.put(`\"`);
190 		else if(ch == '\\') sink.put(`\\`);
191 		else
192 			sink.put(ch);
193 	}
194 
195 	sink.put('"');
196 }
197 
198 void toSDLString(Sink)(dchar value, ref Sink sink) if(isOutputRange!(Sink,char))
199 {
200 	sink.put('\'');
201 
202 	if     (value == '\n') sink.put(`\n`);
203 	else if(value == '\r') sink.put(`\r`);
204 	else if(value == '\t') sink.put(`\t`);
205 	else if(value == '\'') sink.put(`\'`);
206 	else if(value == '\\') sink.put(`\\`);
207 	else
208 		sink.put(value);
209 
210 	sink.put('\'');
211 }
212 
213 void toSDLString(Sink)(int value, ref Sink sink) if(isOutputRange!(Sink,char))
214 {
215 	sink.put( "%s".format(value) );
216 }
217 
218 void toSDLString(Sink)(long value, ref Sink sink) if(isOutputRange!(Sink,char))
219 {
220 	sink.put( "%sL".format(value) );
221 }
222 
223 private void checkUnsupportedFloatingPoint(T)(T value) if(isFloatingPoint!T)
224 {
225 	import std.exception;
226 	import std.math;
227 
228 	enforce!ValidationException(
229 		!isInfinity(value),
230 		"SDLang does not currently support infinity for floating-point types"
231 	);
232 
233 	enforce!ValidationException(
234 		!isNaN(value),
235 		"SDLang does not currently support NaN for floating-point types"
236 	);
237 }
238 
239 private string trimmedDecimal(string str)
240 {
241 	Appender!string sink;
242 	trimmedDecimal(str, sink);
243 	return sink.data;
244 }
245 
246 private void trimmedDecimal(Sink)(string str, ref Sink sink) if(isOutputRange!(Sink,char))
247 {
248 	// Special case
249 	if(str == ".")
250 	{
251 		sink.put("0");
252 		return;
253 	}
254 
255 	for(auto i=str.length-1; i>0; i--)
256 	{
257 		if(str[i] == '.')
258 		{
259 			// Trim up to here, PLUS trim trailing '.'
260 			sink.put(str[0..i]);
261 			return;
262 		}
263 		else if(str[i] != '0')
264 		{
265 			// Trim up to here
266 			sink.put(str[0..i+1]);
267 			return;
268 		}
269 	}
270 
271 	// Nothing to trim
272 	sink.put(str);
273 }
274 
275 @("trimmedDecimal")
276 unittest
277 {
278 	assert(trimmedDecimal("123.456000") == "123.456");
279 	assert(trimmedDecimal("123.456")    == "123.456");
280 	assert(trimmedDecimal("123.000")    == "123");
281 	assert(trimmedDecimal("123.0")      == "123");
282 	assert(trimmedDecimal("123.")       == "123");
283 	assert(trimmedDecimal("123")        == "123");
284 	assert(trimmedDecimal("1.")         == "1");
285 	assert(trimmedDecimal("1")          == "1");
286 	assert(trimmedDecimal("0")          == "0");
287 	assert(trimmedDecimal(".")          == "0");
288 }
289 
290 void toSDLString(Sink)(float value, ref Sink sink) if(isOutputRange!(Sink,char))
291 {
292 	checkUnsupportedFloatingPoint(value);
293 	"%.10f".format(value).trimmedDecimal(sink);
294 	sink.put("F");
295 }
296 
297 void toSDLString(Sink)(double value, ref Sink sink) if(isOutputRange!(Sink,char))
298 {
299 	checkUnsupportedFloatingPoint(value);
300 	"%.30f".format(value).trimmedDecimal(sink);
301 	sink.put("D");
302 }
303 
304 void toSDLString(Sink)(real value, ref Sink sink) if(isOutputRange!(Sink,char))
305 {
306 	checkUnsupportedFloatingPoint(value);
307 	"%.90f".format(value).trimmedDecimal(sink);
308 	sink.put("BD");
309 }
310 
311 // Regression test: Issue #50
312 @("toSDLString: No scientific notation")
313 unittest
314 {
315 	import std.algorithm, gfx.decl.sdlang.parser;
316 	auto tag = parseSource(`
317 	foo \
318 		420000000000000000000f \
319 		42000000000000000000000000000000000000d \
320 		420000000000000000000000000000000000000000000000000000000000000bd \
321 	`).getTag("foo");
322 	import std.stdio;
323 	writeln(tag.values[0].toSDLString);
324 	writeln(tag.values[1].toSDLString);
325 	writeln(tag.values[2].toSDLString);
326 
327 	assert(!tag.values[0].toSDLString.canFind("+"));
328 	assert(!tag.values[0].toSDLString.canFind("-"));
329 
330 	assert(!tag.values[1].toSDLString.canFind("+"));
331 	assert(!tag.values[1].toSDLString.canFind("-"));
332 
333 	assert(!tag.values[2].toSDLString.canFind("+"));
334 	assert(!tag.values[2].toSDLString.canFind("-"));
335 }
336 
337 void toSDLString(Sink)(Date value, ref Sink sink) if(isOutputRange!(Sink,char))
338 {
339 	sink.put(to!string(value.year));
340 	sink.put('/');
341 	sink.put(to!string(cast(int)value.month));
342 	sink.put('/');
343 	sink.put(to!string(value.day));
344 }
345 
346 void toSDLString(Sink)(DateTimeFrac value, ref Sink sink) if(isOutputRange!(Sink,char))
347 {
348 	toSDLString(value.dateTime.date, sink);
349 	sink.put(' ');
350 	sink.put("%.2s".format(value.dateTime.hour));
351 	sink.put(':');
352 	sink.put("%.2s".format(value.dateTime.minute));
353 
354 	if(value.dateTime.second != 0)
355 	{
356 		sink.put(':');
357 		sink.put("%.2s".format(value.dateTime.second));
358 	}
359 
360 	if(value.fracSecs != 0.msecs)
361 	{
362 		sink.put('.');
363 		sink.put("%.3s".format(value.fracSecs.total!"msecs"));
364 	}
365 }
366 
367 void toSDLString(Sink)(SysTime value, ref Sink sink) if(isOutputRange!(Sink,char))
368 {
369 	auto dateTimeFrac = DateTimeFrac(cast(DateTime)value, value.fracSecs);
370 	toSDLString(dateTimeFrac, sink);
371 
372 	sink.put("-");
373 
374 	auto tzString = value.timezone.name;
375 
376 	// If name didn't exist, try abbreviation.
377 	// Note that according to std.datetime docs, on Windows the
378 	// stdName/dstName may not be properly abbreviated.
379 	version(Windows) {} else
380 	if(tzString == "")
381 	{
382 		auto tz = value.timezone;
383 		auto stdTime = value.stdTime;
384 
385 		if(tz.hasDST())
386 			tzString = tz.dstInEffect(stdTime)? tz.dstName : tz.stdName;
387 		else
388 			tzString = tz.stdName;
389 	}
390 
391 	if(tzString == "")
392 	{
393 		auto offset = value.timezone.utcOffsetAt(value.stdTime);
394 		sink.put("GMT");
395 
396 		if(offset < seconds(0))
397 		{
398 			sink.put("-");
399 			offset = -offset;
400 		}
401 		else
402 			sink.put("+");
403 
404 		sink.put("%.2s".format(offset.split.hours));
405 		sink.put(":");
406 		sink.put("%.2s".format(offset.split.minutes));
407 	}
408 	else
409 		sink.put(tzString);
410 }
411 
412 void toSDLString(Sink)(DateTimeFracUnknownZone value, ref Sink sink) if(isOutputRange!(Sink,char))
413 {
414 	auto dateTimeFrac = DateTimeFrac(value.dateTime, value.fracSecs);
415 	toSDLString(dateTimeFrac, sink);
416 
417 	sink.put("-");
418 	sink.put(value.timeZone);
419 }
420 
421 void toSDLString(Sink)(Duration value, ref Sink sink) if(isOutputRange!(Sink,char))
422 {
423 	if(value < seconds(0))
424 	{
425 		sink.put("-");
426 		value = -value;
427 	}
428 
429 	auto days = value.total!"days"();
430 	if(days != 0)
431 	{
432 		sink.put("%s".format(days));
433 		sink.put("d:");
434 	}
435 
436 	sink.put("%.2s".format(value.split.hours));
437 	sink.put(':');
438 	sink.put("%.2s".format(value.split.minutes));
439 	sink.put(':');
440 	sink.put("%.2s".format(value.split.seconds));
441 
442 	if(value.split.msecs != 0)
443 	{
444 		sink.put('.');
445 		sink.put("%.3s".format(value.split.msecs));
446 	}
447 }
448 
449 void toSDLString(Sink)(ubyte[] value, ref Sink sink) if(isOutputRange!(Sink,char))
450 {
451 	sink.put('[');
452 	sink.put( Base64.encode(value) );
453 	sink.put(']');
454 }
455 
456 /// This only represents terminals. Nonterminals aren't
457 /// constructed since the AST is directly built during parsing.
458 struct Token
459 {
460 	Symbol symbol = gfx.decl.sdlang.symbol.symbol!"Error"; /// The "type" of this token
461 	Location location;
462 	Value value; /// Only valid when `symbol` is `symbol!"Value"`, otherwise null
463 	string data; /// Original text from source
464 
465 	@disable this();
466 	this(Symbol symbol, Location location, Value value=Value(null), string data=null)
467 	{
468 		this.symbol   = symbol;
469 		this.location = location;
470 		this.value    = value;
471 		this.data     = data;
472 	}
473 
474 	/// Tokens with differing symbols are always unequal.
475 	/// Tokens with differing values are always unequal.
476 	/// Tokens with differing Value types are always unequal.
477 	/// Member `location` is always ignored for comparison.
478 	/// Member `data` is ignored for comparison *EXCEPT* when the symbol is Ident.
479 	bool opEquals(Token b)
480 	{
481 		return opEquals(b);
482 	}
483 	bool opEquals(ref Token b) ///ditto
484 	{
485 		if(
486 			this.symbol     != b.symbol     ||
487 			this.value.type != b.value.type ||
488 			this.value      != b.value
489 		)
490 			return false;
491 
492 		if(this.symbol == .symbol!"Ident")
493 			return this.data == b.data;
494 
495 		return true;
496 	}
497 
498 	bool matches(string symbolName)()
499 	{
500 		return this.symbol == .symbol!symbolName;
501 	}
502 }
503 
504 @("sdlang token")
505 unittest
506 {
507 	auto loc  = Location("", 0, 0, 0);
508 	auto loc2 = Location("a", 1, 1, 1);
509 
510 	assert(Token(symbol!"EOL",loc) == Token(symbol!"EOL",loc ));
511 	assert(Token(symbol!"EOL",loc) == Token(symbol!"EOL",loc2));
512 	assert(Token(symbol!":",  loc) == Token(symbol!":",  loc ));
513 	assert(Token(symbol!"EOL",loc) != Token(symbol!":",  loc ));
514 	assert(Token(symbol!"EOL",loc,Value(null),"\n") == Token(symbol!"EOL",loc,Value(null),"\n"));
515 
516 	assert(Token(symbol!"EOL",loc,Value(null),"\n") == Token(symbol!"EOL",loc,Value(null),";" ));
517 	assert(Token(symbol!"EOL",loc,Value(null),"A" ) == Token(symbol!"EOL",loc,Value(null),"B" ));
518 	assert(Token(symbol!":",  loc,Value(null),"A" ) == Token(symbol!":",  loc,Value(null),"BB"));
519 	assert(Token(symbol!"EOL",loc,Value(null),"A" ) != Token(symbol!":",  loc,Value(null),"A" ));
520 
521 	assert(Token(symbol!"Ident",loc,Value(null),"foo") == Token(symbol!"Ident",loc,Value(null),"foo"));
522 	assert(Token(symbol!"Ident",loc,Value(null),"foo") != Token(symbol!"Ident",loc,Value(null),"BAR"));
523 
524 	assert(Token(symbol!"Value",loc,Value(null),"foo") == Token(symbol!"Value",loc, Value(null),"foo"));
525 	assert(Token(symbol!"Value",loc,Value(null),"foo") == Token(symbol!"Value",loc2,Value(null),"foo"));
526 	assert(Token(symbol!"Value",loc,Value(null),"foo") == Token(symbol!"Value",loc, Value(null),"BAR"));
527 	assert(Token(symbol!"Value",loc,Value(   7),"foo") == Token(symbol!"Value",loc, Value(   7),"BAR"));
528 	assert(Token(symbol!"Value",loc,Value(   7),"foo") != Token(symbol!"Value",loc, Value( "A"),"foo"));
529 	assert(Token(symbol!"Value",loc,Value(   7),"foo") != Token(symbol!"Value",loc, Value(   2),"foo"));
530 	assert(Token(symbol!"Value",loc,Value(cast(int)7)) != Token(symbol!"Value",loc, Value(cast(long)7)));
531 	assert(Token(symbol!"Value",loc,Value(cast(float)1.2)) != Token(symbol!"Value",loc, Value(cast(double)1.2)));
532 }
533 
534 @("sdlang Value.toSDLString()")
535 unittest
536 {
537 	// Bool and null
538 	assert(Value(null ).toSDLString() == "null");
539 	assert(Value(true ).toSDLString() == "true");
540 	assert(Value(false).toSDLString() == "false");
541 
542 	// Base64 Binary
543 	assert(Value(cast(ubyte[])"hello world".dup).toSDLString() == "[aGVsbG8gd29ybGQ=]");
544 
545 	// Integer
546 	assert(Value(cast( int) 7).toSDLString() ==  "7");
547 	assert(Value(cast( int)-7).toSDLString() == "-7");
548 	assert(Value(cast( int) 0).toSDLString() ==  "0");
549 
550 	assert(Value(cast(long) 7).toSDLString() ==  "7L");
551 	assert(Value(cast(long)-7).toSDLString() == "-7L");
552 	assert(Value(cast(long) 0).toSDLString() ==  "0L");
553 
554 	// Floating point
555 	import std.stdio;
556 	writeln(1.5f);
557 	writeln(Value(cast(float) 1.5).toSDLString());
558 	assert(Value(cast(float) 1.5).toSDLString() ==  "1.5F");
559 	assert(Value(cast(float)-1.5).toSDLString() == "-1.5F");
560 	assert(Value(cast(float)   0).toSDLString() ==    "0F");
561 	assert(Value(cast(float)0.25).toSDLString() == "0.25F");
562 
563 	assert(Value(cast(double) 1.5).toSDLString() ==  "1.5D");
564 	assert(Value(cast(double)-1.5).toSDLString() == "-1.5D");
565 	assert(Value(cast(double)   0).toSDLString() ==    "0D");
566 	assert(Value(cast(double)0.25).toSDLString() == "0.25D");
567 
568 	assert(Value(cast(real) 1.5).toSDLString() ==  "1.5BD");
569 	assert(Value(cast(real)-1.5).toSDLString() == "-1.5BD");
570 	assert(Value(cast(real)   0).toSDLString() ==    "0BD");
571 	assert(Value(cast(real)0.25).toSDLString() == "0.25BD");
572 
573 	// String
574 	assert(Value("hello"  ).toSDLString() == `"hello"`);
575 	assert(Value(" hello ").toSDLString() == `" hello "`);
576 	assert(Value(""       ).toSDLString() == `""`);
577 	assert(Value("hello \r\n\t\"\\ world").toSDLString() == `"hello \r\n\t\"\\ world"`);
578 	assert(Value("日本語").toSDLString() == `"日本語"`);
579 
580 	// Chars
581 	assert(Value(cast(dchar) 'A').toSDLString() ==  `'A'`);
582 	assert(Value(cast(dchar)'\r').toSDLString() == `'\r'`);
583 	assert(Value(cast(dchar)'\n').toSDLString() == `'\n'`);
584 	assert(Value(cast(dchar)'\t').toSDLString() == `'\t'`);
585 	assert(Value(cast(dchar)'\'').toSDLString() == `'\''`);
586 	assert(Value(cast(dchar)'\\').toSDLString() == `'\\'`);
587 	assert(Value(cast(dchar) '月').toSDLString() ==  `'月'`);
588 
589 	// Date
590 	assert(Value(Date( 2004,10,31)).toSDLString() == "2004/10/31");
591 	assert(Value(Date(-2004,10,31)).toSDLString() == "-2004/10/31");
592 
593 	// DateTimeFrac w/o Frac
594 	assert(Value(DateTimeFrac(DateTime(2004,10,31,  14,30,15))).toSDLString() == "2004/10/31 14:30:15");
595 	assert(Value(DateTimeFrac(DateTime(2004,10,31,   1, 2, 3))).toSDLString() == "2004/10/31 01:02:03");
596 	assert(Value(DateTimeFrac(DateTime(-2004,10,31, 14,30,15))).toSDLString() == "-2004/10/31 14:30:15");
597 
598 	// DateTimeFrac w/ Frac
599 	assert(Value(DateTimeFrac(DateTime(2004,10,31,  14,30,15), 123.msecs)).toSDLString() == "2004/10/31 14:30:15.123");
600 	assert(Value(DateTimeFrac(DateTime(2004,10,31,  14,30,15), 120.msecs)).toSDLString() == "2004/10/31 14:30:15.120");
601 	assert(Value(DateTimeFrac(DateTime(2004,10,31,  14,30,15), 100.msecs)).toSDLString() == "2004/10/31 14:30:15.100");
602 	assert(Value(DateTimeFrac(DateTime(2004,10,31,  14,30,15),  12.msecs)).toSDLString() == "2004/10/31 14:30:15.012");
603 	assert(Value(DateTimeFrac(DateTime(2004,10,31,  14,30,15),   1.msecs)).toSDLString() == "2004/10/31 14:30:15.001");
604 	assert(Value(DateTimeFrac(DateTime(-2004,10,31, 14,30,15), 123.msecs)).toSDLString() == "-2004/10/31 14:30:15.123");
605 
606 	// DateTimeFracUnknownZone
607 	assert(Value(DateTimeFracUnknownZone(DateTime(2004,10,31, 14,30,15), 123.msecs, "Foo/Bar")).toSDLString() == "2004/10/31 14:30:15.123-Foo/Bar");
608 
609 	// SysTime
610 	assert(Value(SysTime(DateTime(2004,10,31, 14,30,15), new immutable SimpleTimeZone( hours(0)             ))).toSDLString() == "2004/10/31 14:30:15-GMT+00:00");
611 	assert(Value(SysTime(DateTime(2004,10,31,  1, 2, 3), new immutable SimpleTimeZone( hours(0)             ))).toSDLString() == "2004/10/31 01:02:03-GMT+00:00");
612 	assert(Value(SysTime(DateTime(2004,10,31, 14,30,15), new immutable SimpleTimeZone( hours(2)+minutes(10) ))).toSDLString() == "2004/10/31 14:30:15-GMT+02:10");
613 	assert(Value(SysTime(DateTime(2004,10,31, 14,30,15), new immutable SimpleTimeZone(-hours(5)-minutes(30) ))).toSDLString() == "2004/10/31 14:30:15-GMT-05:30");
614 	assert(Value(SysTime(DateTime(2004,10,31, 14,30,15), new immutable SimpleTimeZone( hours(2)+minutes( 3) ))).toSDLString() == "2004/10/31 14:30:15-GMT+02:03");
615 	assert(Value(SysTime(DateTime(2004,10,31, 14,30,15), 123.msecs, new immutable SimpleTimeZone( hours(0) ))).toSDLString() == "2004/10/31 14:30:15.123-GMT+00:00");
616 
617 	// Duration
618 	assert( "12:14:42"         == Value( days( 0)+hours(12)+minutes(14)+seconds(42)+msecs(  0)).toSDLString());
619 	assert("-12:14:42"         == Value(-days( 0)-hours(12)-minutes(14)-seconds(42)-msecs(  0)).toSDLString());
620 	assert( "00:09:12"         == Value( days( 0)+hours( 0)+minutes( 9)+seconds(12)+msecs(  0)).toSDLString());
621 	assert( "00:00:01.023"     == Value( days( 0)+hours( 0)+minutes( 0)+seconds( 1)+msecs( 23)).toSDLString());
622 	assert( "23d:05:21:23.532" == Value( days(23)+hours( 5)+minutes(21)+seconds(23)+msecs(532)).toSDLString());
623 	assert( "23d:05:21:23.530" == Value( days(23)+hours( 5)+minutes(21)+seconds(23)+msecs(530)).toSDLString());
624 	assert( "23d:05:21:23.500" == Value( days(23)+hours( 5)+minutes(21)+seconds(23)+msecs(500)).toSDLString());
625 	assert("-23d:05:21:23.532" == Value(-days(23)-hours( 5)-minutes(21)-seconds(23)-msecs(532)).toSDLString());
626 	assert("-23d:05:21:23.500" == Value(-days(23)-hours( 5)-minutes(21)-seconds(23)-msecs(500)).toSDLString());
627 	assert( "23d:05:21:23"     == Value( days(23)+hours( 5)+minutes(21)+seconds(23)+msecs(  0)).toSDLString());
628 }