PowerBI - TIME function limitation problem - powerbi

I have column with number of seconds and I'm trying to convert it to HH:mm:ss format.
All is working well if values are not empty or above 32767 since this is limitation of TIME function.
My question is: how can I still convert values above 32767 in HH:mm:ss format?
What I have so far is:
time_elapsed = IF(ISBLANK([time_in_sec]);"NaN";FORMAT(TIME(0;0;[time_in_sec]);"HH:mm:ss"))
But this not even checking because I don't know how to pass empty field as empty field and not Null or "Nan" or anything else when dealing with integer column.
For all other cases function FORMAT(TIME(0;0;[time_in_sec]);"HH:mm:ss") works well.
So 2 problems - how to convert numbers larger than 32767 to HH:mm:ss and how to just pass empty fields.
And in case of negative integer number it should return empty field as well.

1) It's possible that space character or another unprintable character may be present. In this case the value isn't considered BLANK. We need to see a sample of your data to tell exactly what's going on.
2) You can implement the formula, that converts seconds to the HH:MI:SS format, yourself, as follows:
// calculated column
hh_mi_ss =
VAR hr = FLOOR( query[column] / 3600, 1 ) // hours
VAR mn = FLOOR( MOD( query[column], 3600) / 60, 1) // minutes
VAR ss = FLOOR( MOD ( MOD( query[column], 3600) , 60 ), 1) // seconds
RETURN FORMAT( TIME(hr, mn,ss), "HH:mm:ss" )

FORMAT( DIVIDE( [DurationInSeconds] ) , 86400), "HH:mm:ss" )
This one-liner returns time part. It trims the day part if the DurationInSeconds is larger than 1 day (86400 seconds). If Duration is blank it returns blank.

Correct answer from #Nick Krasnov resolved my problem, I only needed to add IF function to regulate appearance of negative numbers, zeros and empty cells.
So I used:
hh_mi_ss = if([time_column]>0,(
VAR hr = FLOOR( [time_column] / 3600, 1) // hours
VAR mn = FLOOR( MOD( [time_column]; 3600) / 60, 1) // minutes
VAR ss = FLOOR( MOD ( MOD( [time_column], 3600) ,60 ), 1) // seconds
RETURN FORMAT( TIME(hr, mn,ss), "HH:mm:ss" ));
"Empty, 0 or negative value")
And in my locale I had to replace , with ; in argument of function.

Related

ternary operator ?: is throwing errors about wrong arguments

