Snowflake UDF in JavaScript does not calculate as expected - javascript

I am trying to calculate the number of minutes a logged job has been running.
Each job has a start time and an end time.
In this particular case, the working hours are between 01:00 and 10:00, and only business days (weekends excluded)
In order to calculate this, I tried and made a JavaScript based UDF like this:
CREATE OR REPLACE FUNCTION JobRuns(f datetime, t datetime)
RETURNS DOUBLE
LANGUAGE JAVASCRIPT
AS
$$
// Based on the Calculation of Business Hours in JavaScript
// https://www.c-sharpcorner.com/UploadFile/36985e/calculating-business-hours-in-javascript/
function workingMinutesBetweenDates(startDate, endDate) {
// Store minutes worked
var minutesWorked = 0;
// Validate input
if (endDate < startDate) {
return 0;
}
// Loop from your Start to End dates (by hour)
var current = startDate;
// Define work range
var workHoursStart = 1;
var workHoursEnd = 10;
var includeWeekends = false;
// Loop while currentDate is less than end Date (by minutes)
while (current <= endDate) {
// Is the current time within a work day (and if it occurs on a weekend or not)
if (current.getHours() >= workHoursStart && current.getHours() <= workHoursEnd && (includeWeekends ? current.getDay() !== 0 && current.getDay() !== 6 : true)) {
minutesWorked++;
}
// Increment current time
current.setTime(current.getTime() + 1000 * 60);
}
// Return the number of minutes
return minutesWorked;
}
return workingMinutesBetweenDates(F,T);
$$
;
But the result I am getting are in some cases rather off from what I had expected.
The JS logic is grabbed from here; https://www.c-sharpcorner.com/UploadFile/36985e/calculating-business-hours-in-javascript/ and when I look at the code, I cannot see any flaws, which might cause these discrepancies.
I am using these test data
CREATE OR REPLACE TABLE "SLA_Test" (
"DocumentID" VARCHAR(16777216),
"From" TIMESTAMP_NTZ(9),
"To" TIMESTAMP_NTZ(9),
"ExpectedTime" INT
);
INSERT INTO "SLA_Test"
VALUES
('ACD7EFC1-8D17-46E3-84DB-C08067466866','2021-03-03 07:12:34.567','2021-03-03 08:12:34.567',60),
('C41FB599-D1EC-4461-BBAF-1AFF67D2F3C2','2021-03-03 09:55:00.000','2021-03-04 01:05:00.000',10),
('B741C663-732B-4FD3-839D-E70330C58990','2021-03-03 09:55:00.000','2021-03-04 00:05:00.000',5),
('C5893C51-F5CE-40E4-85F7-775515BC3E3D','2021-03-03 19:55:00.000','2021-03-04 01:05:00.000',5),
('BAF4ED57-8184-4CDF-8875-DFDA6EAC2033','2021-03-03 09:55:00.000','2021-03-05 01:05:00.000',550),
('F325059E-E78F-4DCE-B675-CC1C59669B3C','2021-03-05 09:55:00.000','2021-03-08 01:05:00.000',10),
('F325059E-E78F-4DCE-B675-CC1C59669B3C','2021-03-05 09:55:00.000','2021-03-07 01:05:00.000',5);
SELECT "DocumentID","From","To",
DATEDIFF(second, "From", "To") AS "TotalElapsedTimeSecond",
DATEDIFF(second, "From", "To")/60 AS "TotalElapsedTimeMinut",
"ExpectedTime",
JobRuns("From","To") AS "ElapsedTimeMinut"
FROM "SLA_Test";
Any ideas why the UDF does not return the expected time?

