root/src/tests/system/libroot/posix/locale_test.cpp
/*
 * Copyright 2010, Oliver Tappe, zooey@hirschkaefer.de
 * Distributed under the terms of the MIT License.
 */

#include <ctype.h>
#include <errno.h>
#include <langinfo.h>
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <wctype.h>


// #pragma mark - setlocale ----------------------------------------------------


void
test_setlocale()
{
        const char* locales[] = {
                "POSIX",
                "C",
                "de_DE",
                "en_US",
                "en_US.US-ASCII",
                "hr_HR.ISO-8859-2",
                "nl_NL",
                "nb_NO",
                "fr_FR.UTF-8@collation=phonebook",
                "de_DE.iso8859-1",
                "De_dE.IsO8859-15",
                "de_DE.utf8",
                "de_DE.UTF-8",
                "de_DE@euro",
                "de_DE@EURO",
                "de_DE.utf-8@Euro",
                "POSIX",
                "C",
                NULL
        };
        const char* expectedLocales[] = {
                "POSIX",
                "POSIX",
                "de_DE",
                "en_US",
                "en_US.US-ASCII",
                "hr_HR.ISO-8859-2",
                "nl_NL",
                "nb_NO",
                "fr_FR.UTF-8@collation=phonebook",
                "de_DE.iso8859-1",
                "De_dE.IsO8859-15",
                "de_DE.utf8",
                "de_DE.UTF-8",
                "de_DE@euro",
                "de_DE@EURO",
                "de_DE.utf-8@Euro",
                "POSIX",
                "POSIX"
        };
        const char* categoryNames[] = {
                "LC_ALL",
                "LC_COLLATE",
                "LC_CTYPE",
                "LC_MONETARY",
                "LC_NUMERIC",
                "LC_TIME",
                "LC_MESSAGES"
        };
        printf("setlocale()\n");

        int problemCount = 0;
        for (int i = 0; locales[i] != NULL; ++i) {
                char* result = setlocale(LC_ALL, locales[i]);
                if (!result || strcmp(result, expectedLocales[i]) != 0) {
                        printf("\tPROBLEM: setlocale(LC_ALL, \"%s\") = \"%s\" "
                                        "(expected \"%s\")\n",
                                locales[i], result, expectedLocales[i]);
                        problemCount++;
                }
        }

        for (int i = 1; i <= LC_LAST; ++i)
                setlocale(i, locales[i + 1]);
        char* result = setlocale(LC_ALL, NULL);
        const char* expectedResult
                = "LC_COLLATE=de_DE;LC_CTYPE=en_US;LC_MESSAGES=nb_NO;"
                        "LC_MONETARY=en_US.US-ASCII;LC_NUMERIC=hr_HR.ISO-8859-2;"
                        "LC_TIME=nl_NL";
        if (!result || strcmp(result, expectedResult) != 0) {
                printf("\tPROBLEM: setlocale(LC_ALL, NULL) = \"%s\" "
                                "(expected \"%s\")\n", result, expectedResult);
                problemCount++;
        }

        if (problemCount)
                printf("\t%d problem(s) found!\n", problemCount);
        else
                printf("\tall fine\n");
}


// #pragma mark - localeconv ---------------------------------------------------


void
dumpGrouping(const char* grouping, char* buf)
{
        for (char* bufPtr = buf; *grouping; ++grouping)
                bufPtr += sprintf(bufPtr, "\\x%02x", *grouping);
}


void
test_localeconv(const char* locale, const lconv* localeConv)
{
        setlocale(LC_MONETARY, locale);
        setlocale(LC_NUMERIC, locale);
        printf("localeconv for '%s'\n", locale);

        int problemCount = 0;
        struct lconv* lc = localeconv();
        if (!lc)
                printf("not ok - got no result from localeconv()\n");
        else {
                if (strcmp(lc->decimal_point, localeConv->decimal_point) != 0) {
                        printf("\tPROBLEM: lc.decimal_point = \"%s\" (expected \"%s\")\n",
                                lc->decimal_point, localeConv->decimal_point);
                        problemCount++;
                }
                if (strcmp(lc->thousands_sep, localeConv->thousands_sep) != 0) {
                        printf("\tPROBLEM: lc.thousands_sep = \"%s\" (expected \"%s\")\n",
                                lc->thousands_sep, localeConv->thousands_sep);
                        problemCount++;
                }
                if (strcmp(lc->grouping, localeConv->grouping) != 0) {
                        char gotGrouping[20], expectedGrouping[20];
                        dumpGrouping(lc->grouping, gotGrouping);
                        dumpGrouping(localeConv->grouping, expectedGrouping);
                        printf("\tPROBLEM: lc.grouping = \"%s\" (expected \"%s\")\n",
                                gotGrouping, expectedGrouping);
                        problemCount++;
                }
                if (strcmp(lc->int_curr_symbol, localeConv->int_curr_symbol) != 0) {
                        printf("\tPROBLEM: lc.int_curr_symbol = \"%s\" (expected \"%s\")\n",
                                lc->int_curr_symbol, localeConv->int_curr_symbol);
                        problemCount++;
                }
                if (strcmp(lc->currency_symbol, localeConv->currency_symbol) != 0) {
                        printf("\tPROBLEM: lc.currency_symbol = \"%s\" (expected \"%s\")\n",
                                lc->currency_symbol, localeConv->currency_symbol);
                        problemCount++;
                }
                if (strcmp(lc->mon_decimal_point, localeConv->mon_decimal_point) != 0) {
                        printf("\tPROBLEM: lc.mon_decimal_point = \"%s\" "
                                        "(expected \"%s\")\n",
                                lc->mon_decimal_point, localeConv->mon_decimal_point);
                        problemCount++;
                }
                if (strcmp(lc->mon_thousands_sep, localeConv->mon_thousands_sep) != 0) {
                        printf("\tPROBLEM: lc.mon_thousands_sep = \"%s\" "
                                        "(expected \"%s\")\n",
                                lc->mon_thousands_sep, localeConv->mon_thousands_sep);
                        problemCount++;
                }
                if (strcmp(lc->mon_grouping, localeConv->mon_grouping) != 0) {
                        char gotGrouping[20], expectedGrouping[20];
                        dumpGrouping(lc->mon_grouping, gotGrouping);
                        dumpGrouping(localeConv->mon_grouping, expectedGrouping);
                        printf("\tPROBLEM: lc.mon_grouping: \"%s\" (expected \"%s\")\n",
                                gotGrouping, expectedGrouping);
                        problemCount++;
                }
                if (strcmp(lc->positive_sign, localeConv->positive_sign) != 0) {
                        printf("\tPROBLEM: lc.positive_sign = \"%s\" (expected \"%s\")\n",
                                lc->positive_sign, localeConv->positive_sign);
                        problemCount++;
                }
                if (strcmp(lc->negative_sign, localeConv->negative_sign) != 0) {
                        printf("\tPROBLEM: lc.negative_sign = \"%s\" (expected \"%s\")\n",
                                lc->negative_sign, localeConv->negative_sign);
                        problemCount++;
                }
                if (lc->frac_digits != localeConv->frac_digits) {
                        printf("\tPROBLEM: lc.frac_digits = %d (expected %d)\n",
                                lc->frac_digits, localeConv->frac_digits);
                        problemCount++;
                }
                if (lc->int_frac_digits != localeConv->int_frac_digits) {
                        printf("\tPROBLEM: lc.int_frac_digits = %d (expected %d)\n",
                                lc->int_frac_digits, localeConv->int_frac_digits);
                        problemCount++;
                }
                if (lc->p_cs_precedes != localeConv->p_cs_precedes) {
                        printf("\tPROBLEM: lc.p_cs_precedes = %d (expected %d)\n",
                                lc->p_cs_precedes, localeConv->p_cs_precedes);
                        problemCount++;
                }
                if (lc->p_sep_by_space != localeConv->p_sep_by_space) {
                        printf("\tPROBLEM: lc.p_sep_by_space = %d (expected %d)\n",
                                lc->p_sep_by_space, localeConv->p_sep_by_space);
                        problemCount++;
                }
                if (lc->p_sign_posn != localeConv->p_sign_posn) {
                        printf("\tPROBLEM: lc.p_sign_posn = %d (expected %d)\n",
                                lc->p_sign_posn, localeConv->p_sign_posn);
                        problemCount++;
                }
                if (lc->n_cs_precedes != localeConv->n_cs_precedes) {
                        printf("\tPROBLEM: lc.n_cs_precedes = %d (expected %d)\n",
                                lc->n_cs_precedes, localeConv->n_cs_precedes);
                        problemCount++;
                }
                if (lc->n_sep_by_space != localeConv->n_sep_by_space) {
                        printf("\tPROBLEM: lc.n_sep_by_space = %d (expected %d)\n",
                                lc->n_sep_by_space, localeConv->n_sep_by_space);
                        problemCount++;
                }
                if (lc->n_sign_posn != localeConv->n_sign_posn) {
                        printf("\tPROBLEM: lc.n_sign_posn = %d (expected %d)\n",
                                lc->n_sign_posn, localeConv->n_sign_posn);
                        problemCount++;
                }
                if (lc->int_p_cs_precedes != localeConv->int_p_cs_precedes) {
                        printf("\tPROBLEM: lc.int_p_cs_precedes = %d (expected %d)\n",
                                lc->int_p_cs_precedes, localeConv->int_p_cs_precedes);
                        problemCount++;
                }
                if (lc->int_p_sep_by_space != localeConv->int_p_sep_by_space) {
                        printf("\tPROBLEM: lc.int_p_sep_by_space = %d (expected %d)\n",
                                lc->int_p_sep_by_space, localeConv->int_p_sep_by_space);
                        problemCount++;
                }
                if (lc->int_p_sign_posn != localeConv->int_p_sign_posn) {
                        printf("\tPROBLEM: lc.int_p_sign_posn = %d (expected %d)\n",
                                lc->int_p_sign_posn, localeConv->int_p_sign_posn);
                        problemCount++;
                }
                if (lc->int_n_cs_precedes != localeConv->int_n_cs_precedes) {
                        printf("\tPROBLEM: lc.int_n_cs_precedes = %d (expected %d)\n",
                                lc->int_n_cs_precedes, localeConv->int_n_cs_precedes);
                        problemCount++;
                }
                if (lc->int_n_sep_by_space != localeConv->int_n_sep_by_space) {
                        printf("\tPROBLEM: lc.int_n_sep_by_space = %d (expected %d)\n",
                                lc->int_n_sep_by_space, localeConv->int_n_sep_by_space);
                        problemCount++;
                }
                if (lc->int_n_sign_posn != localeConv->int_n_sign_posn) {
                        printf("\tPROBLEM: lc.int_n_sign_posn = %d (expected %d)\n",
                                lc->int_n_sign_posn, localeConv->int_n_sign_posn);
                        problemCount++;
                }
        }
        if (problemCount)
                printf("\t%d problem(s) found!\n", problemCount);
        else
                printf("\tall fine\n");
}


