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