3

I need a timestamp in a special format for an API call:

Dates are converted to UTC milliseconds elapsed since 12:00:00 midnight, January 1, 0001.

My first assumption was to use:

auto now = std::chrono::system_clock::now();
std::cout << "millisceconds since epoch: "
          << std::chrono::duration_cast<std::chrono::milliseconds>(
               now.time_since_epoch()).count() 

But of course the output is the time interval from the UNIX epoch Thu Jan 1 00:00:00 1970

So for a now = "Wed Dec 12 13:30:00 2018" it returns 1544617800000ms.

How do I get the milliseconds elapsed since 12:00:00 midnight, January 1, 0001?

Context OSISoft API

The OSISoft API Documentation for specifying a date range is quite strange

Numeric Range Queries

The previous examples were Range Queries against string fields. Numeric values > can also be searched for with Range Queries.

The only fields that are indexed as numeric fields are the CreationDate and ChangeDate fields for the respective PI Point attributes. To index these fields > add them to the list of PI Point Attributes. This configuration may be viewed > or modified on the Settings page.

These date time values are indexed as numeric values via a conversion: Dates are converted to UTC milliseconds elapsed since 12:00:00 midnight, January 1, 0001.

In the following example query is a request for last changed date equal to or > greater than February 26th, 22:16:50.000 (This is Universal Time). This DateTime, following the aforementioned conversion, would be represented as numeric value: 63655280210000. Therefore the query submitted is:

https://MyServer/piwebapi/search/query?q=changedate:[63655280210000 TO *]

From this documention I have asked this question on how to get the milliseconds elapsed since 12:00:00 midnight, January 1, 0001.

I also linked the Question to PISquare

