Easy to use, locale-specific functions

Started by MrBcx, October 08, 2025, 01:52:12 PM

Previous topic - Next topic

MrBcx

Hi Robert,

It works on my PC's, so I'd say your solution is a good one.

I discovered that 8.3.0 and my current 8.3.1 could not compile my demo.
After a bit of digging, I discovered another years old edge case.  I added
a missing line of code and that bug is kaput, so this thread was extra useful.

We left Payson in early November and have been enjoying our pleasant valley weather.

Robert

Hi MrBcx:

Hard to tell bad money from good money. See attached screen snips.

Changed all instances of

locale_currency(516.32)
to

locale_currency(516.32, 1)
to get the display the proper currency symbols.

Getting pretty cool in Payson. You outta there ?

Robert

It's about time !  ;)

Time is money.

Money talks in any language.

  ;D

Thanks

MrBcx

#1
For the BCX users residing outside of the USA, this small BCX code library
might be a useful addition to your BCX Basic toolbox.  It includes a detailed
example of the functions, so you can get up to speed immediately. 

'*************************************************************************
' Easy to use, locale specific functions, for use in console and GUI apps
'         By Kevin Diggins (MrBcx)  October 08, 2025  MIT License
'*************************************************************************
' These locale specific functions let BCX programs retrieve localized
' dates, times, numbers, currency, language, country, and time zone,
' automatically formatted for the user's regional settings.
'******************************************************************************************
' Below are some common LCIDs (for demonstration purposes).  Get more at the link below.
' Ref: https://help.tradestation.com/10_00/eng/tsdevhelp/elobject/class_el/lcid_values.htm
'******************************************************************************************
CONST LCID_ENGLISH_UK        = &H0809  ' en-GB
CONST LCID_FRENCH_FRANCE     = &H040C  ' fr-FR
CONST LCID_ENGLISH_US        = &H0409  ' en-US (for restoring original locale used in this sample)
CONST LCID_GERMAN_GERMANY    = &H0407  ' de-DE
CONST LCID_SPANISH_SPAIN     = &H0C0A  ' es-ES
CONST LCID_ITALIAN_ITALY     = &H0410  ' it-IT
CONST LCID_JAPANESE_JAPAN    = &H0411  ' ja-JP
CONST LCID_CHINESE_CHINA     = &H0804  ' zh-CN
CONST LCID_PORTUGUESE_BRAZIL = &H0416  ' pt-BR
CONST LCID_RUSSIAN_RUSSIA    = &H0419  ' ru-RU
CONST LCID_DUTCH_NETHERLANDS = &H0413  ' nl-NL
CONST LCID_SWEDISH_SWEDEN    = &H041D  ' sv-SE
CONST LCID_KOREAN_KOREA      = &H0412  ' ko-KR


TYPE LocaleBackup   ' Structure to store original locale settings
    OriginalLCID AS DWORD
    Country AS STRING * 64
    EngCountry AS STRING * 64
    CountryCode AS STRING * 16
END TYPE

'******************************
' Begin Sample Application:
'******************************

CALL SetConsoleUTF8               ' Set console to UTF-8

PRINT locale_time()               '  8:16:16 PM
PRINT locale_weekday()            '  Sunday
PRINT locale_month()              '  October
PRINT locale_date()               '  10/5/2025
PRINT locale_long_date()          '  Sunday, October 5, 2025
PRINT locale_country()            '  United States
PRINT locale_country_code()       '  US
PRINT locale_timezone()           '  US Mountain Standard Time
PRINT locale_language()           '  English (United States)
PRINT locale_language_code()      '  en
PRINT locale_native_language()    '  English
PRINT locale_temperature_unit()   '  Fahrenheit
PRINT locale_number(516.32)       '  516.32
PRINT locale_currency(516.32)     ' $516.32

DIM backup AS LocaleBackup

'*****************************
' Test with United Kingdom
'*****************************
PRINT "Testing with United Kingdom locale:"
backup = SetTempLocale("GB")
PRINT "Country: " , locale_country()
PRINT "Country Code: " , locale_country_code()
PRINT "Currency: " , locale_currency(516.32)
PRINT "Date: " , locale_date()
PRINT "Temperature Unit: ", locale_temperature_unit()
RestoreLocale(backup)

