Calculating Jday(Julian Day) in javascript - javascript

I have requirement to calculate jday in javascript , for doing client side validation , Can any one help me how to calculate JDAY in javascript or script to change given JDAY to actual date or vice versa .
To know what is JDay ,I found the following site ,
http://www.pauahtun.org/Software/jday.1.html
Am also refering the below site for calculation which is mentioned in JAVA
http://www.rgagnon.com/javadetails/java-0506.html
Thank you in advance

Julian Day
The Julian Day is the number of elapsed days since the beginning of a cycle of 7980 years.
Invented in 1583 by Joseph Scaliger, the purpose of the system is to make it easy to compute an integer (whole number) difference between one calendar date and another calendar date.
The 7980 year cycle was derived by combining several traditional time cycles (solar, lunar, and a particular Roman tax cycle) for which 7980 was a common multiple.
The starting point for the first Julian cycle began on January 1, 4713 B.C. at noon GMT, and will end on January 22, 3268 at noon GMT, exactly 7980 whole days later.
As an example, the Julian day number for January 1, 2016 was 2,457,389, which is the number of days since January 1, 4713 B.C. at that day.
How to calculate it
As we know that Unix time is the number of seconds since 00:00:00 UTC, January 1, 1970, not counting leap seconds, and also called Epoch, we can use some math to calculate the Julian Day when we already have the Unix time.
GMT and UTC share the same current time in practice, so for this, there should be no difference.
To start with, we need to know the number of days from when the Julian cycle began, until Unix timestamps began.
In other words, the number of days from January 1, 4713 B.C. at 12:00:00 GMT, until January 1, 1970 at 00:00:00 UTC.
Having this set number of days, that never change, we can just add the number of days from January 1, 1970 until today, which is what Javascript returns anyway, to get the Julian Day.
Without adding up all those years, but simply by searching the web, it tells us that the difference in days between the year 4713 B.C. and 1970 A.D. is 2440588 days, and because the Julian Cycle began at noon, not at midnight, we have to subtract exactly half a day, making it 2440587.5 days.
So what we have now is 2440587.5 days + UNIX TIME in days === Julian Day
With some simple math we can figure out that a day is 86,400 seconds long, and the Unix timestamp is in milliseconds when using Javascript, so UNIX TIME / 86400000 would get us the number of days since Thursday, 1 January 1970, until today.
Now for just the day, we wanted the whole number of days, and not the fractional, and can just round it down to the closes whole day, doing something like
Math.floor((UNIX TIME / 86400000) + 2440587.5);
Julian Date
Sometimes in programming, a "Julian Date" has come to mean the number of days since the year started, for instance June 1, 2016 would be 152 days into that year etc.
The correct use of "Julian Date" is a Julian Day with a timestamp added as a fractional part of the day.
Taking the example at the top of this answer, where January 1, 2016 was the Julian Day 2,457,389 , we can add a time to that.
The Julian Day starts at noon, with no fractional time added, and so at midnight it would be 2457389.5 and at 18:00, or six hours after noon, it would be 2457389.25, adding "half a day", "quarter of a day" etc.
Calculating it, again
This means 0.1 Julian Date is the same as 24 hours divided by 10, or 24 / 10 === 2.4 hours, or in other words, Julian Day timestamps are fractional with decimals (one tenth of a day etc).
Lets look at some Javascript functions, firstly the Date constructor.
Javascript only has access to the local time on the computer it runs on, so when we do new Date() it does not neccessarely create an UTC date, even if UNIX time is in UTC, new Date gives you the number of seconds from epoch until whatever local time your computer has, and does not take your timezone into consideration.
Javascript does however have Date.UTC, which would return the date in UTC format, lets check the difference, and this will of course differ according to the timezone you've set the local system to.
var regular_date = new Date(2016, 1, 1, 0, 0, 0);
var UTC_date = Date.UTC(2016, 1, 1, 0, 0, 0);
var difference = UTC_date - regular_date;
document.body.innerHTML = 'The difference between your local time and UTC is ' +(difference/1000)+ ' seconds';
Remember the part at the begin of this chapter, about 0.1 Julian Date being the same as 24 hours divided by 10, or 24 / 10 === 2.4 hours, well, 2.4 hours is 144 minutes, and now lets look quickly at Javascripts getTimezoneOffset() method, the docs say
The getTimezoneOffset() method returns the time-zone offset from UTC,
in minutes, for the current locale.
So, it returns the offset for the systems timezone in minutes, that's interesting as most javascript methods that deal with dates returns milliseconds.
We know that a 1/10 of a day is 144 minutes, so 10/10, or a whole day, would be 1440 minutes, so we could use some math to counteract the local systems timezone, given in minutes, and divide it by the number of minutes in a day, to get the correct fractional value
So now we have
2440587.5 days + UNIX TIME in days === Julian Day
and we know Javascripts Date constructor doesn't really use UTC for the current date, but the system time, so we have to have
TIMEZONEOFFSET / 1440
joining them together we would get
(JAVASCRIPT TIME / 86400000) - (TIMEZONEOFFSET / 1440) + 2440587.5
// ^^ days since epoch ^^ ^^ subtract offset ^^ ^^days from 4713 B.C. to 1970 A.D.
Translating that to javascript would be
var date = new Date(); // a new date
var time = date.getTime(); // the timestamp, not neccessarely using UTC as current time
var julian_day = (time / 86400000) - (date.getTimezoneOffset()/1440) + 2440587.5);
Now this is what we should use to get the Julian Day as well, taking measures to remove the timezone offset, and of course without the fractional time part of the Julian Date.
We would do this by simpy rounding it down to the closest whole integer
var julian_date = Math.floor((time / 86400000) - (date.getTimezoneOffset()/1440) + 2440587.5));
And it's time for my original answer to this question, before I made this extremely long edit to explain why this is the correct approach, after complaints in the comment field.
Date.prototype.getJulian = function() {
return Math.floor((this / 86400000) - (this.getTimezoneOffset() / 1440) + 2440587.5);
}
var today = new Date(); //set any date
var julian = today.getJulian(); //get Julian counterpart
console.log(julian)
.as-console-wrapper {top:0}
And the same with the fracional part
Date.prototype.getJulian = function() {
return (this / 86400000) - (this.getTimezoneOffset() / 1440) + 2440587.5;
}
var today = new Date(); //set any date
var julian = today.getJulian(); //get Julian counterpart
console.log(julian)
.as-console-wrapper { top: 0 }
And to finish of, an example showing why
new Date().getTime()/86400000 + 2440587.5
doesn't work, at least not if your system time is set to a timezone with an offset, i.e. anything other than GMT
// the correct approach
Date.prototype.getJulian = function() {
return (this / 86400000) - (this.getTimezoneOffset() / 1440) + 2440587.5;
}
// the simple approach, that does not take the timezone into consideration
Date.prototype.notReallyJulian = function() {
return this.getTime()/86400000 + 2440587.5;
}
// --------------
// remember how 18:00 should return a fractional 0.25 etc
var date = new Date(2016, 0, 1, 18, 0, 0, 0);
// ^ ^ ^ ^ ^ ^ ^
// year month date hour min sec milli
var julian = date.getJulian(); //get Julian date
var maybe = date.notReallyJulian(); // not so much
console.log(julian); // always returns 2457389.25
console.log(maybe); // returns different fractions, depending on timezone offset
.as-console-wrapper { top: 0 }