void
test_localeconv()
{
        const lconv lconv_posix = {
                (char*)".",
                (char*)"",
                (char*)"",
                (char*)"",
                (char*)"",
                (char*)"",
                (char*)"",
                (char*)"",
                (char*)"",
                (char*)"",
                CHAR_MAX,
                CHAR_MAX,
                CHAR_MAX,
                CHAR_MAX,
                CHAR_MAX,
                CHAR_MAX,
                CHAR_MAX,
                CHAR_MAX,
                CHAR_MAX,
                CHAR_MAX,
                CHAR_MAX,
                CHAR_MAX,
                CHAR_MAX,
                CHAR_MAX
        };
        test_localeconv("POSIX", &lconv_posix);

        const lconv lconv_de = {
                (char*)",",
                (char*)".",
                (char*)"\x03",
                (char*)"EUR ",
                (char*)"€",
                (char*)",",
                (char*)".",
                (char*)"\x03",
                (char*)"",
                (char*)"-",
                2,
                2,
                0,
                1,
                0,
                1,
                1,
                1,
                0,
                1,
                0,
                1,
                1,
                1
        };
        test_localeconv("de_DE", &lconv_de);

        const lconv lconv_de_iso = {
                (char*)",",
                (char*)".",
                (char*)"\x03",
                (char*)"EUR ",
                (char*)"EUR",
                (char*)",",
                (char*)".",
                (char*)"\x03",
                (char*)"",
                (char*)"-",
                2,
                2,
                0,
                1,
                0,
                1,
                1,
                1,
                0,
                1,
                0,
                1,
                1,
                1
        };
        test_localeconv("de_DE.ISO8859-1", &lconv_de_iso);

        const lconv lconv_hr = {
                (char*)",",
                (char*)".",
                (char*)"\x03",
                (char*)"HRK ",
                (char*)"kn",
                (char*)",",
                (char*)".",
                (char*)"\x03",
                (char*)"",
                (char*)"-",
                2,
                2,
                0,
                1,
                0,
                1,
                1,
                1,
                0,
                1,
                0,
                1,
                1,
                1
        };
        test_localeconv("hr_HR.ISO8859-2", &lconv_hr);

        const lconv lconv_de_CH = {
                (char*)".",
                (char*)"'",
                (char*)"\x03",
                (char*)"CHF ",
                (char*)"CHF",
                (char*)".",
                (char*)"'",
                (char*)"\x03",
                (char*)"",
                (char*)"-",
                2,
                2,
                1,
                1,
                1,
                0,
                4,
                4,
                1,
                1,
                1,
                0,
                4,
                4
        };
        test_localeconv("de_CH", &lconv_de_CH);

        const lconv lconv_gu_IN = {
                (char*)".",
                (char*)",",
                (char*)"\x03\x02",
                (char*)"INR ",
                (char*)"\xE2\x82\xB9",
                (char*)".",
                (char*)",",
                (char*)"\x03\x02",
                (char*)"",
                (char*)"-",
                2,
                2,
                1,
                1,
                1,
                1,
                1,
                1,
                1,
                1,
                1,
                1,
                1,
                1
        };
        test_localeconv("gu_IN", &lconv_gu_IN);

        const lconv lconv_it = {
                (char*)",",
                (char*)".",
                (char*)"\x03",
                (char*)"EUR ",
                (char*)"€",
                (char*)",",
                (char*)".",
                (char*)"\x03",
                (char*)"",
                (char*)"-",
                2,
                2,
                1,
                1,
                1,
                1,
                1,
                1,
                1,
                1,
                1,
                1,
                1,
                1
        };
        test_localeconv("it_IT", &lconv_it);

        const lconv lconv_nl = {
                (char*)",",
                (char*)".",
                (char*)"\x03",
                (char*)"EUR ",
                (char*)"€",
                (char*)",",
                (char*)".",
                (char*)"\x03",
                (char*)"",
                (char*)"-",
                2,
                2,
                1,
                1,
                1,
                1,
                2,
                2,
                1,
                1,
                1,
                1,
                2,
                2
        };
        test_localeconv("nl_NL", &lconv_nl);

        const lconv lconv_nb = {
                (char*)",",
                (char*)" ",
                (char*)"\x03",
                (char*)"NOK ",
                (char*)"kr",
                (char*)",",
                (char*)" ",
                (char*)"\x03",
                (char*)"",
                (char*)"-",
                2,
                2,
                1,
                1,
                1,
                1,
                1,
                1,
                1,
                1,
                1,
                1,
                1,
                1
        };
        test_localeconv("nb_NO", &lconv_nb);
}


// #pragma mark - strftime -----------------------------------------------------


struct strftime_data {
        const char* format;
        const char* result;
};


void
test_strftime(const char* locale, const strftime_data data[])
{
        setlocale(LC_TIME, locale);
        printf("strftime for '%s'\n", locale);

        time_t testTimeInSecs = 1279391169;     // Sat Jul 17 18:26:09 2010 UTC
        tm* testTime = localtime(&testTimeInSecs);
        int problemCount = 0;
        for(int i = 0; data[i].format != NULL; ++i) {
                char buf[100];
                strftime(buf, 100, data[i].format, testTime);
                if (strcmp(buf, data[i].result) != 0) {
                        printf("\tPROBLEM: strftime(\"%s\") = \"%s\" (expected \"%s\")\n",
                                data[i].format, buf, data[i].result);
                        problemCount++;
                }
        }
        if (problemCount)
                printf("\t%d problem(s) found!\n", problemCount);
        else
                printf("\tall fine\n");
}


void
test_strftime()
{
        setenv("TZ", "GMT", 1);

        const strftime_data strftime_posix[] = {
                { "%c", "Sat Jul 17 18:26:09 2010" },
                { "%x", "07/17/10" },
                { "%X", "18:26:09" },
                { "%a", "Sat" },
                { "%A", "Saturday" },
                { "%b", "Jul" },
                { "%B", "July" },
                { NULL, NULL }
        };
        test_strftime("POSIX", strftime_posix);

        const strftime_data strftime_de[] = {
                { "%c", "Samstag, 17. Juli 2010 18:26:09 GMT" },
                { "%x", "17.07.2010" },
                { "%X", "18:26:09" },
                { "%a", "Sa." },
                { "%A", "Samstag" },
                { "%b", "Jul" },
                { "%B", "Juli" },
                { NULL, NULL }
        };
        test_strftime("de_DE.UTF-8", strftime_de);

        const strftime_data strftime_hr[] = {
                { "%c", "subota, 17. srpnja 2010. 18:26:09 GMT" },
                { "%x", "17. 07. 2010." },
                { "%X", "18:26:09" },
                { "%a", "sub" },
                { "%A", "subota" },
                { "%b", "srp" },
                { "%B", "srpnja" },
                { NULL, NULL }
        };
        test_strftime("hr_HR.ISO8859-2", strftime_hr);

        const strftime_data strftime_gu[] = {
                { "%c", "શનિવાર, 17 જુલાઈ, 2010 06:26:09 PM GMT" },
                { "%x", "17 જુલાઈ, 2010" },
                { "%X", "06:26:09 PM" },
                { "%a", "શનિ" },
                { "%A", "શનિવાર" },
                { "%b", "જુલાઈ" },
                { "%B", "જુલાઈ" },
                { NULL, NULL }
        };
        test_strftime("gu_IN", strftime_gu);

        const strftime_data strftime_it[] = {
                { "%c", "sabato 17 luglio 2010 18:26:09 GMT" },
                { "%x", "17/lug/2010" },
                { "%X", "18:26:09" },
                { "%a", "sab" },
                { "%A", "sabato" },
                { "%b", "lug" },
                { "%B", "luglio" },
                { NULL, NULL }
        };
        test_strftime("it_IT", strftime_it);

        const strftime_data strftime_nl[] = {
                { "%c", "zaterdag 17 juli 2010 18:26:09 GMT" },
                { "%x", "17 jul. 2010" },
                { "%X", "18:26:09" },
                { "%a", "za" },
                { "%A", "zaterdag" },
                { "%b", "jul." },
                { "%B", "juli" },
                { NULL, NULL }
        };
        test_strftime("nl_NL", strftime_nl);

        const strftime_data strftime_nb[] = {
                { "%c", "kl. 18:26:09 GMT lørdag 17. juli 2010" },
                { "%x", "17. juli 2010" },
                { "%X", "18:26:09" },
                { "%a", "lør." },
                { "%A", "lørdag" },
                { "%b", "juli" },
                { "%B", "juli" },
                { NULL, NULL }
        };
        test_strftime("nb_NO", strftime_nb);
}


// #pragma mark - strftime -----------------------------------------------------


struct strptime_data {
        const char* format;
        const char* dateString;
};


void
test_strptime(const char* locale, const strptime_data data[])
{
        setlocale(LC_TIME, locale);
        printf("strptime for '%s'\n", locale);

        time_t expectedTimeInSecs = 1279391169; // Sat Jul 17 18:26:09 2010 UTC
        int problemCount = 0;
        for(int i = 0; data[i].format != NULL; ++i) {
                struct tm resultTime;
                if (strptime(data[i].dateString, data[i].format, &resultTime) == NULL) {
                        printf("\tPROBLEM: strptime(\"%s\", \"%s\") failed\n",
                                data[i].dateString, data[i].format);
                        problemCount++;
                } else {
                        time_t resultTimeInSecs = mktime(&resultTime);
                        if (resultTimeInSecs != expectedTimeInSecs) {
                                printf("\tPROBLEM: strptime(\"%s\", \"%s\") = \"%d\" (expected \"%d\")\n",
                                        data[i].dateString, data[i].format, resultTimeInSecs, expectedTimeInSecs);
                                problemCount++;
                        }
                }
        }
        if (problemCount)
                printf("\t%d problem(s) found!\n", problemCount);
        else
                printf("\tall fine\n");
}


