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 }