1

I have this code that gives me local time.

#include <sys/types.h>
#include <stdio.h>
#include <time.h>
#include <stdlib.h>

extern char *tzname[];

main(){
    time_t now;
    struct tm *sp;

    (void) time( &now );

     printf("%s", ctime( &now ) );

     sp = localtime(&now);
     printf("%d/%d/%02d %d:%02d %s\n",
         sp->tm_mon + 1, sp->tm_mday,
         sp->tm_year, sp->tm_hour,
         sp->tm_min, tzname[sp->tm_isdst]);
     exit(0);
}

What is the best way to get time in California using C language (or another time zone)?

  • Set the timezone? For example by setting the correct environment variable? – Some programmer dude Sep 11 '22 at 16:31
  • On UNIX, `TZ=US/Pacific ./a.out` (or `TZ=America/Los_Angeles date` or whatever). You can look up timezone names on wikipedia or just run `tzselect` – Useless Sep 11 '22 at 16:57
  • 2
    Note that using just `main(){ … }` has not been valid in standard C for the whole of this millennium. You should specify the return type, and it should be `int` — `int main(void)` should be preferred. Using `return(0);` (or `return 0;`) works too. – Jonathan Leffler Sep 11 '22 at 22:38

4 Answers4

3

This program uses putenv() to set the time zone environment variable, tzset() to alert the C runtime that the time zone might have changed, and then uses localtime() and strftime() to analyze the time.


#include <stdio.h>
#include <time.h>
#include <unistd.h>     /* putenv() */
#include <stdlib.h>

#define DIM(x)  (sizeof(x)/sizeof(*(x)))

static char *arg0;

static char tznames[][32] =
{
    "TZ=US/Pacific",
    "TZ=US/Mountain",
    "TZ=US/Central",
    "TZ=US/Eastern",
    "TZ=UK",
    "TZ=UTC",
    "TZ=UTC0",
    "TZ=Australia/NSW",
    "TZ=Australia/North",
    "TZ=Australia/Queensland",
    "TZ=Australia/South",
    "TZ=Australia/Tasmania",
    "TZ=Australia/Victoria",
    "TZ=Australia/West",
    "TZ=GMT",
    "TZ=GMT-1",
    "TZ=GMT+1",
    "TZ=PST8PDT",
    "TZ=CST6CDT",
    "TZ=GMT0BST",
};

static void tz_time(char *tz, time_t t0)
{
    if (putenv(tz) != 0)
    {
        fprintf(stderr, "%s: putenv(%s) failed\n", arg0, tz);
        exit(EXIT_FAILURE);
    }
    tzset();
    struct tm *lt = localtime(&t0);
    char buffer[64];
    strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S %z", lt);
    printf("%-24s: %lu = %s\n", tz, (unsigned long)t0, buffer);
}

int main(int argc, char **argv)
{
    if (argc > 0)
        arg0 = argv[0];

    time_t t0 = time(0);

    for (size_t i = 0; i < DIM(tznames); i++)
        tz_time(tznames[i], t0);

    return(0);
}

I'm not convinced that UK really works — it probably ends up as UTC because it isn't recognized. However, sample output is:

TZ=US/Pacific           : 1662917121 = 2022-09-11 10:25:21 -0700
TZ=US/Central           : 1662917121 = 2022-09-11 12:25:21 -0500
TZ=US/Eastern           : 1662917121 = 2022-09-11 13:25:21 -0400
TZ=US/Mountain          : 1662917121 = 2022-09-11 11:25:21 -0600
TZ=UK                   : 1662917121 = 2022-09-11 17:25:21 +0000
TZ=UTC                  : 1662917121 = 2022-09-11 17:25:21 +0000
TZ=UTC0                 : 1662917121 = 2022-09-11 17:25:21 +0000
TZ=Australia/NSW        : 1662917121 = 2022-09-12 03:25:21 +1000
TZ=Australia/North      : 1662917121 = 2022-09-12 02:55:21 +0930
TZ=Australia/Queensland : 1662917121 = 2022-09-12 03:25:21 +1000
TZ=Australia/South      : 1662917121 = 2022-09-12 02:55:21 +0930
TZ=Australia/Tasmania   : 1662917121 = 2022-09-12 03:25:21 +1000
TZ=Australia/Victoria   : 1662917121 = 2022-09-12 03:25:21 +1000
TZ=Australia/West       : 1662917121 = 2022-09-12 01:25:21 +0800
TZ=GMT                  : 1662917121 = 2022-09-11 17:25:21 +0000
TZ=GMT-1                : 1662917121 = 2022-09-11 18:25:21 +0100
TZ=GMT+1                : 1662917121 = 2022-09-11 16:25:21 -0100
TZ=PST8PDT              : 1662917121 = 2022-09-11 10:25:21 -0700
TZ=CST6CDT              : 1662917121 = 2022-09-11 12:25:21 -0500
TZ=GMT0BST              : 1662917121 = 2022-09-11 18:25:21 +0100

(This was run on an antique MacBook Pro running macOS Monterey 12.5.1. Said machine will not be allowed to update to macOS Ventura; it is too old in the eyes of Apple and no longer supported.)

Because it uses putenv(), it is not pure standard C (the <unistd.h> header is not part of standard C). It is also not thread-safe because it is messing with the environment. Valid time zone names are not specified by C — notations such as TZ=GMT0BST are specified by POSIX.