void
test_strptime()
{
        setenv("TZ", "GMT", 1);

        const strptime_data strptime_posix[] = {
                { "%c", "Sat Jul 17 18:26:09 2010" },
                { "%x", "07/17/10" },
                { "%X", "18:26:09" },
                { "%a", "Sat" },
                { "%A", "Saturday" },
                { "%b", "Jul" },
                { "%B", "July" },
                { NULL, NULL }
        };
        test_strptime("POSIX", strptime_posix);

        const strptime_data strptime_de[] = {
                { "%c", "Samstag, 17. Juli 2010 18:26:09 GMT" },
                { "%x", "17.07.2010" },
                { "%X", "18:26:09" },
                { "%a", "Sa." },
                { "%A", "Samstag" },
                { "%b", "Jul" },
                { "%B", "Juli" },
                { NULL, NULL }
        };
        test_strptime("de_DE.UTF-8", strptime_de);

        const strptime_data strptime_hr[] = {
                { "%c", "subota, 17. srpnja 2010. 18:26:09 GMT" },
                { "%x", "17. 07. 2010." },
                { "%X", "18:26:09" },
                { "%a", "sub" },
                { "%A", "subota" },
                { "%b", "srp" },
                { "%B", "srpnja" },
                { NULL, NULL }
        };
        test_strptime("hr_HR.ISO8859-2", strptime_hr);

        const strptime_data strptime_gu[] = {
                { "%c", "શનિવાર, 17 જુલાઈ, 2010 06:26:09 PM GMT" },
                { "%x", "17 જુલાઈ, 2010" },
                { "%X", "06:26:09 PM" },
                { "%a", "શનિ" },
                { "%A", "શનિવાર" },
                { "%b", "જુલાઈ" },
                { "%B", "જુલાઈ" },
                { NULL, NULL }
        };
        test_strptime("gu_IN", strptime_gu);

        const strptime_data strptime_it[] = {
                { "%c", "sabato 17 luglio 2010 18:26:09 GMT" },
                { "%x", "17/lug/2010" },
                { "%X", "18:26:09" },
                { "%a", "sab" },
                { "%A", "sabato" },
                { "%b", "lug" },
                { "%B", "luglio" },
                { NULL, NULL }
        };
        test_strptime("it_IT", strptime_it);

        const strptime_data strptime_nl[] = {
                { "%c", "zaterdag 17 juli 2010 18:26:09 GMT" },
                { "%x", "17 jul. 2010" },
                { "%X", "18:26:09" },
                { "%a", "za" },
                { "%A", "zaterdag" },
                { "%b", "jul." },
                { "%B", "juli" },
                { NULL, NULL }
        };
        test_strptime("nl_NL", strptime_nl);

        const strptime_data strptime_nb[] = {
                { "%c", "kl. 18:26:09 GMT lørdag 17. juli 2010" },
                { "%x", "17. juli 2010" },
                { "%X", "18:26:09" },
                { "%a", "lør." },
                { "%A", "lørdag" },
                { "%b", "juli" },
                { "%B", "juli" },
                { NULL, NULL }
        };
        test_strptime("nb_NO", strptime_nb);
}

// #pragma mark - ctype --------------------------------------------------------


unsigned short
determineFullClassInfo(int i)
{
        unsigned short classInfo = 0;

        if (isblank(i))
                classInfo |= _ISblank;
        if (iscntrl(i))
                classInfo |= _IScntrl;
        if (ispunct(i))
                classInfo |= _ISpunct;
        if (isalnum(i))
                classInfo |= _ISalnum;
        if (isupper(i))
                classInfo |= _ISupper;
        if (islower(i))
                classInfo |= _ISlower;
        if (isalpha(i))
                classInfo |= _ISalpha;
        if (isdigit(i))
                classInfo |= _ISdigit;
        if (isxdigit(i))
                classInfo |= _ISxdigit;
        if (isspace(i))
                classInfo |= _ISspace;
        if (isprint(i))
                classInfo |= _ISprint;
        if (isgraph(i))
                classInfo |= _ISgraph;

        return classInfo;
}


void
test_ctype(const char* locale, const unsigned short int classInfos[],
        const int toLowerMap[], const int toUpperMap[])
{
        setlocale(LC_CTYPE, locale);
        printf("ctype of %s locale\n", locale);

        int problemCount = 0;
        for (int i = -1; i < 256; ++i) {
                unsigned short classInfo = determineFullClassInfo(i);

                if (i < 255) {
                        char iAsChar = (char)i;
                        unsigned short classInfoFromChar = determineFullClassInfo(iAsChar);

                        if (classInfo != classInfoFromChar) {
                                printf("\tPROBLEM: ctype((int)%d)=%x, but ctype((char)%d)=%x\n",
                                        i, classInfo, i, classInfoFromChar);
                                problemCount++;
                        }
                }
                if (classInfo != classInfos[i + 1]) {
                        printf("\tPROBLEM: ctype(%d) = %x (expected %x)\n", i, classInfo,
                                classInfos[i + 1]);
                        problemCount++;
                }
                int lower = tolower(i);
                if (lower != toLowerMap[i + 1]) {
                        printf("\tPROBLEM: tolower(%d) = %x (expected %x)\n", i, lower,
                                toLowerMap[i + 1]);
                        problemCount++;
                }
                int upper = toupper(i);
                if (upper != toUpperMap[i + 1]) {
                        printf("\tPROBLEM: toupper(%d) = %x (expected %x)\n", i, upper,
                                toUpperMap[i + 1]);
                        problemCount++;
                }
        }
        if (problemCount)
                printf("\t%d problem(s) found!\n", problemCount);
        else
                printf("\tall fine\n");
}