new Date().getTime()/86400000 + 2440587.5
will get the unix time stamp, convert it to days and add the JD of 1970-01-01, which is the epoch of the unix time stamp.
This is what astronomers call julian date. It is well defined. Since neither Unix time stamp nor JD take leap seconds into account that does not reduce the accuracy. Note that JD need not be in timezone UTC (but usually is). This answer gives you the JD in timezone UTC.

According to wikipedia:
a = (14 - month) / 12
y = year + 4800 - a
m = month + 12a - 3
JDN = day + (153m + 2) / 5 + 365y + y/4 - y/100 + y/400 - 32045
If you're having a more specific problem with the implementation, provide those details in the question so we can help further.
NOTE : This is not correct because the "floor brackets" on Wiki were forgotten here.
The correct formulas are:
a = Int((14 - Month) / 12)
y = Year + 4800 - a
m = Month + 12 * a - 3
JDN = Day + Int((153 * m + 2) / 5) + 365 * y + Int(y / 4) - Int(y / 100) + Int(y / 400) - 32045

JD =>
const millisecondsSince1970Now = new Date+0
const julianDayNow = 2440587.5+new Date/864e5
const dateNow = new Date((julianDayNow-2440587.5)*864e5)
There seems to be confusion about what a Julian-Day is, and how to calculate one.
Javascript time is measured as GMT/UTC milliseconds UInt64 from Jan 1, 1970 at midnight.
The Month, Day, Year aspects of the JavaScript Date function are all implemented using Gregorian Calendar rules. But Julian "Days" are unaffected by that; however mapping a "day-count" to a Julian Month, Day, Year would be.
Calculating Julian-Day conversions are therefore a relative day count from that point in time (Jan 1, 1970 GMT/UTC Gregorian).
The Julian-Day for Jan 1, 1970 is 2440587.5 (0.5 because JulianDays started at NOON).
The 864e5 constant is JavaScript notation for 86,400,000 (milliseconds/day).
The only complexities are in calculating Julian dates (days) prior to adoption of the 1582 Gregorian calendar whose changes mandated from Pope Gregory were to correct for Leap Year drift inaccuracies affecting Easter. It took until around 1752 to be fully adopted throughout most countries in the world which were using the Julian Calendar system or a derivative (Russia and China took until the 20th century).
And the more egregious errors in the first 60 years of Julian date implementation from Julius Caesar's 46BC "reform" mandate where priests made mistakes and people misunderstood as they folded a 14/15 month calendar. (hence errors in many religious dates and times of that period).
🎪 None of which applies in JavaScript computation of Julian Day values.
See also: (from AfEE EdgeS/EdgeShell scripting core notes)
Astronomy Answers - Julian Day Number (multi-calendrical conversions)
Julian calendar
Atomic Clocks
JavaScript operator precedence
Microsoft FILETIME relative to 1/1/1601 epoch units: 10^-7 (100ns)
UUID/GUIDs timestamp formats - useful for time-date conversion also
"Leap Second" details
There is a separate subtlety "leap-second" which applies to astronomical calculations and the use of atomic clocks that has to do with earth orbital path and rotational drift.
i.e., 86,400.000 seconds per day needs "adjustment" to keep calendars (TimeZones, GPS satellites) in sync as it is currently 86,400.002.