If you create a working hours table, you can run the following query:
select
t.id
, sum(datediff(‘second’,
-- calculate the max of the two start time
(case when t.start <=
w.working_day_start_timestamp
then w.working_day_start_timestamp
else t.start
end),
-- calculate the min of the two end times
(case when t.end >=
w.working_day_end_timestamp
then w.working_day_end_timestamp
else t.end
end)
)) / 3600 -- convert to hourly
as working_hour_diff
from
working_days_times w,
cross join time_intervals t
where -- select all intersecting intervals
(
t.start <= w.working_day_end_timestamp
and
t.end >= w.working_day_start_timestamp
)
and -- select only working days
w.is_working_day
group by
t.id
This article also goes into more detail about implementing this as a Javascript UDF: https://medium.com/dandy-engineering-blog/how-to-calculate-the-number-of-working-hours-between-two-timestamps-in-sql-b5696de66e51

this can all be done in SQL,
with SLA_Test(DocumentID, FromTime, ToTime, ExpectedTime) AS (
SELECT column1, column2::timestamp_ntz, column3::timestamp_ntz, column4
FROM
VALUES
('ACD7EFC1-8D17-46E3-84DB-C08067466866','2021-03-03 07:12:34.567','2021-03-03 08:12:34.567',60),
('C41FB599-D1EC-4461-BBAF-1AFF67D2F3C2','2021-03-03 09:55:00.000','2021-03-04 01:05:00.000',10),
('B741C663-732B-4FD3-839D-E70330C58990','2021-03-03 09:55:00.000','2021-03-04 00:05:00.000',5),
('C5893C51-F5CE-40E4-85F7-775515BC3E3D','2021-03-03 19:55:00.000','2021-03-04 01:05:00.000',5),
('BAF4ED57-8184-4CDF-8875-DFDA6EAC2033','2021-03-03 09:55:00.000','2021-03-05 01:05:00.000',550),
('F325059E-E78F-4DCE-B675-CC1C59669B3C','2021-03-05 09:55:00.000','2021-03-08 01:05:00.000',10),
('F325059E-E78F-4DCE-B675-CC1C59669B3C','2021-03-05 09:55:00.000','2021-03-07 01:05:00.000',5)
), days as (
SELECT row_number() over(order by seq8())-1 as num
FROM table(GENERATOR(rowcount=>30))
), enriched as (
SELECT *,
datediff('day', s.fromtime, s.totime) as tot_days
from SLA_Test AS s
), day_sliced AS (
select s.*
,d.*
,date_trunc('day',fromtime) f_s
,dateadd('day', d.num, f_s) as clip_day
,dateadd('hour', 1, clip_day) as clip_start
,dateadd('hour', 10, clip_day) as clip_end
,dayofweekiso(clip_day) as dowi
,dowi >=1 AND dowi <= 5 as work_day
,least(greatest(s.fromtime, clip_start),clip_end) as slice_start
,greatest(least(s.totime, clip_end), clip_start) as slice_end
,DATEDIFF('second', slice_start, slice_end) as slice_sec
,DATEDIFF('minute', slice_start, slice_end) as slice_min
from enriched AS s
join days AS d on d.num <= s.tot_days
qualify work_day = true
)
SELECT
DocumentID
,FromTime
,ToTime
,ExpectedTime
,round(sum(slice_sec)/60,0) as elasped_time_minutes
FROM day_sliced
GROUP BY 1,2,3,4
ORDER BY 1,2;
which gives the results as noted in expected:
DOCUMENTID FROMTIME TOTIME EXPECTEDTIME ELASPED_TIME_MINUTES
ACD7EFC1-8D17-46E3-84DB-C08067466866 2021-03-03 07:12:34.567 2021-03-03 08:12:34.567 60 60
B741C663-732B-4FD3-839D-E70330C58990 2021-03-03 09:55:00.000 2021-03-04 00:05:00.000 5 5
BAF4ED57-8184-4CDF-8875-DFDA6EAC2033 2021-03-03 09:55:00.000 2021-03-05 01:05:00.000 550 550
C41FB599-D1EC-4461-BBAF-1AFF67D2F3C2 2021-03-03 09:55:00.000 2021-03-04 01:05:00.000 10 10
C5893C51-F5CE-40E4-85F7-775515BC3E3D 2021-03-03 19:55:00.000 2021-03-04 01:05:00.000 5 5
F325059E-E78F-4DCE-B675-CC1C59669B3C 2021-03-05 09:55:00.000 2021-03-07 01:05:00.000 5 5
F325059E-E78F-4DCE-B675-CC1C59669B3C 2021-03-05 09:55:00.000 2021-03-08 01:05:00.000 10 10