'*****************************
' Test with France
'*****************************
PRINT "Testing with France locale:"
backup = SetTempLocale("FR")
PRINT "Country: " , locale_country()
PRINT "Country Code: " , locale_country_code()
PRINT "Currency: " , locale_currency(516.32)
PRINT "Date: " , locale_date()
PRINT "Temperature Unit: ", locale_temperature_unit()
RestoreLocale(backup)

'*****************************
' Verify original settings
'*****************************
PRINT "Original settings restored:"
PRINT "Country: " , locale_country()
PRINT "Country Code: " , locale_country_code()
PRINT "Currency: " , locale_currency(516.32)
PRINT "Date: " , locale_date()
PRINT "Temperature Unit: ", locale_temperature_unit()
PAUSE
END   ' end of demo

'****************************
' End of sample application
'   Library begins below
'****************************


SUB SetConsoleUTF8()
    '********************************************************************
    ' SetConsoleUTF8
    ' Sets the console output code page to UTF-8 (65001) to properly
    ' display international characters and currency symbols.
    '********************************************************************
    SetConsoleOutputCP(65001)
END SUB


FUNCTION SetTempLocale(countryCode AS STRING) AS LocaleBackup
    '********************************************************************
    ' SetTempLocale
    ' Temporarily sets the thread locale to a specified country code.
    ' Backs up the current locale settings and returns them in a
    ' LocaleBackup structure for later restoration.
    ' Parameters:
    '   countryCode - Two-letter ISO country code (e.g., "GB", "FR", "DE")
    ' Returns: LocaleBackup structure containing original locale settings
    '********************************************************************
    DIM backup AS LocaleBackup
    DIM buf[64] AS CHAR
    DIM ret AS LONG
    DIM targetLCID AS DWORD

    ' Get current thread locale
    backup.OriginalLCID = GetThreadLocale()

    ' Backup current locale settings
    ret = GetLocaleInfoA(backup.OriginalLCID, LOCALE_SCOUNTRY, buf, SIZEOF(buf))
    IF ret > 0 THEN
        backup.Country = buf
    ELSE
        backup.Country = "Unknown Country"
    END IF

    ret = GetLocaleInfoA(backup.OriginalLCID, LOCALE_SENGCOUNTRY, buf, SIZEOF(buf))
    IF ret > 0 THEN
        backup.EngCountry = buf
    ELSE
        backup.EngCountry = "Unknown Country"
    END IF

    ret = GetLocaleInfoA(backup.OriginalLCID, LOCALE_SISO3166CTRYNAME, buf, SIZEOF(buf))
    IF ret > 0 THEN
        backup.CountryCode = buf
    ELSE
        backup.CountryCode = "??"
    END IF

    ' Map country code to LCID
    SELECT CASE UCASE$(countryCode)
        CASE "GB"
        targetLCID = LCID_ENGLISH_UK
        CASE "FR"
        targetLCID = LCID_FRENCH_FRANCE
        CASE "US"
        targetLCID = LCID_ENGLISH_US
        CASE "DE"
        targetLCID = LCID_GERMAN_GERMANY
        CASE "ES"
        targetLCID = LCID_SPANISH_SPAIN
        CASE "IT"
        targetLCID = LCID_ITALIAN_ITALY
        CASE "JP"
        targetLCID = LCID_JAPANESE_JAPAN
        CASE "CN"
        targetLCID = LCID_CHINESE_CHINA
        CASE "BR"
        targetLCID = LCID_PORTUGUESE_BRAZIL
        CASE "RU"
        targetLCID = LCID_RUSSIAN_RUSSIA
        CASE "NL"
        targetLCID = LCID_DUTCH_NETHERLANDS
        CASE "SE"
        targetLCID = LCID_SWEDISH_SWEDEN
        CASE "KR"
        targetLCID = LCID_KOREAN_KOREA
        CASE ELSE
        PRINT "Unsupported country code: " + countryCode + ". Defaulting to US."
        targetLCID = LCID_ENGLISH_US
    END SELECT

    ' Set new thread locale
    ret = SetThreadLocale(targetLCID)
    IF ret = 0 THEN
        PRINT "Failed to set thread locale to " + countryCode
    ELSE
        PRINT "Successfully set thread locale to " + countryCode
    END IF

    FUNCTION = backup
