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 }