void
test_ctype()
{
        const unsigned short int classInfos_posix[257] = {
                /*  -1 */   0,  // neutral value
                /*   0 */       _IScntrl, _IScntrl, _IScntrl, _IScntrl, _IScntrl, _IScntrl, _IScntrl, _IScntrl,
                /*   8 */       _IScntrl, _ISblank|_IScntrl|_ISspace, _IScntrl|_ISspace, _IScntrl|_ISspace, _IScntrl|_ISspace, _IScntrl|_ISspace, _IScntrl, _IScntrl,
                /*  16 */       _IScntrl, _IScntrl, _IScntrl, _IScntrl, _IScntrl, _IScntrl, _IScntrl, _IScntrl,
                /*  24 */       _IScntrl, _IScntrl, _IScntrl, _IScntrl, _IScntrl, _IScntrl, _IScntrl, _IScntrl,
                /*  32 */       _ISblank|_ISspace|_ISprint, _ISpunct|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph,
                /*  40 */       _ISpunct|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph,
                /*  48 */       _ISalnum|_ISdigit|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISdigit|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISdigit|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISdigit|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISdigit|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISdigit|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISdigit|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISdigit|_ISxdigit|_ISprint|_ISgraph,
                /*  56 */       _ISalnum|_ISdigit|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISdigit|_ISxdigit|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph,
                /*  64 */       _ISpunct|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph,
                /*  72 */       _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph,
                /*  80 */       _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph,
                /*  88 */       _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph,
                /*  96 */       _ISpunct|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph,
                /* 104 */       _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph,
                /* 112 */       _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph,
                /* 120 */       _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _IScntrl,
                /* 128 */       0, 0, 0, 0, 0, 0, 0, 0,
                /* 136 */       0, 0, 0, 0, 0, 0, 0, 0,
                /* 144 */       0, 0, 0, 0, 0, 0, 0, 0,
                /* 152 */       0, 0, 0, 0, 0, 0, 0, 0,
                /* 160 */       0, 0, 0, 0, 0, 0, 0, 0,
                /* 168 */       0, 0, 0, 0, 0, 0, 0, 0,
                /* 176 */       0, 0, 0, 0, 0, 0, 0, 0,
                /* 184 */       0, 0, 0, 0, 0, 0, 0, 0,
                /* 192 */       0, 0, 0, 0, 0, 0, 0, 0,
                /* 200 */       0, 0, 0, 0, 0, 0, 0, 0,
                /* 208 */       0, 0, 0, 0, 0, 0, 0, 0,
                /* 216 */       0, 0, 0, 0, 0, 0, 0, 0,
                /* 224 */       0, 0, 0, 0, 0, 0, 0, 0,
                /* 232 */       0, 0, 0, 0, 0, 0, 0, 0,
                /* 240 */       0, 0, 0, 0, 0, 0, 0, 0,
                /* 248 */       0, 0, 0, 0, 0, 0, 0, 0,
        };
        const int toLowerMap_posix[257] = {
                /*  -1 */    -1,        // identity value
                /*   0 */         0,   1,   2,   3,   4,   5,   6,   7,
                /*   8 */         8,   9,  10,  11,  12,  13,  14,  15,
                /*  16 */        16,  17,  18,  19,  20,  21,  22,  23,
                /*  24 */        24,  25,  26,  27,  28,  29,  30,  31,
                /*  32 */        32,  33,  34,  35,  36,  37,  38,  39,
                /*  40 */        40,  41,  42,  43,  44,  45,  46,  47,
                /*  48 */       '0', '1', '2', '3', '4', '5', '6', '7',
                /*  56 */       '8', '9',  58,  59,  60,  61,  62,  63,
                /*  64 */        64, 'a', 'b', 'c', 'd', 'e', 'f', 'g',
                /*  72 */       'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
                /*  80 */       'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
                /*  88 */       'x', 'y', 'z',  91,  92,  93,  94,  95,
                /*  96 */        96, 'a', 'b', 'c', 'd', 'e', 'f', 'g',
                /* 104 */       'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
                /* 112 */       'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
                /* 120 */       'x', 'y', 'z', 123, 124, 125, 126, 127,
                /* 128 */       128, 129, 130, 131, 132, 133, 134, 135,
                /* 136 */       136, 137, 138, 139, 140, 141, 142, 143,
                /* 144 */       144, 145, 146, 147, 148, 149, 150, 151,
                /* 152 */       152, 153, 154, 155, 156, 157, 158, 159,
                /* 160 */       160, 161, 162, 163, 164, 165, 166, 167,
                /* 168 */       168, 169, 170, 171, 172, 173, 174, 175,
                /* 176 */       176, 177, 178, 179, 180, 181, 182, 183,
                /* 184 */       184, 185, 186, 187, 188, 189, 190, 191,
                /* 192 */       192, 193, 194, 195, 196, 197, 198, 199,
                /* 200 */       200, 201, 202, 203, 204, 205, 206, 207,
                /* 208 */       208, 209, 210, 211, 212, 213, 214, 215,
                /* 216 */       216, 217, 218, 219, 220, 221, 222, 223,
                /* 224 */       224, 225, 226, 227, 228, 229, 230, 231,
                /* 232 */       232, 233, 234, 235, 236, 237, 238, 239,
                /* 240 */       240, 241, 242, 243, 244, 245, 246, 247,
                /* 248 */       248, 249, 250, 251, 252, 253, 254, 255,
        };
        const int toUpperMap_posix[257] = {
                /*  -1 */    -1,        // identity value
                /*   0 */         0,   1,   2,   3,   4,   5,   6,   7,
                /*   8 */         8,   9,  10,  11,  12,  13,  14,  15,
                /*  16 */        16,  17,  18,  19,  20,  21,  22,  23,
                /*  24 */        24,  25,  26,  27,  28,  29,  30,  31,
                /*  32 */        32,  33,  34,  35,  36,  37,  38,  39,
                /*  40 */        40,  41,  42,  43,  44,  45,  46,  47,
                /*  48 */       '0', '1', '2', '3', '4', '5', '6', '7',
                /*  56 */       '8', '9',  58,  59,  60,  61,  62,  63,
                /*  64 */        64, 'A', 'B', 'C', 'D', 'E', 'F', 'G',
                /*  72 */       'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
                /*  80 */       'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
                /*  88 */       'X', 'Y', 'Z',  91,  92,  93,  94,  95,
                /*  96 */        96, 'A', 'B', 'C', 'D', 'E', 'F', 'G',
                /* 104 */       'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
                /* 112 */       'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
                /* 120 */       'X', 'Y', 'Z', 123, 124, 125, 126, 127,
                /* 128 */       128, 129, 130, 131, 132, 133, 134, 135,
                /* 136 */       136, 137, 138, 139, 140, 141, 142, 143,
                /* 144 */       144, 145, 146, 147, 148, 149, 150, 151,
                /* 152 */       152, 153, 154, 155, 156, 157, 158, 159,
                /* 160 */       160, 161, 162, 163, 164, 165, 166, 167,
                /* 168 */       168, 169, 170, 171, 172, 173, 174, 175,
                /* 176 */       176, 177, 178, 179, 180, 181, 182, 183,
                /* 184 */       184, 185, 186, 187, 188, 189, 190, 191,
                /* 192 */       192, 193, 194, 195, 196, 197, 198, 199,
                /* 200 */       200, 201, 202, 203, 204, 205, 206, 207,
                /* 208 */       208, 209, 210, 211, 212, 213, 214, 215,
                /* 216 */       216, 217, 218, 219, 220, 221, 222, 223,
                /* 224 */       224, 225, 226, 227, 228, 229, 230, 231,
                /* 232 */       232, 233, 234, 235, 236, 237, 238, 239,
                /* 240 */       240, 241, 242, 243, 244, 245, 246, 247,
                /* 248 */       248, 249, 250, 251, 252, 253, 254, 255,
        };
        test_ctype("POSIX", classInfos_posix, toLowerMap_posix, toUpperMap_posix);

        const unsigned short int classInfos_de[257] = {
                /*  -1 */   0,  // neutral value
                /*   0 */       _IScntrl, _IScntrl, _IScntrl, _IScntrl, _IScntrl, _IScntrl, _IScntrl, _IScntrl,
                /*   8 */       _IScntrl, _ISblank|_IScntrl|_ISspace, _IScntrl|_ISspace, _IScntrl|_ISspace, _IScntrl|_ISspace, _IScntrl|_ISspace, _IScntrl, _IScntrl,
                /*  16 */       _IScntrl, _IScntrl, _IScntrl, _IScntrl, _IScntrl, _IScntrl, _IScntrl, _IScntrl,
                /*  24 */       _IScntrl, _IScntrl, _IScntrl, _IScntrl, _IScntrl, _IScntrl, _IScntrl, _IScntrl,
                /*  32 */       _ISblank|_ISspace|_ISprint, _ISpunct|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph,
                /*  40 */       _ISpunct|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph,
                /*  48 */       _ISalnum|_ISdigit|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISdigit|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISdigit|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISdigit|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISdigit|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISdigit|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISdigit|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISdigit|_ISxdigit|_ISprint|_ISgraph,
                /*  56 */       _ISalnum|_ISdigit|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISdigit|_ISxdigit|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISprint|_ISgraph, _ISprint|_ISgraph, _ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph,
                /*  64 */       _ISpunct|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph,
                /*  72 */       _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph,
                /*  80 */       _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph,
                /*  88 */       _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph,
                /*  96 */       _ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph,
                /* 104 */       _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph,
                /* 112 */       _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph,
                /* 120 */       _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISprint|_ISgraph, _IScntrl,
                /* 128 */       _IScntrl, _IScntrl, _IScntrl, _IScntrl, _IScntrl, _IScntrl|_ISspace, _IScntrl, _IScntrl,
                /* 136 */       _IScntrl, _IScntrl, _IScntrl, _IScntrl, _IScntrl, _IScntrl, _IScntrl, _IScntrl,
                /* 144 */       _IScntrl, _IScntrl, _IScntrl, _IScntrl, _IScntrl, _IScntrl, _IScntrl, _IScntrl,
                /* 152 */       _IScntrl, _IScntrl, _IScntrl, _IScntrl, _IScntrl, _IScntrl, _IScntrl, _IScntrl,
                /* 160 */       _ISprint|_ISspace|_ISblank, _ISprint|_ISgraph|_ISpunct, _ISprint|_ISgraph, _ISprint|_ISgraph, _ISprint|_ISgraph, _ISprint|_ISgraph, _ISprint|_ISgraph, _ISprint|_ISgraph,
                /* 168 */       _ISprint|_ISgraph, _ISprint|_ISgraph, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISlower, _ISprint|_ISgraph|_ISpunct, _ISprint|_ISgraph, _ISprint|_ISgraph, _ISprint|_ISgraph, _ISprint|_ISgraph,
                /* 176 */       _ISprint|_ISgraph, _ISprint|_ISgraph, _ISprint|_ISgraph, _ISprint|_ISgraph, _ISprint|_ISgraph, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISlower, _ISprint|_ISgraph, _ISprint|_ISgraph|_ISpunct,
                /* 184 */       _ISprint|_ISgraph, _ISprint|_ISgraph, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISlower, _ISprint|_ISgraph|_ISpunct, _ISprint|_ISgraph, _ISprint|_ISgraph, _ISprint|_ISgraph, _ISprint|_ISgraph|_ISpunct,
                /* 192 */       _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISupper, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISupper, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISupper, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISupper, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISupper, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISupper, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISupper, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISupper,
                /* 200 */       _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISupper, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISupper, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISupper, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISupper, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISupper, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISupper, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISupper, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISupper,
                /* 208 */       _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISupper, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISupper, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISupper, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISupper, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISupper, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISupper, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISupper, _ISprint|_ISgraph,
                /* 216 */       _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISupper, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISupper, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISupper, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISupper, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISupper, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISupper, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISupper, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISlower,
                /* 224 */       _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISlower, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISlower, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISlower, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISlower, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISlower, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISlower, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISlower, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISlower,
                /* 232 */       _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISlower, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISlower, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISlower, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISlower, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISlower, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISlower, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISlower, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISlower,
                /* 240 */       _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISlower, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISlower, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISlower, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISlower, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISlower, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISlower, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISlower, _ISprint|_ISgraph,
                /* 248 */       _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISlower, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISlower, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISlower, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISlower, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISlower, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISlower, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISlower, _ISprint|_ISgraph|_ISalnum|_ISalpha|_ISlower,
        };
        const int toLowerMap_de[257] = {
                /*  -1 */    -1,        // identity value
                /*   0 */         0,   1,   2,   3,   4,   5,   6,   7,
                /*   8 */         8,   9,  10,  11,  12,  13,  14,  15,
                /*  16 */        16,  17,  18,  19,  20,  21,  22,  23,
                /*  24 */        24,  25,  26,  27,  28,  29,  30,  31,
                /*  32 */        32,  33,  34,  35,  36,  37,  38,  39,
                /*  40 */        40,  41,  42,  43,  44,  45,  46,  47,
                /*  48 */       '0', '1', '2', '3', '4', '5', '6', '7',
                /*  56 */       '8', '9',  58,  59,  60,  61,  62,  63,
                /*  64 */        64, 'a', 'b', 'c', 'd', 'e', 'f', 'g',
                /*  72 */       'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
                /*  80 */       'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
                /*  88 */       'x', 'y', 'z',  91,  92,  93,  94,  95,
                /*  96 */        96, 'a', 'b', 'c', 'd', 'e', 'f', 'g',
                /* 104 */       'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
                /* 112 */       'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
                /* 120 */       'x', 'y', 'z', 123, 124, 125, 126, 127,
                /* 128 */       128, 129, 130, 131, 132, 133, 134, 135,
                /* 136 */       136, 137, 138, 139, 140, 141, 142, 143,
                /* 144 */       144, 145, 146, 147, 148, 149, 150, 151,
                /* 152 */       152, 153, 154, 155, 156, 157, 158, 159,
                /* 160 */       160, 161, 162, 163, 164, 165, 166, 167,
                /* 168 */       168, 169, 170, 171, 172, 173, 174, 175,
                /* 176 */       176, 177, 178, 179, 180, 181, 182, 183,
                /* 184 */       184, 185, 186, 187, 188, 189, 190, 191,
                /* 192 */       224, 225, 226, 227, 228, 229, 230, 231,
                /* 200 */       232, 233, 234, 235, 236, 237, 238, 239,
                /* 208 */       240, 241, 242, 243, 244, 245, 246, 215,
                /* 216 */       248, 249, 250, 251, 252, 253, 254, 223,
                /* 224 */       224, 225, 226, 227, 228, 229, 230, 231,
                /* 232 */       232, 233, 234, 235, 236, 237, 238, 239,
                /* 240 */       240, 241, 242, 243, 244, 245, 246, 247,
                /* 248 */       248, 249, 250, 251, 252, 253, 254, 255,
        };
        const int toUpperMap_de[257] = {
                /*  -1 */    -1,        // identity value
                /*   0 */         0,   1,   2,   3,   4,   5,   6,   7,
                /*   8 */         8,   9,  10,  11,  12,  13,  14,  15,
                /*  16 */        16,  17,  18,  19,  20,  21,  22,  23,
                /*  24 */        24,  25,  26,  27,  28,  29,  30,  31,
                /*  32 */        32,  33,  34,  35,  36,  37,  38,  39,
                /*  40 */        40,  41,  42,  43,  44,  45,  46,  47,
                /*  48 */       '0', '1', '2', '3', '4', '5', '6', '7',
                /*  56 */       '8', '9',  58,  59,  60,  61,  62,  63,
                /*  64 */        64, 'A', 'B', 'C', 'D', 'E', 'F', 'G',
                /*  72 */       'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
                /*  80 */       'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
                /*  88 */       'X', 'Y', 'Z',  91,  92,  93,  94,  95,
                /*  96 */        96, 'A', 'B', 'C', 'D', 'E', 'F', 'G',
                /* 104 */       'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
                /* 112 */       'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
                /* 120 */       'X', 'Y', 'Z', 123, 124, 125, 126, 127,
                /* 128 */       128, 129, 130, 131, 132, 133, 134, 135,
                /* 136 */       136, 137, 138, 139, 140, 141, 142, 143,
                /* 144 */       144, 145, 146, 147, 148, 149, 150, 151,
                /* 152 */       152, 153, 154, 155, 156, 157, 158, 159,
                /* 160 */       160, 161, 162, 163, 164, 165, 166, 167,
                /* 168 */       168, 169, 170, 171, 172, 173, 174, 175,
                /* 176 */       176, 177, 178, 179, 180, 181, 182, 183,
                /* 184 */       184, 185, 186, 187, 188, 189, 190, 191,
                /* 192 */       192, 193, 194, 195, 196, 197, 198, 199,
                /* 200 */       200, 201, 202, 203, 204, 205, 206, 207,
                /* 208 */       208, 209, 210, 211, 212, 213, 214, 215,
                /* 216 */       216, 217, 218, 219, 220, 221, 222, 223,
                /* 224 */       192, 193, 194, 195, 196, 197, 198, 199,
                /* 232 */       200, 201, 202, 203, 204, 205, 206, 207,
                /* 240 */       208, 209, 210, 211, 212, 213, 214, 247,
                /* 248 */       216, 217, 218, 219, 220, 221, 222, 255,
        };
        test_ctype("de_DE.ISO8859-1", classInfos_de, toLowerMap_de, toUpperMap_de);

        const unsigned short int classInfos_utf8[257] = {
                /*  -1 */   0,  // neutral value
                /*   0 */       _IScntrl, _IScntrl, _IScntrl, _IScntrl, _IScntrl, _IScntrl, _IScntrl, _IScntrl,
                /*   8 */       _IScntrl, _ISblank|_IScntrl|_ISspace, _IScntrl|_ISspace, _IScntrl|_ISspace, _IScntrl|_ISspace, _IScntrl|_ISspace, _IScntrl, _IScntrl,
                /*  16 */       _IScntrl, _IScntrl, _IScntrl, _IScntrl, _IScntrl, _IScntrl, _IScntrl, _IScntrl,
                /*  24 */       _IScntrl, _IScntrl, _IScntrl, _IScntrl, _IScntrl, _IScntrl, _IScntrl, _IScntrl,
                /*  32 */       _ISblank|_ISspace|_ISprint, _ISpunct|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph,
                /*  40 */       _ISpunct|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph,
                /*  48 */       _ISalnum|_ISdigit|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISdigit|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISdigit|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISdigit|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISdigit|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISdigit|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISdigit|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISdigit|_ISxdigit|_ISprint|_ISgraph,
                /*  56 */       _ISalnum|_ISdigit|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISdigit|_ISxdigit|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISprint|_ISgraph, _ISprint|_ISgraph, _ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph,
                /*  64 */       _ISpunct|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph,
                /*  72 */       _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph,
                /*  80 */       _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph,
                /*  88 */       _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISupper|_ISalpha|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph,
                /*  96 */       _ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISxdigit|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph,
                /* 104 */       _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph,
                /* 112 */       _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph,
                /* 120 */       _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph, _ISalnum|_ISlower|_ISalpha|_ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISprint|_ISgraph, _ISpunct|_ISprint|_ISgraph, _ISprint|_ISgraph, _IScntrl,
                /* 128 */       0, 0, 0, 0, 0, 0, 0, 0,
                /* 136 */       0, 0, 0, 0, 0, 0, 0, 0,
                /* 144 */       0, 0, 0, 0, 0, 0, 0, 0,
                /* 152 */       0, 0, 0, 0, 0, 0, 0, 0,
                /* 160 */       0, 0, 0, 0, 0, 0, 0, 0,
                /* 168 */       0, 0, 0, 0, 0, 0, 0, 0,
                /* 176 */       0, 0, 0, 0, 0, 0, 0, 0,
                /* 184 */       0, 0, 0, 0, 0, 0, 0, 0,
                /* 192 */       0, 0, 0, 0, 0, 0, 0, 0,
                /* 200 */       0, 0, 0, 0, 0, 0, 0, 0,
                /* 208 */       0, 0, 0, 0, 0, 0, 0, 0,
                /* 216 */       0, 0, 0, 0, 0, 0, 0, 0,
                /* 224 */       0, 0, 0, 0, 0, 0, 0, 0,
                /* 232 */       0, 0, 0, 0, 0, 0, 0, 0,
                /* 240 */       0, 0, 0, 0, 0, 0, 0, 0,
                /* 248 */       0, 0, 0, 0, 0, 0, 0, 0,
        };
        test_ctype("de_DE.UTF-8", classInfos_utf8, toLowerMap_posix,
                toUpperMap_posix);
}


