There isn't enough information in the OP to determine why the code fails in iOS and Safari. The following might help.
If you're dealing only in whole days and dates after 1 Mar 1900, you can convert an Excel serial date value into an ECMAScript date by adding the value as days to the effective Excel epoch of 30 Dec 1899.
This deals with the issue that Excel serial 1 is 1 Jan 1900 and that 1900 is treated as a leap year, so there are effectively 2 extra days. A consequence is that dates before 1 Mar 1900 will be incorrect.
/* Given an Excel date value, return an ECMAScript Date
* for the same date.
* @param {number} excelValue: Excel date value
* @param {boolean} useUTC: if true, treat excelValue as UTC
* otherwise treat excelValue as local
* @returns {Date}
*/
function excelToDate(excelValue, useUTC = false) {
return useUTC?
new Date(Date.UTC(1899, 11, 30 + excelValue))
:
new Date(1899, 11, 30 + excelValue);
}
// Quick formatter - return date as MMM DD
// If utc == true, treat as UTC
// else treat as local
function toMD(date, utc = false) {
let opts = {month: 'short', day: '2-digit'};
if (utc) opts.timeZone = 'UTC';
return date.toLocaleString('en-us', opts).toUpperCase();
}
[25569, // 01 Jan 1970
27463, // 10 Mar 1975
40537, // 25 Dec 2010
44301, // 15 Apr 2021
].forEach(v =>
console.log(
'Value: ' + v + '\n' +
'Local: ' + toMD(excelToDate(v)) + '\n' +
'UTC : ' + toMD(excelToDate(v, true), true)
)
);
This approach avoids conversion to milliseconds and the problematic use of getTimezoneOffset. Decimal days need a little more work, especially if prior to the excel epoch.
How the date is formatted is really a separate concern. The use of toLocaleString for precise formatting is not generally encouraged, however the formatting of en-us is unlikely to change. Ever.
Edit
A more correct version that handles decimal days and corrects the 1900 leap year issue is below. Excel dates prior to 1 Jan 1900 are another issue again.
function excelToDate(excelValue, useUTC = false) {
// Split into whole and part days
let wholeDays = parseInt(excelValue);
let partDays = excelValue - wholeDays;
// Allow for 1900 leap year issue
wholeDays -= wholeDays >= 60? 1 : 0;
// Create date, assuming fractional days are based on 24 hour day
return useUTC? new Date(Date.UTC(0,0, wholeDays) + 8.64e7 * partDays) :
new Date(0,0, wholeDays, 0, 0, 0, 8.64e7 * partDays);
}
// Examples
[36952.00010416667, // 02 Mar 2001 00:00:09
1, // 01 Jan 1900 00:00:00
40654.3837962963, // 21 Apr 2011 09:12:40
40729, // 05 Jul 2011 00:00:00
43103, // 03 Jan 2018
].forEach(v => console.log(
`${v}\nLocal : ${excelToDate(v).toString()}` +
`\nUTC : ${excelToDate(v, true).toISOString()}`
));
A second part of the question seems to be how to parse timestamps in the format m/d. Simply split into parts and give to the Date constructor. I have not idea how you work out the year, I'll just assume it's the current year.
function parseMD(d) {
let [month, day] = d.split(/\D/);
let date = new Date(new Date().getFullYear(), month - 1, day);
return (month - date.getMonth() == 1 && date.getDate() == day)?
date : new Date(NaN);
}
// Examples
['3/7', // 7 Mar
'12/31', // 31 Dec
'2/29', // 29 Feb, invalid Date in non–leap years
].forEach(d =>
console.log(`${d} => ${parseMD(d).toDateString()}`)
);
Note that all the above uses ECMAScript that is compatible back to ed 3 (1999, except for the options to toLocaleString which should be ignored). It has been tested and works on iOS devices.