123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065
/*******************************************************************************

        copyright:      Copyright (c) 2005 John Chapman. All rights reserved

        license:        BSD style: $(LICENSE)

        version:        Jan 2005: initial release
                        Mar 2009: extracted from locale, and
                                  converted to a struct

        author:         John Chapman, Kris, mwarning

        Support for formatting date/time values, in a locale-specific
        manner. See DateTimeLocale.format() for a description on how
        formatting is performed (below).

        Reference links:
        ---
        http://www.opengroup.org/onlinepubs/007908799/xsh/strftime.html
        http://msdn.microsoft.com/en-us/library/system.globalization.datetimeformatinfo(VS.71).aspx
        ---

******************************************************************************/

module tango.text.convert.DateTime;

private import  tango.core.Exception;

private import  tango.time.WallClock;

private import  tango.time.chrono.Calendar,
                tango.time.chrono.Gregorian;

private import  Utf = tango.text.convert.Utf;

private import  Integer = tango.text.convert.Integer;

version (WithExtensions)
         private import tango.text.convert.Extensions;

/******************************************************************************

        O/S specifics

******************************************************************************/

version (Windows)
         private import tango.sys.win32.UserGdi;
else
{
        private import tango.stdc.stringz;
        private import tango.stdc.posix.langinfo;
}

/******************************************************************************

        The default DateTimeLocale instance

******************************************************************************/

public __gshared DateTimeLocale DateTimeDefault;

shared static this()
{
        DateTimeDefault = DateTimeLocale.create();
version (WithExtensions)
        {
        Extensions8.add  (typeid(Time), &DateTimeDefault.bridge!(char));
        Extensions16.add (typeid(Time), &DateTimeDefault.bridge!(wchar));
        Extensions32.add (typeid(Time), &DateTimeDefault.bridge!(dchar));
        }
}

/******************************************************************************

        How to format locale-specific date/time output

******************************************************************************/

struct DateTimeLocale
{
        enum immutable(char)[]   rfc1123Pattern = "ddd, dd MMM yyyy HH':'mm':'ss 'GMT'";
        enum immutable(char)[]   sortableDateTimePattern = "yyyy'-'MM'-'dd'T'HH':'mm':'ss";
        enum immutable(char)[]   universalSortableDateTimePattern = "yyyy'-'MM'-'dd' 'HH':'mm':'ss'Z'";

        Calendar               assignedCalendar;

        const(char)[]          shortDatePattern,
                               shortTimePattern,
                               longDatePattern,
                               longTimePattern,
                               fullDateTimePattern,
                               generalShortTimePattern,
                               generalLongTimePattern,
                               monthDayPattern,
                               yearMonthPattern;

        const(char)[]          amDesignator,
                               pmDesignator;

        const(char)[]          timeSeparator,
                               dateSeparator;

        const(char)[][]        dayNames,
                               monthNames,
                               abbreviatedDayNames,
                               abbreviatedMonthNames;

        /**********************************************************************

                Format the given Time value into the provided output,
                using the specified layout. The layout can be a generic
                variant or a custom one, where generics are indicated
                via a single character:

                <pre>
                "t" = 7:04
                "T" = 7:04:02 PM
                "d" = 3/30/2009
                "D" = Monday, March 30, 2009
                "f" = Monday, March 30, 2009 7:04 PM
                "F" = Monday, March 30, 2009 7:04:02 PM
                "g" = 3/30/2009 7:04 PM
                "G" = 3/30/2009 7:04:02 PM
                "y"
                "Y" = March, 2009
                "r"
                "R" = Mon, 30 Mar 2009 19:04:02 GMT
                "s" = 2009-03-30T19:04:02
                "u" = 2009-03-30 19:04:02Z
                </pre>

                For the US locale, these generic layouts are expanded in the
                following manner:

                <pre>
                "t" = "h:mm"
                "T" = "h:mm:ss tt"
                "d" = "M/d/yyyy"
                "D" = "dddd, MMMM d, yyyy"
                "f" = "dddd, MMMM d, yyyy h:mm tt"
                "F" = "dddd, MMMM d, yyyy h:mm:ss tt"
                "g" = "M/d/yyyy h:mm tt"
                "G" = "M/d/yyyy h:mm:ss tt"
                "y"
                "Y" = "MMMM, yyyy"
                "r"
                "R" = "ddd, dd MMM yyyy HH':'mm':'ss 'GMT'"
                "s" = "yyyy'-'MM'-'dd'T'HH':'mm':'ss"
                "u" = "yyyy'-'MM'-'dd' 'HH':'mm':'ss'Z'"
                </pre>

                Custom layouts are constructed using a combination of the
                character codes indicated on the right, above. For example,
                a layout of "dddd, dd MMM yyyy HH':'mm':'ss zzzz" will emit
                something like this:
                ---
                Monday, 30 Mar 2009 19:04:02 -08:00
                ---

                Using these format indicators with Layout (Stdout etc) is
                straightforward. Formatting integers, for example, is done
                like so:
                ---
                Stdout.formatln ("{:u}", 5);
                Stdout.formatln ("{:b}", 5);
                Stdout.formatln ("{:x}", 5);
                ---

                Formatting date/time values is similar, where the format
                indicators are provided after the colon:
                ---
                Stdout.formatln ("{:t}", Clock.now);
                Stdout.formatln ("{:D}", Clock.now);
                Stdout.formatln ("{:dddd, dd MMMM yyyy HH:mm}", Clock.now);
                ---

        **********************************************************************/