// #pragma mark - wctype -------------------------------------------------------


unsigned short
determineWideFullClassInfo(int i)
{
        unsigned short classInfo = 0;

        if (iswblank(i))
                classInfo |= _ISblank;
        if (iswcntrl(i))
                classInfo |= _IScntrl;
        if (iswpunct(i))
                classInfo |= _ISpunct;
        if (iswalnum(i))
                classInfo |= _ISalnum;
        if (iswupper(i))
                classInfo |= _ISupper;
        if (iswlower(i))
                classInfo |= _ISlower;
        if (iswalpha(i))
                classInfo |= _ISalpha;
        if (iswdigit(i))
                classInfo |= _ISdigit;
        if (iswxdigit(i))
                classInfo |= _ISxdigit;
        if (iswspace(i))
                classInfo |= _ISspace;
        if (iswprint(i))
                classInfo |= _ISprint;
        if (iswgraph(i))
                classInfo |= _ISgraph;

        return classInfo;
}


void
test_wctype(const char* locale, const wchar_t* text,
        const unsigned short int wcs[], const unsigned short int classInfos[])
{
        setlocale(LC_CTYPE, locale);
        printf("wctype of %s locale\n", locale);

        int problemCount = 0;
        unsigned short classInfo = determineWideFullClassInfo(WEOF);
        if (classInfo != 0) {
                printf("\tPROBLEM: classinfo for WEOF = %x (expected 0)\n", classInfo);
                problemCount++;
        }
        wint_t wc = *text;
        for (int i = 0; i < 48; wc = *++text, ++i) {
                classInfo = determineWideFullClassInfo(wc);
                if (wc != wcs[i]) {
                        printf("\tPROBLEM: wc for char #%d = %x (expected %x)\n", i, wc,
                                wcs[i]);
                        problemCount++;
                }

                if (classInfo != classInfos[i]) {
                        printf("\tPROBLEM: classinfo for #%d = %x (expected %x)\n", i,
                                classInfo, classInfos[i]);
                        problemCount++;
                }
        }
        if (problemCount)
                printf("\t%d problem(s) found!\n", problemCount);
        else
                printf("\tall fine\n");
}


void
test_wctype()
{
        // haiku wide chars are always in UTF32, so nothing should change between
        // different locales

        const wchar_t* text = L"Hi there, how do you do? (äÜößáéúíó€'¤¹²$%#@) 12";

        const unsigned short int wcs[48] = {
                0x48, 0x69, 0x20, 0x74, 0x68, 0x65, 0x72, 0x65,
                0x2c, 0x20, 0x68, 0x6f, 0x77, 0x20, 0x64, 0x6f,
                0x20, 0x79, 0x6f, 0x75, 0x20, 0x64, 0x6f, 0x3f,
                0x20, 0x28, 0xe4, 0xdc, 0xf6, 0xdf, 0xe1, 0xe9,
                0xfa, 0xed, 0xf3, 0x20ac, 0x27, 0xa4, 0xb9, 0xb2,
                0x24, 0x25, 0x23, 0x40, 0x29, 0x20, 0x31, 0x32
        };
        const unsigned short int classInfos[48] = {
                _ISprint|_ISgraph|_ISalpha|_ISalnum|_ISupper,
                _ISprint|_ISgraph|_ISalpha|_ISalnum|_ISlower,
                _ISspace|_ISprint|_ISblank,
                _ISprint|_ISgraph|_ISalpha|_ISalnum|_ISlower,
                _ISprint|_ISgraph|_ISalpha|_ISalnum|_ISlower,
                _ISprint|_ISgraph|_ISalpha|_ISalnum|_ISlower|_ISxdigit,
                _ISprint|_ISgraph|_ISalpha|_ISalnum|_ISlower,
                _ISprint|_ISgraph|_ISalpha|_ISalnum|_ISlower|_ISxdigit,
                _ISprint|_ISgraph|_ISpunct,
                _ISspace|_ISprint|_ISblank,
                _ISprint|_ISgraph|_ISalpha|_ISalnum|_ISlower,
                _ISprint|_ISgraph|_ISalpha|_ISalnum|_ISlower,
                _ISprint|_ISgraph|_ISalpha|_ISalnum|_ISlower,
                _ISspace|_ISprint|_ISblank,
                _ISprint|_ISgraph|_ISalpha|_ISalnum|_ISlower|_ISxdigit,
                _ISprint|_ISgraph|_ISalpha|_ISalnum|_ISlower,
                _ISspace|_ISprint|_ISblank,
                _ISprint|_ISgraph|_ISalpha|_ISalnum|_ISlower,
                _ISprint|_ISgraph|_ISalpha|_ISalnum|_ISlower,
                _ISprint|_ISgraph|_ISalpha|_ISalnum|_ISlower,
                _ISspace|_ISprint|_ISblank,
                _ISprint|_ISgraph|_ISalpha|_ISalnum|_ISlower|_ISxdigit,
                _ISprint|_ISgraph|_ISalpha|_ISalnum|_ISlower,
                _ISprint|_ISgraph|_ISpunct,
                _ISspace|_ISprint|_ISblank,
                _ISprint|_ISgraph|_ISpunct,
                _ISprint|_ISgraph|_ISalpha|_ISalnum|_ISlower,
                _ISprint|_ISgraph|_ISalpha|_ISalnum|_ISupper,
                _ISprint|_ISgraph|_ISalpha|_ISalnum|_ISlower,
                _ISprint|_ISgraph|_ISalpha|_ISalnum|_ISlower,
                _ISprint|_ISgraph|_ISalpha|_ISalnum|_ISlower,
                _ISprint|_ISgraph|_ISalpha|_ISalnum|_ISlower,
                _ISprint|_ISgraph|_ISalpha|_ISalnum|_ISlower,
                _ISprint|_ISgraph|_ISalpha|_ISalnum|_ISlower,
                _ISprint|_ISgraph|_ISalpha|_ISalnum|_ISlower,
                _ISprint|_ISgraph,
                _ISprint|_ISgraph|_ISpunct,
                _ISprint|_ISgraph,
                _ISprint|_ISgraph,
                _ISprint|_ISgraph,
                _ISprint|_ISgraph,
                _ISpunct|_ISprint|_ISgraph,
                _ISpunct|_ISprint|_ISgraph,
                _ISpunct|_ISprint|_ISgraph,
                _ISpunct|_ISprint|_ISgraph,
                _ISspace|_ISprint|_ISblank,
                _ISprint|_ISgraph|_ISalnum|_ISdigit|_ISxdigit,
                _ISprint|_ISgraph|_ISalnum|_ISdigit|_ISxdigit
        };

        test_wctype("POSIX", text, wcs, classInfos);
        test_wctype("de_DE.ISO8859-1", text, wcs, classInfos);
        test_wctype("de_DE.ISO8859-15", text, wcs, classInfos);
        test_wctype("de_DE.UTF-8", text, wcs, classInfos);
}


// #pragma mark - wctrans ------------------------------------------------------


void
test_wctrans(const char* locale, const wchar_t* text, wctrans_t transition,
        const wchar_t* expectedResult)
{
        setlocale(LC_CTYPE, locale);
        printf("towctrans(%s) of %s locale\n",
                transition == _ISlower ? "tolower" : "toupper", locale);

        int problemCount = 0;
        wint_t wc = *text;
        for (int i = 0; wc != 0; wc = *++text, ++i) {
                errno = 0;
                wint_t result = towctrans(wc, transition);
                if (result != expectedResult[i] || errno != 0) {
                        printf("\tPROBLEM: result for char #%d = %x (expected %x), "
                                        "errno = %x (expected %x)\n",
                                i, result, expectedResult[i], errno, 0);
                        problemCount++;
                }
        }
        if (problemCount)
                printf("\t%d problem(s) found!\n", problemCount);
        else
                printf("\tall fine\n");
}


