Why is strftime not respecting locale?

I can’t get libc.strftime to respect the locale, any ideas what’s going on?

My computer locale is set to fr_FR.UTF-8.

This C program works correctly:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <locale.h>

int
main(int argc, char *argv[])
{
   char outstr[200];
   time_t t;
   struct tm *tmp;

   t = time(NULL);
   tmp = localtime(&t);
   if (tmp == NULL) {
       perror("localtime");
       exit(EXIT_FAILURE);
   }

   setlocale(LC_ALL, "");

   if (strftime(outstr, sizeof(outstr), argv[1], tmp) == 0) {
       fprintf(stderr, "strftime returned 0");
       exit(EXIT_FAILURE);
   }

   printf("Result string is \"%s\"\n", outstr);
   exit(EXIT_SUCCESS);
}

When I compile and run it, get the expected results:

$ gcc -o strftime strftime.c -lc
$ ./strftime %B
Result string is "août"
$ ./strftime %a
Result string is "jeu."

Here is the Odin version that I came up with:

package my_program

import "core:fmt"
import "core:time"
import "core:os"
import "core:strings"
import "core:c/libc"

main :: proc () {
    libc.setlocale(libc.Locale_Category.ALL, nil)

    unix_time := libc.time_t(time.to_unix_seconds(time.now()))

    buf: [200]u8
    len := libc.strftime(
        raw_data(&buf),
        size_of(buf),
        strings.unsafe_string_to_cstring(os.args[1]), // Format string from command line argument
        libc.localtime(&unix_time),
    )
    date := string(buf[:len])

    fmt.println("Result string is:", date)
}

It completely disregards the locale, always sending answers in English:

$ odin run odin_strftime.odin -file -- %B
Result string is: August
$ odin run odin_strftime.odin -file -- %a
Result string is: Thu

I’m running this on Ubuntu 24.04, here is the output of locale:

$ locale
LANG=fr_FR.UTF-8
LANGUAGE=fr_FR.UTF-8
LC_CTYPE="fr_FR.UTF-8"
LC_NUMERIC="fr_FR.UTF-8"
LC_TIME="fr_FR.UTF-8"
LC_COLLATE="fr_FR.UTF-8"
LC_MONETARY="fr_FR.UTF-8"
LC_MESSAGES="fr_FR.UTF-8"
LC_PAPER="fr_FR.UTF-8"
LC_NAME="fr_FR.UTF-8"
LC_ADDRESS="fr_FR.UTF-8"
LC_TELEPHONE="fr_FR.UTF-8"
LC_MEASUREMENT="fr_FR.UTF-8"
LC_IDENTIFICATION="fr_FR.UTF-8"
LC_ALL=fr_FR.UTF-8

Any ideas what might be happening here? What am I missing?

What happens if you use the following instead?

libc.setlocale(libc.Locale_Category.TIME, nil)

When I print the return of setLocale on my english locale computer I get:

fmt.println(libc.setlocale(libc.Locale_Category.ALL, nil))
output --> C

…but if I force it, I get the following, and am able to output french day and month using your code

fmt.println(libc.setlocale(libc.Locale_Category.ALL, "fr_FR.UTF-8"))
output --> fr_FR.UTF-8

So I don’t think it’s your code, but something to do with setLocale or your linux instance.

1 Like

Thank you for looking into it, but I don’t think my linux instance is the problem, because the C program works correctly.

I tried adding libc.setlocale(libc.Locale_Category.TIME, nil), the behavior is still the same. Indeed, if I call with the hardcoded value libc.setlocale(libc.Locale_Category.ALL, "fr_FR.UTF-8") like you mentioned, I do get the output in French.

But I don’t want to hardcode the locale in my program, I want it to respect the LC_ and LANG environment variables like it’s supposed it to, just like the C code does.

Oh, you’re right to think on setlocale is the problem though!

The Odin setlocale seems to be completely ignoring the LANG and LC_ environment variables.

Calling setlocale in C works correctly:

$ cat setlocale.c
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <locale.h>

int
main(int argc, char *argv[])
{
   printf("%s\n", setlocale(LC_ALL, ""));
}

$ gcc -o setlocale setlocale.c -lc
$ LC_ALL=fr_FR.UTF-8 ./setlocale 
fr_FR.UTF-8
$ LC_ALL=en_US.UTF-8 ./setlocale 
en_US.UTF-8

Calling setlocale in Odin always returns C:

$ cat odin_setlocale.odin 
package my_program

import "core:fmt"
import "core:c/libc"

main :: proc () {
    fmt.println(libc.setlocale(libc.Locale_Category.ALL, nil))
}

$ odin build odin_setlocale.odin -file
$ LC_ALL=fr_FR.UTF-8 ./odin_setlocale 
C
$ LC_ALL=en_US.UTF-8 ./odin_setlocale 
C

Ahh, found it, I should use empty string instead of nil – when you give it null it will only get the locale !!

package my_program

import "core:fmt"
import "core:c/libc"

main :: proc () {
    fmt.println(libc.setlocale(libc.Locale_Category.ALL, ""))  // <- empty string, NOT nil
}

$ odin build odin_setlocale.odin -file
$ LC_ALL=fr_FR.UTF-8 ./odin_setlocale 
fr_FR.UTF-8
$ LC_ALL=en_US.UTF-8 ./odin_setlocale 
en_US.UTF-8
1 Like

I see. I’ve learned something too. Gonna save this little tid bit off for later.

When using an empty string, I get something that makes more sense:

fmt.println(libc.setlocale(libc.Locale_Category.ALL, ""))
output --> English_United States.1252

Based on the information in the comments for libc.Locale_Category, I also would have interpreted it to mean that nil is used to get the current system local.
core:c/libc/locale/setlocale says the following in the comments:

Returns: the current locale if `locale` is `nil`, the set locale otherwise

should probably say something more like:

Returns: the currently set locale if `locale` is `nil`, or if 'locale' is an empty string it will get the current system locale and set to that, or else sets the locale to the specified 'locale' provide in the string i.e. "fr_FR.UTF-8"

Seems that either the comment got truncated somehow, or it got lost in translation. :wink:

3 Likes