I'm trying to display a different time frame macd on a given time frame chart. so display 5 min macd on 1 min chart etc.
I've decided to accomplish that by multiplying a number 5 to the interval which is an integer and then turn that into a string and use that in the plot.
This works fine since I don;'t have to change it every time I change the time frame of the chart from 1 to 10 min etc, and it will still display the longer time frame macd based on the multiple.
This following code works fine using the ternary operator ?:
//#version = 2
study(title="test")
source = close
fastLength = input(12, minval=1)
slowLength=input(26,minval=1)
signalLength=input(9,minval=1)
// res5 mutiplies the current interval which is an integer by a factor 5 and turns it into a string with the value of "interval*5" or "1D" depending on the value of interval*5
res5= interval*5 < 1440 ? tostring(interval*5) : "1D"
src5=security(tickerid, res5, close)
fastMA5 = ema(src5, fastLength)
slowMA5 = ema(src5, slowLength)
macd5 = fastMA5 - slowMA5
signal5 = sma(macd5, signalLength)
outMacD5 = security(tickerid, res5, macd5)
plot( outMacD5 ? outMacD5 : na, color= red)
But if I were to change it to have more conditions like below, the ternary operator fails.
//#version = 2
study(title="test")
source = close
fastLength = input(12, minval=1)
slowLength=input(26,minval=1)
signalLength=input(9,minval=1)
// res5 mutiplies the current interval which is an integer by a factor 5 and turns it into a string with the value of "interval*5" or "1D" depending on the value 9of inteval*5
//res5= interval*5 < 1440 ? tostring(interval*5) : "1D"
res5= interval*5 < 1440 ? tostring(interval*5) : interval >= 1440 and interval*5 < 2880 ? "1D":na
src5=security(tickerid, res5, close)
fastMA5 = ema(src5, fastLength)
slowMA5 = ema(src5, slowLength)
macd5 = fastMA5 - slowMA5
signal5 = sma(macd5, signalLength)
outMacD5 = security(tickerid, res5, macd5)
plot( outMacD5 ? outMacD5 : na, color= red)
That brings back the error
Add to Chart operation failed, reason: Error: Cannot call `operator ?:` with arguments (bool, literal__string, na); available overloads ...
Using the iff brings back the same error about the arguments being incorrect.
I could really use some help here. I'm so lost in using these conditional operators.
Any tips are helpful.
Use this:
res5= interval*5 < 1440 ? tostring(interval*5) : interval >= 1440 and interval*5 < 2880 ? "1D": ""
plotchar(res5=="5", "res5 test", "", location=location.top)
The plotchar() call will allow you to confirm res5's value. Here it is being tested for "5", so you will be able to verify in the Data Window (doesn't print anything in the indicator's pane so it doesn't disturb scale) that its value is 1 -> true when you are on a 1min chart.
[Edit 2019.08.19 09:02 — LucF]
Your question was about the ternary not working, which the code above resolves. Following your comment, you also need a more complete function to calculate a multiple of the current timeframe in v2. Use this:
f_MultipleOfRes( _mult) =>
// Convert target timeframe in minutes.
_TargetResInMin = interval * _mult * (
isseconds ? 1. / 60. :
isminutes ? 1. :
isdaily ? 1440. :
isweekly ? 7. * 24. * 60. :
ismonthly ? 30.417 * 24. * 60. : na)
// Find best way to express the TF.
_TargetResInMin <= 0.0417 ? "1S" :
_TargetResInMin <= 0.167 ? "5S" :
_TargetResInMin <= 0.376 ? "15S" :
_TargetResInMin <= 0.751 ? "30S" :
_TargetResInMin <= 1440 ? tostring(round(_TargetResInMin)) :
tostring(round(min(_TargetResInMin / 1440, 365))) + "D"
See here for a use case, but don't use that function code as it's v4.

Aggregating time data type in Power BI

How can I make Power BI to show sum of column of time data type?
Scenario: table with columns Phase (text) and Duration (SQL Server time data type, also recognized in Power BI like time). When I add bar chart with Phase as axis and Duration as value, I only have option to show Count and Count Distinct for Duration, no SUM.
Additional clarification after adding DurationMeasure:
Table TBL1 in PowerBI has column Duration (Data type Time).
Measure DurationMeasure is added in TBL1 with formula shown below.
PowerBI does not allow me to add this measure to bar chart to Values property (see image below). Cannot drag-n-drop on Values property. If I just check this measure, it goes to Tooltip property of visual instead of Values property like all other measures. Maybe because it is string data, but I don't know how to format it.
Here is DurationMeasure definition (as in link given by Karl Anka):
DurationMeasure =
// Duration formatting
// * #konstatinos 1/25/2016
// * Given a number of seconds, returns a format of "hh:mm:ss"
//
// We start with a duration in number of seconds
VAR Duration = SUM(TBL1[DurationSeconds])
// There are 3,600 seconds in an hour
VAR Hours =
INT ( Duration / 3600)
// There are 60 seconds in a minute
VAR Minutes =
INT ( MOD( Duration - ( Hours * 3600 );3600 ) / 60)
// Remaining seconds are the remainder of the seconds divided by 60 after subtracting out the hours
VAR Seconds =
ROUNDUP(MOD ( MOD( Duration - ( Hours * 3600 );3600 ); 60 );0) // We round up here to get a whole number
// These intermediate variables ensure that we have leading zero's concatenated onto single digits
// Hours with leading zeros
VAR H =
IF ( LEN ( Hours ) = 1;
CONCATENATE ( "0"; Hours );
CONCATENATE ( ""; Hours )
)
// Minutes with leading zeros
VAR M =
IF (
LEN ( Minutes ) = 1;
CONCATENATE ( "0"; Minutes );
CONCATENATE ( ""; Minutes )
)
// Seconds with leading zeros
VAR S =
IF (
LEN ( Seconds ) = 1;
CONCATENATE ( "0"; Seconds );
CONCATENATE ( ""; Seconds )
)
// Now return hours, minutes and seconds with leading zeros in the proper format "hh:mm:ss"
RETURN
CONCATENATE (
H;
CONCATENATE ( ":"; CONCATENATE ( M; CONCATENATE ( ":"; S ) ) )
)
Thanks in advance
If you convert your duration hour to a decimal number it is possible to show the sum. Then the value 1 = 1 day = 24 hour = 1440 minutes = 86400 seconds. So a value of 0.5 means 12 hours. It is a bit of a hassle to display it as hours and minutes in a chart, but this from the Power BI forums shows how its done.