Did you test this outside of Snowflake? I just created the following file and running node /tmp/dates.js produces this output which matches up with Snowflake
// Col1: function return, Col2: Expected
61 60
71 10
65 5
6 5
671 550
1271 10
671 5
function workingMinutesBetweenDates(startDate, endDate) {
// Store minutes worked
var minutesWorked = 0;
// Validate input
if (endDate < startDate) {
return 0;
}
// Loop from your Start to End dates (by hour)
var current = startDate;
// Define work range
var workHoursStart = 1;
var workHoursEnd = 10;
var includeWeekends = false;
// Loop while currentDate is less than end Date (by minutes)
while (current <= endDate) {
// Is the current time within a work day (and if it occurs on a weekend or not)
if (current.getHours() >= workHoursStart && current.getHours() <= workHoursEnd && (includeWeekends ? current.getDay() !== 0 && current.getDay() !== 6 : true)) {
minutesWorked++;
}
// Increment current time
current.setTime(current.getTime() + 1000 * 60);
}
// Return the number of minutes
return minutesWorked;
}
console.log(workingMinutesBetweenDates((new Date(Date.parse('2021-03-03 07:12:34.567'))), (new Date(Date.parse('2021-03-03 08:12:34.567')))), 60);
console.log(workingMinutesBetweenDates((new Date(Date.parse('2021-03-03 09:55:00.000'))), (new Date(Date.parse('2021-03-04 01:05:00.000')))), 10);
console.log(workingMinutesBetweenDates((new Date(Date.parse('2021-03-03 09:55:00.000'))), (new Date(Date.parse('2021-03-04 00:05:00.000')))), 5);
console.log(workingMinutesBetweenDates((new Date(Date.parse('2021-03-03 19:55:00.000'))), (new Date(Date.parse('2021-03-04 01:05:00.000')))), 5);
console.log(workingMinutesBetweenDates((new Date(Date.parse('2021-03-03 09:55:00.000'))), (new Date(Date.parse('2021-03-05 01:05:00.000')))), 550);
console.log(workingMinutesBetweenDates((new Date(Date.parse('2021-03-05 09:55:00.000'))), (new Date(Date.parse('2021-03-08 01:05:00.000')))), 10);
console.log(workingMinutesBetweenDates((new Date(Date.parse('2021-03-05 09:55:00.000'))), (new Date(Date.parse('2021-03-07 01:05:00.000')))), 5);

I've found at least 2 issues with the code, I think:
It will always over-count by at least 1. On the first loop round the WHILE statement the minutesWorked is incremented but at that point no time has actually been worked - the first minute hasn't been worked until StartDate + 1 minute
Your working day ends at 10 but your logic includes any time where the hour portion <= 10, so it is incrementing minutesWorked up to 10:59:59. I think the logic should be just less than not less than or equals: ... && current.getHours() < workHoursEnd

Related

Class change based on local time : local sunrise/sunset calculation?