END FUNCTION



SUB RestoreLocale(backup AS LocaleBackup)
    '********************************************************************
    ' RestoreLocale
    ' Restores the thread locale to its original settings using the
    ' information stored in a LocaleBackup structure.
    ' Parameters:
    '   backup - LocaleBackup structure containing original locale settings
    '********************************************************************
    DIM ret AS LONG

    ' Restore original thread locale
    ret = SetThreadLocale(backup.OriginalLCID)
    IF ret = 0 THEN
        PRINT "Failed to restore original locale"
    ELSE
        PRINT "Successfully restored original locale"
    END IF
END SUB




FUNCTION locale_currency(amount AS DOUBLE, AppType AS INT = 0) AS STRING
    '********************************************************************
    ' locale_currency
    ' Formats a numeric amount as currency according to the current
    ' thread locale settings (currency symbol, decimal separator, etc.).
    ' Parameters:
    '   amount - Numeric value to format as currency
    '   AppType - TRUE for GUI apps, FALSE for console apps (default FALSE)
    ' Returns: Formatted currency string (e.g., "$516.32", "£516.32", "516,32 €")
    '********************************************************************
    DIM numStr[64] AS WCHAR
    DIM buf[128] AS WCHAR
    DIM ansiBuf[512] AS CHAR
    DIM ret AS INT
    DIM lcid AS DWORD
    DIM tempStr AS STRING
    DIM codePage AS INT

    lcid = GetThreadLocale()

    ' Format the double to a string first, then convert to wide char
    tempStr = STR$(amount, 2)
    MultiByteToWideChar(CP_ACP, 0, tempStr, -1, numStr, 64)

    ret = GetCurrencyFormatW(lcid, 0, numStr, NULL, buf, 128)

    IF ret > 0 THEN
        IF AppType THEN
            codePage = CP_UTF8
        ELSE
            codePage = CP_ACP
        END IF

        ret = WideCharToMultiByte(codePage, 0, buf, -1, ansiBuf, 512, NULL, NULL)
        IF ret > 0 THEN
            FUNCTION = ansiBuf
        ELSE
            FUNCTION = "$" + STR$(amount, 2)
        END IF
    ELSE
        FUNCTION = "$" + STR$(amount, 2)
    END IF
END FUNCTION



FUNCTION locale_weekday() AS STRING
    '********************************************************************
    ' locale_weekday
    ' Returns the localized name of the current day of the week.
    ' Returns: Day name in the current locale (e.g., "Sunday", "Dimanche")
    '********************************************************************
    DIM st AS SYSTEMTIME
    DIM buf[64] AS CHAR
    DIM ret AS INT
    DIM lcid AS DWORD

    lcid = GetThreadLocale()
    GetLocalTime(&st)
    ret = GetDateFormat(lcid, 0, &st, "dddd", buf, SIZEOF(buf))

    IF ret > 0 THEN
        FUNCTION = buf
    ELSE
        FUNCTION = "Unknown"
    END IF
END FUNCTION



FUNCTION locale_month() AS STRING
    '********************************************************************
    ' locale_month
    ' Returns the localized name of the current month.
    ' Returns: Month name in the current locale (e.g., "October", "Octobre")
    '********************************************************************
    DIM st AS SYSTEMTIME
    DIM buf[64] AS CHAR
    DIM ret AS INT
    DIM lcid AS DWORD

    lcid = GetThreadLocale()
    GetLocalTime(&st)
    ret = GetDateFormat(lcid, 0, &st, "MMMM", buf, SIZEOF(buf))
    IF ret > 0 THEN
        FUNCTION = buf
    ELSE
        FUNCTION = "Unknown"
    END IF
END FUNCTION