It seems that the final code given in the accepted answer is wrong. Check the "official" online calculator at US Naval Observarory website:
http://aa.usno.navy.mil/data/docs/JulianDate.php
If someone knows the correct answer to a answer time and calendar, it's USNO.

Additionally, there is an npm package for this:
julian
Convert between Date object and Julian dates used in astronomy and history
var julian = require('julian');
var now = new Date(); // Let's say it's Thu, 21 Nov 2013 10:47:02 GMT
var jd = '';
console.log(jd = julian(now)); // -> '2456617.949335'
console.log(julian.toDate(jd)); // -> Timestamp above in local TZ
https://www.npmjs.com/package/julian

Whatever you do, DON'T USE getTimezoneOffset() on dates before a change of policy in the current Locale, it's completely broken in the past (it doesn't apply iana database rules).
For example, if I enter (UTC date 1st of october 1995 at 00:00:00):
var d=new Date(Date.UTC(1995, 9, 1, 0, 0, 0)); console.log(d.toLocaleString()); console.log(d.getTimezoneOffset());
in the javascript console in Chrome, it prints (I'm in France):
01/10/1995 at 01:00:00 <= this is winter time, +1:00 from UTC
-120 <= BUT this is summer time offset (should be -60 for winter)
Between 1973 and 1995 (included), DST (-120) terminated last Sunday of September, hence for 1st of October 1995, getTimezoneOffset() should return -60, not -120. Note that the formatted date is right (01:00:00 is the expected -60).
Same result in Firefox, but in IE and Edge, it's worse, even the formatted date is wrong (01‎/‎10‎/‎1995‎ ‎02‎:‎00‎:‎00, matching the bad -120 result of getTimezoneOffset()). Whatever the browser (of these 4), getTimezoneOffset() uses the current rules rather than those of the considered date.
Variation on the same problem when DST didn't applied in France (1946-1975), Chrome console:
d=new Date(Date.UTC(1970, 6, 1, 0, 0, 0)); console.log(d.toLocaleString()); console.log(d.getTimezoneOffset());
displayed:
‎01‎/‎07‎/‎1970‎ ‎01:‎00‎:‎00 <= ok, no DST in june 1970, +1:00
-120 <= same problem, should be -60 here too
And also, same thing in Firefox, worse in IE/Edge (01‎/‎07‎/‎1970‎ ‎02:‎00‎:‎00).

I did this for equinox and solistice. You can use the function for any Julian date.
It returns the Julian date in the calender date format: day/month.
Include year and you can format it anyway you want.
It's all there, year, month, day.
Since Equinox and Solistice are time stamps rather than dates, my dates in the code comes back as decimals, hence "day = k.toFixed(0);". For any other Julian date it should be day = k;
// For the HTML-page
<script src="../js/meuusjs.1.0.3.min.js"></script>
<script src="../js/Astro.Solistice.js"></script>
// Javascript, Julian Date to Calender Date
function jdat (jdag) {
var jd, year, month, day, l, n, i, j, k;
jd = jdag;
l = jd + 68569;
n = Math.floor(Math.floor(4 * l) / 146097);
l = l - Math.floor((146097 * n + 3) / 4);
i = Math.floor(4000 * (l + 1) / 1461001);
l = l - Math.floor(1461 * i / 4) + 31;
j = Math.floor(80 * l / 2447);
k = l - Math.floor(2447 * j / 80);
l = Math.floor(j / 11);
j = j + 2 - 12 * l;
i = 100 * (n - 49) + i + l;
year = i;
month = j;
day = k.toFixed(0); // Integer
dat = day.toString() + "/" + month.toString(); // Format anyway you want.
return dat;
}
// Below is only for Equinox and Solistice. Just skip if not relevant.
// Vernal Equinox
var jv = A.Solistice.march(year); // (year) predefined, today.getFullYear()
var vdag = jdat(jv);
// Summer Solistice
var js = A.Solistice.june(year);
var ssol = jdat(js);
//Autumnal Equinox
var jh = A.Solistice.september(year);
var hdag = jdat(jh);
// Winter Solistice
var jw = A.Solistice.december(year);
var vsol = jdat(jw);

Related

Using moment.js timezone to get day extremes in another timezone (but the answer in UTC)

I want to work out the day extremes (midnight to midnight) for a particular day, where that day is a whole number of days relative to now (i.e. relative to Date.now())... but for a different timezone to UTC. But I want the answer (midnight times) back in UTC time (in milliseconds, similar to what you get with Date.now()), so that I can feed those to an API that expects UTC times, to get the exact right time range of data. I have tried to use moment.js timezone but am getting horribly stuck... I found this answer but it doesn't work in a different timezone.
I suggest you consider using Luxon, which is essentially the replacement for moment.js.
The algorithm is create a date for now in the required representative location, subtract the required number of days, then set to the start of day, then get the time value, e.g.
let DateTime = luxon.DateTime;
// Create date for 00:00:00 3 days ago in Denver
let d = DateTime.local() // Create a date
.setZone('America/Denver') // Set to Denver
.minus({days:3}) // Subtract 3 days
.startOf('day'); // Set to start of day
// Create date for end of day
let e = d.endOf('day');
// Denver 3 days ago at start and end of day
console.log('Start of day: ' + d.toISO() + '\nTime value : ' + d.ts);
console.log('End of day: ' + e.toISO() + '\ntime value : ' + e.ts);
<script src="https://cdn.jsdelivr.net/npm/luxon#1.24.1/build/global/luxon.min.js"></script>
Note that 3 days ago in Denver might be a different date to 3 days ago in the host system because of the offset differences. E.g in Sydney, Australia at 9 am on 20 July it's 5 pm on 19 July in Denver, so subtracting 3 days in Sydney is 17 July but 16 July in Denver.
Moment will allow you to do this fairly easily:
function getUTCMidnightMillis(timezone, days_ago) {
return moment() // Current time.
.tz(timezone) // Convert to desired timezone.
.subtract(days_ago, 'days') // Go back x days.
.startOf('day') // Get start of day, aka Midnight!
.utc() // Convert value into UTC.
.valueOf() // Return milliseconds since unix epoch.
}
console.log("Midnight in Denver:", getUTCMidnightMillis("America/Denver", 0))
console.log("Midnight in New York:", getUTCMidnightMillis("America/New_York", 0))
Which produces the output:
Midnight in Denver: 1594274400000
Midnight in New York: 1594267200000
And indeed, you can verify that is correct:
new Date(1594274400000).toISOString()
// Produces: "2020-07-09T06:00:00.000Z"
For the 9th of July, 2020, we observe daylight savings in Denver, making our UTC offset -6 hours. So the above timestamp (6:00am UTC) is the same moment at which it is midnight in Denver.

How to convert a JS Date (programmatically) into a Google Sheet Serial Number and vice-versa?

So, I have a JS date
> new Date()
Mon Aug 05 2019 06:55:46 GMT-0700 (Pacific Daylight Time
In Google Sheets API, the default dateTime render option is DateTimeRenderOption.SERIAL_NUMBER, which as per the documentation says
Instructs date, time, datetime, and duration fields to be output as doubles in "serial number" format, as popularized by Lotus 1-2-3. The whole number portion of the value (left of the decimal) counts the days since December 30th 1899. The fractional portion (right of the decimal) counts the time as a fraction of the day. For example, January 1st 1900 at noon would be 2.5, 2 because it's 2 days after December 30st 1899, and .5 because noon is half a day. February 1st 1900 at 3pm would be 33.625. This correctly treats the year 1900 as not a leap year.
I want to know how to convert the JS Date to SERIAL_NUMBER using API/library and back from SERIAL_NUMBER to JS Date object?
This will get you the serial number. You may or may not want to trim it
function createSerialNum() {
var oneDay = 24 * 60 * 60 * 1000;
var firstDate = new Date(1899, 11, 30);
var secondDate = new Date();
console.log(secondDate);
var secondDateMidnight = new Date(secondDate.getFullYear(), secondDate.getMonth(), secondDate.getDate());
var diff = secondDate.getTime() - secondDateMidnight.getTime();
var left = Math.round(Math.abs((firstDate.getTime() - secondDate.getTime()) / (oneDay))) - 1;
var right = diff / oneDay;
var result = left + right;
console.log(result);
return result;
}
This will turn back it into a date
function createDateFromSerial(serialNum){
serialNum = String(serialNum).split(".");
var ogDate;
var oneDay = 24 * 60 * 60 * 1000;
var firstDate = new Date(1899, 11, 30);
var days = serialNum[0];
var ms = serialNum[1] * oneDay;
ms = String(ms).substring(0, 8);
firstDate.setDate(days);
ogDate = new Date(firstDate.getFullYear(), firstDate.getMonth(), firstDate.getDate(), 0, 0, 0, ms);
console.log(ogDate);
return ogDate;
}
Before we get to conversions first some notes on the serial number format.
Serial Format
As described in the documentation, the serial number has two parts. The integer before the decimal representing the days from December 30th 1899, and the fraction representing the time of day, without a timezone. That is, 0.5 always represents the time 12:00 (Noon), regardless of the user machine or sheet's set timezone, daylight savings, or anything else.
Time zone:
The Serial number is without time zone. This means that a cell value of 0 will always be rendered as "12/30/1899 0:00:00", regardless of the sheet's timezone.
The sheet's timezone only affects functions such as NOW() or TODAY(), and changing the timezone will change their numeric output (and not change the numeric output of TIME() functions).
For example:
Set a cell to the formula =NOW(), then format the cell to render as a number.
Change the sheet's timezone to a different timezone +6 hours from what it previously was.
Make a change to the sheet so the NOW() value recalculates.
Notice that the numeric value will change by about +0.25 to represent 6 hours difference.
Also note that no other date-time cells change their display from the time zone change.
When getting data via spreadsheets.values.get, the default valueRenderOption is
ValueRenderOption.FORMATTED_VALUE, which means that the default dateTimeRenderOption of DateTimeRenderOption.SERIAL_NUMBER is ignored.
Formatting
A DATE_TIME formatted cell will be in a locale format according to the spreadsheet's locale, not the user machine's locale, e.g. "9/12/2020 8:15:40" or "12.9.2020 8:15:40" if the spreadsheet is set to German. If all you're doing with the date is displaying it, and you don't care that there may be a locale mismatch, this is ok. Note that it's not ok to use Date.parse() on this string, as the value will be inconsistent if the spreadsheet locale is something other than English.
If you change valueRenderOption to ValueRenderOption.UNFORMATTED_VALUE, we get the SERIAL_NUMBER value of dates, which I'll give formulas to below.
Writing
To write to the sheet using a JS Date, we don't necessarily need to convert it to lotus serial format. We can write to the cell using a formula:
`=DATE(${date.getFullYear()}, ${date.getMonth() + 1}, ${date.getDate()}) + TIME(${date.getHours()}, ${date.getMinutes()}, ${date.getSeconds()})`
This will have a formula output of something like: =DATE(2020, 9, 12) + TIME(9, 15, 41)
That cell will render as 9/12/2020 9:15:41, regardless of timezone.
In that example the cell was written with the date object's local time. We can write it as the date object's UTC time via:
`=DATE(${date.getUTCFullYear()}, ${date.getUTCMonth() + 1}, ${date.getUTCDate()}) + TIME(${date.getUTCHours()}, ${date.getUTCMinutes()}, ${date.getUTCSeconds()})`
Which you use depends on your use case. Using the local time means that if the local time is 3:15, the cell renders within sheets how you'd expect as "3:15".
Using UTC time means that if the local time is 3:15, and your timezone is -600GMT, the cell will display as "9:15", possibly adding confusion.
Using local time can be a problem however if you are finding the difference between two dates. Take the case of daylight savings. If for example you are in America/Chicago timezone with daylight savings time ending 2am Nov 1, 2020, then the local time difference between 2am and 1am is 2 hours.
(new Date(2020, 10, 1, 2)) - (new Date(2020, 10, 1, 1)) // 7200000 (2 hours) (note JS month is 0-indexed)
But in Sheets, there is no daylight savings time, so subtracting those two dates will be 1 hour, regardless of the spreadsheet's set timezone.
So we have a few options:
Consider the sheet date_time values as UTC times.
Pros: Times are unambiguous. Apps can render the time stamp in the user's timezone and save as UTC.
Cons: Editing the sheet directly can be confusing, as you'd need to convert local times to UTC by hand. Would not work with NOW() / TODAY() functions.
Consider the sheet values to have no time zone.
Pros: Times work with NOW() and TODAY() methods. When editing the sheet directly, times are always entered as the user's local time.
Cons: Timezone information is lost. When calculating the difference between times, daylight savings time changes can cause calculation to be off by an hour.
Consider the sheet values to be relative to the time zone in which the sheet is set.
Pros: Times are unambiguous. Times work with NOW() and TODAY() methods.
Cons: Sheet time zones are always standard time, so editing the sheet directly has the same problems as #1. Converting between sheet's standard time and user's local time is difficult and requires external libraries.
No matter which method we choose, we'll start with the same formulae.
Finally, the Code
(Note that this is very similar to Jeremy Ring's answer, except this will handle DST changes)
/**
* Converts a Lotus serial format to a JS UTC date.
* #param serial {number}
* #return {Date}
*/
function serialToDateUTC(serial) {
const intPart = Math.floor(serial);
const fracPart = serial - intPart;
const time = Math.round(fracPart * DAY_TO_MS);
const date = new Date(Date.UTC(1899, 11, 30 + intPart) + time);
return date;
}
/**
* Converts a JS Date to Lotus serial format.
* #param date {Date}
* #return {number}
*/
function utcDateToSerial(date) {
const dateSansTime = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()));
const intPart = Math.round((dateSansTime.getTime() - Date.UTC(1899, 11, 30)) / DAY_TO_MS);
const fracPart = (date.getTime() - dateSansTime.getTime()) / DAY_TO_MS;
return intPart + fracPart;
}
To display the UTC date, use Date.toUTCString (Or Intl.DateTimeFormat with the timeZone set). Example:
console.log(serialToDateUTC(44086.344212963).toUTCString()); // 9/12/2020 8:15:40
(Option 1)
If we wish to store in sheets as UTC, and display to the user in local time, we read/write to sheets with these UTC conversions, but then display locally via Date.toLocaleString or Intl.DateTimeFormat
Note that this option it may be confusing when editing the time directly within the Google document; the times will need to be entered in the UTC timezone.
(Option 2)
If we're ignoring time zone, this means we're considering the date/time value from sheets to always be local to the user, regardless of their timezone.
For this, if we have a Date with local time 4:24, we want to store 4:24 in sheets (using a formula this is simply =TIME(4, 24, 0).
To do this, we can use the same methods posted earlier, but coerce/pretend that the local time is a UTC time. E.g.
const timestamp = new Date(); new Date(Date.UTC(timestamp.getFullHours(), timestamp.getMonth(), timestamp.getDate(), timestamp.getHours(), timestamp.getMinutes(), timestamp.getSeconds()));
This will make time differences unstable -- Consider a timesheet application where you took a start time and an end time via Date.now() or new Date(), and had a duration cell with the formula end time minus start time. Coercing this to standard time may then lose or gain an hour in the difference if we straddle the time change.
(Option 3)
Treating all times local to the Sheet's timezone.
This requires us to get two pieces of information. The cell data, and the sheet's timezone. JavaScript natively has no way to create a Date in another timezone, nor does it have a way to get a timezone offset from an arbitrary timezone. To do this we'll need an external library such as countries-and-timezones.
Get the timezone with the Google files.get api, then get the dstOffset (not the utc offset) from the countries-and-timezones lookup. Use that offset to adjust the sheet's timezone from the local timezone's Date.getTimezoneOffset(). Yep, kinda gnarly.
I've been using this for Google Sheet API. Works fine for me.
// Hour format: HH:MM
// Date format: YYYY-MM-DD
function serialDate(hour, date) {
let leftDate = date;
leftDate = new Date(leftDate);
leftDate = leftDate.getTime();
let serialNumberReference = (new Date('1899-12-30')).getTime();
leftDate = (leftDate - serialNumberReference) / (3600 * 24 * 1000);
let rightDate = date;
rightDate = (new Date(rightDate + ' ' + hour)).getTime() - (new Date(rightDate + ' 00:00').getTime());
rightDate = rightDate / (3600 * 24 * 1000);
return leftDate + rightDate;
}
(The algorithm for this is very similar to the one for converting between Celsius and Fahrenheit, which you may be vaguely familiar with.)
There are three main differences between the scale used for JavaScript dates and the one you describe for Google Sheets:
1) JS dates are millisecond-based, whereas Google Sheets dates are day-based,
2) JS dates take 1-Jan-1970 (at 12:00:00 AM UTC) to equal zero, while Google Sheets dates use 30-Dec-1900 for this purpose, and
3) The Date (and Time) constructor in Google Sheets expects its input's timezone to match UTC, whereas JavaScript's Date constructor uses the timezone of whatever location the script runs in.
To convert between the two formats -- as well adjusting for timezone handling -- we need to "stretch" the scale (using multiplication/division) to get from days to milliseconds, and we also need to "shift" the scale (using addition / subtraction) to account for the different interpretations of 'zero'.
This shows in detail how it can be done:
(Note that this script has not been tested using Google Sheets, with the exception of the one example value shown here.)
// Defines constants in milliseconds (JavaScript's unit for dates)
const MS_PER_MINUTE = 60000; // Assumes no leap year adjustment in this minute
const MS_PER_DAY = 86400000; // Assumes no leap year adjustment in this day
const MS_PER_70YEARS_2DAYS = 2209161599801; // Diff between gSheets' & unix's "zero"
// Defines functions to build JS UTC Date objects
/* (The first function makes a preliminary Date object, calculates the difference
between the user's timezone and UTC, and use this to make the final Date object) */
function newUTCDateFromComponents(YYYY, MM, DD = 1, hh = 0, mm = 0, ss = 0){
// Takes 2 to 6 arguments (the components of a Date), returns a UTC Date object
const date = new Date(YYYY, MM, DD, hh, mm, ss);
const offsetMillisecs = date.getTimezoneOffset() * MS_PER_MINUTE;
return new Date(date.getTime() - offsetMillisecs);
}
function newUTCDateFromTimestamp(timestamp){
// Takes a timestamp, returns a UTC Date object
const date = new Date(timestamp);
return date;
}
// Defines functions to convert between JavaScript timestamps and gSheets dates
function gsheetsDateToJsTimestamp(days){
const jsTimestamp = (days * MS_PER_DAY) - MS_PER_70YEARS_2DAYS;
return jsTimestamp;
}
function jsUTCTimestampToGsheetsDate(millisecs){
let days = (millisecs + MS_PER_70YEARS_2DAYS) / MS_PER_DAY;
days = Math.ceil(days * 100000) / 100000; // Rounds the result up to 5 digits
return days;
}
//
// Examples
//
// JS to gSheets
const jsUTCDateSource = newUTCDateFromComponents(2019, 0, 1, 16, 20); // 2019-JAN-01, 4:20PM
const jsUTCDateStringSource = jsUTCDateSource.toUTCString();
const jsUTCMillisecSource = jsUTCDateSource.getTime();
const gsheetsDateResult = jsUTCTimestampToGsheetsDate(jsUTCMillisecSource);
console.log(`${jsUTCDateStringSource}
from JS (milliseconds since Jan 1, 1970 began): ${jsUTCMillisecSource}
to Google Sheets (days since Dec 30, 1899 began): ${gsheetsDateResult}`);
console.log(`-----------`);
// gSheets to JS
const gsheetsDateSource = 43466.68056; // 2019-JAN-01, 4:20PM
const jsMillisecsResult = gsheetsDateToJsTimestamp(gsheetsDateSource);
const jsUTCDateResult = newUTCDateFromTimestamp(jsMillisecsResult);
const jsUTCMillisecsResult = jsUTCDateResult.getTime();
const jsUTCDateStringResult = jsUTCDateResult.toUTCString();
console.log(`from Google Sheets: ${gsheetsDateSource}
to JS: ${jsUTCMillisecsResult} // off by a fraction of a second (see below)
human-readable string: ${jsUTCDateStringResult}`);
console.log(`NOTE: The result is off by a fraction of a second due to rounding
but evaluates to the same human-readable string.`);
Date to Serial Number: (date.getTime() - new Date(1899, 11, 30).getTime()) / (24 * 60 * 60 * 1000)
Serial Number to Date: new Date(serialNumber * 24 * 60 * 60 * 1000 + new Date(1899, 11, 30).getTime())