I'm new to JavaScript and put together this code to change the background on my page based on visitors local time.
I can't really test it yet, so first question would be, is this actually working? :D
And the main question: is there a way to set the sunrise and sunset times (set to 6 and 20 now) to follow the actual local times of the visitor?
Any help very much appreciated!!
<script type="text/javascript">
setInterval(change_background, 1000 * 60 * 60);
function change_background() {
var d = new Date();
var n = d.getHours();
console.log(n);
if (n == 6 || n == 20) {
document.getElementById("intro").className = "inter";
} else {
if (n == 23 || n < 7) {
document.getElementById("intro").className = "night";
} else {
document.getElementById("intro").className = "day";
}
console.log("test");
}
console.log("test");
}
change_background();
</script>
I suggest this shorter and reordered code – main difference is we set the class each time the function is called, your code would not have done anything after hour 7, execpt in exactly the hours 6, 20 and 23.
function change_background() {
var nowHour = new Date().getHours();
document.querySelector("#intro").className = ( nowHour >= 6 && nowHour <= 20 ) ? "inter" : ( (nowHour >= 23 || nowHour <= 5) ? "night" : "day" );
}
setInterval(change_background, 1000 * 60 * 60); change_background(); // trigger interval and first check
To use the local timezone you can use getTimezoneOffset – but things can become nightmarish with timezones, if you want to be as near to accurate as you can get. Just think about a science station in the polar regions, sunrise and sunset lose their meaning in such extreme scenarios. But with the offset you can use that to link it to a known good sunrise time .. but then you need to edit your file every day (or at least couple of days) to match the progression throughout the year.

jquery if hours and minutes <> [duplicate]

This question already has an answer here:
The correct way to compare time in javascript? [duplicate]
(1 answer)
Closed 1 year ago.
Please, How can I set IF when time is < 21:30 ??
var dt = new Date();
if ((dt.getHours() <= 21) && (dt.getMinutes() <= 30)) { alert("check"); }
This not working when time is example 20:45
You need to check two different things.
if hours <= 20, than everything is true.
if hours == 21, than check minutes.
var dt = new Date('2021/03/18 20:45:00');
if (dt.getHours() <= 20 || (dt.getHours() == 21 && dt.getMinutes() <= 30)) {
alert("check");
}
You could always take the time and convert it to minutes in the day - 1440 (60*24) so 21:30 becomes 21 * 60 + 30 = 1,290.
We can calculate the current minute value by taking the current date time Date.now() (milliseconds since 1/1/1970) mod 86,400,000 (milliseconds in a day) * further divide this by 60,000 (milliseconds in a minute).
(Date.now() % 86_400_000) / (60_000)
It is then trivial to compare these two values
const nineFortyFivePM = 21 * 60 + 30;
const currentMinutes = (Date.now() % 86_400_000) / (60_000);
console.log(`21:45 = ${nineFortyFivePM}`);
console.log(`currentMinutes = ${currentMinutes}`);
if (currentMinutes < nineFortyFivePM)
alert('check');

How to check if time is less than or equal to particular hour in java script

I am trying to validate if the given time value is less than or equal to 13
i get the values from UI like hour and minutes and am/pm.
how do i check if total hours does not exceed 13 in javascript?
after some calculation i have hour and minutes and i tried to validate as follows
if ((fromHour != 13 & fromMinute == 0) & (toHour < fromHour & toMinute == 0)) //
{
alert("Time cannot be greater than 13 hours");
}
but it is not working as i expected.
could someone help pls.
Convert the times to minutes by multiplying the hour by 60 and adding the minutes. Then subtract the times and see if it's less than 13*60. And if the times cross over midnight, so that the to time is lower than the from time, add the number of minutes in a day first.
fromTime = fromHour * 60 + fromMinute;
toTime = toHour * 60 * toMinute;
if (toTime < fromTime) {
toTime += 60*24;
}
if (toTime - fromTime > 13*60) {
alert("Time cannot be greater than 13 hours");
}

Javascript time difference via timepicker

