Parse and manipulate date times with micro seconds - javascript

I have date time strings with the following format: YYYY-MM-DD HH:MM:SS.SSSSSS
For example:
const d1 = '2022-07-03 03:45:15.679570'
const d2 = '2022-07-03 03:45:15.679638'
What I'd like to achieve is the ability to subtract these date times, e.g. in the above example the result would be:
console.log(subtractDates(d1, d2)) // -0000-00-00 00-00-00.000068
console.log(subtractDates(d2, d1)) // 0000-00-00 00-00-00.000068
I was looking for different libraries but they all have resolution of 0-999 ms and that's it.

The subtraction of dates becomes ambiguous when months are subtracted. For instance, there are several possibilities on how to represent the difference between 2022-03-31 and 2022-02-28. If this is considered to be a difference of 1 months and 3 days, then what if we add one day to both dates? Is then the difference (of the same number of days) suddenly an exact month?
To avoid this ambiguity, I would suggest to express the difference in a number of days (and smaller units of time) even when that number of days exceeds a month or even a year. Just keep the greatest unit of measure to the day when dealing with durations.
Here is a pair of classes that could easily be extended to provide more functionality. The general idea is to use the numeric system of the native Date type, but to multiply it by 1000.
class MicroDuration {
constructor(us) {
this.us = Math.abs(us); // Don't work with negative durations.
}
toString() { // dddddd hh:mm:ss.ssssss
return (Math.floor(this.us / 86_400_000_000) + " "
+ new Date(Math.floor(this.us / 1000)).toJSON().slice(11, 23)
+ (this.us % 1000 + "").padStart(3, "0")
).padStart(22, "0");
}
}
class MicroDate {
constructor(iso) {
iso = iso.replace(" ", "T").replace(/\d$/, "$&Z");
this.us = Date.parse(iso) * 1000 + parseInt(iso.slice(-4));
}
diff(arg) {
if (!(arg instanceof MicroDate)) arg = new MicroDate(arg);
return new MicroDuration(this.us - arg.us);
}
toString() {
return new Date(Math.floor(this.us / 1000)).toJSON()
.replace("Z", (this.us % 1000 + "").padStart(3, "0"))
.replace("T", " ");
}
}
// Demo
const d1 = '2022-07-03 03:45:15.679570'
const d2 = '2024-08-03 15:45:15.678638'
const diff = new MicroDate(d1).diff(d2).toString();
console.log(diff);

Related

Why does Javascript's Date.getDate() .setDate() behave so unpredictably?

Hobbyist coder here, and this problem is above my pay grade. I'm trying to build a dynamic html / css calendar, where the cells are filled in based on today's date. I get today's date, and then try to add days to fill in another 13 days (looping thru html elements.innerHTML).
If I try to setDate(30 + 2) and then getDate(). The code works fine. Javascript figures out that June ends at the 30th day, and the result is 2 as desired (July 2nd)
But this only works if there's only one call, if I have a loop, or call this code multiple times, then the result is different. Is there some async stuff gumming up the works? Here's code:
If you leave the "result2" call and comment the others, works great, but multiple calls, things break and numbers get repeated. Please help!
const theDate = new Date();
const todaysDate = 30;
theDate.setDate(todaysDate + 1);
let result1 = theDate.getDate();
theDate.setDate(todaysDate + 2);
let result2 = theDate.getDate();
theDate.setDate(todaysDate + 3);
let result3 = theDate.getDate();
theDate.setDate(todaysDate + 4);
let result4 = theDate.getDate();
console.log(result1);
console.log(result2);
console.log(result3);
console.log(result4);
June has 30 days but July has 31 days.
When you set the date to 32 for the first time, you are setting it to the 32nd of June and the dates after June 30 push it to July 2nd. (32-30=2)
When you set to 32 again, it is already July so the dates after July 31 push it to August 1st (32-31=1).
In answer to your question, the setDate() function is behaving so strangely for you because each time you are setting the date you are setting it relative to the previous setting, so incrementing each time by 31, 32, or 33 days instead of by 1, 2, or 3. See the brilliant answer by #Quentin for more information, this finding was entirely his and I just wanted to mention the root cause in my answer as well as my own fix to your problem.
An alternative solution if you just want to generate the dates:
const dayOfMonth = 30;
const date = new Date();
date.setDate(dayOfMonth);
console.log("Date:", date);
let timestamp = Date.parse(date);
for (let i = 1; i <= 14; i++) {
const newTimestamp = timestamp + i * (1000 * 60 * 60 * 24);
const newDate = new Date(newTimestamp);
console.log("New date:", newDate);
}
This method will manipulate the timestamp and generate new dates for each of the timestamps added to the number of milliseconds in a day.
You could use your date logic within the loop to populate the calendar as you mentioned.
If you use the Date() constructor on each iteration, you don't have to worry about the varying days of a particular month.
Details are commented in example
/**
* #desc - return a range of dates starting today (or a given
* date) and a given number of days (including start)
* #param {number} range - The number of days
* #param {string<date>} start - The date to start the range
* if not defined #default is today
* #return {array<date>} An array of dates
*/
function dayRange(range, start) {
// If undefined default is today
let now = start ? new Date(start) : new Date();
// Create an array of empty slots - .length === range
let rng = [...new Array(range)];
/*
.map() through array rng
If it's the first iteration add today's date...
... otherwise get tommorow's date...
and return it in local format
*/
return rng.map((_, i) => {
if (i === 0) {
return now.toLocaleDateString();
}
let day = now.getDate() + 1;
now.setDate(day);
return now.toLocaleDateString();
});
}
console.log("Pass the first parameter if the start day is today");
console.log(JSON.stringify(dayRange(14)));
console.log("Pass a properly formatted date string as the second parameter if you want to start on a date other than today");
console.log(JSON.stringify(dayRange(10, '05/12/2020')));