How do I go about getting the real result for 50%60 in C++

I please check this problem I'm creating a Time Base app but I'm having problem getting to work around the modulus oper (%) I want the remainder of 50%60 which I'm expecting to output 10 but it just give me the Lhvalues instead i.e 50. How do I go about it.
Here is a part review of the code.
void setM(int m){
if ((m+min)>59){
hour+=((min+m)/60);
min=0;
min=(min+m)%60;
}
else min+=m;
}
In the code m is passed in as 50 and min is passed in as 10
How do I get the output to be 10 for min in this equation min=(min+m)%60; without reversing the equation i.e
60%(min+m)
in C++ expression a % b returns remainder of division of a by b (if they are positive. For negative numbers sign of result is implementation defined).
you should do : 60 % 50 if you want to divide by 50
Or, if you want to get mins, i think you don't need to make min=0.
When you do 50 % 60, you get a remiainder of 50 since 50 cannot be divided by 60.
To get around this error, you can try doing do something like 70 % 60 to get the correct value as a result, since you do not want to use 60 % 50
This would follow the following logic:
Find the difference between 60 and min + m after min is set to zero if min + mis less than 60. Store it in a variable var initially set to zero.
check if the result is negative; if it is, then set it to positive by multiplying it by -1
When you do the operation, do min = ((min + m) + var) % 60; instead.
***Note: As I am unfamiliar with a Time Base App and what its purpose is, this solution may or may not be required, hence please inform me in the comments before downvoting if I there is anything wrong with my answer. Thanks!
It looks like you are trying to convert an integral number of minutes to an hour/minute pair. That would look more like this instead:
void setM(int m)
{
hour = m / 60;
min = m % 60;
}
If you are trying to add an integral number of minutes to an existing hour/minute pair, it would look more like this:
void addM(int m)
{
int value = (hour * 60) + min;
value += m;
hour = value / 60;
min = value % 60;
}
Or
void addM(int m)
{
setM(((hour * 60) + min) + m);
}

Math to convert seconds since 1970 into date and vice versa

