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 }