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 }