I have seconds since Jan 1 1970 00:00 as an int64 in nanoseconds and I'm trying to convert it into month/day/year/day of week.
It's easy to do this iteratively, I have that working but I want to do it formulaically. I'm looking for the actual math.
New answer for old question:
Rationale for this new answer: The existing answers either do not show the algorithms for the conversion from nanoseconds to year/month/day (e.g. they use libraries with the source hidden), or they use iteration in the algorithms they do show.
This answer has no iteration whatsoever.
The algorithms are here, and explained in excruciating detail. They are also unit tested for correctness over a span of +/- a million years (way more than you need).
The algorithms don't count leap seconds. If you need that, it can be done, but requires a table lookup, and that table grows with time.
The date algorithms deal only with units of days, and not nanoseconds. To convert days to nanoseconds, multiply by 86400*1000000000 (taking care to ensure you're using 64 bit arithmetic). To convert nanoseconds to days, divide by the same amount. Or better yet, use the C++11 <chrono> library.
There are three date algorithms from this paper that are needed to answer this question.
1. days_from_civil:
// Returns number of days since civil 1970-01-01. Negative values indicate
// days prior to 1970-01-01.
// Preconditions: y-m-d represents a date in the civil (Gregorian) calendar
// m is in [1, 12]
// d is in [1, last_day_of_month(y, m)]
// y is "approximately" in
// [numeric_limits<Int>::min()/366, numeric_limits<Int>::max()/366]
// Exact range of validity is:
// [civil_from_days(numeric_limits<Int>::min()),
// civil_from_days(numeric_limits<Int>::max()-719468)]
template <class Int>
constexpr
Int
days_from_civil(Int y, unsigned m, unsigned d) noexcept
{
static_assert(std::numeric_limits<unsigned>::digits >= 18,
"This algorithm has not been ported to a 16 bit unsigned integer");
static_assert(std::numeric_limits<Int>::digits >= 20,
"This algorithm has not been ported to a 16 bit signed integer");
y -= m <= 2;
const Int era = (y >= 0 ? y : y-399) / 400;
const unsigned yoe = static_cast<unsigned>(y - era * 400); // [0, 399]
const unsigned doy = (153*(m + (m > 2 ? -3 : 9)) + 2)/5 + d-1; // [0, 365]
const unsigned doe = yoe * 365 + yoe/4 - yoe/100 + doy; // [0, 146096]
return era * 146097 + static_cast<Int>(doe) - 719468;
}
2. civil_from_days:
// Returns year/month/day triple in civil calendar
// Preconditions: z is number of days since 1970-01-01 and is in the range:
// [numeric_limits<Int>::min(), numeric_limits<Int>::max()-719468].
template <class Int>
constexpr
std::tuple<Int, unsigned, unsigned>
civil_from_days(Int z) noexcept
{
static_assert(std::numeric_limits<unsigned>::digits >= 18,
"This algorithm has not been ported to a 16 bit unsigned integer");
static_assert(std::numeric_limits<Int>::digits >= 20,
"This algorithm has not been ported to a 16 bit signed integer");
z += 719468;
const Int era = (z >= 0 ? z : z - 146096) / 146097;
const unsigned doe = static_cast<unsigned>(z - era * 146097); // [0, 146096]
const unsigned yoe = (doe - doe/1460 + doe/36524 - doe/146096) / 365; // [0, 399]
const Int y = static_cast<Int>(yoe) + era * 400;
const unsigned doy = doe - (365*yoe + yoe/4 - yoe/100); // [0, 365]
const unsigned mp = (5*doy + 2)/153; // [0, 11]
const unsigned d = doy - (153*mp+2)/5 + 1; // [1, 31]
const unsigned m = mp + (mp < 10 ? 3 : -9); // [1, 12]
return std::tuple<Int, unsigned, unsigned>(y + (m <= 2), m, d);
}
3. weekday_from_days:
// Returns day of week in civil calendar [0, 6] -> [Sun, Sat]
// Preconditions: z is number of days since 1970-01-01 and is in the range:
// [numeric_limits<Int>::min(), numeric_limits<Int>::max()-4].
template <class Int>
constexpr
unsigned
weekday_from_days(Int z) noexcept
{
return static_cast<unsigned>(z >= -4 ? (z+4) % 7 : (z+5) % 7 + 6);
}
These algorithms are written for C++14. If you have C++11, remove the constexpr. If you have C++98/03, remove the constexpr, the noexcept, and the static_asserts.
Note the lack of iteration in any of these three algorithms.
They can be used like this:
#include <iostream>
int
main()
{
int64_t z = days_from_civil(2015LL, 8, 22);
int64_t ns = z*86400*1000000000;
std::cout << ns << '\n';
const char* weekdays[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
unsigned wd = weekday_from_days(z);
int64_t y;
unsigned m, d;
std::tie(y, m, d) = civil_from_days(ns/86400/1000000000);
std::cout << y << '-' << m << '-' << d << ' ' << weekdays[wd] << '\n';
}
which outputs:
1440201600000000000
2015-8-22 Sat
The algorithms are in the public domain. Use them however you want. The date algorithms paper has several more useful date algorithms if needed (e.g. weekday_difference is both remarkably simple and remarkably useful).
These algorithms are wrapped up in an open source, cross platform, type-safe date library if needed.
If timezone or leap second support is needed, there exists a timezone library built on top of the date library.
Update: Different local zones in same app
See how to convert among different time zones.
Update: Are there any pitfalls to ignoring leap seconds when doing date calculations in this manner?
This is a good question from the comments below.
Answer: There are some pitfalls. And there are some benefits. It is good to know what they both are.
Almost every source of time from an OS is based on Unix Time. Unix Time is a count of time since 1970-01-01 excluding leap seconds. This includes functions like the C time(nullptr) and the C++ std::chrono::system_clock::now(), as well as the POSIX gettimeofday and clock_gettime. This is not a fact specified by the standard (except it is specified by POSIX), but it is the de facto standard.
So if your source of seconds (nanoseconds, whatever) neglects leap seconds, it is exactly correct to ignore leap seconds when converting to field types such as {year, month, day, hours, minutes, seconds, nanoseconds}. In fact to take leap seconds into account in such a context would actually introduce errors.
So it is good to know your source of time, and especially to know if it also neglects leap seconds as Unix Time does.
If your source of time does not neglect leap seconds, you can still get the correct answer down to the second. You just need to know the set of leap seconds that have been inserted. Here is the current list.
For example if you get a count of seconds since 1970-01-01 00:00:00 UTC which includes leap seconds and you know that this represents "now" (which is currently 2016-09-26), the current number of leap seconds inserted between now and 1970-01-01 is 26. So you could subtract 26 from your count, and then follow these algorithms, getting the exact result.
This library can automate leap-second-aware computations for you. For example to get the number of seconds between 2016-09-26 00:00:00 UTC and 1970-01-01 00:00:00 UTC including leap seconds, you could do this:
#include "date/tz.h"
#include <iostream>
int
main()
{
using namespace date;
auto now = clock_cast<utc_clock>(sys_days{2016_y/September/26});
auto then = clock_cast<utc_clock>(sys_days{1970_y/January/1});
std::cout << now - then << '\n';
}
which outputs:
1474848026s
Neglecting leap seconds (Unix Time) looks like:
#include "date/date.h"
#include <iostream>
int
main()
{
using namespace date;
using namespace std::chrono_literals;
auto now = sys_days{2016_y/September/26} + 0s;
auto then = sys_days{1970_y/January/1};
std::cout << now - then << '\n';
}
which outputs:
1474848000s
For a difference of 26s.
This upcoming New Years (2017-01-01) we will insert the 27th leap second.
Between 1958-01-01 and 1970-01-01 10 "leap seconds" were inserted, but in units smaller than a second, and not just at the end of Dec or Jun. Documentation on exactly how much time was inserted and exactly when is sketchy, and I have not been able to track down a reliable source.
Atomic time keeping services began experimentally in 1955, and the first atomic-based international time standard TAI has an epoch of 1958-01-01 00:00:00 GMT (what is now UTC). Prior to that the best we had was quartz-based clocks which were not accurate enough to worry about leap seconds.
The Single Unix Specification gives a formula for Seconds since the Epoch:
A value that approximates the number of seconds that have elapsed
since the Epoch. A Coordinated Universal Time name (specified in terms
of seconds (tm_sec), minutes (tm_min), hours (tm_hour), days since
January 1 of the year (tm_yday), and calendar year minus 1900
(tm_year)) is related to a time represented as seconds since the
Epoch, according to the expression below.
If the year is <1970 or the value is negative, the relationship is
undefined. If the year is >=1970 and the value is non-negative, the
value is related to a Coordinated Universal Time name according to the
C-language expression, where tm_sec, tm_min, tm_hour, tm_yday, and
tm_year are all integer types:
tm_sec + tm_min*60 + tm_hour*3600 + tm_yday*86400 +
(tm_year-70)*31536000 + ((tm_year-69)/4)*86400 -
((tm_year-1)/100)*86400 + ((tm_year+299)/400)*86400
The relationship between the actual time of day and the current value
for seconds since the Epoch is unspecified.
How any changes to the value of seconds since the Epoch are made to
align to a desired relationship with the current actual time is
implementation-defined. As represented in seconds since the Epoch,
each and every day shall be accounted for by exactly 86400 seconds.
Note:
The last three terms of the expression add in a day for each year that follows a leap year starting with the first leap year since the
Epoch. The first term adds a day every 4 years starting in 1973, the
second subtracts a day back out every 100 years starting in 2001, and
the third adds a day back in every 400 years starting in 2001. The
divisions in the formula are integer divisions; that is, the remainder
is discarded leaving only the integer quotient.
You'll need to convert month and day of month to tm_yday to use this formula and that too should be done taking into account leap years. The rest in the formula is trivial.
Try to figure out from this how to get back date and time from seconds.
EDIT:
I've implemented a convertor in integer arithmetic in this answer.
See a test run at ideone.
Depends on which time you want gmtime or localtime then just read the struct_tm
This code works...
Usage:
uint32_t getSecsSinceEpoch(1970, month, day, years_since_epoch, hour, minute, second);
Example:
timestamp = getSecsSinceEpoch(1970, 6, 12, (2014 - 1970), 15, 29, 0)
Returns: 1402586940
You can verify at www.epochconverter.com.
Took about 20 mins to write it and most of that was spent arguing with a friend as to whether I should include leap-seconds, nano-seconds, etc. Blech.
Have fun...
Dr. Bryan Wilcutt
#define DAYSPERWEEK (7)
#define DAYSPERNORMYEAR (365U)
#define DAYSPERLEAPYEAR (366U)
#define SECSPERDAY (86400UL) /* == ( 24 * 60 * 60) */
#define SECSPERHOUR (3600UL) /* == ( 60 * 60) */
#define SECSPERMIN (60UL) /* == ( 60) */
#define LEAPYEAR(year) (!((year) % 4) && (((year) % 100) || !((year) % 400)))
const int _ytab[2][12] = {
{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
{31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
};
/****************************************************
* Class:Function : getSecsSomceEpoch
* Input : uint16_t epoch date (ie, 1970)
* Input : uint8 ptr to returned month
* Input : uint8 ptr to returned day
* Input : uint8 ptr to returned years since Epoch
* Input : uint8 ptr to returned hour
* Input : uint8 ptr to returned minute
* Input : uint8 ptr to returned seconds
* Output : uint32_t Seconds between Epoch year and timestamp
* Behavior :
*
* Converts MM/DD/YY HH:MM:SS to actual seconds since epoch.
* Epoch year is assumed at Jan 1, 00:00:01am.
****************************************************/
uint32_t getSecsSinceEpoch(uint16_t epoch, uint8_t month, uint8_t day, uint8_t years, uint8_t hour, uint8_t minute, uint8_t second)
{
unsigned long secs = 0;
int countleap = 0;
int i;
int dayspermonth;
secs = years * (SECSPERDAY * 365);
for (i = 0; i < (years - 1); i++)
{
if (LEAPYEAR((epoch + i)))
countleap++;
}
secs += (countleap * SECSPERDAY);
secs += second;
secs += (hour * SECSPERHOUR);
secs += (minute * SECSPERMIN);
secs += ((day - 1) * SECSPERDAY);
if (month > 1)
{
dayspermonth = 0;
if (LEAPYEAR((epoch + years))) // Only counts when we're on leap day or past it
{
if (month > 2)
{
dayspermonth = 1;
} else if (month == 2 && day >= 29) {
dayspermonth = 1;
}
}
for (i = 0; i < month - 1; i++)
{
secs += (_ytab[dayspermonth][i] * SECSPERDAY);
}
}
return secs;
}
bool FloatToTime(float seconds_since_epoch, bool local_time, struct tm *timest)
{
struct tm *ret;
time_t t=(time_t) seconds_since_epoch;
if (local_time) ret=localtime(&t);
else ret=gmtime(&t);
if(ret==NULL) return false;
memcpy(timest, ret, sizeof(struct tm));
return true;
}
Pass it the seconds as the first parameter. The second parameter should be true for local time, false for GMT. The third parameter is a pointer to a structure to hold the response.
The return structures are (from the man page):
tm_sec: The number of seconds after the minute, normally in the range 0 to
59, but can be up to 60 to allow for leap seconds.
tm_min: The number of minutes after the hour, in the range 0 to 59.
tm_hour: The number of hours past midnight, in the range 0 to 23.
tm_mday: The day of the month, in the range 1 to 31.
tm_mon: The number of months since January, in the range 0 to 11.
tm_year: The number of years since 1900.
tm_wday: The number of days since Sunday, in the range 0 to 6.
tm_yday: The number of days since January 1, in the range 0 to 365.
tm_isdst: A flag that indicates whether daylight saving time is in effect
at the time described. The value is positive if daylight saving
time is in effect, zero if it is not, and negative if the
information is not available.
First of all, do not store your seconds as a float. If you need micro/nanoseconds, store them separately. You're going to need integers to do these calculations.
It depends on your time zone (DST rules, leap years, leap seconds), but I would say first get the number of days by integer dividing by 86400. Then find out what's left over, by modulo dividing by 86400. Now you can figure out how many years have passed by first integer dividing the number of days by 365, and then subtracting the number of leap days from the remaining days (calculated by modulo dividing the number of days by 365). You'll also want to subtract the number of leap seconds from the number of remaining seconds (already calculated). If that subtraction drives those numbers below zero, then subtract from the next biggest denomination. Then you can calculate the day of month using explicit logic for your calendar. Make sure to add an hour (or whatever the DST offset is) if you land in DST.
Personally, I would just use Boost.Date_Time, since it does all this and more (probably with fewer mistakes than you or I would make in the first few iterations), but I figured I'd take a shot at your question...
BEFORE
for (i = 0; i < (years - 1); i++)
{
if (LEAPYEAR((epoch + i)))
countleap++;
}
LATER:
for (i = 0; i < years; i++)
{
if (LEAPYEAR((epoch + i)))
countleap++;
}
After the correction the code worked for me.
I needed to implement conversion to Unix time at a low-end 8-bit MCU without HW multiplier. Below is the C# code that requires only a general 8-bit multiplication and a division by constant values 4 and 100. Both on 32-bit (long) operand. The C# code can be easily ported to the final framework. It gives the same result as DateTimeOffset.ToUnixTimeSeconds() from .NET.
static long UnixTime ( int sec, int min, int hour, int day, int month, int year )
{
// Cumulative days for each previous month of the year
int[] mdays = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };
// Year is to be relative to the epoch start
year -= 1970;
// Compensation of the non-leap years
int minusYear = 0;
// Detect potential lead day (February 29th) in this year?
if ( month >= 3 )
{
// Then add this year into "sum of leap days" computation
year++;
// Compute one year less in the non-leap years sum
minusYear = 1;
}
return
// + Seconds from computed minutes
60 * (
// + Minutes from computed hours
60 * (
// + Hours from computed days
24 * (
// + Day (zero index)
day - 1
// + days in previous months (leap day not included)
+ mdays[month - 1]
// + days for each year divisible by 4 (starting from 1973)
+ ( ( year + 1 ) / 4 )
// - days for each year divisible by 100 (starting from 2001)
- ( ( year + 69 ) / 100 )
// + days for each year divisible by 400 (starting from 2001)
+ ( ( year + 369 ) / 100 / 4 )
// + days for each year (as all are non-leap years) from 1970 (minus this year if potential leap day taken into account)
+ ( 5 * 73 /*=365*/ ) * ( year - minusYear )
// + Hours
) + hour
// + Minutes
) + min
// + Seconds
) + sec;
}
Hope it helps.
Edited:
Below is the optimized code for 8-bit PIC MCU and CC5X compiler.
uns32 unixTime;
...
// Test data returning 0xFfFfFfFf UnixTime
uns8 year = 2106 - 1970;
uns8 month = 2;
uns8 day = 7;
uns8 hour = 6;
uns8 min = 28;
uns8 sec = 15;
// See original C# code below
//### Compute days
// ( 5 * 73 /*=365*/ ) * year
unixTime = year;
mulUnixTime( 5 );
mulUnixTime( 73 );
// if ( month >= 3 ) year++;
if ( month > 3 )
year++;
// if ( year > 130 ) => minus 1 total days ( year-=4 makes a result of the next division by 4 less by 1)
if ( year > 130 )
year -= 4;
// + ( ( year + 1 ) / 4 )
addUnixTime( ( year + 1 ) / 4 );
// + mdays[month - 1]
addUnixTime( daysInMonths( month ) );
// + day - 1
addUnixTime( day - 1 );
//### Compute hours
// Hours from computed days
mulUnixTime( 24 );
// + Hours
addUnixTime( hour );
//### Compute minutes
// Minutes from computed hours
mulUnixTime( 60 );
// + Minutes
addUnixTime( min );
//### Compute seconds
// Seconds from computed minutes
mulUnixTime( 60 );
// + Seconds
addUnixTime( sec );
...
void mulUnixTime( uns8 mul )
{
unixTime *= mul;
}
void addUnixTime( uns8 add )
{
unixTime += add;
}
uns8 daysInMonths( uns8 month # W )
{
skip( month );
#pragma computedGoto 1
return 0xFF;// Dummy value for month 0
return 0; // January
return 31; // February
return 59; // ...
return 90;
return 120;
return 151;
return 181;
return 212;
return 243;
return 273;
return 304; // ...
return 334; // December
#pragma computedGoto 0
}
/*
static long UnixTime ( int sec, int min, int hour, int day, int month, int year )
{
// Cumulative days for each previous month of the year
int[] mdays = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };
// Year is to be relative to the epoch start
year -= 1970;
// Compensation of the non-leap years
int minusYear = 0;
// Detect potential lead day (February 29th) in this year?
if ( month >= 3 )
{
// Then add this year into "sum of leap days" computation
year++;
// Compute one year less in the non-leap years sum
minusYear = 1;
}
return
// + Seconds from computed minutes
60 * (
// + Minutes from computed hours
60 * (
// + Hours from computed days
24L * (
// + Day (zero index)
day - 1
// + days in previous months (leap day not included)
+ mdays[month - 1]
// + days for each year divisible by 4 (starting from 1973)
+ ( ( year + 1 ) / 4 )
// - days after year 2000
- ( ( year > 130 ) ? 1 : 0 )
// + days for each year (as all are non-leap years) from 1970 (minus this year if potential leap day taken into account)
+ ( 5 * 73 ) * ( year - minusYear )
// + Hours
) + hour
// + Minutes
) + min
// + Seconds
) + sec;
}
*/

Convert Date/Time (as Double) to struct* tm in C++

I get a date/time as double value from C# (DateTime.ToOADate(), which is a OLE Date Time). It contains the passed days from 1899/12/31, with the fraction being the passed part of the day. Multiplied with 86400 I get the seconds and from that finally the day's time.
Getting the date however is harder. I still don't have an solution except for the dates that the UNIX time covers (1970/01/01 to 2038/01/19). During this time, mktime() can be used to convert the passed days to a datetime.
double OleDateTimeValue = 28170.654351851852; // 14.02.1977 15:42:16
struct tm * timeinfo;
int passedDays = (int)OLEDateTimeValue;
// between 1.1.1970 and 18.1.2038
if((passedDays >= 25569) && (passedDays <= 50423))
{
timeinfo->tm_year = 70; //1970
timeinfo->tm_mon = 0;
timeinfo->tm_mday = 1 + (passedDays - 25569);
mktime(timeinfo);
}
else // date outside the UNIX date/time
{
}
Now, mktime() formats the tm struct so that it represents the requested date, or returns -1 if the value is outside the given dates.
Is there a generic way to do the calculation? Unfortunately I can't use MFC and have to use Visual C++ 6.0.
Thanks,
Markus
I believe you can use the VariantTimeToSystemTime function to convert into a SYSTEMTIME.
I love date conversions - they make such a nice puzzle!
Here's some code that works for the dates from 1900-03-01 to 2100-02-28. The reason it doesn't work past those boundaries is that 1900 and 2100 are not leap years. This has been tested against Microsoft's COleDateTime and matches every day within the range.
int leapDays = (int)((OleDateTimeValue + 1400) / 1461);
timeinfo->tm_year = (int)((OleDateTimeValue - leapDays - 1) / 365);
int janFirst = timeinfo->tm_year * 365 + (int)((timeinfo->tm_year + 7) / 4);
int wholeDays = (int)OleDateTimeValue - janFirst;
if (timeinfo->tm_year % 4 != 0 && wholeDays > 58)
++wholeDays;
static int firstOfMonth[12] = { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335 };
timeinfo->tm_mon = std::upper_bound(&firstOfMonth[0], &firstOfMonth[12], wholeDays) - &firstOfMonth[0];
timeinfo->tm_mday = wholeDays - firstOfMonth[timeinfo->tm_mon - 1] + 1;
Conversion of the time portion is trivial, and I leave it as an exercise to the reader.
Converting to time_t should be easy (multiply by 86400 and add a constant offset), then you can use the localtime function. But you'll still be limited to the range of UNIX time. If you really need beyond that range then villintehaspam's answer, along with copying all the individual fields, looks like the way to go.