        char[] format (char[] output, Time dateTime, const(char)[] layout)
        {
                // default to general format
                if (layout.length is 0)
                    layout = "G";

                // might be one of our shortcuts
                if (layout.length is 1)
                    layout = expandKnownFormat (layout);

                auto res=Result(output);
                return formatCustom (res, dateTime, layout);
        }

        /**********************************************************************

        **********************************************************************/

        T[] formatWide(T) (T[] output, Time dateTime, const(T)[] fmt)
        {
                static if (is (T == char))
                           return format (output, dateTime, fmt);
                else
                   {
                   char[128] tmp0 = void;
                   char[128] tmp1 = void;
                   return Utf.fromString8(format(tmp0, dateTime, Utf.toString(fmt, tmp1)), output);
                   }
        }

        /**********************************************************************

                Return a generic English/US instance

        **********************************************************************/

        @property static DateTimeLocale* generic ()
        {
                return &EngUS;
        }

        /**********************************************************************

                Return the assigned Calendar instance, using Gregorian
                as the default

        **********************************************************************/

        @property Calendar calendar ()
        {
                if (assignedCalendar is null)
                    assignedCalendar = Gregorian.generic;
                return assignedCalendar;
        }

        /**********************************************************************

                Return a short day name

        **********************************************************************/

        const(char)[] abbreviatedDayName (Calendar.DayOfWeek dayOfWeek)
        {
                return abbreviatedDayNames [cast(int) dayOfWeek];
        }

        /**********************************************************************

                Return a long day name

        **********************************************************************/

        const(char)[] dayName (Calendar.DayOfWeek dayOfWeek)
        {
                return dayNames [cast(int) dayOfWeek];
        }

        /**********************************************************************

                Return a short month name

        **********************************************************************/

        const(char)[] abbreviatedMonthName (int month)
        {
                assert (month > 0 && month < 13);
                return abbreviatedMonthNames [month - 1];
        }

        /**********************************************************************

                Return a long month name

        **********************************************************************/