There are few people who would argue that the timezone handling in standard C (or even POSIX) are anywhere close to optimal. Frankly, they're appalling.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
1

I would suggest you get the UTC time using gmtime() and then computing the offset for the desired timezone. See the accepted answer in How to convert from UTC to local time in C? for a good tutorial on how to apply these offsets.

1

There are several ways to do this.

(1) You can set the environment variable TZ before running your program, as suggested by @Useless in a comment.

(2) You can set the environment variable TZ from within your program, using the putenv or setenv calls. After doing that, you must call tzset() to tell the timezone machinery in the C library to reexamine the environment variable. Here's your program, fleshed out with the necessary calls to setenv and tzset for comparison. (See also @Jonathan Leffler's answer for an example using putenv.)

sp = localtime(&now);
printf("%d/%d/%02d %d:%02d %s\n",
    sp->tm_mon + 1, sp->tm_mday,
    1900 + sp->tm_year, sp->tm_hour,
    sp->tm_min, tzname[sp->tm_isdst]);

setenv("TZ", "America/Los_Angeles", 1);
tzset();

sp = localtime(&now);
printf("%d/%d/%02d %d:%02d %s\n",
    sp->tm_mon + 1, sp->tm_mday,
    1900 + sp->tm_year, sp->tm_hour,
    sp->tm_min, tzname[sp->tm_isdst]);

(3) You might be able to use the BSD tzalloc and localtime_rz functions to directly convert to a specified local time zone:

timezone_t tzp = tzalloc("America/Los_Angeles");
if(tzp == NULL) { fprintf(stderr, "no such zone\n"); exit(1); }
struct tm tmbuf;

sp = localtime_rz(tzp, &now, &tmbuf);
printf("%d/%d/%02d %d:%02d %s\n",
    sp->tm_mon + 1, sp->tm_mday,
    1900 + sp->tm_year, sp->tm_hour,
    sp->tm_min, sp->tm_zone);

(In this last example, I have quietly used a completely different method of printing the time zone name.)

Everything else being equal, I would say that #3 is vastly preferable, since passing an explicit argument to a function — in this case, passing tzp to localtime_rz() — is much, much cleaner than manipulating a global variable. Unfortunately, however, the tzalloc and localtime_rz functions are not standard, and they're actually rather rare these days, so I won't be at all surprised if they're not available to you. See also this answer.

Steve Summit
  • 45,437
  • 7
  • 70
  • 103
-2

Just checked. Novosibirsk, with no DST shifting, is (currently) 14hrs ahead of CA (that is currently on DST).

Just take your time ('HH'), add 24, subtract 14 and work it out modulo 24.

Your 22:xx => (22 + 24 - 14) % 24 => 08:xx

This is valid until CA change their clocks at the end of their (summer-ish) DST phase, reverting to "standard time". During 'winter', use 13 instead of 14.

Any "code" solution worth its salt will also have to account for whether-or-not DST is applicable and the date of change is arbitrary in many jurisdictions.

Fe2O3
  • 6,077
  • 2
  • 4
  • 20
  • 1
    Time zone adjustments are seethingly complex and notoriously subject to change. Your deduction that Novosibirsk is 13/14hrs ahead of California might be true today, but it won't be true the next time Novosibirsk changes its rules, or the next time California changes its rules. So using standard library functions, along with up-to-date databases like tzdb, is vastly, vastly preferable to rolling one's own corrections like this. – Steve Summit Sep 11 '22 at 18:37
  • @SteveSummit "seethingly complex"... Tell me something I don't know already... https://www.cbc.ca/news/canada/british-columbia/kootenay-creston-dayligh-saving-time-1.5193936 Bother, eh? https://www.timeanddate.com/time/change/canada/creston Who want to "code" this situation using generic TZ values? – Fe2O3 Sep 11 '22 at 20:36
  • 1
    _"The question is limited to two locations."_ -- It does seem that OP is interested in a more general solution with _"What is the best way to get time in California using C language (**or another time zone**)?"_ Using library functions to handle complexity is probably the better solution. – ad absurdum Sep 11 '22 at 22:45
  • @adabsurdum I fully agree with "use library functions"... All the way!! However, the links prove the old adage, "Every rule has its exceptions"... It's a complicated world we've built... Even the "International Date Line" can be changed... – Fe2O3 Sep 11 '22 at 22:57
  • @SteveSummit Thank you for your comment here. Just checked the meta article from last week https://meta.stackoverflow.com/q/420236/17592432 12 DV's for raising an issue and 10UV's for the heavy-handed moderator who deleted my light-hearted answer. Water off a duck's back, really... I stand by 'posting' this (highly specific) algorithm that does not address the OP's broader closing question... Jus' tryina be helpful... Time for a break... Thank you again. (one not-a-robot to another `:-)` – Fe2O3 Sep 12 '22 at 11:12
  • 1
    @Fe2O3 My reluctant habit is to try to avoid asking, answering, or even reading questions on meta.stackoverflow.com at all. It is almost always a deeply unsatisfying experience. I had a half-composed post in defense of "outside the box" answers (in fact it's still sitting in a browser tab next door) but, remembering my own rule, I abandoned and did not post it after all. – Steve Summit Sep 12 '22 at 13:34