1 /******************************************************************************* 2 3 copyright: Copyright (c) 2005 John Chapman. All rights reserved 4 5 license: BSD style: $(LICENSE) 6 7 version: Initial release: 2005 8 9 author: John Chapman 10 11 ******************************************************************************/ 12 13 module tango.text.locale.Convert; 14 15 private import tango.time.WallClock; 16 17 private import tango.core.Exception; 18 19 private import tango.text.locale.Core; 20 21 private import tango.time.chrono.Calendar; 22 23 private import Integer = tango.text.convert.Integer; 24 25 /****************************************************************************** 26 27 ******************************************************************************/ 28 29 private struct Result 30 { 31 private size_t index; 32 private char[] target_; 33 34 /********************************************************************** 35 36 **********************************************************************/ 37 38 private static Result opCall (char[] target) 39 { 40 Result result; 41 42 result.target_ = target; 43 return result; 44 } 45 46 /********************************************************************** 47 48 **********************************************************************/ 49 50 private void opOpAssign(immutable(char)[] s : "~") (const(char)[] rhs) 51 { 52 auto end = index + rhs.length; 53 54 target_[index .. end] = rhs[]; 55 index = end; 56 } 57 58 /********************************************************************** 59 60 **********************************************************************/ 61 62 private void opOpAssign(immutable(char)[] s : "~") (char rhs) 63 { 64 target_[index++] = rhs; 65 } 66 67 /********************************************************************** 68 69 **********************************************************************/ 70 71 private char[] get () 72 { 73 return target_[0 .. index]; 74 } 75 76 /********************************************************************** 77 78 **********************************************************************/ 79 80 private char[] scratch () 81 { 82 return target_; 83 } 84 } 85 86 87 /****************************************************************************** 88 89 * Converts the value of this instance to its equivalent string representation using the specified _format and culture-specific formatting information. 90 * Params: 91 * format = A _format string. 92 * formatService = An IFormatService that provides culture-specific formatting information. 93 * Returns: A string representation of the value of this instance as specified by format and formatService. 94 * Remarks: See $(LINK2 datetimeformat.html, Time Formatting) for more information about date and time formatting. 95 * Examples: 96 * --- 97 * import tango.io.Print, tango.text.locale.Core, tango.time.WallClock; 98 * 99 * void main() { 100 * Culture culture = Culture.current; 101 * Time now = WallClock.now; 102 * 103 * Println("Current date and time: %s", now.toString()); 104 * Println(); 105 * 106 * // Format the current date and time in a number of ways. 107 * Println("Culture: %s", culture.englishName); 108 * Println(); 109 * 110 * Println("Short date: %s", now.toString("d")); 111 * Println("Long date: %s", now.toString("D")); 112 * Println("Short time: %s", now.toString("t")); 113 * Println("Long time: %s", now.toString("T")); 114 * Println("General date short time: %s", now.toString("g")); 115 * Println("General date long time: %s", now.toString("G")); 116 * Println("Month: %s", now.toString("M")); 117 * Println("RFC1123: %s", now.toString("R")); 118 * Println("Sortable: %s", now.toString("s")); 119 * Println("Year: %s", now.toString("Y")); 120 * Println(); 121 * 122 * // Display the same values using a different culture. 123 * culture = Culture.getCulture("fr-FR"); 124 * Println("Culture: %s", culture.englishName); 125 * Println(); 126 * 127 * Println("Short date: %s", now.toString("d", culture)); 128 * Println("Long date: %s", now.toString("D", culture)); 129 * Println("Short time: %s", now.toString("t", culture)); 130 * Println("Long time: %s", now.toString("T", culture)); 131 * Println("General date short time: %s", now.toString("g", culture)); 132 * Println("General date long time: %s", now.toString("G", culture)); 133 * Println("Month: %s", now.toString("M", culture)); 134 * Println("RFC1123: %s", now.toString("R", culture)); 135 * Println("Sortable: %s", now.toString("s", culture)); 136 * Println("Year: %s", now.toString("Y", culture)); 137 * Println(); 138 * } 139 * 140 * // Produces the following output: 141 * // Current date and time: 26/05/2006 10:04:57 AM 142 * // 143 * // Culture: English (United Kingdom) 144 * // 145 * // Short date: 26/05/2006 146 * // Long date: 26 May 2006 147 * // Short time: 10:04 148 * // Long time: 10:04:57 AM 149 * // General date short time: 26/05/2006 10:04 150 * // General date long time: 26/05/2006 10:04:57 AM 151 * // Month: 26 May 152 * // RFC1123: Fri, 26 May 2006 10:04:57 GMT 153 * // Sortable: 2006-05-26T10:04:57 154 * // Year: May 2006 155 * // 156 * // Culture: French (France) 157 * // 158 * // Short date: 26/05/2006 159 * // Long date: vendredi 26 mai 2006 160 * // Short time: 10:04 161 * // Long time: 10:04:57 162 * // General date short time: 26/05/2006 10:04 163 * // General date long time: 26/05/2006 10:04:57 164 * // Month: 26 mai 165 * // RFC1123: ven., 26 mai 2006 10:04:57 GMT 166 * // Sortable: 2006-05-26T10:04:57 167 * // Year: mai 2006 168 * --- 169 170 ******************************************************************************/ 171 172 public char[] formatDateTime (char[] output, Time dateTime, const(char)[] format, IFormatService formatService = null) 173 { 174 return formatDateTime (output, dateTime, format, DateTimeFormat.getInstance(formatService)); 175 } 176 177 char[] formatDateTime (char[] output, Time dateTime, const(char)[] format, DateTimeFormat dtf) 178 { 179 /********************************************************************** 180 181 **********************************************************************/ 182 183 const(char)[] expandKnownFormat(const(char)[] format, ref Time dateTime) 184 { 185 const(char)[] f; 186 187 switch (format[0]) 188 { 189 case 'd': 190 f = dtf.shortDatePattern; 191 break; 192 case 'D': 193 f = dtf.longDatePattern; 194 break; 195 case 'f': 196 f = dtf.longDatePattern ~ " " ~ dtf.shortTimePattern; 197 break; 198 case 'F': 199 f = dtf.fullDateTimePattern; 200 break; 201 case 'g': 202 f = dtf.generalShortTimePattern; 203 break; 204 case 'G': 205 f = dtf.generalLongTimePattern; 206 break; 207 case 'm': 208 case 'M': 209 f = dtf.monthDayPattern; 210 break; 211 case 'r': 212 case 'R': 213 f = dtf.rfc1123Pattern; 214 break; 215 case 's': 216 f = dtf.sortableDateTimePattern; 217 break; 218 case 't': 219 f = dtf.shortTimePattern; 220 break; 221 case 'T': 222 f = dtf.longTimePattern; 223 break; 224 version (Full) 225 { 226 case 'u': 227 dateTime = dateTime.toUniversalTime(); 228 dtf = DateTimeFormat.invariantFormat; 229 f = dtf.universalSortableDateTimePattern; 230 break; 231 case 'U': 232 dtf = cast(DateTimeFormat) dtf.clone(); 233 dateTime = dateTime.toUniversalTime(); 234 if (typeid(typeof(dtf.calendar)) !is typeid(Gregorian)) 235 dtf.calendar = Gregorian.generic; 236 f = dtf.fullDateTimePattern; 237 break; 238 } 239 case 'y': 240 case 'Y': 241 f = dtf.yearMonthPattern; 242 break; 243 default: 244 throw new IllegalArgumentException("Invalid date format."); 245 } 246 247 return f; 248 } 249 250 /********************************************************************** 251 252 **********************************************************************/ 253 254 char[] formatCustom (ref Result result, Time dateTime, const(char)[] format) 255 { 256 257 int parseRepeat(const(char)[] format, int pos, char c) 258 { 259 int n = pos + 1; 260 while (n < format.length && format[n] is c) 261 n++; 262 return n - pos; 263 } 264 265 const(char)[] formatDayOfWeek(Calendar.DayOfWeek dayOfWeek, int rpt) 266 { 267 if (rpt is 3) 268 return dtf.getAbbreviatedDayName(dayOfWeek); 269 return dtf.getDayName(dayOfWeek); 270 } 271 272 const(char)[] formatMonth(int month, int rpt) 273 { 274 if (rpt is 3) 275 return dtf.getAbbreviatedMonthName(month); 276 return dtf.getMonthName(month); 277 } 278 279 char[] formatInt (char[] tmp, int v, int minimum) 280 { 281 auto num = Integer.format (tmp, v, "u"); 282 if ((minimum -= num.length) > 0) 283 { 284 auto p = tmp.ptr + tmp.length - num.length; 285 while (minimum--) 286 *--p = '0'; 287 num = tmp [p-tmp.ptr .. $]; 288 } 289 return num; 290 } 291 292 int parseQuote(const(char)[] format, int pos, out char[] result) 293 { 294 int start = pos; 295 char chQuote = format[pos++]; 296 bool found; 297 while (pos < format.length) 298 { 299 char c = format[pos++]; 300 if (c is chQuote) 301 { 302 found = true; 303 break; 304 } 305 else 306 if (c is '\\') 307 { // escaped 308 if (pos < format.length) 309 result ~= format[pos++]; 310 } 311 else 312 result ~= c; 313 } 314 return pos - start; 315 } 316 317 318 Calendar calendar = dtf.calendar; 319 bool justTime = true; 320 int index, len; 321 char[10] tmp; 322 323 if (format[0] is '%') 324 { 325 // specifiers for both standard format strings and custom ones 326 __gshared immutable immutable(char)[] commonSpecs = "dmMsty"; 327 foreach (c; commonSpecs) 328 if (format[1] is c) 329 { 330 index += 1; 331 break; 332 } 333 } 334 335 while (index < format.length) 336 { 337 char c = format[index]; 338 auto time = dateTime.time; 339 340 switch (c) 341 { 342 case 'd': // day 343 len = parseRepeat(format, index, c); 344 if (len <= 2) 345 { 346 int day = calendar.getDayOfMonth(dateTime); 347 result ~= formatInt (tmp, day, len); 348 } 349 else 350 result ~= formatDayOfWeek(calendar.getDayOfWeek(dateTime), len); 351 justTime = false; 352 break; 353 354 case 'M': // month 355 len = parseRepeat(format, index, c); 356 int month = calendar.getMonth(dateTime); 357 if (len <= 2) 358 result ~= formatInt (tmp, month, len); 359 else 360 result ~= formatMonth(month, len); 361 justTime = false; 362 break; 363 case 'y': // year 364 len = parseRepeat(format, index, c); 365 int year = calendar.getYear(dateTime); 366 // Two-digit years for Japanese 367 if (calendar.id is Calendar.JAPAN) 368 result ~= formatInt (tmp, year, 2); 369 else 370 { 371 if (len <= 2) 372 result ~= formatInt (tmp, year % 100, len); 373 else 374 result ~= formatInt (tmp, year, len); 375 } 376 justTime = false; 377 break; 378 case 'h': // hour (12-hour clock) 379 len = parseRepeat(format, index, c); 380 int hour = time.hours % 12; 381 if (hour is 0) 382 hour = 12; 383 result ~= formatInt (tmp, hour, len); 384 break; 385 case 'H': // hour (24-hour clock) 386 len = parseRepeat(format, index, c); 387 result ~= formatInt (tmp, time.hours, len); 388 break; 389 case 'm': // minute 390 len = parseRepeat(format, index, c); 391 result ~= formatInt (tmp, time.minutes, len); 392 break; 393 case 's': // second 394 len = parseRepeat(format, index, c); 395 result ~= formatInt (tmp, time.seconds, len); 396 break; 397 case 't': // AM/PM 398 len = parseRepeat(format, index, c); 399 if (len is 1) 400 { 401 if (time.hours < 12) 402 { 403 if (dtf.amDesignator.length != 0) 404 result ~= dtf.amDesignator[0]; 405 } 406 else 407 { 408 if (dtf.pmDesignator.length != 0) 409 result ~= dtf.pmDesignator[0]; 410 } 411 } 412 else 413 result ~= (time.hours < 12) ? dtf.amDesignator : dtf.pmDesignator; 414 break; 415 case 'z': // timezone offset 416 len = parseRepeat(format, index, c); 417 version (Full) 418 { 419 TimeSpan offset = (justTime && dateTime.ticks < TICKS_PER_DAY) 420 ? TimeZone.current.getUtcOffset(WallClock.now) 421 : TimeZone.current.getUtcOffset(dateTime); 422 int hours = offset.hours; 423 int minutes = offset.minutes; 424 result ~= (offset.backward) ? '-' : '+'; 425 } 426 else 427 { 428 auto minutes = cast(int) (WallClock.zone.minutes); 429 if (minutes < 0) 430 minutes = -minutes, result ~= '-'; 431 else 432 result ~= '+'; 433 int hours = minutes / 60; 434 minutes %= 60; 435 } 436 if (len is 1) 437 result ~= formatInt (tmp, hours, 1); 438 else 439 if (len is 2) 440 result ~= formatInt (tmp, hours, 2); 441 else 442 { 443 result ~= formatInt (tmp, hours, 2); 444 result ~= ':'; 445 result ~= formatInt (tmp, minutes, 2); 446 } 447 break; 448 case ':': // time separator 449 len = 1; 450 result ~= dtf.timeSeparator; 451 break; 452 case '/': // date separator 453 len = 1; 454 result ~= dtf.dateSeparator; 455 break; 456 case '\"': // string literal 457 case '\'': // char literal 458 char[] quote; 459 len = parseQuote(format, index, quote); 460 result ~= quote; 461 break; 462 default: 463 len = 1; 464 result ~= c; 465 break; 466 } 467 index += len; 468 } 469 return result.get(); 470 } 471 472 473 auto result = Result (output); 474 475 if (format is null) 476 format = "G"; // Default to general format. 477 478 if (format.length is 1) // It might be one of our shortcuts. 479 format = expandKnownFormat (format, dateTime); 480 481 return formatCustom (result, dateTime, format); 482 } 483 484 485 486 /******************************************************************************* 487 488 *******************************************************************************/ 489 490 private extern (C) char* ecvt(double d, int digits, out int decpt, out bool sign); 491 492 /******************************************************************************* 493 494 *******************************************************************************/ 495 496 // Must match NumberFormat.decimalPositivePattern 497 package __gshared immutable immutable(char)[] positiveNumberFormat = "#"; 498 499 // Must match NumberFormat.decimalNegativePattern 500 package __gshared immutable immutable(char)[][] negativeNumberFormats = 501 [ 502 "(#)", "-#", "- #", "#-", "# -" 503 ]; 504 505 // Must match NumberFormat.currencyPositivePattern 506 package __gshared immutable immutable(char)[][] positiveCurrencyFormats = 507 [ 508 "$#", "#$", "$ #", "# $" 509 ]; 510 511 // Must match NumberFormat.currencyNegativePattern 512 package __gshared immutable immutable(char)[][] negativeCurrencyFormats = 513 [ 514 "($#)", "-$#", "$-#", "$#-", "(#$)", 515 "-#$", "#-$", "#$-", "-# $", "-$ #", 516 "# $-", "$ #-", "$ -#", "#- $", "($ #)", "(# $)" 517 ]; 518 519 /******************************************************************************* 520 521 *******************************************************************************/ 522 523 package template charTerm (T) 524 { 525 package int charTerm(T* s) 526 { 527 int i; 528 while (*s++ != '\0') 529 i++; 530 return i; 531 } 532 } 533 534 /******************************************************************************* 535 536 *******************************************************************************/ 537 538 char[] longToString (char[] buffer, long value, int digits, const(char)[] negativeSign) 539 { 540 if (digits < 1) 541 digits = 1; 542 543 auto n = buffer.length; 544 ulong uv = (value >= 0) ? value : cast(ulong) -value; 545 546 if (uv > uint.max) 547 { 548 while (--digits >= 0 || uv != 0) 549 { 550 buffer[--n] = cast(char)(uv % 10 + '0'); 551 uv /= 10; 552 } 553 } 554 else 555 { 556 uint v = cast(uint) uv; 557 while (--digits >= 0 || v != 0) 558 { 559 buffer[--n] = cast(char)(v % 10 + '0'); 560 v /= 10; 561 } 562 } 563 564 565 if (value < 0) 566 { 567 for (size_t i = negativeSign.length; i > 0;) 568 buffer[--n] = negativeSign[--i]; 569 } 570 571 return buffer[n .. $]; 572 } 573 574 /******************************************************************************* 575 576 *******************************************************************************/ 577 578 char[] longToHexString (char[] buffer, ulong value, int digits, char format) 579 { 580 if (digits < 1) 581 digits = 1; 582 583 auto n = buffer.length; 584 while (--digits >= 0 || value != 0) 585 { 586 auto v = cast(uint) value & 0xF; 587 buffer[--n] = cast(char)((v < 10) ? v + '0' : v + format - ('X' - 'A' + 10)); 588 value >>= 4; 589 } 590 591 return buffer[n .. $]; 592 } 593 594 /******************************************************************************* 595 596 *******************************************************************************/ 597 598 char[] longToBinString (char[] buffer, ulong value, int digits) 599 { 600 if (digits < 1) 601 digits = 1; 602 603 auto n = buffer.length; 604 while (--digits >= 0 || value != 0) 605 { 606 buffer[--n] = cast(char)((value & 1) + '0'); 607 value >>= 1; 608 } 609 610 return buffer[n .. $]; 611 } 612 613 /******************************************************************************* 614 615 *******************************************************************************/ 616 617 char parseFormatSpecifier (const(char)[] format, out int length) 618 { 619 int i = -1; 620 char specifier; 621 622 if (format.length) 623 { 624 auto s = format[0]; 625 626 if (s >= 'A' && s <= 'Z' || s >= 'a' && s <= 'z') 627 { 628 specifier = s; 629 630 foreach (char c; format [1..$]) 631 if (c >= '0' && c <= '9') 632 { 633 c -= '0'; 634 if (i < 0) 635 i = c; 636 else 637 i = i * 10 + c; 638 } 639 else 640 break; 641 } 642 } 643 else 644 specifier = 'G'; 645 646 length = i; 647 return specifier; 648 } 649 650 /******************************************************************************* 651 652 *******************************************************************************/ 653 654 char[] formatInteger (char[] output, long value, const(char)[] format, NumberFormat nf) 655 { 656 int length; 657 auto specifier = parseFormatSpecifier (format, length); 658 659 switch (specifier) 660 { 661 case 'g': 662 case 'G': 663 if (length > 0) 664 break; 665 // Fall through. 666 goto case; 667 case 'd': 668 case 'D': 669 return longToString (output, value, length, nf.negativeSign); 670 671 case 'x': 672 case 'X': 673 return longToHexString (output, cast(ulong)value, length, specifier); 674 675 case 'b': 676 case 'B': 677 return longToBinString (output, cast(ulong)value, length); 678 679 default: 680 break; 681 } 682 683 Result result = Result (output); 684 Number number = Number (value); 685 if (specifier != char.init) 686 return toString (number, result, specifier, length, nf); 687 688 return number.toStringFormat (result, format, nf); 689 } 690 691 /******************************************************************************* 692 693 *******************************************************************************/ 694 695 private enum { 696 EXP = 0x7ff, 697 NAN_FLAG = 0x80000000, 698 INFINITY_FLAG = 0x7fffffff, 699 } 700 701 char[] formatDouble (char[] output, double value, const(char)[] format, NumberFormat nf) 702 { 703 int length; 704 int precision = 6; 705 Result result = Result (output); 706 char specifier = parseFormatSpecifier (format, length); 707 708 switch (specifier) 709 { 710 case 'r': 711 case 'R': 712 Number number = Number (value, 15); 713 714 if (number.scale == NAN_FLAG) 715 // Bad dup? 716 return nf.nanSymbol.dup; 717 718 if (number.scale == INFINITY_FLAG) 719 // Bad dup? 720 return number.sign ? nf.negativeInfinitySymbol.dup 721 : nf.positiveInfinitySymbol.dup; 722 723 double d; 724 number.toDouble(d); 725 if (d == value) 726 return toString (number, result, 'G', 15, nf); 727 728 number = Number(value, 17); 729 return toString (number, result, 'G', 17, nf); 730 731 case 'g': 732 case 'G': 733 if (length > 15) 734 precision = 17; 735 // Fall through. 736 goto default; 737 default: 738 break; 739 } 740 741 Number number = Number(value, precision); 742 743 if (number.scale == NAN_FLAG) 744 // Bad dup? 745 return nf.nanSymbol.dup; 746 747 if (number.scale == INFINITY_FLAG) 748 // Bad dup? 749 return number.sign ? nf.negativeInfinitySymbol.dup 750 : nf.positiveInfinitySymbol.dup; 751 752 if (specifier != char.init) 753 return toString (number, result, specifier, length, nf); 754 755 return number.toStringFormat (result, format, nf); 756 } 757 758 /******************************************************************************* 759 760 *******************************************************************************/ 761 762 void formatGeneral (ref Number number, ref Result target, int length, char format, NumberFormat nf) 763 { 764 int pos = number.scale; 765 766 auto p = number.digits.ptr; 767 if (pos > 0) 768 { 769 while (pos > 0) 770 { 771 target ~= (*p != '\0') ? *p++ : '0'; 772 pos--; 773 } 774 } 775 else 776 target ~= '0'; 777 778 if (*p != '\0') 779 { 780 target ~= nf.numberDecimalSeparator; 781 while (pos < 0) 782 { 783 target ~= '0'; 784 pos++; 785 } 786 787 while (*p != '\0') 788 target ~= *p++; 789 } 790 } 791 792 /******************************************************************************* 793 794 *******************************************************************************/ 795 796 void formatNumber (ref Number number, ref Result target, int length, NumberFormat nf) 797 { 798 const(char)[] format = number.sign ? negativeNumberFormats[nf.numberNegativePattern] 799 : positiveNumberFormat; 800 801 // Parse the format. 802 foreach (char c; format) 803 { 804 switch (c) 805 { 806 case '#': 807 formatFixed (number, target, length, nf.numberGroupSizes, 808 nf.numberDecimalSeparator, nf.numberGroupSeparator); 809 break; 810 811 case '-': 812 target ~= nf.negativeSign; 813 break; 814 815 default: 816 target ~= c; 817 break; 818 } 819 } 820 } 821 822 /******************************************************************************* 823 824 *******************************************************************************/ 825 826 void formatCurrency (ref Number number, ref Result target, int length, NumberFormat nf) 827 { 828 const(char)[] format = number.sign ? negativeCurrencyFormats[nf.currencyNegativePattern] 829 : positiveCurrencyFormats[nf.currencyPositivePattern]; 830 831 // Parse the format. 832 foreach (char c; format) 833 { 834 switch (c) 835 { 836 case '#': 837 formatFixed (number, target, length, nf.currencyGroupSizes, 838 nf.currencyDecimalSeparator, nf.currencyGroupSeparator); 839 break; 840 841 case '-': 842 target ~= nf.negativeSign; 843 break; 844 845 case '$': 846 target ~= nf.currencySymbol; 847 break; 848 849 default: 850 target ~= c; 851 break; 852 } 853 } 854 } 855 856 /******************************************************************************* 857 858 *******************************************************************************/ 859 860 void formatFixed (ref Number number, ref Result target, int length, 861 const(int)[] groupSizes, const(char)[] decimalSeparator, const(char)[] groupSeparator) 862 { 863 int pos = number.scale; 864 auto p = number.digits.ptr; 865 866 if (pos > 0) 867 { 868 if (groupSizes.length != 0) 869 { 870 // Calculate whether we have enough digits to format. 871 int count = groupSizes[0]; 872 int index, size; 873 874 while (pos > count) 875 { 876 size = groupSizes[index]; 877 if (size == 0) 878 break; 879 880 if (index < groupSizes.length - 1) 881 index++; 882 883 count += groupSizes[index]; 884 } 885 886 size = (count == 0) ? 0 : groupSizes[0]; 887 888 // Insert the separator according to groupSizes. 889 int end = charTerm(p); 890 int start = (pos < end) ? pos : end; 891 892 893 const(char)[] separator = groupSeparator; 894 index = 0; 895 896 // questionable: use the back end of the output buffer to 897 // format the separators, and then copy back to start 898 char[] temp = target.scratch(); 899 auto ii = temp.length; 900 901 for (int c, i = pos - 1; i >= 0; i--) 902 { 903 temp[--ii] = (i < start) ? number.digits[i] : '0'; 904 if (size > 0) 905 { 906 c++; 907 if (c == size && i != 0) 908 { 909 size_t iii = ii - separator.length; 910 temp[iii .. ii] = separator[]; 911 ii = iii; 912 913 if (index < groupSizes.length - 1) 914 size = groupSizes[++index]; 915 916 c = 0; 917 } 918 } 919 } 920 target ~= temp[ii..$]; 921 p += start; 922 } 923 else 924 { 925 while (pos > 0) 926 { 927 target ~= (*p != '\0') ? *p++ : '0'; 928 pos--; 929 } 930 } 931 } 932 else 933 // Negative scale. 934 target ~= '0'; 935 936 if (length > 0) 937 { 938 target ~= decimalSeparator; 939 while (pos < 0 && length > 0) 940 { 941 target ~= '0'; 942 pos++; 943 length--; 944 } 945 946 while (length > 0) 947 { 948 target ~= (*p != '\0') ? *p++ : '0'; 949 length--; 950 } 951 } 952 } 953 954 /****************************************************************************** 955 956 ******************************************************************************/ 957 958 char[] toString (ref Number number, ref Result result, char format, int length, NumberFormat nf) 959 { 960 switch (format) 961 { 962 case 'c': 963 case 'C': 964 // Currency 965 if (length < 0) 966 length = nf.currencyDecimalDigits; 967 968 number.round(number.scale + length); 969 formatCurrency (number, result, length, nf); 970 break; 971 972 case 'f': 973 case 'F': 974 // Fixed 975 if (length < 0) 976 length = nf.numberDecimalDigits; 977 978 number.round(number.scale + length); 979 if (number.sign) 980 result ~= nf.negativeSign; 981 982 formatFixed (number, result, length, null, nf.numberDecimalSeparator, null); 983 break; 984 985 case 'n': 986 case 'N': 987 // Number 988 if (length < 0) 989 length = nf.numberDecimalDigits; 990 991 number.round (number.scale + length); 992 formatNumber (number, result, length, nf); 993 break; 994 995 case 'g': 996 case 'G': 997 // General 998 if (length < 1) 999 length = number.precision; 1000 1001 number.round(length); 1002 if (number.sign) 1003 result ~= nf.negativeSign; 1004 1005 formatGeneral (number, result, length, (format == 'g') ? 'e' : 'E', nf); 1006 break; 1007 1008 default: 1009 return "{invalid FP format specifier '".dup ~ format ~ "'}".dup; 1010 } 1011 return result.get(); 1012 } 1013 1014 1015 /******************************************************************************* 1016 1017 *******************************************************************************/ 1018 1019 private struct Number 1020 { 1021 int scale; 1022 bool sign; 1023 int precision; 1024 char[32] digits = void; 1025 1026 /********************************************************************** 1027 1028 **********************************************************************/ 1029 1030 private static Number opCall (long value) 1031 { 1032 Number number; 1033 number.precision = 20; 1034 1035 if (value < 0) 1036 { 1037 number.sign = true; 1038 value = -value; 1039 } 1040 1041 char[20] buffer = void; 1042 int n = buffer.length; 1043 1044 while (value != 0) 1045 { 1046 buffer[--n] = cast(char)(value % 10 + '0'); 1047 value /= 10; 1048 } 1049 1050 int end = number.scale = -(n - cast(int)buffer.length); 1051 number.digits[0 .. end] = buffer[n .. n + end]; 1052 number.digits[end] = '\0'; 1053 1054 return number; 1055 } 1056 1057 /********************************************************************** 1058 1059 **********************************************************************/ 1060 1061 private static Number opCall (double value, int precision) 1062 { 1063 Number number; 1064 number.precision = precision; 1065 1066 auto p = number.digits.ptr; 1067 long bits = *cast(long*) & value; 1068 long mant = bits & 0x000FFFFFFFFFFFFFL; 1069 int exp = cast(int)((bits >> 52) & EXP); 1070 1071 if (exp == EXP) 1072 { 1073 number.scale = (mant != 0) ? NAN_FLAG : INFINITY_FLAG; 1074 if (((bits >> 63) & 1) != 0) 1075 number.sign = true; 1076 } 1077 else 1078 { 1079 // Get the digits, decimal point and sign. 1080 char* chars = ecvt(value, number.precision, number.scale, number.sign); 1081 if (*chars != '\0') 1082 { 1083 while (*chars != '\0') 1084 *p++ = *chars++; 1085 } 1086 } 1087 1088 *p = '\0'; 1089 return number; 1090 } 1091 1092 /********************************************************************** 1093 1094 **********************************************************************/ 1095 1096 private bool toDouble(out double value) 1097 { 1098 __gshared immutable ulong[] pow10 = 1099 [ 1100 0xa000000000000000UL, 1101 0xc800000000000000UL, 1102 0xfa00000000000000UL, 1103 0x9c40000000000000UL, 1104 0xc350000000000000UL, 1105 0xf424000000000000UL, 1106 0x9896800000000000UL, 1107 0xbebc200000000000UL, 1108 0xee6b280000000000UL, 1109 0x9502f90000000000UL, 1110 0xba43b74000000000UL, 1111 0xe8d4a51000000000UL, 1112 0x9184e72a00000000UL, 1113 0xb5e620f480000000UL, 1114 0xe35fa931a0000000UL, 1115 0xcccccccccccccccdUL, 1116 0xa3d70a3d70a3d70bUL, 1117 0x83126e978d4fdf3cUL, 1118 0xd1b71758e219652eUL, 1119 0xa7c5ac471b478425UL, 1120 0x8637bd05af6c69b7UL, 1121 0xd6bf94d5e57a42beUL, 1122 0xabcc77118461ceffUL, 1123 0x89705f4136b4a599UL, 1124 0xdbe6fecebdedd5c2UL, 1125 0xafebff0bcb24ab02UL, 1126 0x8cbccc096f5088cfUL, 1127 0xe12e13424bb40e18UL, 1128 0xb424dc35095cd813UL, 1129 0x901d7cf73ab0acdcUL, 1130 0x8e1bc9bf04000000UL, 1131 0x9dc5ada82b70b59eUL, 1132 0xaf298d050e4395d6UL, 1133 0xc2781f49ffcfa6d4UL, 1134 0xd7e77a8f87daf7faUL, 1135 0xefb3ab16c59b14a0UL, 1136 0x850fadc09923329cUL, 1137 0x93ba47c980e98cdeUL, 1138 0xa402b9c5a8d3a6e6UL, 1139 0xb616a12b7fe617a8UL, 1140 0xca28a291859bbf90UL, 1141 0xe070f78d39275566UL, 1142 0xf92e0c3537826140UL, 1143 0x8a5296ffe33cc92cUL, 1144 0x9991a6f3d6bf1762UL, 1145 0xaa7eebfb9df9de8aUL, 1146 0xbd49d14aa79dbc7eUL, 1147 0xd226fc195c6a2f88UL, 1148 0xe950df20247c83f8UL, 1149 0x81842f29f2cce373UL, 1150 0x8fcac257558ee4e2UL, 1151 ]; 1152 1153 __gshared immutable uint[] pow10Exp = 1154 [ 1155 4, 7, 10, 14, 17, 20, 24, 27, 30, 34, 1156 37, 40, 44, 47, 50, 54, 107, 160, 213, 266, 1157 319, 373, 426, 479, 532, 585, 638, 691, 745, 798, 1158 851, 904, 957, 1010, 1064, 1117 1159 ]; 1160 1161 uint getDigits(char* p, int len) 1162 { 1163 char* end = p + len; 1164 uint r = *p - '0'; 1165 p++; 1166 while (p < end) 1167 { 1168 r = 10 * r + *p - '0'; 1169 p++; 1170 } 1171 return r; 1172 } 1173 1174 ulong mult64(uint val1, uint val2) 1175 { 1176 return cast(ulong)val1 * cast(ulong)val2; 1177 } 1178 1179 ulong mult64L(ulong val1, ulong val2) 1180 { 1181 ulong v = mult64(cast(uint)(val1 >> 32), cast(uint)(val2 >> 32)); 1182 v += mult64(cast(uint)(val1 >> 32), cast(uint)val2) >> 32; 1183 v += mult64(cast(uint)val1, cast(uint)(val2 >> 32)) >> 32; 1184 return v; 1185 } 1186 1187 auto p = digits.ptr; 1188 int count = charTerm(p); 1189 int left = count; 1190 1191 while (*p == '0') 1192 { 1193 left--; 1194 p++; 1195 } 1196 1197 // If the digits consist of nothing but zeros... 1198 if (left == 0) 1199 { 1200 value = 0.0; 1201 return true; 1202 } 1203 1204 // Get digits, 9 at a time. 1205 int n = (left > 9) ? 9 : left; 1206 left -= n; 1207 ulong bits = getDigits(p, n); 1208 if (left > 0) 1209 { 1210 n = (left > 9) ? 9 : left; 1211 left -= n; 1212 bits = mult64(cast(uint)bits, cast(uint)(pow10[n - 1] >>> (64 - pow10Exp[n - 1]))); 1213 bits += getDigits(p + 9, n); 1214 } 1215 1216 int scale = this.scale - (count - left); 1217 int s = (scale < 0) ? -scale : scale; 1218 1219 if (s >= 352) 1220 { 1221 *cast(long*)&value = (scale > 0) ? 0x7FF0000000000000 : 0; 1222 return false; 1223 } 1224 1225 // Normalise mantissa and bits. 1226 int bexp = 64; 1227 int nzero; 1228 if ((bits >> 32) != 0) 1229 nzero = 32; 1230 1231 if ((bits >> (16 + nzero)) != 0) 1232 nzero += 16; 1233 1234 if ((bits >> (8 + nzero)) != 0) 1235 nzero += 8; 1236 1237 if ((bits >> (4 + nzero)) != 0) 1238 nzero += 4; 1239 1240 if ((bits >> (2 + nzero)) != 0) 1241 nzero += 2; 1242 1243 if ((bits >> (1 + nzero)) != 0) 1244 nzero++; 1245 1246 if ((bits >> nzero) != 0) 1247 nzero++; 1248 1249 bits <<= 64 - nzero; 1250 bexp -= 64 - nzero; 1251 1252 // Get decimal exponent. 1253 if ((s & 15) != 0) 1254 { 1255 int expMult = pow10Exp[(s & 15) - 1]; 1256 bexp += (scale < 0) ? ( -expMult + 1) : expMult; 1257 bits = mult64L(bits, pow10[(s & 15) + ((scale < 0) ? 15 : 0) - 1]); 1258 if ((bits & 0x8000000000000000L) == 0) 1259 { 1260 bits <<= 1; 1261 bexp--; 1262 } 1263 } 1264 1265 if ((s >> 4) != 0) 1266 { 1267 int expMult = pow10Exp[15 + ((s >> 4) - 1)]; 1268 bexp += (scale < 0) ? ( -expMult + 1) : expMult; 1269 bits = mult64L(bits, pow10[30 + ((s >> 4) + ((scale < 0) ? 21 : 0) - 1)]); 1270 if ((bits & 0x8000000000000000L) == 0) 1271 { 1272 bits <<= 1; 1273 bexp--; 1274 } 1275 } 1276 1277 // Round and scale. 1278 if ((cast(uint)bits & (1 << 10)) != 0) 1279 { 1280 bits += (1 << 10) - 1 + (bits >>> 11) & 1; 1281 bits >>= 11; 1282 if (bits == 0) 1283 bexp++; 1284 } 1285 else 1286 bits >>= 11; 1287 1288 bexp += 1022; 1289 if (bexp <= 0) 1290 { 1291 if (bexp < -53) 1292 bits = 0; 1293 else 1294 bits >>= ( -bexp + 1); 1295 } 1296 bits = (cast(ulong)bexp << 52) + (bits & 0x000FFFFFFFFFFFFFL); 1297 1298 if (sign) 1299 bits |= 0x8000000000000000L; 1300 1301 value = *cast(double*) & bits; 1302 return true; 1303 } 1304 1305 1306 1307 /********************************************************************** 1308 1309 **********************************************************************/ 1310 1311 private char[] toStringFormat (ref Result result, const(char)[] format, NumberFormat nf) 1312 { 1313 bool hasGroups; 1314 int groupCount; 1315 int groupPos = -1, pointPos = -1; 1316 int first = int.max, last, count; 1317 bool scientific; 1318 int n; 1319 char c; 1320 1321 while (n < format.length) 1322 { 1323 c = format[n++]; 1324 switch (c) 1325 { 1326 case '#': 1327 count++; 1328 break; 1329 1330 case '0': 1331 if (first == int.max) 1332 first = count; 1333 count++; 1334 last = count; 1335 break; 1336 1337 case '.': 1338 if (pointPos < 0) 1339 pointPos = count; 1340 break; 1341 1342 case ',': 1343 if (count > 0 && pointPos < 0) 1344 { 1345 if (groupPos >= 0) 1346 { 1347 if (groupPos == count) 1348 { 1349 groupCount++; 1350 break; 1351 } 1352 hasGroups = true; 1353 } 1354 groupPos = count; 1355 groupCount = 1; 1356 } 1357 break; 1358 1359 case '\'': 1360 case '\"': 1361 while (n < format.length && format[n++] != c) 1362 {} 1363 break; 1364 1365 case '\\': 1366 if (n < format.length) 1367 n++; 1368 break; 1369 1370 default: 1371 break; 1372 } 1373 } 1374 1375 if (pointPos < 0) 1376 pointPos = count; 1377 1378 int adjust; 1379 if (groupPos >= 0) 1380 { 1381 if (groupPos == pointPos) 1382 adjust -= groupCount * 3; 1383 else 1384 hasGroups = true; 1385 } 1386 1387 if (digits[0] != '\0') 1388 { 1389 scale += adjust; 1390 round(scientific ? count : scale + count - pointPos); 1391 } 1392 1393 first = (first < pointPos) ? pointPos - first : 0; 1394 last = (last > pointPos) ? pointPos - last : 0; 1395 1396 int pos = pointPos; 1397 int extra; 1398 if (!scientific) 1399 { 1400 pos = (scale > pointPos) ? scale : pointPos; 1401 extra = scale - pointPos; 1402 } 1403 1404 const(char)[] groupSeparator = nf.numberGroupSeparator; 1405 const(char)[] decimalSeparator = nf.numberDecimalSeparator; 1406 1407 // Work out the positions of the group separator. 1408 int[] groupPositions; 1409 int groupIndex = -1; 1410 if (hasGroups) 1411 { 1412 if (nf.numberGroupSizes.length == 0) 1413 hasGroups = false; 1414 else 1415 { 1416 int groupSizesTotal = nf.numberGroupSizes[0]; 1417 int groupSize = groupSizesTotal; 1418 int digitsTotal = pos + ((extra < 0) ? extra : 0); 1419 int digitCount = (first > digitsTotal) ? first : digitsTotal; 1420 1421 int sizeIndex; 1422 while (digitCount > groupSizesTotal) 1423 { 1424 if (groupSize == 0) 1425 break; 1426 1427 groupPositions ~= groupSizesTotal; 1428 groupIndex++; 1429 1430 if (sizeIndex < nf.numberGroupSizes.length - 1) 1431 groupSize = nf.numberGroupSizes[++sizeIndex]; 1432 1433 groupSizesTotal += groupSize; 1434 } 1435 } 1436 } 1437 1438 //char[] result; 1439 if (sign) 1440 result ~= nf.negativeSign; 1441 1442 auto p = digits.ptr; 1443 n = 0; 1444 bool pointWritten; 1445 1446 while (n < format.length) 1447 { 1448 c = format[n++]; 1449 if (extra > 0 && (c == '#' || c == '0' || c == '.')) 1450 { 1451 while (extra > 0) 1452 { 1453 result ~= (*p != '\0') ? *p++ : '0'; 1454 1455 if (hasGroups && pos > 1 && groupIndex >= 0) 1456 { 1457 if (pos == groupPositions[groupIndex] + 1) 1458 { 1459 result ~= groupSeparator; 1460 groupIndex--; 1461 } 1462 } 1463 pos--; 1464 extra--; 1465 } 1466 } 1467 1468 switch (c) 1469 { 1470 case '#': 1471 case '0': 1472 if (extra < 0) 1473 { 1474 extra++; 1475 c = (pos <= first) ? '0' : char.init; 1476 } 1477 else 1478 c = (*p != '\0') ? *p++ : pos > last ? '0' : char.init; 1479 1480 if (c != char.init) 1481 { 1482 result ~= c; 1483 1484 if (hasGroups && pos > 1 && groupIndex >= 0) 1485 { 1486 if (pos == groupPositions[groupIndex] + 1) 1487 { 1488 result ~= groupSeparator; 1489 groupIndex--; 1490 } 1491 } 1492 } 1493 pos--; 1494 break; 1495 1496 case '.': 1497 if (pos != 0 || pointWritten) 1498 break; 1499 if (last < 0 || (pointPos < count && *p != '\0')) 1500 { 1501 result ~= decimalSeparator; 1502 pointWritten = true; 1503 } 1504 break; 1505 1506 case ',': 1507 break; 1508 1509 case '\'': 1510 case '\"': 1511 if (n < format.length) 1512 n++; 1513 break; 1514 1515 case '\\': 1516 if (n < format.length) 1517 result ~= format[n++]; 1518 break; 1519 1520 default: 1521 result ~= c; 1522 break; 1523 } 1524 } 1525 return result.get(); 1526 } 1527 1528 /********************************************************************** 1529 1530 **********************************************************************/ 1531 1532 private void round (int pos) 1533 { 1534 int index; 1535 while (index < pos && digits[index] != '\0') 1536 index++; 1537 1538 if (index == pos && digits[index] >= '5') 1539 { 1540 while (index > 0 && digits[index - 1] == '9') 1541 index--; 1542 1543 if (index > 0) 1544 digits[index - 1]++; 1545 else 1546 { 1547 scale++; 1548 digits[0] = '1'; 1549 index = 1; 1550 } 1551 } 1552 else 1553 while (index > 0 && digits[index - 1] == '0') 1554 index--; 1555 1556 if (index == 0) 1557 { 1558 scale = 0; 1559 sign = false; 1560 } 1561 1562 digits[index] = '\0'; 1563 } 1564 }