The Shmoo
  • 358
  • 1
  • 16
  • 2
    You first would need to have a datatype big enough to hold that value, no? – πάντα ῥεῖ Dec 12 '18 at 12:41
  • 1
    how many milliseconds are there between 1.1.1 and 1.1.1970? Just add that to your result – 463035818_is_not_an_ai Dec 12 '18 at 12:41
  • @πάντα ῥεῖ: I only need the value as a string for the api call. (see https://techsupport.osisoft.com/Documentation/PI-Web-PI/help/topics/search-queries.html) – The Shmoo Dec 12 '18 at 12:42
  • @user463035818: Because of leap seconds, that is not trivial to compute. – The Shmoo Dec 12 '18 at 12:44
  • 1
    my point was just that you need to compute it only once and then simply use the method that uses the unix epoch. Actually I wonder why you want to add some constant offset to all your counters. You need a bigger type without really adding information – 463035818_is_not_an_ai Dec 12 '18 at 12:46
  • I cannot open the link you provide in the comment, are you sure that there is no misunderstanding? Counting milliseconds from 1.1.1 is rather uncommon. – 463035818_is_not_an_ai Dec 12 '18 at 12:47
  • 2
    *"How do I get the milliseconds elapsed since 12:00:00 midnight, January 1, 0001?"*, I doubt you want that. You want milliseconds elapsed since 1.1.1970 (for example), and then you want the offset for that point in time as required by the API. I'm not even sure if "12:00:00 midnight, January 1, 0001" UTC is well defined, or even if it is, we probably don't *know* how many milliseconds have passed since... – hyde Dec 12 '18 at 12:48
  • 5
    The Gregorian calendar was introduced in 1582. You're asking to calculate time intervals from a date that is more than a millennium before our calendar even started. It takes a so-called proleptic calendar to "reconstruct" those dates - stuff like leap days/seconds are going to be the least of your worries here. I can't access the link to your API because I get "The requested URL was rejected.", but the practical use between a milisecond-precision time interval from here to over 2 millennia ago seems questionable. Are you sure you're not misunderstanding something? – Blaze Dec 12 '18 at 12:48
  • @user463035818 you are right with everything you are saying. 1) how can I compute this offset? 2) I have to have that format for the API call (which is not my API) – The Shmoo Dec 12 '18 at 12:50
  • what API? The link is broken and the sentence you quote in your question rather sounds as if you pass a Date and the API will convert it to milliseconds since 1.Jan.1 – 463035818_is_not_an_ai Dec 12 '18 at 12:52
  • https://www.epochconverter.com/seconds-days-since-y0 can give you a value (I am not sure if it is a correct one). – mch Dec 12 '18 at 12:52
  • if an API requires you to provide some exotic value (which this is) it should tell you how to get it, hence imho it is essential for this question to tell us what you are trying to call – 463035818_is_not_an_ai Dec 12 '18 at 12:53
  • Here is the docu from the link: The previous examples were Range Queries against string fields. Numeric values can also be searched for with Range Queries. The only fields that are indexed as numeric fields are the CreationDate and ChangeDate fields for the respective PI Point attributes. To index these fields add them to the list of PI Point Attributes. This configuration may be viewed or modified on the Settings page. These date time values are indexed as numeric values via a conversion: Dates are converted to UTC milliseconds elapsed since 12:00:00 midnight, January 1, 0001. – The Shmoo Dec 12 '18 at 12:53
  • 1
    [Fixed Link to the API Docu](https://techsupport.osisoft.com/Documentation/PI-Web-API/help/topics/search-queries.html) – The Shmoo Dec 12 '18 at 12:55
  • 1
    there is nothing in this paragraph that says that you would have to provide this counter. I still read it as: you provide a Data and the library will internally use milliseonconds since 1.1.1 as an index. Of course I might be wrong (actually very likely) – 463035818_is_not_an_ai Dec 12 '18 at 12:55
  • 2
    Further an example is given: In the following example query is a request for last changed date equal to or greater than February 26th, 22:16:50.000 (This is Universal Time). This DateTime, following the aforementioned conversion, would be represented as numeric value: 63655280210000. Therefore the query submitted is: `https://MyServer/piwebapi/search/query?q=changedate:[63655280210000 TO *]` – The Shmoo Dec 12 '18 at 12:58
  • Yep, that's pretty crappy. The kicker is that it's not _enough_ to convert your date into the requested format. You have to extrapolate the number of milliseconds the same way they do internally. – Peter Ruderman Dec 12 '18 at 13:00
  • 2
    thats just an example, what happens if you pass a date string as described [here](https://techsupport.osisoft.com/Documentation/PI-Web-API/help/topics/time-strings.html). btw I start wondering what this has to do with c++ ;) – 463035818_is_not_an_ai Dec 12 '18 at 13:00
  • 1
    I would recommend you to change the question. Currently it is rather close to being a [xy problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). "How to get the milliseconds since 1.1.1" is too broad imho (cf comments above) while "How to use that API" is what you actually want to know and there must be a trivial solution (cannot believe everybody using it has to write his own conversion, i'd expect if you just dig deeper they document how to do this conversion in detail or it turns out you dont really need it) – 463035818_is_not_an_ai Dec 12 '18 at 13:03
  • 1
    @user463035818 you are completely right! I will change the question! And I think you already provided the answer. I don't need the milliseconds. The given example (see above) was just confusing. Thank you. – The Shmoo Dec 12 '18 at 13:08
  • Leap seconds and calendars are irrelevant for historical dates. I only know of .NET using an epoch of 1/1/1. Get the timestamp from the Unix epoch, store in long long, add 62135596800000LL. – Hans Passant Dec 12 '18 at 13:08
  • 1
    @TheShmoo You can have the question about the API here, but personally I think I would contact the software vendor for support. This is a commercial product and the documentation is really unclear about a very uncommon characteristic of the API, they should give you the necessary guidance about how to use their product. – jdehesa Dec 12 '18 at 13:13
  • Thank you all for your help! The question is now changed. – The Shmoo Dec 12 '18 at 13:27

2 Answers2

5

There is no defined way to calculate:

UTC milliseconds elapsed since 12:00:00 midnight, January 1, 0001.

Judging by the examples they are using the same algorithm as https://www.epochconverter.com/seconds-days-since-y0. To get the same result you can just add 719162 days to the unix epoch:

auto now = std::chrono::system_clock::now();
std::cout << "millisceconds since epoch: "
          << std::chrono::duration_cast<std::chrono::milliseconds>(
               now.time_since_epoch() + std::chrono::hours(24 * 719162)).count()

Note c++20 introduces std::chrono::days which you could use instead of 24 hours. Depending on the resolution of your system clock you may need to cast to milliseconds before adding the offset to avoid overflows (719162 days is more than 2^64 nanoseconds).

Alan Birtles
  • 32,622
  • 4
  • 31
  • 60
-1

This is easy using Howard Hinnant's date/time library:

#include "date/date.h"
#include <iostream>

std::chrono::milliseconds
convert(std::chrono::system_clock::time_point tp)
{
    using namespace date;
    using namespace std::chrono;
    return (floor<milliseconds>(tp) + 
            (sys_days{1970_y/January/1} - sys_days{1_y/January/1})).time_since_epoch();
}

int
main()
{
    using namespace date;
    using namespace std::chrono;
    std::cout << convert(system_clock::now()) << '\n';
}

convert simply adds the difference between the two epochs to the system_clock::time_point, truncated to milliseconds precision, and extracts the duration to return it as milliseconds.

This program for me just output:

63680221359193ms

The range on milliseconds is plenty big enough to handle this computation, but the range on your system_clock::time_point may not be. Thus it is important to truncate to milliseconds right away as done in the code above to avoid overflow.

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • 3
    The [self-promotion policy](/help/promotion) specifically requires you to be explicit that you are linking to a resource that you are the owner of. – tripleee Jan 03 '19 at 10:32
  • I'm aware of the policy and feel that I've been completely transparent. The link is obviously identifiable with my user name. I have addressed the user's question, complete with demo code. And the software is free and open source. I am not selling anything, nor asking for anything. If you were able to figure out that I wrote the resource that I linked to, then I think everyone else can too. If you still feel I am in violation of this policy, please feel free to alert a moderator. – Howard Hinnant Jan 03 '19 at 14:28
  • It's easy to overlook; I know because it happened to me a number of times. But me, I'm just curious as to why you specifically reverted the recent edit which made this clearer. – tripleee Jan 03 '19 at 15:14
  • Ah, because my library has a pretty good following, and people search for it by terms such as "Howard Hinnant's date/time library". Furthermore the library has been accepted into the draft C++20 spec, and so even more people are interested in getting an advance look at how this part of C++20 will work. In essence, searching for "Hinnant" will catch this post, while searching for "my" won't. Yes, that leans towards self promotion. But it is also on topic for this this question, and beneficial to the larger C++ community. Adding: "I'm the author" feels both redundant and more self-promoting. – Howard Hinnant Jan 03 '19 at 17:51