I'm working on a web timesheet where users use timepicker to determine start & end times and I'd like to have the form automatically find the difference between the two times and place it in a 3rd input box. I understand that I need to get the values, convert them to milliseconds, then subtract the first number from the second, convert the difference back to human time and display that in the third box. But I can't seem to wrap my head around time conversion in javascript. Here's what I have so far:
function date1math(){
var date1in = document.getElementById("date-1-in").value;
var date1out = document.getElementById("date-1-out").value;
date1in = date1in.split(":");
date1out = date1out.split(":");
var date1inDate = new Date(0, 0, 0, date1in[0], date1in[1], 0);
var date1outDate = new Date(0, 0, 0, date1out[0], date1out[1], 0);
var date1math = date1outDate.getTime() - date1inDate.getTime();
var hours = Math.floor(date1math / 1000 / 60 / 60);
date1math -= hours * 1000 * 60 * 60;
var minutes = Math.floor(date1math / 1000 / 60);
return (hours < 9 ? "0" : "") + hours + ":" + (minutes < 9 ? "0" : "") + minutes;
document.getElementById("date-1-subtotal").value = date1math(date1in, date1out);
}
I want to take the timepicker result (say 9:00am) from the input date-1-in, the timepicker result (say 5:00pm) from the input date-1-out, and then place the difference as a number in date-1-subtotal.
Presumably the input is a string in the format hh:mm (e.g. 09:54) and that the two strings represent a time on the same day. You don't mention whether an am/pm suffix is included, but it's there in the text so I'll assume it might be.
If daylight saving changes can be ignored, the simplest method is to convert the string to minutes, find the difference, then convert back to hours and minutes, e.g.:
// Convert hh:mm[am/pm] to minutes
function timeStringToMins(s) {
s = s.split(':');
s[0] = /m$/i.test(s[1]) && s[0] == 12? 0 : s[0];
return s[0]*60 + parseInt(s[1]) + (/pm$/i.test(s[1])? 720 : 0);
}
// Return difference between two times in hh:mm[am/pm] format as hh:mm
function getTimeDifference(t0, t1) {
// Small helper function to padd single digits
function z(n){return (n<10?'0':'') + n;}
// Get difference in minutes
var diff = timeStringToMins(t1) - timeStringToMins(t0);
// Format difference as hh:mm and return
return z(diff/60 | 0) + ':' + z(diff % 60);
}
var t0 = '09:15am';
var t1 = '05:00pm';
console.log(getTimeDifference('09:15am', '05:00pm')); // 07:45
console.log(getTimeDifference('09:15', '17:00')); // 07:45
If daylight saving is to be incorporated, you'll need to include the date so that date objects can be created and used for the time difference. The above can use either 12 or 24 hr time format.

subtracting military time in javascript [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
check time difference in javascript
calculate time difference in javascript
this is my block of code to pull times from a form input,
start = group.find('.startTime').val().replace(':',''), // like 0100
end = group.find('.endTime').val().replace(':',''), // like 0300
timeDiff = (end - start) < 0 ? (end - start + 2400) : (end - start),
timeDiff accounts for times passing midnight, so like if I try and subtract 2300 from 0100, and get -2200, it adds the 2400 to get the correct difference of 0200, or 2 hours.
my problem arises where i try to subtract some times like 2100 - 2030 (which should give me a half hour) but because its just a raw number i get the actual difference of 70. my question is how would I correctly subtract these? If i need to convert it to a date or time object what would be the proper way of doing so? I looked into the setTime method but that didn't sound like what i needed.
Thanks in advance.
Without Date objects (which seems OK for time-only values), you should convert the time to minutes or hours - always calculate with only one unit (and especially never mix them into one decimal number representing a sexagesimal one)!
function toTime(string) {
var m = string.match(/(\d{2}):({\d2})/);
return m && 60 * parseInt(m[1], 10) + parseInt(m[2], 10),
}
var start = toTime( group.find('.startTime').val() ),
end = toTime( group.find('.endTime').val() );
var timeDiff = Math.abs(end - start);
function fromTime(minutes) {
var m = minutes % 60,
h = (minutes - m) / 60;
return ("0"+h).substr(-2)+":"+("0"+m).substr(-2);
}

Categories