FUNCTION locale_time() AS STRING
    '********************************************************************
    ' locale_time
    ' Returns the current time formatted according to the current
    ' thread locale settings.
    ' Returns: Formatted time string (e.g., "8:16:16 PM", "20:16:16")
    '********************************************************************
    DIM st AS SYSTEMTIME
    DIM buf[64] AS CHAR
    DIM ret AS INT
    DIM lcid AS DWORD

    lcid = GetThreadLocale()
    GetLocalTime(&st)
    ret = GetTimeFormat(lcid, 0, &st, NULL, buf, SIZEOF(buf))
    IF ret > 0 THEN
        FUNCTION = buf
    ELSE
        FUNCTION = "00:00"
    END IF
END FUNCTION



FUNCTION locale_date() AS STRING
    '********************************************************************
    ' locale_date
    ' Returns the current date formatted as a short date according to
    ' the current thread locale settings.
    ' Returns: Short date string (e.g., "10/8/2025", "08/10/2025")
    '********************************************************************
    DIM st AS SYSTEMTIME
    DIM buf[64] AS CHAR
    DIM ret AS INT
    DIM lcid AS DWORD

    lcid = GetThreadLocale()
    GetLocalTime(&st)
    ret = GetDateFormat(lcid, DATE_SHORTDATE, &st, NULL, buf, SIZEOF(buf))
    IF ret > 0 THEN
        FUNCTION = buf
    ELSE
        FUNCTION = "01/01/1970"
    END IF
END FUNCTION



FUNCTION locale_long_date() AS STRING
    '********************************************************************
    ' locale_long_date
    ' Returns the current date formatted as a long date according to
    ' the current thread locale settings.
    ' Returns: Long date string (e.g., "Wednesday, October 8, 2025")
    '********************************************************************
    DIM st AS SYSTEMTIME
    DIM buf[64] AS CHAR
    DIM ret AS INT
    DIM lcid AS DWORD

    lcid = GetThreadLocale()
    GetLocalTime(&st)
    ret = GetDateFormat(lcid, DATE_LONGDATE, &st, NULL, buf, SIZEOF(buf))
    IF ret > 0 THEN
        FUNCTION = buf
    ELSE
        FUNCTION = "January 1, 1970"
    END IF
END FUNCTION



FUNCTION locale_language() AS STRING
    '********************************************************************
    ' locale_language
    ' Returns the full language name for the current thread locale.
    ' Returns: Language name (e.g., "English (United States)", "Français (France)")
    '********************************************************************
    DIM buf[64] AS CHAR
    DIM ret AS INT
    DIM lcid AS DWORD

    lcid = GetThreadLocale()
    ret = GetLocaleInfoA(lcid, LOCALE_SLANGUAGE, buf, SIZEOF(buf))
    IF ret > 0 THEN
        FUNCTION = buf
    ELSE
        FUNCTION = "Unknown Language"
    END IF
END FUNCTION



FUNCTION locale_country() AS STRING
    '********************************************************************
    ' locale_country
    ' Returns the country name for the current thread locale.
    ' Returns: Country name (e.g., "United States", "United Kingdom", "France")
    '********************************************************************
    DIM buf[64] AS CHAR
    DIM ret AS INT
    DIM lcid AS DWORD

    lcid = GetThreadLocale()
    ret = GetLocaleInfoA(lcid, LOCALE_SCOUNTRY, buf, SIZEOF(buf))
    IF ret > 0 THEN
        FUNCTION = buf
    ELSE
        FUNCTION = "Unknown Country"
    END IF
END FUNCTION



FUNCTION locale_language_code() AS STRING
    '********************************************************************
    ' locale_language_code
    ' Returns the two-letter ISO 639-1 language code for the current
    ' thread locale.
    ' Returns: Language code (e.g., "en", "fr", "de")
    '********************************************************************
    DIM buf[16] AS CHAR
    DIM ret AS INT
    DIM lcid AS DWORD

    lcid = GetThreadLocale()
    ret = GetLocaleInfoA(lcid, &H59, buf, SIZEOF(buf))  ' &H59 = ISO language code
    IF ret > 0 THEN
        FUNCTION = buf
    ELSE
        FUNCTION = "??"
    END IF
END FUNCTION