JavaScript Date to Delphi TDateTime

I need to send via AJAX a date for a delphi server that accept the date in float format and set a TDateTime property.
Eg.
var
date: TDateTime;
begin
date := StrToFloat(Request.QueryFields.Values['date']);
end;
Delphi TDateTime start from 30/12/1989, i have tested with:
var
date: TDateTime;
begin
date := StrToFloat('0');
ShowMessage( DateTimeToStr(date) ); // show: 30/12/1899
end;
JavaScript Date start from unix epoch, i have tested with:
console.log(new Date(0)); // Thu Jan 01 1970 01:00:00 GMT+0100
a simple conversion seem subtract the difference, but doesn't works, eg.:
// javascipt
var delphiTime = (new Date("07-24-2019") - new Date("12-30-1899")) / 1000;
console.log(delphiTime ); // 3773084400
// delphi
ShowMessage( DateTimeToStr(3773084400) ); // show 00/00/00
the strange fact, on delphi, Now is 43670.654378:
ShowMessage( FloatToStr(Now) ); // 43670.654378
In delphi 0 is 1899 and 43670 is 2019...
How works the date format in Delphi and how convert a unix date to delphi date with math?
Side note: i can't modify the server, i need to solve the issue client side with javascript
UPDATE:
In Delphi the float value = 1 would be 31.12.1899, 2 = 01.01.1900 and so on. Each unit seem a day.
function jsDateToDelphiDate(date){
const seconds = (new Date(date).getTime() - new Date("12-30-1899").getTime()) / 1000;
const days = seconds / 60 / 60 / 24;
return days;
}
console.log(jsDateToDelphiDate(new Date("07-24-2019 16:00:00"))); // 43670.625
43670.625 on delphi is 23/07/2019 15:00.
Why i lose 1 hour?
How works the date format in Delphi
This is fully documented on Embarcadero's DocWiki:
System.TDateTime
The TDateTime class inherits a val data member--declared as a double--that holds the date-time value. The integral part of a TDateTime value is the number of days that have passed since December 30, 1899. The fractional part of a TDateTime value is the time of day.
...
The following table displays examples of TDateTime values and their corresponding dates and times:
Value Description
0 December 30, 1899; 12:00 A.M.
2.75 January 1, 1900; 6:00 P.M.
-1.25 December 29, 1899; 6:00 A.M.
35065 January 1, 1996; 12:00 A.M.
how convert a unix date to delphi date with math?
Side note: i can't modify the server, i need to solve the issue client side with javascript
A Unix date/time is represented as the number of seconds since January 1 1970 00:00:00 UTC. Delphi has a UnixDateDelta constant in the SysUtils unit which is defined as 25569, the number of days from December 31 1899 to January 1 1970. So, a TDateTime value of 25569.0 exactly represents January 1 1970 00:00:00 (UTC vs local is up to you to decide when creating a TDateTime). You can then add seconds to that value to get the final TDateTime value for any Unix date/time.
In a TDateTime, you can add whole days to the integral portion (ie, Unix + 1 day = 25569.0 + 1 = 25570.0), but adding seconds within a day is slightly more work, as seconds are not represented as-is in TDateTime, as you can see in the table above. 0.25 is 6:00 AM (21600 seconds after midnight) and 0.75 is 6:00 PM (64800 seconds after midnight). So seconds are represented in TDateTime as a fraction with 86400 (the number of seconds in a day) as the denominator.
A JavaScript Date object is represented as the number of milliseconds since midnight on January 1 1970. You can divide a Date value by 1000 to get whole seconds, and divide that value by 86400 to get whole days and fractional seconds, which you can then add to 25569.0 to produce a TDateTime value.
function jsDateToDelphiDate(dateToConvert){
const UnixDateDelta = 25569.0;
const SecsPerDay = 86400;
const MSecsPerSec = 1000;
var UnixSeconds = dateToConvert.getTime() / MSecsPerSec; // 1563984000
var SecsToAdd = UnixSeconds / SecsPerDay; // 18101.666666666668
return UnixDateDelta + SecsToAdd;
}
// don't forget to force UTC, or else the Date value
// will be skewed by the local timezone offset...
console.log(jsDateToDelphiDate(new Date("2019-07-24T16:00:00Z"))); // 43670.66666666667
console.log(jsDateToDelphiDate(new Date(Date.UTC(2019, 6, 24, 16, 0, 0)))); // 43670.66666666667
Delphi has a UnixToDateTime() function in the DateUtils unit which performs this calculation for you. So, if you can change your AJAX code to pass a Unix timestamp as-is to Delphi, you can let Delphi calculate a suitable TDateTime.
Note, in this example, the resulting TDateTime value is in UTC. After transmitting the value via AJAX to Delphi, if your Delphi code needs a TDateTime in local time, that is a simple calculation to adjust the TDateTime based on the local machine's timezone offset in minutes, which you can get using platform APIs, such as GetTimeZoneInformation() on Windows, etc. Delphi has an IncMinute() function in the DateUtils unit that you can use for that adjustment.
In JS, valueOf Date is in milliseconds. If you want to convert it to days, simply divide it by 24*60*60*1000.
To convert the return value of new Date().valueOf() to Delphi TDateTime you can use this function:
function jsDateToDelphiDate(millis: int64): TDateTime;
begin
var UnixSeconds: Integer := Round(millis / MSecsPerSec);
Result := UnixToDateTime(UnixSeconds, False);
end;

