1 /*******************************************************************************
2 
3         copyright:      Copyright (c) 2005 John Chapman. All rights reserved
4 
5         license:        BSD style: $(LICENSE)
6 
7         version:        Jan 2005: initial release
8                         Mar 2009: extracted from locale, and
9                                   converted to a struct
10 
11         author:         John Chapman, Kris, mwarning
12 
13         Support for formatting date/time values, in a locale-specific
14         manner. See DateTimeLocale.format() for a description on how
15         formatting is performed (below).
16 
17         Reference links:
18         ---
19         http://www.opengroup.org/onlinepubs/007908799/xsh/strftime.html
20         http://msdn.microsoft.com/en-us/library/system.globalization.datetimeformatinfo(VS.71).aspx
21         ---
22 
23 ******************************************************************************/
24 
25 module tango.text.convert.DateTime;
26 
27 private import  tango.core.Exception;
28 
29 private import  tango.time.WallClock;
30 
31 private import  tango.time.chrono.Calendar,
32                 tango.time.chrono.Gregorian;
33 
34 private import  Utf = tango.text.convert.Utf;
35 
36 private import  Integer = tango.text.convert.Integer;
37 
38 version (WithExtensions)
39          private import tango.text.convert.Extensions;
40 
41 /******************************************************************************
42 
43         O/S specifics
44 
45 ******************************************************************************/
46 
47 version (Windows)
48          private import tango.sys.win32.UserGdi;
49 else
50 {
51         private import tango.stdc.stringz;
52         private import tango.stdc.posix.langinfo;
53 }
54 
55 /******************************************************************************
56 
57         The default DateTimeLocale instance
58 
59 ******************************************************************************/
60 
61 public __gshared DateTimeLocale DateTimeDefault;
62 
63 shared static this()
64 {
65         DateTimeDefault = DateTimeLocale.create();
66 version (WithExtensions)
67         {
68         Extensions8.add  (typeid(Time), &DateTimeDefault.bridge!(char));
69         Extensions16.add (typeid(Time), &DateTimeDefault.bridge!(wchar));
70         Extensions32.add (typeid(Time), &DateTimeDefault.bridge!(dchar));
71         }
72 }
73 
74 /******************************************************************************
75 
76         How to format locale-specific date/time output
77 
78 ******************************************************************************/
79 
80 struct DateTimeLocale
81 {
82         __gshared immutable immutable(char)[]   rfc1123Pattern = "ddd, dd MMM yyyy HH':'mm':'ss 'GMT'";
83         __gshared immutable immutable(char)[]   sortableDateTimePattern = "yyyy'-'MM'-'dd'T'HH':'mm':'ss";
84         __gshared immutable immutable(char)[]   universalSortableDateTimePattern = "yyyy'-'MM'-'dd' 'HH':'mm':'ss'Z'";
85 
86         Calendar               assignedCalendar;
87 
88         const(char)[]          shortDatePattern,
89                                shortTimePattern,
90                                longDatePattern,
91                                longTimePattern,
92                                fullDateTimePattern,
93                                generalShortTimePattern,
94                                generalLongTimePattern,
95                                monthDayPattern,
96                                yearMonthPattern;
97 
98         const(char)[]          amDesignator,
99                                pmDesignator;
100 
101         const(char)[]          timeSeparator,
102                                dateSeparator;
103 
104         const(char)[][]        dayNames,
105                                monthNames,
106                                abbreviatedDayNames,
107                                abbreviatedMonthNames;
108 
109         /**********************************************************************
110 
111                 Format the given Time value into the provided output,
112                 using the specified layout. The layout can be a generic
113                 variant or a custom one, where generics are indicated
114                 via a single character:
115 
116                 <pre>
117                 "t" = 7:04
118                 "T" = 7:04:02 PM
119                 "d" = 3/30/2009
120                 "D" = Monday, March 30, 2009
121                 "f" = Monday, March 30, 2009 7:04 PM
122                 "F" = Monday, March 30, 2009 7:04:02 PM
123                 "g" = 3/30/2009 7:04 PM
124                 "G" = 3/30/2009 7:04:02 PM
125                 "y"
126                 "Y" = March, 2009
127                 "r"
128                 "R" = Mon, 30 Mar 2009 19:04:02 GMT
129                 "s" = 2009-03-30T19:04:02
130                 "u" = 2009-03-30 19:04:02Z
131                 </pre>
132 
133                 For the US locale, these generic layouts are expanded in the
134                 following manner:
135 
136                 <pre>
137                 "t" = "h:mm"
138                 "T" = "h:mm:ss tt"
139                 "d" = "M/d/yyyy"
140                 "D" = "dddd, MMMM d, yyyy"
141                 "f" = "dddd, MMMM d, yyyy h:mm tt"
142                 "F" = "dddd, MMMM d, yyyy h:mm:ss tt"
143                 "g" = "M/d/yyyy h:mm tt"
144                 "G" = "M/d/yyyy h:mm:ss tt"
145                 "y"
146                 "Y" = "MMMM, yyyy"
147                 "r"
148                 "R" = "ddd, dd MMM yyyy HH':'mm':'ss 'GMT'"
149                 "s" = "yyyy'-'MM'-'dd'T'HH':'mm':'ss"
150                 "u" = "yyyy'-'MM'-'dd' 'HH':'mm':'ss'Z'"
151                 </pre>
152 
153                 Custom layouts are constructed using a combination of the
154                 character codes indicated on the right, above. For example,
155                 a layout of "dddd, dd MMM yyyy HH':'mm':'ss zzzz" will emit
156                 something like this:
157                 ---
158                 Monday, 30 Mar 2009 19:04:02 -08:00
159                 ---
160 
161                 Using these format indicators with Layout (Stdout etc) is
162                 straightforward. Formatting integers, for example, is done
163                 like so:
164                 ---
165                 Stdout.formatln ("{:u}", 5);
166                 Stdout.formatln ("{:b}", 5);
167                 Stdout.formatln ("{:x}", 5);
168                 ---
169 
170                 Formatting date/time values is similar, where the format
171                 indicators are provided after the colon:
172                 ---
173                 Stdout.formatln ("{:t}", Clock.now);
174                 Stdout.formatln ("{:D}", Clock.now);
175                 Stdout.formatln ("{:dddd, dd MMMM yyyy HH:mm}", Clock.now);
176                 ---
177 
178         **********************************************************************/
179 
180         char[] format (char[] output, Time dateTime, const(char)[] layout)
181         {
182                 // default to general format
183                 if (layout.length is 0)
184                     layout = "G";
185 
186                 // might be one of our shortcuts
187                 if (layout.length is 1)
188                     layout = expandKnownFormat (layout);
189 
190                 auto res=Result(output);
191                 return formatCustom (res, dateTime, layout);
192         }
193 
194         /**********************************************************************
195 
196         **********************************************************************/
197 
198         T[] formatWide(T) (T[] output, Time dateTime, const(T)[] fmt)
199         {
200                 static if (is (T == char))
201                            return format (output, dateTime, fmt);
202                 else
203                    {
204                    char[128] tmp0 = void;
205                    char[128] tmp1 = void;
206                    return Utf.fromString8(format(tmp0, dateTime, Utf.toString(fmt, tmp1)), output);
207                    }
208         }
209 
210         /**********************************************************************
211 
212                 Return a generic English/US instance
213 
214         **********************************************************************/
215 
216         @property static DateTimeLocale* generic ()
217         {
218                 return &EngUS;
219         }
220 
221         /**********************************************************************
222 
223                 Return the assigned Calendar instance, using Gregorian
224                 as the default
225 
226         **********************************************************************/
227 
228         @property Calendar calendar ()
229         {
230                 if (assignedCalendar is null)
231                     assignedCalendar = Gregorian.generic;
232                 return assignedCalendar;
233         }
234 
235         /**********************************************************************
236 
237                 Return a short day name
238 
239         **********************************************************************/
240 
241         const(char)[] abbreviatedDayName (Calendar.DayOfWeek dayOfWeek)
242         {
243                 return abbreviatedDayNames [cast(int) dayOfWeek];
244         }
245 
246         /**********************************************************************
247 
248                 Return a long day name
249 
250         **********************************************************************/
251 
252         const(char)[] dayName (Calendar.DayOfWeek dayOfWeek)
253         {
254                 return dayNames [cast(int) dayOfWeek];
255         }
256 
257         /**********************************************************************
258 
259                 Return a short month name
260 
261         **********************************************************************/
262 
263         const(char)[] abbreviatedMonthName (int month)
264         {
265                 assert (month > 0 && month < 13);
266                 return abbreviatedMonthNames [month - 1];
267         }
268 
269         /**********************************************************************
270 
271                 Return a long month name
272 
273         **********************************************************************/
274 
275         const(char)[] monthName (int month)
276         {
277                 assert (month > 0 && month < 13);
278                 return monthNames [month - 1];
279         }
280 
281 version (Windows)
282         {
283         /**********************************************************************
284 
285                 create and populate an instance via O/S configuration
286                 for the current user
287 
288         **********************************************************************/
289 
290         static DateTimeLocale create ()
291         {
292                 static char[] toString (char[] dst, LCID id, LCTYPE type)
293                 {
294                         wchar[256] wide = void;
295 
296                         auto len = GetLocaleInfoW (id, type, null, 0);
297                         if (len && len < wide.length)
298                            {
299                            GetLocaleInfoW (id, type, wide.ptr, wide.length);
300                            len = WideCharToMultiByte (CP_UTF8, 0, wide.ptr, len-1,
301                                                       cast(PCHAR)dst.ptr, dst.length,
302                                                       null, null);
303                            return dst [0..len].dup;
304                            }
305                         throw new Exception ("DateTime :: GetLocaleInfo failed");
306                 }
307 
308                 DateTimeLocale dt;
309                 char[256] tmp = void;
310                 auto lcid = LOCALE_USER_DEFAULT;
311 
312                 for (auto i=LOCALE_SDAYNAME1; i <= LOCALE_SDAYNAME7; ++i)
313                      dt.dayNames ~= toString (tmp, lcid, i);
314 
315                 for (auto i=LOCALE_SABBREVDAYNAME1; i <= LOCALE_SABBREVDAYNAME7; ++i)
316                      dt.abbreviatedDayNames ~= toString (tmp, lcid, i);
317 
318                 for (auto i=LOCALE_SMONTHNAME1; i <= LOCALE_SMONTHNAME12; ++i)
319                      dt.monthNames ~= toString (tmp, lcid, i);
320 
321                 for (auto i=LOCALE_SABBREVMONTHNAME1; i <= LOCALE_SABBREVMONTHNAME12; ++i)
322                      dt.abbreviatedMonthNames ~= toString (tmp, lcid, i);
323 
324                 dt.dateSeparator    = toString (tmp, lcid, LOCALE_SDATE);
325                 dt.timeSeparator    = toString (tmp, lcid, LOCALE_STIME);
326                 dt.amDesignator     = toString (tmp, lcid, LOCALE_S1159);
327                 dt.pmDesignator     = toString (tmp, lcid, LOCALE_S2359);
328                 dt.longDatePattern  = toString (tmp, lcid, LOCALE_SLONGDATE);
329                 dt.shortDatePattern = toString (tmp, lcid, LOCALE_SSHORTDATE);
330                 dt.yearMonthPattern = toString (tmp, lcid, LOCALE_SYEARMONTH);
331                 dt.longTimePattern  = toString (tmp, lcid, LOCALE_STIMEFORMAT);
332 
333                 // synthesize a short time
334                 auto s = dt.shortTimePattern = dt.longTimePattern;
335                 for (auto i=s.length; i--;)
336                      if (s[i] is dt.timeSeparator[0])
337                         {
338                         dt.shortTimePattern = s[0..i];
339                         break;
340                         }
341 
342                 dt.fullDateTimePattern = dt.longDatePattern ~ " " ~
343                                          dt.longTimePattern;
344                 dt.generalLongTimePattern = dt.shortDatePattern ~ " " ~
345                                             dt.longTimePattern;
346                 dt.generalShortTimePattern = dt.shortDatePattern ~ " " ~
347                                              dt.shortTimePattern;
348                 return dt;
349         }
350         }
351 else
352         {
353         /**********************************************************************
354 
355                 create and populate an instance via O/S configuration
356                 for the current user
357 
358         **********************************************************************/
359 
360         static DateTimeLocale create ()
361         {
362                 //extract separator
363                 const(char)[] extractSeparator(const(char)[] str, const(char)[] def)
364                 {
365                         for (auto i = 0; i < str.length; ++i)
366                             {
367                             char c = str[i];
368                             if ((c == '%') || (c == ' ') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
369                                 continue;
370                             return str[i..i+1];
371                             }
372                         return def;
373                 }
374 
375                 const(char)[] getString(nl_item id, const(char)[] def = null)
376                 {
377                         char* p = nl_langinfo(id);
378                         return p ? fromStringz(p).dup : def;
379                 }
380 
381                 const(char)[] getFormatString(nl_item id, const(char)[] def = null)
382                 {
383                         const(char)[] posix_str = getString(id, def);
384                         return convert(posix_str);
385                 }
386 
387                 DateTimeLocale dt;
388 
389                 for (auto i = DAY_1; i <= DAY_7; ++i)
390                      dt.dayNames ~= getString (i);
391 
392                 for (auto i = ABDAY_1; i <= ABDAY_7; ++i)
393                      dt.abbreviatedDayNames ~= getString (i);
394 
395                 for (auto i = MON_1; i <= MON_12; ++i)
396                      dt.monthNames ~= getString (i);
397 
398                 for (auto i = ABMON_1; i <= ABMON_12; ++i)
399                      dt.abbreviatedMonthNames ~= getString (i);
400 
401                 dt.amDesignator = getString (AM_STR, "AM");
402                 dt.pmDesignator = getString (PM_STR, "PM");
403 
404                 dt.longDatePattern = "dddd, MMMM d, yyyy"; //default
405                 dt.shortDatePattern = getFormatString(D_FMT, "M/d/yyyy");
406 
407                 dt.longTimePattern = getFormatString(T_FMT, "h:mm:ss tt");
408                 dt.shortTimePattern = "h:mm"; //default
409 
410                 dt.yearMonthPattern = "MMMM, yyyy"; //no posix equivalent?
411                 dt.fullDateTimePattern = getFormatString(D_T_FMT, "dddd, MMMM d, yyyy h:mm:ss tt");
412 
413                 dt.dateSeparator = extractSeparator(dt.shortDatePattern, "/");
414                 dt.timeSeparator = extractSeparator(dt.longTimePattern, ":");
415 
416                 //extract shortTimePattern from longTimePattern
417                 for (auto i = dt.longTimePattern.length; i--;)
418                     {
419                     if (dt.longTimePattern[i] == dt.timeSeparator[$-1])
420                        {
421                        dt.shortTimePattern = dt.longTimePattern[0..i];
422                        break;
423                        }
424                     }
425 
426                 //extract longDatePattern from fullDateTimePattern
427                 auto pos = dt.fullDateTimePattern.length - dt.longTimePattern.length - 2;
428                 if (pos < dt.fullDateTimePattern.length)
429                     dt.longDatePattern = dt.fullDateTimePattern[0..pos];
430 
431                 dt.fullDateTimePattern = dt.longDatePattern ~ " " ~ dt.longTimePattern;
432                 dt.generalLongTimePattern = dt.shortDatePattern ~ " " ~  dt.longTimePattern;
433                 dt.generalShortTimePattern = dt.shortDatePattern ~ " " ~  dt.shortTimePattern;
434 
435                 return dt;
436         }
437 
438         /**********************************************************************
439 
440                 Convert POSIX date time format to .NET format syntax.
441 
442         **********************************************************************/
443 
444         private static char[] convert(const(char)[] fmt)
445         {
446                 char[32] ret;
447                 size_t len;
448 
449                 void put(const(char)[] str)
450                 {
451                         assert((len+str.length) <= ret.length);
452                         ret[len..len+str.length] = str[];
453                         len += str.length;
454                 }
455 
456                 for (auto i = 0; i < fmt.length; ++i)
457                     {
458                     char c = fmt[i];
459 
460                     if (c != '%')
461                        {
462                         assert((len+1) <= ret.length);
463                         ret[len] = c;
464                         len += 1;
465                        continue;
466                        }
467 
468                     i++;
469                     if (i >= fmt.length)
470                         break;
471 
472                     c = fmt[i];
473                     switch (c)
474                            {
475                            case 'a': //locale's abbreviated weekday name.
476                                 put("ddd"); //The abbreviated name of the day of the week,
477                                 break;
478 
479                            case 'A': //locale's full weekday name.
480                                 put("dddd");
481                                 break;
482 
483                            case 'b': //locale's abbreviated month name
484                                 put("MMM");
485                                 break;
486 
487                            case 'B': //locale's full month name
488                                 put("MMMM");
489                                 break;
490 
491                            case 'd': //day of the month as a decimal number [01,31]
492                                 put("dd"); // The day of the month. Single-digit
493                                 //days will have a leading zero.
494                                 break;
495 
496                            case 'D': //same as %m/%d/%y.
497                                 put("MM/dd/yy");
498                                 break;
499 
500                            case 'e': //day of the month as a decimal number [1,31];
501                                 //a single digit is preceded by a space
502                                 put("d"); //The day of the month. Single-digit days
503                                 //will not have a leading zero.
504                                 break;
505 
506                            case 'h': //same as %b.
507                                 put("MMM");
508                                 break;
509 
510                            case 'H':
511                                 //hour (24-hour clock) as a decimal number [00,23]
512                                 put("HH"); //The hour in a 24-hour clock. Single-digit
513                                 //hours will have a leading zero.
514                                 break;
515 
516                            case 'I': //the hour (12-hour clock) as a decimal number [01,12]
517                                 put("hh"); //The hour in a 12-hour clock.
518                                 //Single-digit hours will have a leading zero.
519                                 break;
520 
521                            case 'm': //month as a decimal number [01,12]
522                                 put("MM"); //The numeric month. Single-digit
523                                 //months will have a leading zero.
524                                 break;
525 
526                            case 'M': //minute as a decimal number [00,59]
527                                 put("mm"); //The minute. Single-digit minutes
528                                 //will have a leading zero.
529                                 break;
530 
531                            case 'n': //newline character
532                                 put("\n");
533                                 break;
534 
535                            case 'p': //locale's equivalent of either a.m. or p.m
536                                 put("tt");
537                                 break;
538 
539                            case 'r': //time in a.m. and p.m. notation;
540                                 //equivalent to %I:%M:%S %p.
541                                 put("hh:mm:ss tt");
542                                 break;
543 
544                            case 'R': //time in 24 hour notation (%H:%M)
545                                 put("HH:mm");
546                                 break;
547 
548                            case 'S': //second as a decimal number [00,61]
549                                 put("ss"); //The second. Single-digit seconds
550                                 //will have a leading zero.
551                                 break;
552 
553                            case 't': //tab character.
554                                 put("\t");
555                                 break;
556 
557                            case 'T': //equivalent to (%H:%M:%S)
558                                 put("HH:mm:ss");
559                                 break;
560 
561                            case 'u': //weekday as a decimal number [1,7],
562                                 //with 1 representing Monday
563                            case 'U': //week number of the year
564                                 //(Sunday as the first day of the week) as a decimal number [00,53]
565                            case 'V': //week number of the year
566                                 //(Monday as the first day of the week) as a decimal number [01,53].
567                                 //If the week containing 1 January has four or more days
568                                 //in the new year, then it is considered week 1.
569                                 //Otherwise, it is the last week of the previous year, and the next week is week 1.
570                            case 'w': //weekday as a decimal number [0,6], with 0 representing Sunday
571                            case 'W': //week number of the year (Monday as the first day of the week)
572                                 //as a decimal number [00,53].
573                                 //All days in a new year preceding the first Monday
574                                 //are considered to be in week 0.
575                            case 'x': //locale's appropriate date representation
576                            case 'X': //locale's appropriate time representation
577                            case 'c': //locale's appropriate date and time representation
578                            case 'C': //century number (the year divided by 100 and
579                                 //truncated to an integer) as a decimal number [00-99]
580                            case 'j': //day of the year as a decimal number [001,366]
581                                 assert(0);
582                                 //break;
583 
584                            case 'y': //year without century as a decimal number [00,99]
585                                 put("yy"); // The year without the century. If the year without
586                                 //the century is less than 10, the year is displayed with a leading zero.
587                                 break;
588 
589                            case 'Y': //year with century as a decimal number
590                                 put("yyyy"); //The year in four digits, including the century.
591                                 break;
592 
593                            case 'Z': //timezone name or abbreviation,
594                                 //or by no bytes if no timezone information exists
595                                 //assert(0);
596                                 break;
597 
598                            case '%':
599                                 put("%");
600                                 break;
601 
602                            default:
603                                 assert(0);
604                            }
605                     }
606                 return ret[0..len].dup;
607         }
608         }
609 
610         /**********************************************************************
611 
612         **********************************************************************/
613 
614         private const(char)[] expandKnownFormat (const(char)[] format)
615         {
616                 const(char)[] f;
617 
618                 switch (format[0])
619                        {
620                        case 'd':
621                             f = shortDatePattern;
622                             break;
623                        case 'D':
624                             f = longDatePattern;
625                             break;
626                        case 'f':
627                             f = longDatePattern ~ " " ~ shortTimePattern;
628                             break;
629                        case 'F':
630                             f = fullDateTimePattern;
631                             break;
632                        case 'g':
633                             f = generalShortTimePattern;
634                             break;
635                        case 'G':
636                             f = generalLongTimePattern;
637                             break;
638                        case 'r':
639                        case 'R':
640                             f = rfc1123Pattern;
641                             break;
642                        case 's':
643                             f = sortableDateTimePattern;
644                             break;
645                        case 'u':
646                             f = universalSortableDateTimePattern;
647                             break;
648                        case 't':
649                             f = shortTimePattern;
650                             break;
651                        case 'T':
652                             f = longTimePattern;
653                             break;
654                        case 'y':
655                        case 'Y':
656                             f = yearMonthPattern;
657                             break;
658                        default:
659                            return ("'{invalid time format}'");
660                        }
661                 return f;
662         }
663 
664         /**********************************************************************
665 
666         **********************************************************************/
667 
668         private char[] formatCustom (ref Result result, Time dateTime, const(char)[] format)
669         {
670                 uint            len,
671                                 doy,
672                                 dow,
673                                 era;
674                 uint            day,
675                                 year,
676                                 month;
677                 int             index;
678                 char[10]        tmp = void;
679                 auto            time = dateTime.time;
680 
681                 // extract date components
682                 calendar.split (dateTime, year, month, day, doy, dow, era);
683 
684                 // sweep format specifiers ...
685                 while (index < format.length)
686                       {
687                       char c = format[index];
688 
689                       switch (c)
690                              {
691                              // day
692                              case 'd':
693                                   len = parseRepeat (format, index, c);
694                                   if (len <= 2)
695                                      result ~= formatInt (tmp, day, len);
696                                   else
697                                      result ~= formatDayOfWeek (cast(Calendar.DayOfWeek) dow, len);
698                                   break;
699 
700                              // millis
701                             case 'f':
702                                 len = parseRepeat (format, index, c);
703                                 auto num = Integer.itoa (tmp, time.millis);
704                                 if(len > num.length)
705                                 {
706                                     result ~= num;
707 
708                                     // append '0's
709                                     static char[8] zeros = '0';
710                                     auto zc = len - num.length;
711                                     zc = (zc > zeros.length) ? zeros.length : zc;
712                                     result ~= zeros[0..zc];
713                                 }
714                                 else
715                                     result ~= num[0..len];
716                                 break;
717 
718                              // millis, no trailing zeros
719                             case 'F':
720                                 len = parseRepeat (format, index, c);
721                                 auto num = Integer.itoa (tmp, time.millis);
722                                 auto idx = (len < num.length) ? len : num.length;
723 
724                                 // strip '0's
725                                 while(idx && num[idx-1] is '0')
726                                     --idx;
727 
728                                 result ~= num[0..idx];
729                                 break;
730 
731                              // month
732                              case 'M':
733                                   len = parseRepeat (format, index, c);
734                                   if (len <= 2)
735                                       result ~= formatInt (tmp, month, len);
736                                   else
737                                      result ~= formatMonth (month, len);
738                                   break;
739 
740                              // year
741                              case 'y':
742                                   len = parseRepeat (format, index, c);
743 
744                                   // Two-digit years for Japanese
745                                   if (calendar.id is Calendar.JAPAN)
746                                       result ~= formatInt (tmp, year, 2);
747                                   else
748                                      {
749                                      if (len <= 2)
750                                          result ~= formatInt (tmp, year % 100, len);
751                                      else
752                                         result ~= formatInt (tmp, year, len);
753                                      }
754                                   break;
755 
756                              // hour (12-hour clock)
757                              case 'h':
758                                   len = parseRepeat (format, index, c);
759                                   int hour = time.hours % 12;
760                                   if (hour is 0)
761                                       hour = 12;
762                                   result ~= formatInt (tmp, hour, len);
763                                   break;
764 
765                              // hour (24-hour clock)
766                              case 'H':
767                                   len = parseRepeat (format, index, c);
768                                   result ~= formatInt (tmp, time.hours, len);
769                                   break;
770 
771                              // minute
772                              case 'm':
773                                   len = parseRepeat (format, index, c);
774                                   result ~= formatInt (tmp, time.minutes, len);
775                                   break;
776 
777                              // second
778                              case 's':
779                                   len = parseRepeat (format, index, c);
780                                   result ~= formatInt (tmp, time.seconds, len);
781                                   break;
782 
783                              // AM/PM
784                              case 't':
785                                   len = parseRepeat (format, index, c);
786                                   if (len is 1)
787                                      {
788                                      if (time.hours < 12)
789                                         {
790                                         if (amDesignator.length != 0)
791                                             result ~= amDesignator[0];
792                                         }
793                                      else
794                                         {
795                                         if (pmDesignator.length != 0)
796                                             result ~= pmDesignator[0];
797                                         }
798                                      }
799                                   else
800                                      result ~= (time.hours < 12) ? amDesignator : pmDesignator;
801                                   break;
802 
803                              // timezone offset
804                              case 'z':
805                                   len = parseRepeat (format, index, c);
806                                   auto minutes = cast(int) (WallClock.zone.minutes);
807                                   if (minutes < 0)
808                                      {
809                                      minutes = -minutes;
810                                      result ~= '-';
811                                      }
812                                   else
813                                      result ~= '+';
814                                   int hours = minutes / 60;
815                                   minutes %= 60;
816 
817                                   if (len is 1)
818                                       result ~= formatInt (tmp, hours, 1);
819                                   else
820                                      if (len is 2)
821                                          result ~= formatInt (tmp, hours, 2);
822                                      else
823                                         {
824                                         result ~= formatInt (tmp, hours, 2);
825                                         result ~= formatInt (tmp, minutes, 2);
826                                         }
827                                   break;
828 
829                              // time separator
830                              case ':':
831                                   len = 1;
832                                   result ~= timeSeparator;
833                                   break;
834 
835                              // date separator
836                              case '/':
837                                   len = 1;
838                                   result ~= dateSeparator;
839                                   break;
840 
841                              // string literal
842                              case '\"':
843                              case '\'':
844                                   len = parseQuote (result, format, index);
845                                   break;
846 
847                              // other
848                              default:
849                                  len = 1;
850                                  result ~= c;
851                                  break;
852                              }
853                       index += len;
854                       }
855                 return result.get;
856         }
857 
858         /**********************************************************************
859 
860         **********************************************************************/
861 
862         private const(char)[] formatMonth (int month, int rpt)
863         {
864                 if (rpt is 3)
865                     return abbreviatedMonthName (month);
866                 return monthName (month);
867         }
868 
869         /**********************************************************************
870 
871         **********************************************************************/
872 
873         private const(char)[] formatDayOfWeek (Calendar.DayOfWeek dayOfWeek, int rpt)
874         {
875                 if (rpt is 3)
876                     return abbreviatedDayName (dayOfWeek);
877                 return dayName (dayOfWeek);
878         }
879 
880         /**********************************************************************
881 
882         **********************************************************************/
883 
884         private T[] bridge(T) (T[] result, void* arg, const(T)[] format)
885         {
886                 return formatWide (result, *cast(Time*) arg, format);
887         }
888 
889         /**********************************************************************
890 
891         **********************************************************************/
892 
893         private static int parseRepeat(const(char)[] format, int pos, char c)
894         {
895                 int n = pos + 1;
896                 while (n < format.length && format[n] is c)
897                        n++;
898                 return n - pos;
899         }
900 
901         /**********************************************************************
902 
903         **********************************************************************/
904 
905         private static char[] formatInt (char[] tmp, int v, int minimum)
906         {
907                 auto num = Integer.itoa (tmp, v);
908                 if ((minimum -= num.length) > 0)
909                    {
910                    auto p = tmp.ptr + tmp.length - num.length;
911                    while (minimum--)
912                           *--p = '0';
913                    num = tmp [p-tmp.ptr .. $];
914                    }
915                 return num;
916         }
917 
918         /**********************************************************************
919 
920         **********************************************************************/
921 
922         private static int parseQuote (ref Result result, const(char)[] format, int pos)
923         {
924                 int start = pos;
925                 char chQuote = format[pos++];
926                 bool found;
927                 while (pos < format.length)
928                       {
929                       char c = format[pos++];
930                       if (c is chQuote)
931                          {
932                          found = true;
933                          break;
934                          }
935                       else
936                          if (c is '\\')
937                             { // escaped
938                             if (pos < format.length)
939                                 result ~= format[pos++];
940                             }
941                          else
942                             result ~= c;
943                       }
944                 return pos - start;
945         }
946 }
947 
948 /******************************************************************************
949 
950         An english/usa locale
951         Used as generic DateTimeLocale.
952 
953 ******************************************************************************/
954 
955 private DateTimeLocale EngUS =
956 {
957         shortDatePattern                : "M/d/yyyy",
958         shortTimePattern                : "h:mm",
959         longDatePattern                 : "dddd, MMMM d, yyyy",
960         longTimePattern                 : "h:mm:ss tt",
961         fullDateTimePattern             : "dddd, MMMM d, yyyy h:mm:ss tt",
962         generalShortTimePattern         : "M/d/yyyy h:mm",
963         generalLongTimePattern          : "M/d/yyyy h:mm:ss tt",
964         monthDayPattern                 : "MMMM d",
965         yearMonthPattern                : "MMMM, yyyy",
966         amDesignator                    : "AM",
967         pmDesignator                    : "PM",
968         timeSeparator                   : ":",
969         dateSeparator                   : "/",
970         dayNames                        : ["Sunday", "Monday", "Tuesday", "Wednesday",
971                                            "Thursday", "Friday", "Saturday"],
972         monthNames                      : ["January", "February", "March", "April",
973                                            "May", "June", "July", "August", "September",
974                                            "October" "November", "December"],
975         abbreviatedDayNames             : ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
976         abbreviatedMonthNames           : ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
977                                            "Jul", "Aug", "Sep", "Oct" "Nov", "Dec"],
978 };
979 
980 
981 /******************************************************************************
982 
983 ******************************************************************************/
984 
985 private struct Result
986 {
987         private size_t    index;
988         private char[]  target_;
989 
990         /**********************************************************************
991 
992         **********************************************************************/
993 
994         private static Result opCall (char[] target)
995         {
996                 Result result;
997 
998                 result.target_ = target;
999                 return result;
1000         }
1001 
1002         /**********************************************************************
1003 
1004         **********************************************************************/
1005 
1006         private void opCatAssign (const(char)[] rhs)
1007         {
1008                 auto end = index + rhs.length;
1009                 assert (end < target_.length);
1010 
1011                 target_[index .. end] = rhs[];
1012                 index = end;
1013         }
1014 
1015         /**********************************************************************
1016 
1017         **********************************************************************/
1018 
1019         private void opCatAssign (char rhs)
1020         {
1021                 assert (index < target_.length);
1022                 target_[index++] = rhs;
1023         }
1024 
1025         /**********************************************************************
1026 
1027         **********************************************************************/
1028 
1029         @property private char[] get ()
1030         {
1031                 return target_[0 .. index];
1032         }
1033 }
1034 
1035 /******************************************************************************
1036 
1037 ******************************************************************************/
1038 
1039 debug (DateTime)
1040 {
1041         import tango.io.Stdout;
1042 
1043         void main()
1044         {
1045                 char[100] tmp;
1046                 auto time = WallClock.now;
1047                 auto locale = DateTimeLocale.create;
1048 
1049                 Stdout.formatln ("d: {}", locale.format (tmp, time, "d"));
1050                 Stdout.formatln ("D: {}", locale.format (tmp, time, "D"));
1051                 Stdout.formatln ("f: {}", locale.format (tmp, time, "f"));
1052                 Stdout.formatln ("F: {}", locale.format (tmp, time, "F"));
1053                 Stdout.formatln ("g: {}", locale.format (tmp, time, "g"));
1054                 Stdout.formatln ("G: {}", locale.format (tmp, time, "G"));
1055                 Stdout.formatln ("r: {}", locale.format (tmp, time, "r"));
1056                 Stdout.formatln ("s: {}", locale.format (tmp, time, "s"));
1057                 Stdout.formatln ("t: {}", locale.format (tmp, time, "t"));
1058                 Stdout.formatln ("T: {}", locale.format (tmp, time, "T"));
1059                 Stdout.formatln ("y: {}", locale.format (tmp, time, "y"));
1060                 Stdout.formatln ("u: {}", locale.format (tmp, time, "u"));
1061                 Stdout.formatln ("@: {}", locale.format (tmp, time, "@"));
1062                 Stdout.formatln ("{}", locale.generic.format (tmp, time, "ddd, dd MMM yyyy HH':'mm':'ss zzzz"));
1063         }
1064 }