void
test_wctrans()
{
        // haiku wide chars are always in UTF32, so nothing should change between
        // different locales

        setlocale(LC_CTYPE, "POSIX");
        printf("wctrans setup\n");

        int problemCount = 0;
        errno = 0;
        wctrans_t toU = wctrans("toupper");
        if (errno != 0 || toU != _ISupper) {
                printf("\tPROBLEM: wctrans(\"upper\") = %x (expected %x), "
                                "errno=%x (expected %x)\n",
                        toU, _ISupper, errno, 0);
                problemCount++;
        }
        errno = 0;
        wctrans_t toL = wctrans("tolower");
        if (errno != 0 || toL != _ISlower) {
                printf("\tPROBLEM: wctrans(\"lower\") = %x (expected %x), "
                                "errno=%x (expected %x)\n",
                        toL, _ISlower, errno, 0);
                problemCount++;
        }
        errno = 0;
        wctrans_t invalid1 = wctrans(NULL);
        if (errno != EINVAL || invalid1 != 0) {
                printf("\tPROBLEM: wctrans(NULL) = %x (expected %x), "
                                "errno=%x (expected %x)\n",
                        invalid1, 0, errno, EINVAL);
                problemCount++;
        }
        errno = 0;
        wctrans_t invalid2 = wctrans("invalid");
        if (errno != EINVAL || invalid2 != 0) {
                printf("\tPROBLEM: wctrans(\"invalid\") = %x (expected %x), "
                                "errno=%x (expected %x)\n",
                        invalid2, 0, errno, EINVAL);
                problemCount++;
        }
        if (problemCount)
                printf("\t%d problem(s) found!\n", problemCount);
        else
                printf("\tall fine\n");

        const wchar_t* text = L"Hi there, how do you do? (äÜößáéúíó€'¤¹²$%#@) 12";
        const wchar_t* textU = L"HI THERE, HOW DO YOU DO? (ÄÜÖßÁÉÚÍÓ€'¤¹²$%#@) 12";
        const wchar_t* textL = L"hi there, how do you do? (äüößáéúíó€'¤¹²$%#@) 12";

        test_wctrans("POSIX", text, toU, textU);
        test_wctrans("de_DE.ISO8859-1", text, toU, textU);
        test_wctrans("de_DE.ISO8859-15", text, toU, textU);
        test_wctrans("de_DE.UTF-8", text, toU, textU);
        test_wctrans("fr_Fr", text, toU, textU);

        test_wctrans("POSIX", text, toL, textL);
        test_wctrans("de_DE.ISO8859-1", text, toL, textL);
        test_wctrans("de_DE.ISO8859-15", text, toL, textL);
        test_wctrans("de_DE.UTF-8", text, toL, textL);
        test_wctrans("fr_Fr", text, toL, textL);
}


// #pragma mark - wcwidth ------------------------------------------------------


void
test_wcwidth()
{
        setlocale(LC_ALL, "fr_FR.UTF-8");
        printf("wcwidth()\n");

        /* many of the following tests have been copied from gnulib */

        int problemCount = 0;
        int result = 0;

        /* Test width of ASCII characters.  */
        for (wchar_t wc = 0x20; wc < 0x7F; wc++) {
                result = wcwidth(wc);
                if (result != 1) {
                        printf("\tPROBLEM: wcwidth(%x)=%x (expected %x)\n", wc, result, 1);
                        problemCount++;
                }
        }

        struct {
                wchar_t wc;
                int result;
        } data[] = {
                { 0x0, 0 },
                { 0x1, -1 },
                { 0x1F, -1 },
                { 0x80, -1 },
                { 0x9F, -1 },
                { 0xA0, 1 },
                { 0x0301, 0 },
                { 0x05B0, 0 },
                { 0x200E, 0 },
                { 0x2060, 0 },
                { 0xE0001, 0 },
                { 0xE0044, 0 },
                { 0x200B, 0 },
                { 0xFEFF, 0 },
                { 0x3000, 2 },
                { 0xB250, 2 },
                { 0xFF1A, 2 },
                { 0x20369, 2 },
                { 0x2F876, 2 },
                { 0x0, 0 },
        };
        for (int i = 0; data[i].wc != 0 || i == 0; i++) {
                result = wcwidth(data[i].wc);
                if (result != data[i].result) {
                        printf("\tPROBLEM: wcwidth(%x)=%x (expected %x)\n", data[i].wc,
                                result, data[i].result);
                        problemCount++;
                }
        }

        if (problemCount)
                printf("\t%d problem(s) found!\n", problemCount);
        else
                printf("\tall fine\n");
}


// #pragma mark - nl_langinfo --------------------------------------------------


void
test_langinfo(const char* locale, const char* langinfos[])
{
        setlocale(LC_ALL, locale);
        printf("langinfo of %s locale\n", locale);

        int problemCount = 0;
        for (int i = -1; langinfos[i + 1] != NULL; ++i) {
                const char* langinfo = nl_langinfo(i);
                if (strcmp(langinfo, langinfos[i + 1]) != 0) {
                        printf("\tPROBLEM: langinfo for #%d = '%s' (expected '%s')\n", i,
                                langinfo, langinfos[i + 1]);
                        problemCount++;
                }
        }
        if (problemCount)
                printf("\t%d problem(s) found!\n", problemCount);
        else
                printf("\tall fine\n");
}


void
test_langinfo()
{
        const char* li_posix[] = {
                "",     // out of bounds
                "US-ASCII",
                "%a %b %e %H:%M:%S %Y",
                "%m/%d/%y",
                "%H:%M:%S",
                "%I:%M:%S %p",
                "AM",
                "PM",

                "Sunday", "Monday", "Tuesday", "Wednesday",     "Thursday", "Friday",
                "Saturday",

                "Sun", "Mon", "Tue", "Wed",     "Thu", "Fri", "Sat",

                "January", "February", "March", "April", "May", "June",
                "July", "August", "September", "October", "November", "December",

                "Jan", "Feb", "Mar", "Apr", "May", "Jun",
                "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",

                "%EC, %Ey, %EY",
                "%Ex",
                "%Ec",
                "%EX",
                "%O",

                ".",
                "",

                "^[yY]",
                "^[nN]",

                "",

                "",     // out of bounds
                NULL
        };
        test_langinfo("POSIX", li_posix);

        const char* li_de[] = {
                "",     // out of bounds
                "UTF-8",
                "%A, %e. %B %Y %H:%M:%S %Z",
                "%d.%m.%Y",
                "%H:%M:%S",
                "%I:%M:%S %p",
                "vorm.",
                "nachm.",

                "Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag",
                "Samstag",

                "So.", "Mo.", "Di.", "Mi.", "Do.", "Fr.", "Sa.",

                "Januar", "Februar", "März", "April", "Mai", "Juni",
                "Juli", "August", "September", "Oktober", "November", "Dezember",

                "Jan", "Feb", "Mär", "Apr", "Mai", "Jun",
                "Jul", "Aug", "Sep", "Okt", "Nov", "Dez",

                "%EC, %Ey, %EY",
                "%Ex",
                "%Ec",
                "%EX",
                "%O",

                ",",
                ".",

                "^[yY]",
                "^[nN]",

                "€",

                "",     // out of bounds
                NULL,
        };
        test_langinfo("de_DE.UTF-8", li_de);

        const char* li_de_iso[] = {
                "",     // out of bounds
                "ISO8859-15",
                "%A, %e. %B %Y %H:%M:%S %Z",
                "%d.%m.%Y",
                "%H:%M:%S",
                "%I:%M:%S %p",
                "vorm.",
                "nachm.",

                "Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag",
                "Samstag",

                "So.", "Mo.", "Di.", "Mi.", "Do.", "Fr.", "Sa.",

                "Januar", "Februar", "M\xE4rz", "April", "Mai", "Juni",
                "Juli", "August", "September", "Oktober", "November", "Dezember",

                "Jan", "Feb", "M\xE4r", "Apr", "Mai", "Jun",
                "Jul", "Aug", "Sep", "Okt", "Nov", "Dez",

                "%EC, %Ey, %EY",
                "%Ex",
                "%Ec",
                "%EX",
                "%O",

                ",",
                ".",

                "^[yY]",
                "^[nN]",

                "\xA4",

                "",     // out of bounds
                NULL
        };
        test_langinfo("de_DE.ISO8859-15", li_de_iso);
}


// #pragma mark - collation ----------------------------------------------------


struct coll_data {
        const char* a;
        const char* b;
        int result;
        int err;
};


static int sign (int a)
{
        if (a < 0)
                return -1;
        if (a > 0)
                return 1;
        return 0;
}


void
test_coll(bool useStrxfrm, const char* locale, const coll_data* coll)
{
        setlocale(LC_COLLATE, locale);
        printf("%s in %s locale\n", useStrxfrm ? "strxfrm" : "strcoll", locale);

        int problemCount = 0;
        for (unsigned int i = 0; coll[i].a != NULL; ++i) {
                errno = 0;
                int result;
                char funcCall[100];
                if (useStrxfrm) {
                        char sortKeyA[100], sortKeyB[100];
                        strxfrm(sortKeyA, coll[i].a, 100);
                        strxfrm(sortKeyB, coll[i].b, 100);
                        result = sign(strcmp(sortKeyA, sortKeyB));
                        sprintf(funcCall, "strcmp(strxfrm(\"%s\"), strxfrm(\"%s\"))",
                                coll[i].a, coll[i].b);
                } else {
                        result = sign(strcoll(coll[i].a, coll[i].b));
                        sprintf(funcCall, "strcoll(\"%s\", \"%s\")", coll[i].a, coll[i].b);
                }

                if (result != coll[i].result || errno != coll[i].err) {
                        printf(
                                "\tPROBLEM: %s = %d (expected %d), errno = %x (expected %x)\n",
                                funcCall, result, coll[i].result, errno, coll[i].err);
                        problemCount++;
                }
        }
        if (problemCount)
                printf("\t%d problem(s) found!\n", problemCount);
        else
                printf("\tall fine\n");
}


