Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Convert unix timestamp (s) to day of week in C++?

How could I determine the day of the week in California (Pacific Time) based on an arbitrary Unix timestamp (seconds)? I have searched around, but have not found a built-in library for C++.

UTC is generally 8 hours ahead of PT, but simply subtracting 8 hours from the Unix timestamp and creating a tm struct doesn't work since this discounts daylight savings nuances.

like image 607
John Hoffman Avatar asked Nov 14 '16 07:11

John Hoffman


2 Answers

Here's how to do it using Howard Hinnant's date library. This is a free MIT-licensed C++11/14 library based on <chrono>:

#include "tz.h"
#include <iostream>

int
main()
{
    using namespace std::chrono_literals;
    using namespace date;
    auto unix_timestamp = sys_seconds{1479664467s};
    auto zt = make_zoned("America/Los_Angeles", unix_timestamp);
    auto wd = weekday{floor<days>(zt.get_local_time())};
    std::cout << wd << '\n';
}

The first line just constructs the unix timestamp (which you may have from other sources). In C++11 we do not have the chrono-literals and so this line would instead be:

auto unix_timestamp = sys_seconds{seconds{1479664467}};

make_zoned() creates a zoned_time<seconds> based on the unix_timestamp and the time_zone "America/Los_Angeles". A zoned_time is a collection of a time_zone and unix timestamp.

You can get the local time out of a zoned_time with zt.get_local_time(). This is a chrono::time_point but with no clock associated with it. The precision of this time point will be equal to the precision of your original time stamp (seconds in this case).

You can get the local day of the week of the local_time by truncating the local_time to a precision of days:

floor<days>(zt.get_local_time())

And this day-precision time_point will convert to type weekday.

A weekday can be printed out, participate in weekday arithmetic and comparisons, or be explicitly converted to an unsigned in the range [0, 6].

The program above outputs:

Sun

The timezone part of this library is a faithful representation of the IANA timezone database. It will give correct adjustments between local time zones and UTC as far back as the mid 1800's up through present day. If you pass the library timestamps prior to the mid 1800's the earliest known UTC offset is extrapolated backwards to the year -32768. You can also pass timestamps as far in the future as the year 32767 and the current offset and daylight saving rules will be propagated forward.

As this library is built on <chrono>, it will handle any precision that <chrono> supplies. Note that some chrono::durations such as nanoseconds have a more limited range (+/-292 years for nanoseconds).

The library / process is thread safe. That is, the program works directly with whatever time zone you specify without changing your computer's time zone or environment variables under the hood. It also does not involve the part of the C timing API which is known to not be thread safe because of non-const function local statics.

Ported to recent versions of gcc, clang and VS.

Update C++20

The above program can trivially be ported to C++20 (some vendors support this as I write and some don't yet):

#include <chrono>
#include <iostream>

int
main()
{
    using namespace std::chrono;
    auto unix_timestamp = sys_seconds{1479664467s};
    auto zt = zoned_time{"America/Los_Angeles", unix_timestamp};
    auto wd = weekday{floor<days>(zt.get_local_time())};
    std::cout << wd << '\n';
}
like image 190
Howard Hinnant Avatar answered Sep 23 '22 19:09

Howard Hinnant


Hacky way using standard UNIX C (non thread safe version):

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

void print_times() {
  char my_buf[100];
  time_t now = time(NULL);
  printf(ctime(&now));
  strftime(my_buf, 100, "DoW=%w Tz=%Z\n", localtime(&now));
  printf(my_buf);
}

int main( int argc, char * argv[] ) {
  print_times();
  setenv("TZ", "America/Los_Angeles", 1);
  print_times();
  unsetenv("TZ");
  print_times();
}

Unfortunately, TZ env var or changing the file /etc/localtime are the only ways you can effect timezone settings for the UNIX time functions (Under the hood tzset() uses these sources to initialize a set of shared variables that encode the timezone settings for all the library functions).

An issue is you need to come with the UNIX TZ string(s) for the zone(s) you need. zoneinfo format is the simplest and recommended format to use. I used tzselect to generate that TZ value for Pacific time.

Also see man 5 localtime, man tzselect for info on the standard UNIX timezone setup.

like image 42
spinkus Avatar answered Sep 23 '22 19:09

spinkus