Date appears to be changing time zone from daylight to standard upon calculation

I have a jQuery range slider that is supposed to increase and decrease the date range by a week with each value. When incrementing past a certain point (November 1st in my example) the time zone changes from daylight to standard. I assume it has something to do with the way I'm calculating the new date but I can't seem to figure out what it is.
I calculate it with:
new Date(minDate.getTime() + (sliderValue * 7) * 1000 * 60 * 60 * 24)
Here's an example fiddle: https://jsfiddle.net/aedryan/f4pvg84e/
Your code is actually working fine. The reason it changes after November 1 is because we switch from Eastern Daylight Time (EDT) to Eastern Standard Time on that date - in both cases you are representing the eastern time zone. Btw, for the future this is a nice way of adding days to a date:
/**
* extends the functionality of the Date() object to include a function called addDays that adds days to
* a javascript date based on an integer
* #param days
* #returns {Date}
*/
Date.prototype.addDays = function(days){
var dat = new Date(this.valueOf());
dat.setDate(dat.getDate() + days);
return dat;
};
You were calculating days by millisecond. However, November 1st was in fact day light savings (welcome to the wonderful world of time culture). As a result your times are off by 1 hour and that means you count 23 hours for one day and only get +6.
As opposed to making these changes by millisecond, simply add the days. This can be done easily in JavaScript as when the days extend past the amount available that month it just returns the adjusted date. That means you can use this for your newDate forumla
var newDate = new Date(2015, 7, 23+(ui.value*7));
Which looks like this: https://jsfiddle.net/u2h261ka/

