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