Subtracting 100s of minutes accurately with Javascript

If I have two values, each representing a date such as YYYYMMDDHHMM (YearMonthDayHourMinute) like:
202012141800
202012141614
What I was trying to convey in the question is that this gives me 186 minutes, but this isn't accurate, however, since the last two digits will never be larger than 59 given 60 minutes in an hour. The 100 in 186 comes from hours 18 (6pm) and 16 (4pm).
How can I subtract these in Javascript to account for the extra 40 minutes tacked on if the two timestamps are more than an hour apart?
I have this, but it's not that efficient since I'd need to know the maximum number of hours two timestamps could be apart:
var end_time = $('#the-agenda li.current time').data('end-time'),
time_now = current_display_number,
timer_duration = end_time - time_now;
if (timer_duration > 200) {
// if more than 2 hours, subtract 80 minutes
timer_duration = timer_duration - 80;
}
else if (timer_duration > 100) {
// if more than 1 hour, subtract 40 minutes
timer_duration = timer_duration - 40;
}
I feel like the answer may somehow be in this question's answer, but I am not sure how to apply that parseInt to this situation.
You wouldn't use parseInt. You would use Date.parse, except that the string has to be in a predefined format. Without using a specialized library, you'll have to parse the parts yourself and then create a new Date with the parts. Fortunately though the incoming strings seem straightforward to parse. Do something like this:
let startTimeStr = '202012141614';
let endTimeStr = '202012141800';
let asDateTime = (d) => new Date(
d.substring(0,4),
d.substring(4,6) - 1,
d.substring(6,8),
d.substring(8,10),
d.substring(10,12)
)
let startTime = asDateTime(startTimeStr);
let endTime = asDateTime(endTimeStr);
let result = (endTime - startTime) / 60000;
console.log(result);
// Different in milliseconds
const difference = (new Date('2020-12-14T18:00:00')) - (new Date('2020-12-14T16:14:00'));
const inMinutes = Math.floor(difference / 60000);
You need to convert the string formats to a date object to get accurate date info.

Javascript: convert number of days to years, months and days