Date subtraction in JavaScript

I have two text boxes which accept Start Date and End Date respectively, in format YYYY/MM/DD.
I need to alert the user if he selects an end date that exceeds the start date by 50 days.
Here's what I have so far:
var startDate = new Date(document.getElementsByName('MYSTARTDATE').value);
var endDate = new Date(document.getElementsByName('MYENDDATE').value);
if ((endDate - startDate) > 50)
{
alert('End date exceeds specification');
return false;
}
Just as an example, when I select Start Date as 2012/01/22 and End Date as 2012/02/29
startDate = 'Sun Jan 22 00:00:00 UTC +0530 2012'
endDate = 'Wed Feb 29 00:00:00 UTC +0530 2012'
And the result for endDate - startDate is 3283200000, instead of 38.What am I doing wrong?
3283200000 is 38 days in milliseconds.
38 days x 24 hours x 60 minutes x 60 seconds x 1000 milliseconds
Also, there are 38 days between those two dates, not 39.
An easy solution is to have a variable (constant really) defined as the number of milliseconds in a day:
var days = 24*60*60*1000;
And use that variable as a "unit" in your comparison:
if ((endDate - startDate) > 50*days) {
...
}
The solution by Jeff B is incorrect, because of daylight savings time. If the the startDate is, say, on October 31, and the endDate is on December 20, the amount of time that has actually elapsed between those two times is 50 days plus 1 hour, not 50 days. This is especially bad if your program doesn't care about the time of day and sets all times at midnight - in that case, all calculations would be off by one for the entire winter.
Part of the correct way to subtract dates is to instantiate a new Date object for each of the times, and then use an Array with the number of days in each month to compute how many days have actually passed.
But to make things worse, the dates that Europe changes the clocks are different than the dates that the clocks are changed in New York. Therefore, timezone alone is not sufficient to determine how many days have elapsed; location is also necessary.
Daylight savings time adds significant complexity to date handling. In my website, the function needed to accurately compute the number of days passed is nearly a hundred lines. The actual complexity is dependent on whether the calculations are client side or server side or both and who is viewing the data.

Categories