How do I round a datetime to the nearest minute in KDB? - kdb

I have an array of timestamps of the form:
2022.05.23T14:31:04.222
I'm trying to round them to the nearest date-minute, such that the output would be 2022.05.23T14:31:00.000.
I've various casts (e.g. `minute$t), and also 00:01:00 xbar t, but none of these worked.
How should it be done?

A few suggestions:
Don't use datetime datatype (aka .z.Z, aka type 15, aka "z"$) use timestamp datatype (aka .z.P, aka type 12, aka "p"$). The former is underpinned by a float (not good for bar'ing) while the latter is underpinned by long.
Your 00:01:00 xbar t approach almost works, you just need to use timestamps for t and datespan (aka .z.N, aka type 16, aka "n"$) for the xbar:
q)t:"p"$2022.05.23T14:31:04.222 2022.05.23T14:32:04.222;
q)0D00:01:00 xbar t
2022.05.23D14:31:00.000000000 2022.05.23D14:32:00.000000000

Add 30 seconds; cast to year and minute; sum.
q)t
2022.05.23T14:31:04.222
q)f:sum "du"$ ("v"$30)+ / composition
q)f t
2022.05.23D14:31:00.000000000
q)f t+"v"$40
2022.05.23D14:32:00.000000000

Here's one method (this will only round down though):
q){`datetime$(`date$x)+`minute$x}2022.05.23T14:31:04.222
2022.05.23T14:31:00.000
This should round up & down:
q){"z"$("d"$x)+{$[30=`ss$x;00:00:30+;]x}30 xbar"v"$x}2022.05.23T14:31:04.222
2022.05.23T14:31:00.000
q){"z"$("d"$x)+{$[30=`ss$x;00:00:30+;]x}30 xbar"v"$x}2022.05.23T14:31:34.222
2022.05.23T14:32:00.000

Related

Powershell cast string to time only

I have created a Powershell/XAML app, that on button press makes a RESTAPI call, parses the JSON response into fields in the app front end. All fine so far.
These fields will be populated with a string representing a time, so "1800" or "2000" etc.
The user can then change this from 1800 to 1900 for example.
This is all fine, and in the background the app will use 1900 to update a setting to be used in a POST back.
However there are other settings that are offset by 90 mins of the time above. I don't want the user to have update each one, which is why I am trying to programmatically.
But try as I might, I cannot take a string of 1800, add 90 mins to it and make the value 1930 (not 1890).
You could parse the input as a DateTime object (ignoring the date part) and then use the AddMinutes method.
$input = '1800'
$hour = $input.Substring(0,2)
$minute = $input.Substring(2,2)
$dateInputStr = "0001-01-01,${hour}:${minute}:00"
[datetime]$dateInput = ([datetime]$dateInputStr)
$dateInput = $dateInput.AddMinutes(90)
$dateInput.ToString("HHmm")
Using [timespan] instances is another option:
$time = '1800'
([timespan] ($time -replace '(?<=^..)', ':') + '01:30').ToString('hhmm') #->'1930'
$time -replace '(?<=^..)', ':' uses the regex-based -replace operator to insert : after the first two characters - see this regex101.com page for an explanation of the regex and the ability to experiment with it.
Due to expressing the results only in terms of hours and minutes, the calculation wraps around at midnight, so that adding '05:30', for instance, would yield '0030'
The RHS operand needn't be cast to [timespan] directly, because the data type of the LHS - with its explicit [timespan] cast - implicitly converts the RHS to [timespan] too, with '01:30' representing 1 hour and 30 minutes, i.e. 90 minutes.
If you want to define the duration to add in terms of 90 minutes, use the following instead (there are analogous static methods for other units, such as ::FromSeconds():
[timespan]::FromMinutes(90)
Alternatively, you can cast a number to [timespan], which is interpreted as ticks, which are 100-nanosecond units; there are 1e9 (10 to the power of 9) nanoseconds in a second, and therefore 1e7 100-nanosecond units in a second. Thus, multiplying with 1e7 gives you seconds, and multiplying that with 60 minutes.
# 90 minutes expressed as ticks
[timespan] 90 * (60 * 1e7)
When I read this question I wanted to solve it with minimal string manipulation, leaning on time related objects and methods instead. datetime was the first object I thought of, but it expects a date (year, month, day). Things actually simplify if we use timespan. Its static method, ParseExact, can parse the string directly.
$offsetTimeSpan = [timespan]::FromMinutes(90)
$timeField = '830'
$timeStr = $timeField.PadLeft(4, '0')
$timeSpan = [timespan]::ParseExact($timeStr, 'hhmm', [CultureInfo]::InvariantCulture)
$offsetTime = $timeSpan.Add($offsetTimeSpan)
$offsetTime.ToString('hhmm')
$timeField is used to represent the time you get from the RESTAPI. PadLeft is only needed if it's possible for a leading 0 to be missing. ParseExact does the heavy lifting of converting the string to a time type. Because timespan doesn't have an AddMinutes member, we use the Add method passing in a timespan of 90 minutes, $offsetTimeSpan.
You don't mention anything about overflowing past midnight. You can test for overflow using $offsetTime.Days, if any special processing is required.

Make a list with the quarter and year based on a date range of quarters KDB+/Q

I have a list of date ranges for the past 8 quarters given by the below function
q) findLastYQuarters:{reverse("d"$(-3*til y)+m),'-1+"d"$(-3*-1+til y)+m:3 bar"m"$x}[currentDate;8]
q) findLastYQuarters
2020.01.01 2020.03.31
2020.04.01 2020.06.30
2020.07.01 2020.09.30
2020.10.01 2020.12.31
2021.01.01 2021.03.31
2021.04.01 2021.06.30
2021.07.01 2021.09.30
2021.10.01 2021.12.31
I need to produce a separate list that labels each item in this list by a specific format; the second list would need to be
1Q20,2Q20,3Q20,4Q20,1Q21,2Q21,3Q21,4Q21
This code needs to be able to run on it's own, so how can I take the first list as an input and produce the second list? I thought about casting the latter date in the range as a month and dividing it by 3 to get the quarter and extracting the year, but I couldn't figure out how to actually implement that. Any advice would be much appreciated!
I'm sure there are many ways to solve this, a function like f defined below would do the trick:
q)f:{`$string[1+mod[`month$d;12]%3],'"Q",/:string[`year$d:x[;0]][;2 3]}
q)lyq
2020.01.01 2020.03.31
2020.04.01 2020.06.30
2020.07.01 2020.09.30
2020.10.01 2020.12.31
2021.01.01 2021.03.31
2021.04.01 2021.06.30
2021.07.01 2021.09.30
2021.10.01 2021.12.31
q)f lyq
`1Q20`2Q20`3Q20`4Q20`1Q21`2Q21`3Q21`4Q21
Figured it out.
crop:findLastYQuarters;
crop[0]:crop[0][1];
crop[1]:crop[1][1];
crop[2]:crop[2][1];
crop[3]:crop[3][1];
crop[4]:crop[4][1];
crop[5]:crop[5][1];
crop[6]:crop[6][1];
crop[7]:crop[7][1];
labels:()
labelingFunc:{[r] temp:("." vs string["m"$r]); labels,((string(("J"$temp[1])%3)),"Q",(temp[0][2,3])};
leblingFunc each crop;
labels

Rounding timestamp to the nearest 30 seconds

My table is as follows:
t: ([]dt: 2021.10.25T09:30:28 2021.10.25T09:30:32;price:9.99 10.00)
I wish to round the timestamp to the nearest 30sec mark.
I tried using xbar like so:
update roundedDt: 30 xbar dt.second from t
However it seems to have floored the results.
The desired result should be 09:30:30 for both rows.
How can one round to the nearest 30 second mark?
Jonathon's answer is the most flexible for modifying the rounding for not just seconds specifically but an alternative simple solution for just seconds would be to offset by 15:
q)update roundedDt:30 xbar 15+dt.second from t
dt price roundedDt
---------------------------------------
2021.10.25T09:30:28.000 9.99 09:30:30
2021.10.25T09:30:32.000 10 09:30:30
Edit: If you want the full dateTime rounded, I would convert it to timestamp as easy to work with and adjust my offset/xbar to match.
q)update roundedDt:30000000000 xbar 15000000000 + `timestamp$dt from t
dt price roundedDt
-----------------------------------------------------------
2021.10.25T09:30:28.000 9.99 2021.10.25D09:30:30.000000000
2021.10.25T09:30:32.000 10 2021.10.25D09:30:30.000000000
2020.10.25T23:59:59.000 9.99 2020.10.26D00:00:00.000000000
2020.10.26T00:00:01.000 10 2020.10.26D00:00:00.000000000
You can try something like this:
update roundedDt:?[(`ss$dt)within(0;14);`time$(`int$`time$dt)-1000*`ss$dt;
?[(`ss$dt)within(15;44);`time$30000+(`int$`time$dt)-1000*`ss$dt;`time$60000+(`int$`time$dt)-1000*`ss$dt]] from t
You could use a modified version of xbar that rounds to nearest int instead of flooring:
q)xbar2:{type[y]$x*"j"$y%x:$[16h=abs type x;"j"$x;x]}
q)update roundedDt:xbar2[30;dt.second] from t
dt price roundedDt
---------------------------------------
2021.10.25T09:30:28.000 9.99 09:30:30
2021.10.25T09:30:32.000 10 09:30:30
Note that because this function is defined in root namespace you must use bracket notation (xbar2[30;dt.second]). If you wish to use infix notation (30 xbar2 dt.second), you'll need to define the function in .q namespace i.e. .q.xbar2:{type[y]$x*"j"$y%x:$[16h=abs type x;"j"$x;x]}.
xbar2 is based on the original xbar, but where xbar uses div which has the effect of flooring the result, here % is used which will produce a float output and this is then cast to a long int which will round to the nearest integer.
What about this solutions:
/ x is your timestamp
/ y is the timebucket (in seconds)
.time.round:{
:"z"$+[`date$x;`time$1e3*y*`int$%[`time$x;y*1e3]];
};
As example, if you want to round at the nearest 30 seconds, you need to use this as follows:
ts1:2020.10.30T10:32:35
.time.round[ts1;30]
In your case, simply type:
t[`round_time]:{.time.round[x;30]} each t[`dt]
As a side note, some of the proposed solutions would round timestamps like 2020.10.25T23:59:59 and 2020.10.26T00:00:01 to 24:00:00 and 00:00:00 respectively, which is not what we would like I suppose.

How to get a date in yyyy-MM-dd'T'HH: mm: ss.SSSZ format

I want to get the current time, and format it in the following way:
yyyy-MM-dd'T'HH:mm:ss.SSSZ
(where SSS is the milliseconds and Z the time zone)
the code so far I have it as follows:
formatted_date() ->
{{Y,M,D},{H,Min,S}} = erlang:localtime(),
{Ms, _, _} = os:timestamp(),
{Days, {Hours, _,_}} = calendar:time_difference(erlang:universaltime(), erlang:localtime()),
Difference = 24*Days + Hours,
Tz = [case Difference < 0 of
true ->
$-;
false ->
$+
end | io_lib:format("~2..0B00",[Difference])],
io_lib:format("[~4..0B-~2..0B-~2..0BT~2..0B:~2..0B:~2..0B.~3..0B ~5.s]",
[Y, M, D, H, Min, S, Ms, Tz]).
The fact is that it always returns the same Ms, therefore, I think that I am not doing it well, and in other questions I only see how to obtain the total time in milliseconds, but not how to format it in this way.
Thank you.
The easiest way is to use the library function calendar:system_time_to_rfc3339/2 - it seems to fulfil all your requirements.
> calendar:system_time_to_rfc3339(os:system_time(millisecond), [{unit, millisecond}]).
"2021-03-03T18:42:08.497+05:30"
This function was added in Erlang/OTP 21.0, which was released in June 2018.
The reason your code always gets the same value for Ms is that the first value in the tuple returned by os:timestamp/0 is megaseconds, not milliseconds; the three values are megaseconds, seconds and microseconds. This comes from a time when Erlang did not support large integers, so splitting up the timestamp was necessary. These days you can just call os:system_time/1 or a number of other functions, depending on what kind of time you need, and get the result in a single integer.

Can't convert division result into float or decimal type

I have a calculation in my t-sql code that I expect will show decimal result (with at least 2 digits after comma)
My fields that I am using are integer type, but the calculations result is decimal
I tried using CAST as float, but won't work
(COUNT(ct.[ClientFK]) / ehrprg.AnnualGoalClientsServed) AS [AnnualGoal]
I tried:
CAST((COUNT(ct.[ClientFK]) / ehrprg.AnnualGoalClientsServed) as float)
AS[AnnualGoal]
I expect to see at lest two digits after comma -
2/50 to be 0.04 while now I am getting 0
Any advice / help would be much appreciated
Try explicitly casting the denominator to float before the quotient is taken:
COUNT(ct.[ClientFK]) / CAST(ehrprg.AnnualGoalClientsServed AS float) AS [AnnualGoal]
In the above approach, because one of the two terms in the quotient is floating point, the other term (in this case, the count) should be promoted to float as well.