FUNCTION locale_country_code() AS STRING
    '********************************************************************
    ' locale_country_code
    ' Returns the two-letter ISO 3166 country code for the current
    ' thread locale.
    ' Returns: Country code (e.g., "US", "GB", "FR")
    '********************************************************************
    DIM buf[16] AS CHAR
    DIM ret AS INT
    DIM lcid AS DWORD

    lcid = GetThreadLocale()
    ret = GetLocaleInfoA(lcid, &H5A, buf, SIZEOF(buf))  ' &H5A = ISO country code
    IF ret > 0 THEN
        FUNCTION = buf
    ELSE
        FUNCTION = "??"
    END IF
END FUNCTION



FUNCTION locale_number(amount AS DOUBLE) AS STRING
    '********************************************************************
    ' locale_number
    ' Formats a numeric value according to the current thread locale
    ' settings (decimal separator, thousands separator, etc.).
    ' Parameters:
    '   amount - Numeric value to format
    ' Returns: Formatted number string (e.g., "516.32", "516,32")
    '********************************************************************
    DIM numStr[64] AS CHAR
    DIM buf[64] AS CHAR
    DIM ret AS INT
    DIM lcid AS DWORD

    lcid = GetThreadLocale()
    sprintf(numStr, "%.6f", amount)
    ret = GetNumberFormat(lcid, 0, numStr, NULL, buf, SIZEOF(buf))

    IF ret > 0 THEN
        FUNCTION = buf
    ELSE
        FUNCTION = numStr
    END IF
END FUNCTION




FUNCTION locale_timezone(AppType AS INT = 0) AS STRING
    '********************************************************************
    ' locale_timezone
    ' Returns the time zone name for the current system settings.
    ' Parameters:
    '   AppType - TRUE for GUI apps, FALSE for console apps (default FALSE)
    ' Returns: Time zone name (e.g., "US Mountain Standard Time",
    '          "Pacific Standard Time")
    '********************************************************************
    DIM tz AS TIME_ZONE_INFORMATION
    DIM buf[128] AS CHAR
    DIM ret AS DWORD
    DIM chars AS INT
    DIM codePage AS INT

    ret = GetTimeZoneInformation(&tz)
 
    IF AppType THEN
        codePage = CP_UTF8
    ELSE
        codePage = CP_ACP
    END IF

    SELECT CASE ret
        CASE 0, 1
        chars = WideCharToMultiByte(codePage, 0, tz.StandardName, -1, buf, SIZEOF(buf), NULL, NULL)
        CASE 2
        chars = WideCharToMultiByte(codePage, 0, tz.DaylightName, -1, buf, SIZEOF(buf), NULL, NULL)
        CASE ELSE
        FUNCTION = "Unknown Timezone"
    END SELECT

    IF chars > 0 THEN
        FUNCTION = buf
    ELSE
        FUNCTION = "Unknown Timezone"
    END IF
END FUNCTION



FUNCTION locale_native_language() AS STRING
    '********************************************************************
    ' locale_native_language
    ' Returns the native language name (in the language itself) for the
    ' current thread locale.
    ' Returns: Native language name (e.g., "English", "Français", "Deutsch")
    '********************************************************************
    DIM buf[64] AS CHAR
    DIM lcid AS DWORD
    DIM ret AS INT

    lcid = GetThreadLocale()
    ret = GetLocaleInfoA(lcid, LOCALE_SNATIVELANGNAME, buf, SIZEOF(buf))
    IF ret > 0 THEN
        FUNCTION = buf
    ELSE
        FUNCTION = "English"
    END IF
END FUNCTION



FUNCTION locale_temperature_unit() AS STRING
    '********************************************************************
    ' locale_temperature_unit
    ' Returns the preferred temperature unit for the current thread locale.
    ' Returns: "Celsius" for metric countries, "Fahrenheit" for US
    '********************************************************************
    DIM buf[4] AS CHAR
    DIM lcid AS DWORD
    DIM ret AS INT

    lcid = GetThreadLocale()
    ret = GetLocaleInfoA(lcid, LOCALE_IMEASURE, buf, SIZEOF(buf))
    IF ret > 0 THEN
        IF buf = "0" THEN
            FUNCTION = "Celsius"
        ELSE
            FUNCTION = "Fahrenheit"
        END IF
    ELSE
        FUNCTION = "Fahrenheit"
    END IF
END FUNCTION