I have days, months and years. I'm doing calculations between them. That means I have to divide 2 years 3 months and 10 days by 1/4. Now i have following code:
const getCurrentDate = moment().format("YYYY-MM-DD");
const timeEnd = moment(moment(DefEndDate).format("YYYY-MM-DD"));
const diff = timeEnd.diff(getCurrentDate);
const diffDuration = moment.duration(diff);
const diffCount = moment.duration(diff).asDays();
console.log(diffCount);
console.log("Years:", diffDuration.years());
console.log("Month:", diffDuration.months());
console.log("Days:", diffDuration.days());
const diffCount = moment.duration(diff).asDays(); //Get it as days
const [unserve, setUnserve] = useState(''); //set value to variable
const res = unserve.split('/'); //split 1/4 to 1.4
const x = parseFloat(res[0] + "." + res[1]); //convert it to float
var quotient = Math.floor(diffCount/x); //calculate
console.log(quotient);
//returned 832 / 1.4 = 594 days
Now I need to return the output number (days) to the year, month and day. I can't do that. How do I convert? And another question is, can this way be the optimal solution?
I can't decide whether what you really want to do is divide a date range in to a fixed number of periods with equal days, or to start with a date, add a period in years, months and days to get an end date, then divide that into equal periods.
The following assumes the latter.
I have to divide 2 years 3 months and 10 days by 1/4
The number of days covered by that period varies depending the dates it is to and from, so you have to start with the start and end dates of the range.
In your code:
const getCurrentDate = moment().format("YYYY-MM-DD");
Sets getCurrentDate to a string like 2020-02-11.
const timeEnd = moment(moment(DefEndDate).format("YYYY-MM-DD"));
Creates a moment object from the string value of getCurrentDate and sets timeEnd to another string.
const diff = timeEnd.diff(getCurrentDate);
This attempts to call the diff method of timeEnd, which is a string. Strings don't have a diff method so the expression returns undefined, attempting to call it throws an error something like TypeError: '2020-02-11'.diff is not a function.
The rest of your code seems to be based on a algorithm
If you have a predetermined period in years, months days, etc. you can start with a start date, add the period, then get the number of days difference. Divide that difference by the number of periods you want, then add that sequentially to get the various end dates.
The following example uses moment.js since that's what you appear to be using, however a version without a library is about the same difficulty. It returns an array of dates, starting with the start date so there is one more date than periods.
function getDates(
start = new Date(),
years = 0,
months = 0,
days = 0,
parts = 1) {
// Get start and end as moment objects
let m = moment(start).startOf('day');
let end = moment(m);
end.add({years:years, months:months, days:days});
// Get days difference and number of days to add for each period
let daysDiff = end.diff(m, 'days');
let f = daysDiff / parts;
let dayArray = [m.format('YYYY-MM-DD')];
let i = 0;
while ((f * ++i) <= daysDiff) {
let d = moment(m).add(f * i, 'days')
dayArray.push(d.format('YYYY-MM-DD'));
}
return dayArray;
}
// Get dates for 4 even periods over 2 years, 3 months and
// 10 days from today
console.log(getDates(new Date(), 2, 3, 10, 4));
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js"></script>
As the sub–period length is nearly always not an even number of days (in the above case it's about 207.5), I've allowed formatting to effectively truncate the decimal part of a day. You might want to use some other rounding algorithm that more evenly distributes the remainder.
If, on the other hand, you have start and end dates and want an equal number of periods, the following is much simpler (and doesn't use a library):
// Helpers - use UTC do avoid DST issues
function toUTCDate(s) {
let b = s.split(/\D/);
return new Date(Date.UTC(b[0], --b[1], b[2]));
}
function formatUTC(date) {
return date.toISOString().substr(0, 10);
}
/* #param {string} start - date string in format 'YYYY-MM-DD'
** #param {string} end - date string in format 'YYYY-MM-DD'
** #param (numbe} n - number of required periods
** #returns {Array} array of date strings in format 'YYYY-MM-DD'
*/
function periods(start, end, n) {
let s = toUTCDate(start);
let e = toUTCDate(end);
let diff = e - s;
let f = diff / n;
let result = [formatUTC(s)];
// Allow for rounding of decimal f in comparison
while (e - s > n) {
s.setTime(s.getTime() + f);
result.push(formatUTC(s))
}
return result;
}
console.log(periods('2020-02-09','2022-05-19', 4));
The two methods produce slightly different results, you'll need to work out if that matters or not.
Long time ago I used such code to convert from seconds to hours, minutes and seconds:
var hours = Math.floor(sec / 3600);
var minutes = Math.floor((sec - hours * 3600) / 60);
var seconds = Math.round(sec - hours * 3600 - minutes * 60);
Math with days, months and years should be somewhat similar but my rusty brain doesn't want to think about it
UPD
This function seems to do what you need
const daysFmt = days => {
const years = Math.floor(days / 365);
const months = Math.floor((days - years * 365) / 30);
const d = Math.round(days - years * 365 - months * 30);
let res = [];
if (years > 0) {
res.push(years + ' y');
}
if (months > 0) {
res.push(months + ' m');
}
if (d > 0) {
res.push(d + ' d');
}
return res.join(', ');
}
But this solution has one nuance: it assumes that month = 30 days. You might want to add if statement to return '1 m' if input is 31 or just return number of days if it is less than 32. Test results:
daysFmt(31);
"1 m, 1 d"
daysFmt(180);
"6 m"
daysFmt(185);
"6 m, 5 d"
daysFmt(356);
"11 m, 26 d"
daysFmt(365);
"1 y"
daysFmt(420);
"1 y, 1 m, 25 d"
daysFmt(3650);
"10 y"
daysFmt(3685);
"10 y, 1 m, 5 d"

GoogleCalendar Datekey

Google is using an unique DateKeys for each days in the GoogleCalendar HTML e.g.
<div
data-datekey="129"
role="gridcell"
tabindex="-1"
jsname="RjPD4e"
aria-labelledby="tsc-0"
data-column-index="0"
data-principal-ids="amFuLm5pY2tsYXNAbmFtaWNzLmNvbQ"
class="YvjgZe Qbfsob">
Is there any formular to calculate the date for a given datekey?
It looks like the dateKey represents the days since 1.1.1970 in a format optimised for byte shifting.
One year has 2^9 (512) days.
One month has 2^5 (32) days.
To calculate the datekey for 01.01.1970 you would calculate:
0 years (since 1970) * 512
+ 1 month * 32
+ 1 day
= 33
To calculate the datekey for 01.01.2000 you would calculate:
30 years (since 1970) * 512
+ 1 month * 32
+ 1 day
= 15393
To calculate the date for a given date key you can do the opposite.
A modulo calculation could look like the following:
function getDate(dateKey) {
const yearOffset = (dateKey - 32) % 512;
const year = (dateKey - 32 - yearOffset) / 512;
const day = yearOffset % 32;
const month = (yearOffset - day) / 32;
return new Date(year + 1970, month, day);
}
Does anyone know why they came up with such a logic?
In response to #jantimon's answer: I guess they had to do a lot of calculations with dates and wanted it to be efficient.
The odd thing is that if you look at the little calendar in the sidebar, rather than using a data-datekey attribute, it uses data-date with a YYYYMMDD format.
Anyway, I rewrote your conversion function using bitwise operations, since it's easier to read this way.
function datekeyToDate(key) {
/* # BITS: [year_rel(6)][month(4)][day(5)] */
const day = key & 0b11111;
const month = (key>>5) & 0b1111;
const year_rel = (key>>9);
const year = 1970 + year_rel;
return new Date(year, month, day);
}
function dateToDatekey(date) {
const y = date.getFullYear() - 1970;
const m = date.getMonth()+1; /* getMonth() returns 0-based index */
const d = date.getDate();
return (y<<9) + (m<<5) + d;
}

Compare 2 ISO 8601 timestamps and output seconds/minutes difference

I need to write JavaScript that's going to allow me to compare two ISO timestamps and then print out the difference between them, for example: "32 seconds".
Below is a function I found on Stack Overflow, it turns an ordinary date into an ISO formatted one. So, that's the first thing out the way, getting the current time in ISO format.
The next thing I need to do is get another ISO timestamp to compare it with, well, I have that stored in an object. It can be accessed like this: marker.timestamp (as shown in the code below). Now I need to compare those two two timestamps and work out the difference between them. If it's < 60 seconds, it should output in seconds, if it's > 60 seconds, it should output 1 minute and 12 seconds ago for example.
Thanks!
function ISODateString(d){
function pad(n){return n<10 ? '0'+n : n}
return d.getUTCFullYear()+'-'
+ pad(d.getUTCMonth()+1)+'-'
+ pad(d.getUTCDate())+'T'
+ pad(d.getUTCHours())+':'
+ pad(d.getUTCMinutes())+':'
+ pad(d.getUTCSeconds())+'Z'}
var date = new Date();
var currentISODateTime = ISODateString(date);
var ISODateTimeToCompareWith = marker.timestamp;
// Now how do I compare them?
Comparing two dates is as simple as
var differenceInMs = dateNewer - dateOlder;
So, convert the timestamps back into Date instances
var d1 = new Date('2013-08-02T10:09:08Z'), // 10:09 to
d2 = new Date('2013-08-02T10:20:08Z'); // 10:20 is 11 mins
Get the difference
var diff = d2 - d1;
Format this as desired
if (diff > 60e3) console.log(
Math.floor(diff / 60e3), 'minutes ago'
);
else console.log(
Math.floor(diff / 1e3), 'seconds ago'
);
// 11 minutes ago
I would just store the Date object as part of your ISODate class. You can just do the string conversion when you need to display it, say in a toString method. That way you can just use very simple logic with the Date class to determine the difference between two ISODates:
var difference = ISODate.date - ISODateToCompare.date;
if (difference > 60000) {
// display minutes and seconds
} else {
// display seconds
}
I'd recommend getting the time in seconds from both timestamps, like this:
// currentISODateTime and ISODateTimeToCompareWith are ISO 8601 strings as defined in the original post
var firstDate = new Date(currentISODateTime),
secondDate = new Date(ISODateTimeToCompareWith),
firstDateInSeconds = firstDate.getTime() / 1000,
secondDateInSeconds = secondDate.getTime() / 1000,
difference = Math.abs(firstDateInSeconds - secondDateInSeconds);
And then working with the difference. For example:
if (difference < 60) {
alert(difference + ' seconds');
} else if (difference < 3600) {
alert(Math.floor(difference / 60) + ' minutes');
} else {
alert(Math.floor(difference / 3600) + ' hours');
}
Important: I used Math.abs to compare the dates in seconds to obtain the absolute difference between them, regardless of which is earlier.

Categories

Resources