12

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.

Coder
  • 1,917
  • 3
  • 17
  • 33
John Hoffman
  • 17,857
  • 20
  • 58
  • 81
  • 1
    I didn't downvote you but how about [`std::localtime`](http://en.cppreference.com/w/cpp/chrono/c/localtime)? – Danh Nov 14 '16 at 07:49
  • 1
    Thank you - what if local time is not PT for me? – John Hoffman Nov 14 '16 at 07:52
  • 3
    [strftime](http://en.cppreference.com/w/cpp/chrono/c/strftime). Or [time_put](http://en.cppreference.com/w/cpp/locale/time_put). Or [Howard Hinnant's date](https://github.com/HowardHinnant/date) - pointers, not answers, this is why a comment. – Adrian Colomitchi Nov 14 '16 at 07:58
  • 1
    If your program only works for that timezone, your can use `_tzset`. Or [this library](https://github.com/HowardHinnant/date), it's in progress to standardize it. See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0355r1.html – Danh Nov 14 '16 at 08:00
  • So ... I've read the docs, and I still can't figure out how to cast a uint64 to a time_t numeric, make a tm struct out of the time_t, and then obtain the day of the week in PT by reading from that struct. Could someone show the whole process? Thanks! – John Hoffman Nov 14 '16 at 21:44
  • Why do you have an `uint64`? A `time_t` should be signed 32 bit integer, while `time64_t` is a 64 bit integer. On newer Windows versions, `time_t` has been defined to `time64_t`; are you on Windows? – Karsten Koop Nov 18 '16 at 08:52
  • Oh, you're right! We can just use int32. – John Hoffman Nov 19 '16 at 22:16

3 Answers3

11

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';
}
Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
2

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.

spinkus
  • 7,694
  • 4
  • 38
  • 62
1

Can you use Boost library? If you can figure out the Posix time zone string (IEEE Std 1003.1, see http://www.boost.org/doc/libs/1_62_0/doc/html/date_time/local_time.html#date_time.local_time.posix_time_zone) for your timezone (PST), then the example below might help.

#include <iostream>
#include <ctime>
#include <boost/date_time.hpp>

int main(int argc, char ** argv)
{
    std::string posix_time_zone_string = "EST-05:00:00";  // you need to change this to the posix time representation of your desired timezone

    time_t unix_time = 1479641855;  //1479641855 = Sun, 20 Nov 2016 11:37:35 GMT

    boost::posix_time::ptime pt = boost::posix_time::from_time_t(unix_time);
    std::cout << "time in UTC: " << boost::posix_time::to_iso_extended_string(pt) << std::endl;


    boost::local_time::time_zone_ptr zone(new boost::local_time::posix_time_zone(posix_time_zone_string));

    boost::local_time::local_date_time dt_with_zone(pt, zone);
    std::cout << "time in local timezone: " << boost::posix_time::to_iso_extended_string(dt_with_zone.local_time()) << std::endl;

    // Get the week day (alternative 1)
    std::cout << "local week day integer: " << boost::local_time::to_tm(dt_with_zone).tm_wday << std::endl;   // 0=sunday, 1=monday, etc.

    // Get the week day (alternative 2)
    std::cout << "local week day name: " << dt_with_zone.local_time().date().day_of_week() << std::endl;
    std::cout << "local week day integer: " << int(dt_with_zone.local_time().date().day_of_week()) << std::endl;
    return 0;
}
foolo
  • 804
  • 12
  • 22