        const(char)[] monthName (int month)
        {
                assert (month > 0 && month < 13);
                return monthNames [month - 1];
        }

version (Windows)
        {
        /**********************************************************************

                create and populate an instance via O/S configuration
                for the current user

        **********************************************************************/

        static DateTimeLocale create ()
        {
                static char[] toString (char[] dst, LCID id, LCTYPE type)
                {
                        wchar[256] wide = void;

                        auto len = GetLocaleInfoW (id, type, null, 0);
                        if (len && len < wide.length)
                           {
                           GetLocaleInfoW (id, type, wide.ptr, wide.length);
                           len = WideCharToMultiByte (CP_UTF8, 0, wide.ptr, len-1,
                                                      cast(PCHAR)dst.ptr, dst.length,
                                                      null, null);
                           return dst [0..len].dup;
                           }
                        throw new Exception ("DateTime :: GetLocaleInfo failed");
                }

                DateTimeLocale dt;
                char[256] tmp = void;
                auto lcid = LOCALE_USER_DEFAULT;

                for (auto i=LOCALE_SDAYNAME1; i <= LOCALE_SDAYNAME7; ++i)
                     dt.dayNames ~= toString (tmp, lcid, i);

                for (auto i=LOCALE_SABBREVDAYNAME1; i <= LOCALE_SABBREVDAYNAME7; ++i)
                     dt.abbreviatedDayNames ~= toString (tmp, lcid, i);

                for (auto i=LOCALE_SMONTHNAME1; i <= LOCALE_SMONTHNAME12; ++i)
                     dt.monthNames ~= toString (tmp, lcid, i);

                for (auto i=LOCALE_SABBREVMONTHNAME1; i <= LOCALE_SABBREVMONTHNAME12; ++i)
                     dt.abbreviatedMonthNames ~= toString (tmp, lcid, i);

                dt.dateSeparator    = toString (tmp, lcid, LOCALE_SDATE);
                dt.timeSeparator    = toString (tmp, lcid, LOCALE_STIME);
                dt.amDesignator     = toString (tmp, lcid, LOCALE_S1159);
                dt.pmDesignator     = toString (tmp, lcid, LOCALE_S2359);
                dt.longDatePattern  = toString (tmp, lcid, LOCALE_SLONGDATE);
                dt.shortDatePattern = toString (tmp, lcid, LOCALE_SSHORTDATE);
                dt.yearMonthPattern = toString (tmp, lcid, LOCALE_SYEARMONTH);
                dt.longTimePattern  = toString (tmp, lcid, LOCALE_STIMEFORMAT);

                // synthesize a short time
                auto s = dt.shortTimePattern = dt.longTimePattern;
                for (auto i=s.length; i--;)
                     if (s[i] is dt.timeSeparator[0])
                        {
                        dt.shortTimePattern = s[0..i];
                        break;
                        }

                dt.fullDateTimePattern = dt.longDatePattern ~ " " ~
                                         dt.longTimePattern;
                dt.generalLongTimePattern = dt.shortDatePattern ~ " " ~
                                            dt.longTimePattern;
                dt.generalShortTimePattern = dt.shortDatePattern ~ " " ~
                                             dt.shortTimePattern;
                return dt;
        }
        }
else
        {
        /**********************************************************************

                create and populate an instance via O/S configuration
                for the current user

        **********************************************************************/

        static DateTimeLocale create ()
        {
                //extract separator
                const(char)[] extractSeparator(const(char)[] str, const(char)[] def)
                {
                        for (auto i = 0; i < str.length; ++i)
                            {
                            char c = str[i];
                            if ((c == '%') || (c == ' ') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
                                continue;
                            return str[i..i+1];
                            }
                        return def;
                }

                const(char)[] getString(nl_item id, const(char)[] def = null)
                {
                        char* p = nl_langinfo(id);
                        return p ? fromStringz(p).dup : def;
                }

                const(char)[] getFormatString(nl_item id, const(char)[] def = null)
                {
                        const(char)[] posix_str = getString(id, def);
                        return convert(posix_str);
                }

                DateTimeLocale dt;

                for (auto i = DAY_1; i <= DAY_7; ++i)
                     dt.dayNames ~= getString (i);

                for (auto i = ABDAY_1; i <= ABDAY_7; ++i)
                     dt.abbreviatedDayNames ~= getString (i);

                for (auto i = MON_1; i <= MON_12; ++i)
                     dt.monthNames ~= getString (i);

                for (auto i = ABMON_1; i <= ABMON_12; ++i)
                     dt.abbreviatedMonthNames ~= getString (i);

                dt.amDesignator = getString (AM_STR, "AM");
                dt.pmDesignator = getString (PM_STR, "PM");

                dt.longDatePattern = "dddd, MMMM d, yyyy"; //default
                dt.shortDatePattern = getFormatString(D_FMT, "M/d/yyyy");

                dt.longTimePattern = getFormatString(T_FMT, "h:mm:ss tt");
                dt.shortTimePattern = "h:mm"; //default

                dt.yearMonthPattern = "MMMM, yyyy"; //no posix equivalent?
                dt.fullDateTimePattern = getFormatString(D_T_FMT, "dddd, MMMM d, yyyy h:mm:ss tt");

                dt.dateSeparator = extractSeparator(dt.shortDatePattern, "/");
                dt.timeSeparator = extractSeparator(dt.longTimePattern, ":");

                //extract shortTimePattern from longTimePattern
                for (auto i = dt.longTimePattern.length; i--;)
                    {
                    if (dt.longTimePattern[i] == dt.timeSeparator[$-1])
                       {
                       dt.shortTimePattern = dt.longTimePattern[0..i];
                       break;
                       }
                    }

                //extract longDatePattern from fullDateTimePattern
                auto pos = dt.fullDateTimePattern.length - dt.longTimePattern.length - 2;
                if (pos < dt.fullDateTimePattern.length)
                    dt.longDatePattern = dt.fullDateTimePattern[0..pos];

                dt.fullDateTimePattern = dt.longDatePattern ~ " " ~ dt.longTimePattern;
                dt.generalLongTimePattern = dt.shortDatePattern ~ " " ~  dt.longTimePattern;
                dt.generalShortTimePattern = dt.shortDatePattern ~ " " ~  dt.shortTimePattern;

                return dt;
        }

        /**********************************************************************

                Convert POSIX date time format to .NET format syntax.

        **********************************************************************/

        private static char[] convert(const(char)[] fmt)
        {
                char[32] ret;
                size_t len;

                void put(const(char)[] str)
                {
                        assert((len+str.length) <= ret.length);
                        ret[len..len+str.length] = str;
                        len += str.length;
                }

                for (auto i = 0; i < fmt.length; ++i)
                    {
                    char c = fmt[i];

                    if (c != '%')
                       {
                        assert((len+1) <= ret.length);
                        ret[len] = c;
                        len += 1;
                       continue;
                       }

                    i++;
                    if (i >= fmt.length)
                        break;

                    c = fmt[i];
                    switch (c)
                           {
                           case 'a': //locale's abbreviated weekday name.
                                put("ddd"); //The abbreviated name of the day of the week,
                                break;

                           case 'A': //locale's full weekday name.
                                put("dddd");
                                break;

                           case 'b': //locale's abbreviated month name
                                put("MMM");
                                break;

                           case 'B': //locale's full month name
                                put("MMMM");
                                break;

                           case 'd': //day of the month as a decimal number [01,31]
                                put("dd"); // The day of the month. Single-digit
                                //days will have a leading zero.
                                break;

                           case 'D': //same as %m/%d/%y.
                                put("MM/dd/yy");
                                break;

                           case 'e': //day of the month as a decimal number [1,31];
                                //a single digit is preceded by a space
                                put("d"); //The day of the month. Single-digit days
                                //will not have a leading zero.
                                break;

                           case 'h': //same as %b.
                                put("MMM");
                                break;

                           case 'H':
                                //hour (24-hour clock) as a decimal number [00,23]
                                put("HH"); //The hour in a 24-hour clock. Single-digit
                                //hours will have a leading zero.
                                break;

                           case 'I': //the hour (12-hour clock) as a decimal number [01,12]
                                put("hh"); //The hour in a 12-hour clock.
                                //Single-digit hours will have a leading zero.
                                break;

                           case 'm': //month as a decimal number [01,12]
                                put("MM"); //The numeric month. Single-digit
                                //months will have a leading zero.
                                break;

                           case 'M': //minute as a decimal number [00,59]
                                put("mm"); //The minute. Single-digit minutes
                                //will have a leading zero.
                                break;

                           case 'n': //newline character
                                put("\n");
                                break;

                           case 'p': //locale's equivalent of either a.m. or p.m
                                put("tt");
                                break;

                           case 'r': //time in a.m. and p.m. notation;
                                //equivalent to %I:%M:%S %p.
                                put("hh:mm:ss tt");
                                break;

                           case 'R': //time in 24 hour notation (%H:%M)
                                put("HH:mm");
                                break;

                           case 'S': //second as a decimal number [00,61]
                                put("ss"); //The second. Single-digit seconds
                                //will have a leading zero.
                                break;

                           case 't': //tab character.
                                put("\t");
                                break;

                           case 'T': //equivalent to (%H:%M:%S)
                                put("HH:mm:ss");
                                break;

                           case 'u': //weekday as a decimal number [1,7],
                                //with 1 representing Monday
                           case 'U': //week number of the year
                                //(Sunday as the first day of the week) as a decimal number [00,53]
                           case 'V': //week number of the year
                                //(Monday as the first day of the week) as a decimal number [01,53].
                                //If the week containing 1 January has four or more days
                                //in the new year, then it is considered week 1.
                                //Otherwise, it is the last week of the previous year, and the next week is week 1.
                           case 'w': //weekday as a decimal number [0,6], with 0 representing Sunday
                           case 'W': //week number of the year (Monday as the first day of the week)
                                //as a decimal number [00,53].
                                //All days in a new year preceding the first Monday
                                //are considered to be in week 0.
                           case 'x': //locale's appropriate date representation
                           case 'X': //locale's appropriate time representation
                           case 'c': //locale's appropriate date and time representation
                           case 'C': //century number (the year divided by 100 and
                                //truncated to an integer) as a decimal number [00-99]
                           case 'j': //day of the year as a decimal number [001,366]
                                assert(0);
                                //break;

                           case 'y': //year without century as a decimal number [00,99]
                                put("yy"); // The year without the century. If the year without
                                //the century is less than 10, the year is displayed with a leading zero.
                                break;

                           case 'Y': //year with century as a decimal number
                                put("yyyy"); //The year in four digits, including the century.
                                break;

                           case 'Z': //timezone name or abbreviation,
                                //or by no bytes if no timezone information exists
                                //assert(0);
                                break;

                           case '%':
                                put("%");
                                break;

                           default:
                                assert(0);
                           }
                    }
                return ret[0..len].dup;
        }
        }

        /**********************************************************************

        **********************************************************************/

        private const(char)[] expandKnownFormat (const(char)[] format)
        {
                const(char)[] f;

                switch (format[0])
                       {
                       case 'd':
                            f = shortDatePattern;
                            break;
                       case 'D':
                            f = longDatePattern;
                            break;
                       case 'f':
                            f = longDatePattern ~ " " ~ shortTimePattern;
                            break;
                       case 'F':
                            f = fullDateTimePattern;
                            break;
                       case 'g':
                            f = generalShortTimePattern;
                            break;
                       case 'G':
                            f = generalLongTimePattern;
                            break;
                       case 'r':
                       case 'R':
                            f = rfc1123Pattern;
                            break;
                       case 's':
                            f = sortableDateTimePattern;
                            break;
                       case 'u':
                            f = universalSortableDateTimePattern;
                            break;
                       case 't':
                            f = shortTimePattern;
                            break;
                       case 'T':
                            f = longTimePattern;
                            break;
                       case 'y':
                       case 'Y':
                            f = yearMonthPattern;
                            break;
                       default:
                           return ("'{invalid time format}'");
                       }
                return f;
        }

        /**********************************************************************

        **********************************************************************/

        private char[] formatCustom (ref Result result, Time dateTime, const(char)[] format)
        {
                uint            len,
                                doy,
                                dow,
                                era;
                uint            day,
                                year,
                                month;
                int             index;
                char[10]        tmp = void;
                auto            time = dateTime.time;

                // extract date components
                calendar.split (dateTime, year, month, day, doy, dow, era);

                // sweep format specifiers ...
                while (index < format.length)
                      {
                      char c = format[index];

                      switch (c)
                             {
                             // day
                             case 'd':
                                  len = parseRepeat (format, index, c);
                                  if (len <= 2)
                                     result ~= formatInt (tmp, day, len);
                                  else
                                     result ~= formatDayOfWeek (cast(Calendar.DayOfWeek) dow, len);
                                  break;

                             // millis
                            case 'f':
                                len = parseRepeat (format, index, c);
                                auto num = Integer.itoa (tmp, time.millis);
                                if(len > num.length)
                                {
                                    result ~= num;

                                    // append '0's
                                    static char[8] zeros = '0';
                                    auto zc = len - num.length;
                                    zc = (zc > zeros.length) ? zeros.length : zc;
                                    result ~= zeros[0..zc];
                                }
                                else
                                    result ~= num[0..len];
                                break;

                             // millis, no trailing zeros
                            case 'F':
                                len = parseRepeat (format, index, c);
                                auto num = Integer.itoa (tmp, time.millis);
                                auto idx = (len < num.length) ? len : num.length;

                                // strip '0's
                                while(idx && num[idx-1] is '0')
                                    --idx;

                                result ~= num[0..idx];
                                break;

                             // month
                             case 'M':
                                  len = parseRepeat (format, index, c);
                                  if (len <= 2)
                                      result ~= formatInt (tmp, month, len);
                                  else
                                     result ~= formatMonth (month, len);
                                  break;

                             // year
                             case 'y':
                                  len = parseRepeat (format, index, c);

                                  // Two-digit years for Japanese
                                  if (calendar.id is Calendar.JAPAN)
                                      result ~= formatInt (tmp, year, 2);
                                  else
                                     {
                                     if (len <= 2)
                                         result ~= formatInt (tmp, year % 100, len);
                                     else
                                        result ~= formatInt (tmp, year, len);
                                     }
                                  break;

                             // hour (12-hour clock)
                             case 'h':
                                  len = parseRepeat (format, index, c);
                                  int hour = time.hours % 12;
                                  if (hour is 0)
                                      hour = 12;
                                  result ~= formatInt (tmp, hour, len);
                                  break;

                             // hour (24-hour clock)
                             case 'H':
                                  len = parseRepeat (format, index, c);
                                  result ~= formatInt (tmp, time.hours, len);
                                  break;

                             // minute
                             case 'm':
                                  len = parseRepeat (format, index, c);
                                  result ~= formatInt (tmp, time.minutes, len);
                                  break;

                             // second
                             case 's':
                                  len = parseRepeat (format, index, c);
                                  result ~= formatInt (tmp, time.seconds, len);
                                  break;

                             // AM/PM
                             case 't':
                                  len = parseRepeat (format, index, c);
                                  if (len is 1)
                                     {
                                     if (time.hours < 12)
                                        {
                                        if (amDesignator.length != 0)
                                            result ~= amDesignator[0];
                                        }
                                     else
                                        {
                                        if (pmDesignator.length != 0)
                                            result ~= pmDesignator[0];
                                        }
                                     }
                                  else
                                     result ~= (time.hours < 12) ? amDesignator : pmDesignator;
                                  break;

                             // timezone offset
                             case 'z':
                                  len = parseRepeat (format, index, c);
                                  auto minutes = cast(int) (WallClock.zone.minutes);
                                  if (minutes < 0)
                                     {
                                     minutes = -minutes;
                                     result ~= '-';
                                     }
                                  else
                                     result ~= '+';
                                  int hours = minutes / 60;
                                  minutes %= 60;

                                  if (len is 1)
                                      result ~= formatInt (tmp, hours, 1);
                                  else
                                     if (len is 2)
                                         result ~= formatInt (tmp, hours, 2);
                                     else
                                        {
                                        result ~= formatInt (tmp, hours, 2);
                                        result ~= formatInt (tmp, minutes, 2);
                                        }
                                  break;

                             // time separator
                             case ':':
                                  len = 1;
                                  result ~= timeSeparator;
                                  break;

                             // date separator
                             case '/':
                                  len = 1;
                                  result ~= dateSeparator;
                                  break;

                             // string literal
                             case '\"':
                             case '\'':
                                  len = parseQuote (result, format, index);
                                  break;

                             // other
                             default:
                                 len = 1;
                                 result ~= c;
                                 break;
                             }
                      index += len;
                      }
                return result.get;
        }

        /**********************************************************************

        **********************************************************************/

        private const(char)[] formatMonth (int month, int rpt)
        {
                if (rpt is 3)
                    return abbreviatedMonthName (month);
                return monthName (month);
        }

        /**********************************************************************

        **********************************************************************/

        private const(char)[] formatDayOfWeek (Calendar.DayOfWeek dayOfWeek, int rpt)
        {
                if (rpt is 3)
                    return abbreviatedDayName (dayOfWeek);
                return dayName (dayOfWeek);
        }

        /**********************************************************************

        **********************************************************************/

        private T[] bridge(T) (T[] result, void* arg, const(T)[] format)
        {
                return formatWide (result, *cast(Time*) arg, format);
        }

        /**********************************************************************

        **********************************************************************/

        private static int parseRepeat(const(char)[] format, int pos, char c)
        {
                int n = pos + 1;
                while (n < format.length && format[n] is c)
                       n++;
                return n - pos;
        }

        /**********************************************************************

        **********************************************************************/

        private static char[] formatInt (char[] tmp, int v, int minimum)
        {
                auto num = Integer.itoa (tmp, v);
                if ((minimum -= num.length) > 0)
                   {
                   auto p = tmp.ptr + tmp.length - num.length;
                   while (minimum--)
                          *--p = '0';
                   num = tmp [p-tmp.ptr .. $];
                   }
                return num;
        }

        /**********************************************************************

        **********************************************************************/

        private static int parseQuote (ref Result result, const(char)[] format, int pos)
        {
                int start = pos;
                char chQuote = format[pos++];
                bool found;
                while (pos < format.length)
                      {
                      char c = format[pos++];
                      if (c is chQuote)
                         {
                         found = true;
                         break;
                         }
                      else
                         if (c is '\\')
                            { // escaped
                            if (pos < format.length)
                                result ~= format[pos++];
                            }
                         else
                            result ~= c;
                      }
                return pos - start;
        }
}

/******************************************************************************

        An english/usa locale
        Used as generic DateTimeLocale.

******************************************************************************/

private DateTimeLocale EngUS =
{
        shortDatePattern                : "M/d/yyyy",
        shortTimePattern                : "h:mm",
        longDatePattern                 : "dddd, MMMM d, yyyy",
        longTimePattern                 : "h:mm:ss tt",
        fullDateTimePattern             : "dddd, MMMM d, yyyy h:mm:ss tt",
        generalShortTimePattern         : "M/d/yyyy h:mm",
        generalLongTimePattern          : "M/d/yyyy h:mm:ss tt",
        monthDayPattern                 : "MMMM d",
        yearMonthPattern                : "MMMM, yyyy",
        amDesignator                    : "AM",
        pmDesignator                    : "PM",
        timeSeparator                   : ":",
        dateSeparator                   : "/",
        dayNames                        : ["Sunday", "Monday", "Tuesday", "Wednesday",
                                           "Thursday", "Friday", "Saturday"],
        monthNames                      : ["January", "February", "March", "April",
                                           "May", "June", "July", "August", "September",
                                           "October" "November", "December"],
        abbreviatedDayNames             : ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
        abbreviatedMonthNames           : ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
                                           "Jul", "Aug", "Sep", "Oct" "Nov", "Dec"],
};


