1 /** 2 * 3 * Contains functions for formatting a SysTime object 4 * 5 **/ 6 7 module datetimeformat; 8 9 private import std.datetime; 10 private import std.ascii : toLower, isDigit, isAlpha; 11 private import std.utf : validate, toUTF32, toUTF8, toUCSindex, toUTFindex, encode; 12 private import std..string : toStringz, format; 13 private import std.conv : to; 14 15 /// Short (three-letter) Days of the week 16 immutable string[] SHORT_DAY_NAME = [ 17 DayOfWeek.sun: "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" 18 ]; 19 20 /// Full names of the days of the week. 21 immutable string[] LONG_DAY_NAME = [ 22 DayOfWeek.sun: "Sunday", "Monday", "Tuesday", "Wednesday", 23 "Thursday", "Friday", "Saturday" 24 ]; 25 26 /// Short (three-letter) names of the months of the year. 27 immutable string[] SHORT_MONTH_NAME = [ 28 Month.jan: "Jan", "Feb", "Mar", "Apr", "May", "Jun", 29 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" 30 ]; 31 32 /// Full names of the months of the year. 33 immutable string[Month.max + 1] LONG_MONTH_NAME = [ 34 Month.jan: "January", "February", "March", "April", "May", "June", 35 "July", "August", "September", "October", "November", "December" 36 ]; 37 38 /** Formats dt according to formatString. 39 * 40 * Returns: 41 * the formatted date string. 42 * Throws: 43 * SysTimeFormatException if the formatting fails, e.g. because of an error in the format 44 * string. 45 * UtfException if formatString is not a correctly-formed UTF-8 string. 46 */ 47 48 string format(const SysTime dt, string formatString) { 49 validate(formatString); 50 return format(dt, dt.dayOfWeek, formatString); 51 } 52 53 string format(const SysTime dt, DayOfWeek dayOfWeek, string formatString) { 54 validate(formatString); 55 bool nonNull; 56 immutable(char)* charPos = toStringz(formatString); 57 scope(success) assert (*charPos == '\0'); 58 59 return format(dt, dayOfWeek, charPos, nonNull, '\0'); 60 } 61 62 63 private { 64 65 // taken from Phobos (where it is private in D2) 66 const ubyte[256] UTF8stride = [ 67 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 68 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 69 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 70 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 71 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 72 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 73 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 74 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 75 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 76 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 77 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 78 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 79 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 80 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 81 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, 82 4,4,4,4,4,4,4,4,5,5,5,5,6,6,0xFF,0xFF, 83 ]; 84 85 string format(const SysTime dt, DayOfWeek dayOfWeek, 86 ref immutable(char)* charPos, out bool nonNull, char portionEnd) { 87 88 // function uses null-terminated string to make finding the end easier 89 long lastNumber = int.min; 90 string result; 91 92 while (*charPos != portionEnd) { 93 if (beginsElement(*charPos)) { 94 bool newNonNull; 95 result ~= formatElement(dt, dayOfWeek, charPos, newNonNull, lastNumber); 96 if (newNonNull) nonNull = true; 97 } else if (beginsLiteral(*charPos)) { 98 result ~= formatLiteral(charPos); 99 } else switch (*charPos) { 100 case '\0': // unclosed portion 101 assert (portionEnd == '}'); 102 throw new SysTimeFormatException(E_UNCLOSED_COLLAPSIBLE); 103 104 case '}': 105 throw new SysTimeFormatException(E_UNOPENED_COLLAPSIBLE); 106 107 case ']': 108 throw new SysTimeFormatException(E_UNOPENED_FIELD); 109 110 default: // self-literal character 111 result ~= *(charPos++); 112 } 113 } 114 115 return result; 116 } 117 118 119 /* Processes a single format element. A format element is any of the following: 120 * - an alphabetical format specifier 121 * - a collapsible portion 122 * - an alignment field 123 * Literals and alignment field widths are not included. The purpose is 124 * to deal with those elements that cannot be part of an alignment field 125 * padding or width. 126 */ 127 string formatElement(const SysTime dt, DayOfWeek dayOfWeek, 128 ref immutable(char)* charPos, out bool nonNull, ref long lastNumber) 129 in { 130 assert (beginsElement(*charPos)); 131 } body { 132 switch (*charPos) { 133 case '[': { 134 charPos++; 135 string portion = formatField(dt, dayOfWeek, charPos, nonNull); 136 charPos++; 137 return portion; 138 } 139 140 case '{': { 141 charPos++; 142 string portion = format(dt, dayOfWeek, charPos, nonNull, '}'); 143 charPos++; 144 return nonNull ? portion : null; 145 } 146 147 default: 148 char letter = cast(char) toLower(*charPos); 149 immutable(char)* beginSpec = charPos; 150 151 do { 152 ++charPos; 153 } while (toLower(*charPos) == letter); 154 155 string formatted = formatBySpec(dt, dayOfWeek, 156 beginSpec[0 .. charPos-beginSpec], lastNumber); 157 158 if (formatted.length != 0) { 159 nonNull = true; 160 return formatted; 161 } else { 162 return null; 163 } 164 } 165 } 166 167 168 string formatLiteral(ref immutable(char)* charPos) 169 in { 170 assert (beginsLiteral(*charPos)); 171 } body { 172 switch (*charPos) { 173 case '`': { // literal character 174 if (*++charPos == '\0') { 175 throw new SysTimeFormatException(E_MISSING_LITERAL); 176 } 177 uint len = UTF8stride[*charPos]; 178 scope(exit) charPos += len; 179 return charPos[0..len]; 180 } 181 182 case '\'': { // literal portion 183 immutable(char)* beginLiteral = ++charPos; 184 while (*charPos != '\'') { 185 if (*charPos == '\0') { 186 throw new SysTimeFormatException(E_UNCLOSED_LITERAL); 187 } 188 charPos++; 189 } 190 return beginLiteral[0 .. (charPos++) - beginLiteral]; 191 } 192 193 default: assert (false); 194 } 195 } 196 197 198 struct Piece { 199 dstring dtext; 200 string text; 201 ubyte type; // 0 = raw, 1 = literal; 2 = formatted 202 203 @property uint asNumber() { 204 if (dtext.length > 9) throw new SysTimeFormatException(E_OVERFLOW_WIDTH); 205 uint result; 206 foreach (c; dtext) { 207 assert (c >= '0' && c <= '9'); 208 result = result * 10 + (c - '0'); 209 } 210 return result; 211 } 212 } 213 214 215 string formatField(const SysTime dt, DayOfWeek dayOfWeek, 216 ref immutable(char)* charPos, out bool nonNull) { 217 Piece[] pieces; 218 219 // first parse the format string within the [...] 220 { 221 Piece[] tempPieces; 222 long lastNumber = int.min; 223 224 while (*charPos != ']') { 225 if (beginsElement(*charPos)) { 226 bool newNonNull; 227 tempPieces ~= Piece(null, formatElement(dt, dayOfWeek, charPos, newNonNull, lastNumber), 2); 228 if (newNonNull) nonNull = true; 229 } else if (beginsLiteral(*charPos)) { 230 tempPieces ~= Piece(null, formatLiteral(charPos), 1); 231 } else switch (*charPos) { 232 case '\0': 233 throw new SysTimeFormatException(E_UNCLOSED_FIELD); 234 235 case '}': 236 throw new SysTimeFormatException(E_UNOPENED_COLLAPSIBLE); 237 238 default: { 239 immutable(char)* begin = charPos; 240 do { 241 charPos++; 242 } while (*charPos != '\0' && *charPos != ']' && *charPos != '}' 243 && !beginsElement(*charPos) && !beginsLiteral(*charPos)); 244 245 tempPieces ~= Piece(null, begin[0 .. charPos - begin], 0); 246 } 247 } 248 } 249 250 /* convert tempPieces into a form in which 251 * - no two consecutive tempPieces have the same type 252 * - only non-literalised numbers have type 0 253 */ 254 ubyte lastType = ubyte.max; 255 256 foreach (piece; tempPieces) { 257 switch (piece.type) { 258 case 0: 259 foreach (dchar c; piece.text) { 260 if (isDigit(c)) { 261 if (lastType == 0) { 262 pieces[$-1].dtext ~= c; 263 } else { 264 pieces ~= Piece([c], null, 0); 265 lastType = 0; 266 } 267 } else { 268 if (lastType == 1) { 269 pieces[$-1].dtext ~= c; 270 } else { 271 pieces ~= Piece([c], null, 1); 272 lastType = 1; 273 } 274 } 275 } 276 break; 277 278 case 1: 279 if (lastType == 1) { 280 pieces[$-1].dtext ~= toUTF32(piece.text); 281 } else { 282 pieces ~= Piece(toUTF32(piece.text), null, 1); 283 lastType = 1; 284 } 285 break; 286 287 case 2: 288 if (lastType == 2) { 289 pieces[$-1].text ~= piece.text; 290 } else { 291 pieces ~= piece; 292 lastType = 2; 293 } 294 break; 295 296 default: 297 assert (false); 298 } 299 } 300 } 301 302 if (pieces.length < 2) throw new SysTimeFormatException(E_INCOMPLETE_FIELD); 303 304 // detect the field width/padding 305 dchar padLeft, padRight; 306 size_t fieldWidth = 0; 307 bool moreOnRight; 308 309 if (pieces[0].type == 0) { 310 // field width on left 311 if (pieces[$-1].type == 0) throw new SysTimeFormatException(E_DOUBLE_WIDTH); 312 313 fieldWidth = pieces[0].asNumber; 314 if (fieldWidth == 0) throw new SysTimeFormatException(E_ZERO_FIELD); 315 if (pieces[1].type != 1) throw new SysTimeFormatException(E_INCOMPLETE_FIELD); 316 317 pieces = pieces[1..$]; 318 padLeft = pieces[0].dtext[0]; 319 pieces[0].dtext = pieces[0].dtext[1..$]; 320 if (pieces[$-1].type == 1) { 321 padRight = pieces[$-1].dtext[$-1]; 322 pieces[$-1].dtext.length = pieces[$-1].dtext.length - 1; 323 } 324 325 } else if (pieces[$-1].type == 0) { 326 // field width on right 327 moreOnRight = true; 328 fieldWidth = pieces[$-1].asNumber; 329 if (fieldWidth == 0) throw new SysTimeFormatException(E_ZERO_FIELD); 330 if (pieces[$-2].type != 1) throw new SysTimeFormatException(E_INCOMPLETE_FIELD); 331 332 pieces = pieces[0..$-1]; 333 padRight = pieces[$-1].dtext[$-1]; 334 pieces[$-1].dtext.length = pieces[$-1].dtext.length - 1; 335 if (pieces[0].type == 1) { 336 padLeft = pieces[0].dtext[0]; 337 pieces[0].dtext = pieces[0].dtext[1..$]; 338 } 339 340 } else { 341 // field width given by number of padding characters 342 if (pieces[0].type == 1) { 343 padLeft = pieces[0].dtext[0]; 344 for (fieldWidth = 1; 345 fieldWidth < pieces[0].dtext.length && pieces[0].dtext[fieldWidth] == padLeft; 346 fieldWidth++) {} 347 pieces[0].dtext = pieces[0].dtext[fieldWidth..$]; 348 } 349 if (pieces[$-1].type == 1) { 350 padRight = pieces[$-1].dtext[$-1]; 351 ulong pos; 352 for (pos = pieces[$-1].dtext.length - 1; 353 pos > 0 && pieces[$-1].dtext[pos - 1] == padRight; 354 pos--) {} 355 if (pieces[$-1].dtext.length - pos > fieldWidth) moreOnRight = true; 356 fieldWidth += pieces[$-1].dtext.length - pos; 357 pieces[$-1].dtext.length = pos; 358 } 359 } 360 361 assert (fieldWidth != 0); 362 363 debug (datetimeformat) { 364 writefln("padding chars: %s %s.", padLeft == dchar.init ? "none" : [padLeft], 365 padRight == dchar.init ? "none" : [padRight]); 366 writefln("width: %d", fieldWidth); 367 writefln("%d pieces", pieces.length); 368 } 369 370 // read the field format - now use it 371 // but first, concatenate and measure the content 372 size_t contentLength; 373 string formattedContent; 374 foreach (piece; pieces) { 375 assert (piece.dtext.length == 0 || piece.text.length == 0); 376 if (piece.text.length == 0) { 377 formattedContent ~= toUTF8(piece.dtext); 378 contentLength += piece.dtext.length; 379 } else { 380 formattedContent ~= piece.text; 381 contentLength += toUCSindex(piece.text, piece.text.length); 382 } 383 } 384 debug (datetimeformat) writefln("content length %d: %s", contentLength, formattedContent); 385 386 if (contentLength > fieldWidth) { 387 throw new SysTimeFormatException(E_FIELD_OVERFLOW); 388 } 389 if (contentLength >= fieldWidth) return formattedContent; 390 assert (formattedContent.length == toUTFindex(formattedContent, contentLength)); 391 392 // distribute padding 393 ulong padWidth = fieldWidth - contentLength, padLeftWidth = 0, padRightWidth = 0; 394 if (padLeft == dchar.init) { 395 padRightWidth = padWidth; 396 } else if (padRight == dchar.init) { 397 padLeftWidth = padWidth; 398 } else { 399 padLeftWidth = padRightWidth = padWidth / 2; 400 if (padWidth % 2 == 1) { 401 if (moreOnRight) { 402 padRightWidth++; 403 } else { 404 padLeftWidth++; 405 } 406 } 407 } 408 debug (datetimeformat) writefln("Padding distribution: %d %d %d = %d", 409 padLeftWidth, contentLength, padRightWidth, fieldWidth); 410 assert (padLeftWidth + contentLength + padRightWidth == fieldWidth); 411 412 // now do it! 413 char[] result; 414 415 for (int i = 0; i < padLeftWidth; i++) encode(result, padLeft); 416 result ~= formattedContent; 417 for (int i = 0; i < padRightWidth; i++) encode(result, padRight); 418 return cast(string) result; 419 } 420 421 bool beginsElement(char c) { return isAlpha(c) || c == '[' || c == '{'; } 422 bool beginsLiteral(char c) { return c == '\'' || c == '`'; } 423 424 immutable string DIGITS12 = "110123456789"; 425 /+TEN = DIGITS12[1..3], 426 ELEVEN = DIGITS12[0..2], 427 TWELVE = DIGITS12[3..5];+/ 428 429 /*const E_BAD_UTF 430 = "Error in date/time format string: invalid UTF-8 sequence";*/ 431 immutable E_MISSING_LITERAL 432 = "Error in date/time format string: missing character after '`'"; 433 immutable E_UNCLOSED_LITERAL 434 = "Error in date/time format string: unterminated literal portion"; 435 immutable E_UNCLOSED_FIELD 436 = "Error in date/time format string: '[' without matching ']'"; 437 immutable E_UNCLOSED_COLLAPSIBLE 438 = "Error in date/time format string: '{' without matching '}'"; 439 immutable E_UNOPENED_FIELD 440 = "Error in date/time format string: ']' without matching '['"; 441 immutable E_UNOPENED_COLLAPSIBLE 442 = "Error in date/time format string: '}' without matching '{'"; 443 immutable E_INCOMPLETE_FIELD 444 = "Error in date/time format string: Incomplete alignment field"; 445 immutable E_ZERO_FIELD 446 = "Error in date/time format string: Zero-width alignment field"; 447 immutable E_DOUBLE_WIDTH 448 = "Error in date/time format string: Width of alignment field doubly specified"; 449 immutable E_OVERFLOW_WIDTH 450 = "Error in date/time format string: Field width too large"; 451 immutable E_FIELD_OVERFLOW 452 = "Date/time formatting failed: Insufficient field width to hold content"; 453 immutable E_BC_YY 454 = "Date/time formatting failed: Format 'yy' for BC dates undefined"; 455 immutable E_INVALID_DATE_TIME 456 = "Date/time formatting failed: Invalid date/time"; 457 458 string formatBySpec(const SysTime dt, DayOfWeek dow, 459 string spec, ref long lastNumber) { 460 with (dt) switch (spec) { 461 462 case "yy": 463 lastNumber = year; 464 if (year <= 0) { 465 throw new SysTimeFormatException(E_BC_YY); 466 } 467 return formatTwoDigit(cast(byte) (year % 100)); 468 469 case "yyy": 470 lastNumber = year; 471 return to!string(year > 0 ? year : (lastNumber = 1 - dt.year)); 472 473 case "yyyy": 474 lastNumber = year; 475 return format("%04d", year > 0 ? year : 476 (lastNumber = 1 - dt.year)); 477 478 case "YYY": 479 lastNumber = year < 0 ? -year : year; // year.min remains the same 480 return to!string(year); 481 482 case "b": 483 return (year == year.min || year > 0) ? null : "bc"; 484 485 case "bb": 486 return year > 0 ? "ad" : "bc"; 487 488 case "bbb": 489 return year > 0 ? "ce" : "bce"; 490 491 case "bbbb": 492 return (year == year.min || year > 0) ? null : "bce"; 493 494 case "B": 495 return (year == year.min || year > 0) ? null : "BC"; 496 497 case "BB": 498 return year > 0 ? "AD" : "BC"; 499 500 case "BBB": 501 return year > 0 ? "CE" : "BCE"; 502 503 case "BBBB": 504 return (year == year.min || year > 0) ? null : "BCE"; 505 506 case "m": 507 lastNumber = month; 508 return format12(month); 509 510 case "mm": 511 lastNumber = month; 512 char[] fmt = new char[2]; 513 if (month < 10) { 514 fmt[0] = '0'; 515 fmt[1] = cast(char) ('0' + month); 516 } else { 517 fmt[0] = '1'; 518 fmt[1] = cast(char) ('0' - 10 + month); 519 } 520 return cast(string) fmt; 521 522 case "mmm": 523 return SHORT_L_MONTH_NAME[month]; 524 525 case "Mmm": 526 return SHORT_MONTH_NAME[month]; 527 528 case "MMM": 529 return SHORT_U_MONTH_NAME[month]; 530 531 case "mmmm": 532 return LONG_L_MONTH_NAME[month]; 533 534 case "Mmmm": 535 return LONG_MONTH_NAME[month]; 536 537 case "MMMM": 538 return LONG_U_MONTH_NAME[month]; 539 540 case "d": 541 lastNumber = day; 542 return to!string(day); 543 544 case "dd": 545 lastNumber = day; 546 return formatTwoDigit(day); 547 548 case "t": 549 return ordinalSuffix(lastNumber, false); 550 551 case "T": 552 return ordinalSuffix(lastNumber, true); 553 554 case "www": 555 return SHORT_L_DAY_NAME[dow]; 556 557 case "Www": 558 debug (datetimeformat) writefln("Day of week: %d", cast(byte) dow); 559 return SHORT_DAY_NAME[dow]; 560 561 case "WWW": 562 return SHORT_U_DAY_NAME[dow]; 563 564 case "wwww": 565 return LONG_L_DAY_NAME[dow]; 566 567 case "Wwww": 568 return LONG_DAY_NAME[dow]; 569 570 case "WWWW": 571 return LONG_U_DAY_NAME[dow]; 572 573 case "h": 574 lastNumber = hour; 575 if (hour == 0) { 576 return DIGITS12[3..5]; 577 } else if (hour <= 12) { 578 return format12(hour); 579 } else { 580 return format12(hour - 12); 581 } 582 583 case "hh": 584 lastNumber = hour; 585 if (hour == 0) { 586 return DIGITS12[3..5]; 587 } else if (hour <= 12) { 588 return formatTwoDigit(hour); 589 } else { 590 return formatTwoDigit(hour - 12); 591 } 592 593 case "H": 594 lastNumber = hour; 595 return to!string(hour); 596 597 case "HH": 598 lastNumber = hour; 599 return formatTwoDigit(hour); 600 601 case "a": 602 return hour < 12 ? "a" : "p"; 603 604 case "aa": 605 return hour < 12 ? "am" : "pm"; 606 607 case "A": 608 return hour < 12 ? "A" : "P"; 609 610 case "AA": 611 return hour < 12 ? "AM" : "PM"; 612 613 case "i": 614 lastNumber = minute; 615 return to!string(minute); 616 617 case "ii": 618 lastNumber = minute; 619 return formatTwoDigit(minute); 620 621 case "s": 622 lastNumber = second; 623 return to!string(second); 624 625 case "ss": 626 lastNumber = second; 627 return formatTwoDigit(second); 628 629 case "f": 630 lastNumber = fracSecs().total!"msecs" / 100; 631 return DIGITS12[lastNumber+2..lastNumber+3]; 632 633 case "ff": 634 lastNumber = fracSecs().total!"msecs" / 10; 635 return to!string(lastNumber); 636 637 case "FF": 638 lastNumber = fracSecs().total!"msecs" / 10; 639 return formatTwoDigit(cast(byte) lastNumber); 640 641 case "fff": 642 lastNumber = fracSecs().total!"msecs"; 643 return to!string(fracSecs().total!"msecs"); 644 645 case "FFF": 646 lastNumber = fracSecs().total!"msecs"; 647 return format("%03d", fracSecs().total!"msecs"); 648 649 650 /* 651 case "zzzz": 652 return hour == hour.min ? null : 653 timezone().utcOffsetAt(stdTime) >= 0 ? 654 format("+%02d%02d", timezone().utcOffsetAt(stdTime) / 60, 655 timezone().utcOffsetAt(stdTime) % 60) : 656 format("-%02d%02d", -timezone().utcOffsetAt(stdTime) / 60, 657 -timezone().utcOffsetAt(stdTime) % 60); 658 */ 659 660 default: 661 throw new SysTimeFormatException(cast(string) 662 ("Error in date/time format string: Undefined format specifier '" ~ spec ~ "'")); 663 } 664 } 665 666 string formatTwoDigit(int b) 667 in { 668 assert (b == byte.min || (b >= 0 && b <= 99)); 669 } body { 670 if (b == byte.min) return null; 671 char[] fmt = new char[2]; 672 fmt[0] = cast(byte) ('0' + b / 10); 673 fmt[1] = cast(byte) ('0' + b % 10); 674 return cast(string) fmt; 675 } 676 677 string format12(int b) 678 in { 679 assert (b >= 0); 680 assert (b <= 12); 681 } body { 682 switch (b) { 683 case 10: return DIGITS12[1..3]; 684 case 11: return DIGITS12[0..2]; 685 case 12: return DIGITS12[3..5]; 686 default: return DIGITS12[2+b .. 3+b]; 687 } 688 } 689 690 string ordinalSuffix(long lastNumber, bool upperCase) { 691 if (lastNumber < 0) return null; 692 lastNumber %= 100; 693 if (lastNumber >= 4 && lastNumber <= 20) { 694 return upperCase ? "TH" : "th"; 695 } 696 switch (lastNumber % 10) { 697 case 1: 698 return upperCase ? "ST" : "st"; 699 700 case 2: 701 return upperCase ? "ND" : "nd"; 702 703 case 3: 704 return upperCase ? "RD" : "rd"; 705 706 default: 707 return upperCase ? "TH" : "th"; 708 } 709 } 710 } 711 712 713 /// Exception thrown if there was a problem in formatting a date or time. 714 class SysTimeFormatException : Exception { 715 private this(string msg) { 716 super(msg); 717 } 718 } 719 720 721 /// Short (three-letter) names of the days of the week. 722 immutable string[7] SHORT_L_DAY_NAME = [ 723 DayOfWeek.sun: "sun", "mon", "tue", "wed", "thu", "fri", "sat" 724 ]; 725 726 /// Short (three-letter) names of the days of the week. 727 immutable string[7] SHORT_U_DAY_NAME = [ 728 DayOfWeek.sun: "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" 729 ]; 730 731 /// Full names of the days of the week. 732 immutable string[7] LONG_L_DAY_NAME = [ 733 DayOfWeek.sun: "sunday", "monday", "tuesday", "wednesday", 734 "thursday", "friday", "saturday" 735 ]; 736 737 /// Full names of the days of the week. 738 immutable string[7] LONG_U_DAY_NAME = [ 739 DayOfWeek.sun: "SUNDAY", "MONDAY", "TUESDAY", "WEDNESDAY", 740 "THURSDAY", "FRIDAY", "SATURDAY" 741 ]; 742 743 /// Short (three-letter) names of the months of the year. 744 immutable string[13] SHORT_L_MONTH_NAME = [ 745 ['\xFF', '\xFF', '\xFF'], 746 Month.jan: "jan", "feb", "mar", "apr", "may", "jun", 747 "jul", "aug", "sep", "oct", "nov", "dec" 748 ]; 749 750 /// Short (three-letter) names of the months of the year. 751 immutable string[13] SHORT_U_MONTH_NAME = [ 752 ['\xFF', '\xFF', '\xFF'], 753 Month.jan: "JAN", "FEB", "MAR", "APR", "MAY", "JUN", 754 "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" 755 ]; 756 757 /// Full names of the months of the year. 758 immutable string[13] LONG_L_MONTH_NAME = [ 759 null, Month.jan: "january", "february", "march", "april", "may", "june", 760 "july", "august", "september", "october", "november", "december" 761 ]; 762 763 /// Full names of the months of the year. 764 immutable string[13] LONG_U_MONTH_NAME = [ 765 null, Month.jan: "JANUARY", "FEBRUARY", "MARCH", "APRIL", "MAY", "JUNE", 766 "JULY", "AUGUST", "SEPTEMBER", "OCTOBER", "NOVEMBER", "DECEMBER" 767 ]; 768 769 unittest { 770 import std.stdio; 771 772 writefln("Unittest commenced at %s", Clock.currTime.toString); 773 774 SysTime dt = SysTime(DateTime(2005, 9, 8, 16, 51, 9), dur!"msecs"(427)); 775 // basic formatting 776 assert (dt.format("dd/mm/yy") == "08/09/05"); 777 assert (dt.format("Www dt Mmm yyyy BB") == "Thu 8th Sep 2005 AD"); 778 assert (dt.format("h:ii AA") == "4:51 PM"); 779 assert (dt.format("yyyy-mm-dd HH:ii:ss") == "2005-09-08 16:51:09"); 780 assert (dt.format("HH:ii:ss.FFF") == "16:51:09.427"); 781 // alignment fields 782 assert (dt.format("[------Wwww.....]") == "--Thursday."); 783 assert (dt.format("[11-Wwww.]") == "--Thursday."); 784 assert (dt.format("[-----Wwww......]") == "-Thursday.."); 785 assert (dt.format("[-Wwww.11]") == "-Thursday.."); 786 assert (dt.format("[9`1Www]") == "111111Thu"); 787 assert (dt.format("[`1Wwww-10]") == "1Thursday-"); 788 assert (dt.format("[d/m/yyy ]HH:ii:ss") == "8/9/2005 16:51:09"); 789 790 assert (dt.format("d Mmm yyy{ B}{ HH:ii:ss}") == "8 Sep 2005 16:51:09"); 791 assert (dt.format("{d }{Mmm }yyy BB") == "8 Sep 2005 AD"); 792 assert (dt.format("HH:ii{:ss}{.FFF}") == "16:51:09.427"); 793 794 assert (dt.format("HH:ii{:ss}{.FFF}") == "16:51:09.427"); 795 dt.fracSecs(dur!"msecs"(0)); 796 assert (dt.format("HH:ii{:ss}{.FFF}") == "16:51:09.000"); 797 dt.second = 0; 798 assert (dt.format("HH:ii{:ss}{.FFF}") == "16:51:00.000"); 799 assert (dt.format("d Mmm yyy{ B}{ HH:ii:ss}") == "8 Sep 2005 16:51:00"); 800 dt.hour = 0; 801 assert (dt.format("d Mmm yyy{ B}{ HH:ii:ss}") == "8 Sep 2005 00:51:00"); 802 dt.minute = 0; 803 assert (dt.format("d Mmm yyy{ B}{ HH:ii:ss}") == "8 Sep 2005 00:00:00"); 804 assert (dt.format("{d }{Mmm }yyy BB") == "8 Sep 2005 AD"); 805 dt.month = Month.min; 806 assert (dt.format("{d }{Mmm }yyy BB") == "8 Jan 2005 AD"); 807 dt.day = 1; 808 assert (dt.format("{d }{Mmm }yyy BB") == "1 Jan 2005 AD"); 809 810 dt.month = Month.sep; 811 dt.day = 8; 812 813 // nesting of fields and collapsible portions 814 assert (dt.format("[13 Mmmm [d..]]") == " September 8."); 815 assert (dt.format("[13 Mmmm{ d}]") == " September 8"); 816 dt.day = 1; 817 assert (dt.format("[13 Mmmm{ d}]") == " September 1"); 818 assert (dt.format("{[13 Mmmm{ d}]}") == " September 1"); 819 dt.month = Month.min; 820 assert (dt.format("{[13 Mmmm{ d}]}") == " January 1"); 821 dt.day = 8; 822 assert (dt.format("{[13 Mmmm{ d}]}") == " January 8"); 823 }