void
test_collation()
{
        const coll_data coll_posix[] = {
                { "", "", 0, 0 },
                { "test", "test", 0, 0 },
                { "tester", "test", 1, 0 },
                { "tEst", "teSt", -1, 0 },
                { "test", "tester", -1, 0 },
                { "tast", "täst", -1, EINVAL },
                { "tæst", "test", 1, EINVAL },
                { NULL, NULL, 0, 0 },
        };
        test_coll(0, "POSIX", coll_posix);
        test_coll(1, "POSIX", coll_posix);

        const coll_data coll_en[] = {
                { "", "", 0, 0 },
                { "test", "test", 0, 0 },
                { "tester", "test", 1, 0 },
                { "tEst", "test", 1, 0 },
                { "test", "tester", -1, 0 },
                { "täst", "täst", 0, 0 },
                { "tast", "täst", -1, 0 },
                { "tbst", "täst", 1, 0 },
                { "tbst", "tæst", 1, 0 },
                { "täst", "tÄst", -1, 0 },
                { "tBst", "tÄst", 1, 0 },
                { "tBst", "täst", 1, 0 },
                { "taest", "tæst", -1, 0 },
                { "tafst", "tæst", 1, 0 },
                { "taa", "täa", -1, 0 },
                { "tab", "täb", -1, 0 },
                { "tad", "täd", -1, 0 },
                { "tae", "täe", -1, 0 },
                { "taf", "täf", -1, 0 },
                { "cote", "coté", -1, 0 },
                { "coté", "côte", -1, 0 },
                { "côte", "côté", -1, 0 },
                { NULL, NULL, 0, 0 },
        };
        test_coll(0, "en_US.UTF-8", coll_en);
        test_coll(1, "en_US.UTF-8", coll_en);

        const coll_data coll_de[] = {
                { "", "", 0, 0 },
                { "test", "test", 0, 0 },
                { "tester", "test", 1, 0 },
                { "tEst", "test", 1, 0 },
                { "test", "tester", -1, 0 },
                { "täst", "täst", 0, 0 },
                { "tast", "täst", -1, 0 },
                { "tbst", "täst", 1, 0 },
                { "tbst", "tæst", 1, 0 },
                { "täst", "tÄst", -1, 0 },
                { "tBst", "tÄst", 1, 0 },
                { "tBst", "täst", 1, 0 },
                { "taest", "tæst", -1, 0 },
                { "tafst", "tæst", 1, 0 },
                { "taa", "tä", 1, 0 },
                { "tab", "tä", 1, 0 },
                { "tad", "tä", 1, 0 },
                { "tae", "tä", 1, 0 },
                { "taf", "tä", 1, 0 },
                { "cote", "coté", -1, 0 },
                { "coté", "côte", -1, 0 },
                { "côte", "côté", -1, 0 },
                { NULL, NULL, 0, 0 },
        };
        test_coll(0, "de_DE.UTF-8", coll_de);
        test_coll(1, "de_DE.UTF-8", coll_de);

        const coll_data coll_de_phonebook[] = {
                { "", "", 0, 0 },
                { "test", "test", 0, 0 },
                { "tester", "test", 1, 0 },
                { "tEst", "test", 1, 0 },
                { "test", "tester", -1, 0 },
                { "täst", "täst", 0, 0 },
                { "tast", "täst", 1, 0 },
                { "tbst", "täst", 1, 0 },
                { "tbst", "tæst", 1, 0 },
                { "täst", "tÄst", -1, 0 },
                { "tBst", "tÄst", 1, 0 },
                { "tBst", "täst", 1, 0 },
                { "taest", "tæst", -1, 0 },
                { "tafst", "tæst", 1, 0 },
                { "taa", "tä", -1, 0 },
                { "tab", "tä", -1, 0 },
                { "tad", "tä", -1, 0 },
                { "tae", "tä", -1, 0 },
                { "taf", "tä", 1, 0 },
                { "cote", "coté", -1, 0 },
                { "coté", "côte", -1, 0 },
                { "côte", "côté", -1, 0 },
                { NULL, NULL, 0, 0 },
        };
        test_coll(0, "de_DE.UTF-8@collation=phonebook", coll_de_phonebook);
        test_coll(1, "de_DE.UTF-8@collation=phonebook", coll_de_phonebook);

        const coll_data coll_fr[] = {
                { "", "", 0, 0 },
                { "test", "test", 0, 0 },
                { "tester", "test", 1, 0 },
                { "tEst", "test", 1, 0 },
                { "test", "tester", -1, 0 },
                { "täst", "täst", 0, 0 },
                { "tast", "täst", -1, 0 },
                { "tbst", "täst", 1, 0 },
                { "tbst", "tæst", 1, 0 },
                { "täst", "tÄst", -1, 0 },
                { "tBst", "tÄst", 1, 0 },
                { "tBst", "täst", 1, 0 },
                { "taest", "tæst", -1, 0 },
                { "tafst", "tæst", 1, 0 },
                { "taa", "tä", 1, 0 },
                { "tab", "tä", 1, 0 },
                { "tad", "tä", 1, 0 },
                { "tae", "tä", 1, 0 },
                { "taf", "tä", 1, 0 },
                { "cote", "coté", -1, 0 },
                { "coté", "côte", 1, 0 },
                { "côte", "côté", -1, 0 },
                { NULL, NULL, 0, 0 },
        };
        // CLDR-1.9 has adjusted the defaults of fr_FR to no longer do reverse
        // ordering of secondary differences (accents), but fr_CA still does that
        // by default
        test_coll(0, "fr_CA.UTF-8", coll_fr);
        test_coll(1, "fr_CA.UTF-8", coll_fr);
}


// #pragma mark - time conversion ----------------------------------------------


void
test_localtime(const char* tz, time_t nowSecs, const tm& expected)
{
        setenv("TZ", tz, 1);
        printf("localtime for '%s'\n", tz);

        tm now;
        tm* result = localtime_r(&nowSecs, &now);
        int problemCount = 0;
        if (result == NULL) {
                printf("\tPROBLEM: localtime(\"%ld\") = NULL\n", nowSecs);
                problemCount++;
        }
        if (now.tm_year != expected.tm_year) {
                printf("\tPROBLEM: localtime().tm_year = %d (expected %d)\n",
                        now.tm_year, expected.tm_year);
                problemCount++;
        }
        if (now.tm_mon != expected.tm_mon) {
                printf("\tPROBLEM: localtime().tm_mon = %d (expected %d)\n",
                        now.tm_mon, expected.tm_mon);
                problemCount++;
        }
        if (now.tm_mday != expected.tm_mday) {
                printf("\tPROBLEM: localtime().tm_mday = %d (expected %d)\n",
                        now.tm_mday, expected.tm_mday);
                problemCount++;
        }
        if (now.tm_hour != expected.tm_hour) {
                printf("\tPROBLEM: localtime().tm_hour = %d (expected %d)\n",
                        now.tm_hour, expected.tm_hour);
                problemCount++;
        }
        if (now.tm_min != expected.tm_min) {
                printf("\tPROBLEM: localtime().tm_min = %d (expected %d)\n",
                        now.tm_min, expected.tm_min);
                problemCount++;
        }
        if (now.tm_sec != expected.tm_sec) {
                printf("\tPROBLEM: localtime().tm_sec = %d (expected %d)\n",
                        now.tm_sec, expected.tm_sec);
                problemCount++;
        }
        if (now.tm_wday != expected.tm_wday) {
                printf("\tPROBLEM: localtime().tm_wday = %d (expected %d)\n",
                        now.tm_wday, expected.tm_wday);
                problemCount++;
        }
        if (now.tm_yday != expected.tm_yday) {
                printf("\tPROBLEM: localtime().tm_yday = %d (expected %d)\n",
                        now.tm_yday, expected.tm_yday);
                problemCount++;
        }
        if (now.tm_isdst != expected.tm_isdst) {
                printf("\tPROBLEM: localtime().tm_isdst = %d (expected %d)\n",
                        now.tm_isdst, expected.tm_isdst);
                problemCount++;
        }
        if (now.tm_gmtoff != expected.tm_gmtoff) {
                printf("\tPROBLEM: localtime().tm_gmtoff = %d (expected %d)\n",
                        now.tm_gmtoff, expected.tm_gmtoff);
                problemCount++;
        }
        if (strcasecmp(now.tm_zone, expected.tm_zone) != 0) {
                printf("\tPROBLEM: localtime().tm_zone = '%s' (expected '%s')\n",
                        now.tm_zone, expected.tm_zone);
                problemCount++;
        }
        if (problemCount)
                printf("\t%d problem(s) found!\n", problemCount);
        else
                printf("\tall fine\n");
}


void
test_gmtime(const char* tz, time_t nowSecs, const tm& expected)
{
        setenv("TZ", tz, 1);
        printf("gmtime for '%s'\n", tz);

        tm now;
        tm* result = gmtime_r(&nowSecs, &now);
        int problemCount = 0;
        if (result == NULL) {
                printf("\tPROBLEM: localtime(\"%ld\") = NULL\n", nowSecs);
                problemCount++;
        }
        if (now.tm_year != expected.tm_year) {
                printf("\tPROBLEM: localtime().tm_year = %d (expected %d)\n",
                        now.tm_year, expected.tm_year);
                problemCount++;
        }
        if (now.tm_mon != expected.tm_mon) {
                printf("\tPROBLEM: localtime().tm_mon = %d (expected %d)\n",
                        now.tm_mon, expected.tm_mon);
                problemCount++;
        }
        if (now.tm_mday != expected.tm_mday) {
                printf("\tPROBLEM: localtime().tm_mday = %d (expected %d)\n",
                        now.tm_mday, expected.tm_mday);
                problemCount++;
        }
        if (now.tm_hour != expected.tm_hour) {
                printf("\tPROBLEM: localtime().tm_hour = %d (expected %d)\n",
                        now.tm_hour, expected.tm_hour);
                problemCount++;
        }
        if (now.tm_min != expected.tm_min) {
                printf("\tPROBLEM: localtime().tm_min = %d (expected %d)\n",
                        now.tm_min, expected.tm_min);
                problemCount++;
        }
        if (now.tm_sec != expected.tm_sec) {
                printf("\tPROBLEM: localtime().tm_sec = %d (expected %d)\n",
                        now.tm_sec, expected.tm_sec);
                problemCount++;
        }
        if (now.tm_wday != expected.tm_wday) {
                printf("\tPROBLEM: localtime().tm_wday = %d (expected %d)\n",
                        now.tm_wday, expected.tm_wday);
                problemCount++;
        }
        if (now.tm_yday != expected.tm_yday) {
                printf("\tPROBLEM: localtime().tm_yday = %d (expected %d)\n",
                        now.tm_yday, expected.tm_yday);
                problemCount++;
        }
        if (now.tm_isdst != expected.tm_isdst) {
                printf("\tPROBLEM: localtime().tm_isdst = %d (expected %d)\n",
                        now.tm_isdst, expected.tm_isdst);
                problemCount++;
        }
        if (now.tm_gmtoff != expected.tm_gmtoff) {
                printf("\tPROBLEM: localtime().tm_gmtoff = %d (expected %d)\n",
                        now.tm_gmtoff, expected.tm_gmtoff);
                problemCount++;
        }
        if (strcasecmp(now.tm_zone, expected.tm_zone) != 0) {
                printf("\tPROBLEM: localtime().tm_zone = '%s' (expected '%s')\n",
                        now.tm_zone, expected.tm_zone);
                problemCount++;
        }
        if (problemCount)
                printf("\t%d problem(s) found!\n", problemCount);
        else
                printf("\tall fine\n");
}


void
test_mktime(const char* tz, tm& tm, time_t expected, int expectedWeekDay,
        int expectedYearDay)
{
        setenv("TZ", tz, 1);
        printf("mktime for '%s'\n", tz);

        time_t result = mktime(&tm);
        int problemCount = 0;
        if (result != expected) {
                printf("\tPROBLEM: mktime() = %ld (expected %ld)\n", result, expected);
                problemCount++;
        }
        if (tm.tm_wday != expectedWeekDay) {
                printf("\tPROBLEM: mktime().tm_wday = %d (expected %d)\n",
                        tm.tm_wday, expectedWeekDay);
                problemCount++;
        }
        if (tm.tm_yday != expectedYearDay) {
                printf("\tPROBLEM: mktime().tm_yday = %d (expected %d)\n",
                        tm.tm_yday, expectedYearDay);
                problemCount++;
        }
        if (problemCount)
                printf("\t%d problem(s) found!\n", problemCount);
        else
                printf("\tall fine\n");
}