/******************************************************************************

******************************************************************************/

private struct Result
{
        private size_t    index;
        private char[]  target_;

        /**********************************************************************

        **********************************************************************/

        private static Result opCall (char[] target)
        {
                Result result;

                result.target_ = target;
                return result;
        }

        /**********************************************************************

        **********************************************************************/

        private void opCatAssign (const(char)[] rhs)
        {
                auto end = index + rhs.length;
                assert (end < target_.length);

                target_[index .. end] = rhs;
                index = end;
        }

        /**********************************************************************

        **********************************************************************/

        private void opCatAssign (char rhs)
        {
                assert (index < target_.length);
                target_[index++] = rhs;
        }

        /**********************************************************************

        **********************************************************************/

        @property private char[] get ()
        {
                return target_[0 .. index];
        }
}

/******************************************************************************

******************************************************************************/

debug (DateTime)
{
        import tango.io.Stdout;

        void main()
        {
                char[100] tmp;
                auto time = WallClock.now;
                auto locale = DateTimeLocale.create;

                Stdout.formatln ("d: {}", locale.format (tmp, time, "d"));
                Stdout.formatln ("D: {}", locale.format (tmp, time, "D"));
                Stdout.formatln ("f: {}", locale.format (tmp, time, "f"));
                Stdout.formatln ("F: {}", locale.format (tmp, time, "F"));
                Stdout.formatln ("g: {}", locale.format (tmp, time, "g"));
                Stdout.formatln ("G: {}", locale.format (tmp, time, "G"));
                Stdout.formatln ("r: {}", locale.format (tmp, time, "r"));
                Stdout.formatln ("s: {}", locale.format (tmp, time, "s"));
                Stdout.formatln ("t: {}", locale.format (tmp, time, "t"));
                Stdout.formatln ("T: {}", locale.format (tmp, time, "T"));
                Stdout.formatln ("y: {}", locale.format (tmp, time, "y"));
                Stdout.formatln ("u: {}", locale.format (tmp, time, "u"));
                Stdout.formatln ("@: {}", locale.format (tmp, time, "@"));
                Stdout.formatln ("{}", locale.generic.format (tmp, time, "ddd, dd MMM yyyy HH':'mm':'ss zzzz"));
        }
}