void
test_timeconversions()
{
        setlocale(LC_ALL, "en_US");
        {
                time_t testTime = 1279391169;   // Sat Jul 17 18:26:09 GMT 2010
                tm gtm = {
                        9, 26, 18, 17, 6, 110, 6, 197, 0, 0, (char*)"GMT"
                };
                test_localtime("GMT", testTime, gtm);
                test_gmtime("GMT", testTime, gtm);
                gtm.tm_wday = -1;
                gtm.tm_yday = -1;
                test_mktime("GMT", gtm, testTime, 6, 197);

                tm gtmplus2 = {
                        9, 26, 16, 17, 6, 110, 6, 197, 0, -2 * 3600, (char*)"GMT+2"
                };
                test_localtime("GMT+2", testTime, gtmplus2);
                test_gmtime("GMT+2", testTime, gtm);
                gtmplus2.tm_wday = -1;
                gtmplus2.tm_yday = -1;
                test_mktime("GMT+2", gtmplus2, testTime, 6, 197);

                tm gtmminus2 = {
                        9, 26, 20, 17, 6, 110, 6, 197, 0, 2 * 3600, (char*)"GMT-2"
                };
                test_localtime("GMT-2", testTime, gtmminus2);
                test_gmtime("GMT-2", testTime, gtm);
                gtmminus2.tm_wday = -1;
                gtmminus2.tm_yday = -1;
                test_mktime("GMT-2", gtmminus2, testTime, 6, 197);

                tm btm = {
                        9, 26, 20, 17, 6, 110, 6, 197, 1, 2 * 3600, (char*)"CEST"
                };
                test_localtime(":Europe/Berlin", testTime, btm);
                test_gmtime(":Europe/Berlin", testTime, gtm);
                btm.tm_wday = -1;
                btm.tm_yday = -1;
                test_mktime(":Europe/Berlin", btm, testTime, 6, 197);

                tm ctm = {
                        9, 26, 20, 17, 6, 110, 6, 197, 1, 2 * 3600, (char*)"CEST"
                };
                test_localtime("CET", testTime, ctm);
                test_gmtime("CET", testTime, gtm);
                ctm.tm_wday = -1;
                ctm.tm_yday = -1;
                test_mktime("CET", ctm, testTime, 6, 197);

                tm latm = {
                        9, 26, 11, 17, 6, 110, 6, 197, 1, -7 * 3600, (char*)"PDT"
                };
                test_localtime(":America/Los_Angeles", testTime, latm);
                test_gmtime(":America/Los_Angeles", testTime, gtm);
                latm.tm_wday = -1;
                latm.tm_yday = -1;
                test_mktime(":America/Los_Angeles", latm, testTime, 6, 197);

                tm ttm = {
                        9, 26, 3, 18, 6, 110, 0, 198, 0, 9 * 3600, (char*)"GMT+09:00"
                };
                test_localtime(":Asia/Tokyo", testTime, ttm);
                test_gmtime(":Asia/Tokyo", testTime, gtm);
                ttm.tm_wday = -1;
                ttm.tm_yday = -1;
                test_mktime(":Asia/Tokyo", ttm, testTime, 0, 198);
        }

        {
                time_t testTime = 1268159169;   // Tue Mar 9 18:26:09 GMT 2010
                tm gtm = {
                        9, 26, 18, 9, 2, 110, 2, 67, 0, 0, (char*)"GMT"
                };
                test_localtime("GMT", testTime, gtm);
                test_gmtime("GMT", testTime, gtm);
                gtm.tm_wday = -1;
                gtm.tm_yday = -1;
                test_mktime("GMT", gtm, testTime, 2, 67);

                tm btm = {
                        9, 26, 19, 9, 2, 110, 2, 67, 0, 3600, (char*)"CET"
                };
                test_localtime(":Europe/Berlin", testTime, btm);
                test_gmtime(":Europe/Berlin", testTime, gtm);
                btm.tm_wday = -1;
                btm.tm_yday = -1;
                test_mktime(":Europe/Berlin", btm, testTime, 2, 67);

                tm ctm = {
                        9, 26, 19, 9, 2, 110, 2, 67, 0, 3600, (char*)"CET"
                };
                test_localtime("CET", testTime, ctm);
                test_gmtime("CET", testTime, gtm);
                ctm.tm_wday = -1;
                ctm.tm_yday = -1;
                test_mktime("CET", ctm, testTime, 2, 67);

                tm latm = {
                        9, 26, 10, 9, 2, 110, 2, 67, 0, -8 * 3600, (char*)"PST"
                };
                test_localtime(":America/Los_Angeles", testTime, latm);
                test_gmtime(":America/Los_Angeles", testTime, gtm);
                latm.tm_wday = -1;
                latm.tm_yday = -1;
                test_mktime(":America/Los_Angeles", latm, testTime, 2, 67);

                tm ttm = {
                        9, 26, 3, 10, 2, 110, 3, 68, 0, 9 * 3600, (char*)"GMT+09:00"
                };
                test_localtime(":Asia/Tokyo", testTime, ttm);
                test_gmtime(":Asia/Tokyo", testTime, gtm);
                ttm.tm_wday = -1;
                ttm.tm_yday = -1;
                test_mktime(":Asia/Tokyo", ttm, testTime, 3, 68);
        }

        {
                time_t testTime = 0;    // Thu Jan 1 00:00:00 GMT 1970
                tm gtm = {
                        0, 0, 0, 1, 0, 70, 4, 0, 0, 0, (char*)"GMT"
                };
                test_localtime("GMT", testTime, gtm);
                test_gmtime("GMT", testTime, gtm);
                gtm.tm_wday = -1;
                gtm.tm_yday = -1;
                test_mktime("GMT", gtm, testTime, 4, 0);

                tm btm = {
                        0, 0, 1, 1, 0, 70, 4, 0, 0, 1 * 3600, (char*)"CET"
                };
                test_localtime(":Europe/Berlin", testTime, btm);
                test_gmtime(":Europe/Berlin", testTime, gtm);
                btm.tm_wday = -1;
                btm.tm_yday = -1;
                test_mktime(":Europe/Berlin", btm, testTime, 4, 0);

                tm ctm = {
                        0, 0, 1, 1, 0, 70, 4, 0, 0, 1 * 3600, (char*)"CET"
                };
                test_localtime("CET", testTime, ctm);
                test_gmtime("CET", testTime, gtm);
                ctm.tm_wday = -1;
                ctm.tm_yday = -1;
                test_mktime("CET", ctm, testTime, 4, 0);

                tm latm = {
                        0, 0, 16, 31, 11, 69, 3, 364, 0, -8 * 3600, (char*)"PST"
                };
                test_localtime(":America/Los_Angeles", testTime, latm);
                test_gmtime(":America/Los_Angeles", testTime, gtm);
                latm.tm_wday = -1;
                latm.tm_yday = -1;
                test_mktime(":America/Los_Angeles", latm, testTime, 3, 364);

                tm ttm = {
                        0, 0, 9, 1, 0, 70, 4, 0, 0, 9 * 3600, (char*)"GMT+09:00"
                };
                test_localtime(":Asia/Tokyo", testTime, ttm);
                test_gmtime(":Asia/Tokyo", testTime, gtm);
                ttm.tm_wday = -1;
                ttm.tm_yday = -1;
                test_mktime(":Asia/Tokyo", ttm, testTime, 4, 0);
        }
}


// #pragma mark - printf -------------------------------------------------------


struct sprintf_data {
        const char* format;
        double value;
        const char* result;
};


void
test_sprintf(const char* locale, const sprintf_data data[])
{
        setlocale(LC_ALL, locale);
        printf("sprintf for '%s'\n", locale);

        int problemCount = 0;
        for(int i = 0; data[i].format != NULL; ++i) {
                char buf[100];
                if (strchr(data[i].format, 'd') != NULL)
                        sprintf(buf, data[i].format, (int)data[i].value);
                else if (strchr(data[i].format, 'f') != NULL)
                        sprintf(buf, data[i].format, data[i].value);
                if (strcmp(buf, data[i].result) != 0) {
                        printf("\tPROBLEM: sprintf(\"%s\") = \"%s\" (expected \"%s\")\n",
                                data[i].format, buf, data[i].result);
                        problemCount++;
                }
        }
        if (problemCount)
                printf("\t%d problem(s) found!\n", problemCount);
        else
                printf("\tall fine\n");
}


void
test_sprintf()
{
        const sprintf_data sprintf_posix[] = {
                { "%d", 123, "123" },
                { "%d", -123, "-123" },
                { "%d", 123456, "123456" },
                { "%'d", 123456, "123456" },
                { "%f", 123, "123.000000" },
                { "%f", -123, "-123.000000" },
                { "%.2f", 123456.789, "123456.79" },
                { "%'.2f", 123456.789, "123456.79" },
                { NULL, 0.0, NULL }
        };
        test_sprintf("POSIX", sprintf_posix);

        const sprintf_data sprintf_de[] = {
                { "%d", 123, "123" },
                { "%d", -123, "-123" },
                { "%d", 123456, "123456" },
                { "%'d", 123456, "123.456" },
                { "%f", 123, "123,000000" },
                { "%f", -123, "-123,000000" },
                { "%.2f", 123456.789, "123456,79" },
                { "%'.2f", 123456.789, "123.456,79" },
                { NULL, 0.0, NULL }
        };
        test_sprintf("de_DE.UTF-8", sprintf_de);

        const sprintf_data sprintf_gu[] = {
                { "%d", 123, "123" },
                { "%d", -123, "-123" },
                { "%d", 123456, "123456" },
                { "%'d", 123456, "123,456" },
                { "%f", 123, "123.000000" },
                { "%f", -123, "-123.000000" },
                { "%.2f", 123456.789, "123456.79" },
                { "%'.2f", 123456.789, "1,23,456.79" },
                { NULL, 0.0, NULL }
        };
        test_sprintf("gu_IN", sprintf_gu);

        const sprintf_data sprintf_nb[] = {
                { "%d", 123, "123" },
                { "%d", -123, "-123" },
                { "%d", 123456, "123456" },
                { "%'d", 123456, "123 456" },
                { "%f", 123, "123,000000" },
                { "%f", -123, "-123,000000" },
                { "%.2f", 123456.789, "123456,79" },
                { "%'.2f", 123456.789, "123 456,79" },
                { NULL, 0.0, NULL }
        };
        test_sprintf("nb_NO", sprintf_nb);
}


// #pragma mark - main ---------------------------------------------------------


/*
 * Test several different aspects of the POSIX locale and the functions
 * influenced by it.
 */
int
main(void)
{
        test_setlocale();
        test_localeconv();
        test_strftime();
        test_strptime();
        test_ctype();
        test_wctype();
        test_wctrans();
        test_wcwidth();
        test_langinfo();
        test_collation();
        test_timeconversions();
        test_sprintf();

        return 0;
}