Convert an iso8601 time string to the current timezone in JS - javascript

I'm using the https://fullcalendar.io/ calendar plugin in an app. I provide the calendar with data with the dates in iso8601 format, like this, "2021-09-15T14:30:00+01:00", and the calendar does a good job of showing those times adjusted for the timezone set in the browser.
Eg, if the browser is in a "+01:00" timezone then that time shows up as 14:30, but if it's in a "+02:00" timezone it shows up as 15:30, etc.
My problem is that I'd like to show those times in other contexts, outside of the calendar, and I don't want to output different things from the server - I want the conversion to happen in the browser, using javascript.
It occurred to me that I could do something like this: output the time in a span with the iso861 string in a data attribute, perhaps with a class as well which acts as a trigger for some general js on the page. But, is there a simple way in JS to convert the iso8601 time string to a time using the browser's timezone setting? I suppose I would need to have another data attribute that has some info on how I want it to be formatted, something like "%H:%M" in this case (if I want to output "14:30" or "15:30").
Note - we use jquery and i will use that in my example JS below.
Something like this:
<!-- in html - the span contains the default unconverted time -->
<span class="convertable-time" data-iso8601="2021-09-15T14:30:00+01:00" data-datetime-format="%H:%M">14:30</span>
and then in the JS something like this (this is using jquery but the solution doesn't need to)
$(".convertable-time").each(function(i,el){
el = $(el);
el.html(convertIso8601StringToLocalTime(el.data("iso8601"), el.data("datetime-format"));
});
function convertIso8601StringToLocalTime(string, format){
//eg string = "2021-09-15T14:30:00+01:00"
//eg format = "%H:%M"
// ??
}
What would I put in the convertIso8601StringToLocalTime function?
Note - i'm not wedded to the exact format string of "%H:%M", if there is a more standard way of specifying a datetime format in JS then i'm happy to use it. thanks.

This is what I ended up doing:
//expects timestring to be an iso8601 formatted datetime string eg "2021-09-15T14:30:00+01:00"
//accepted strings for "format": copied from ruby's DateTime#strftime method
// %M - minutes
// %H - hours
// %S - seconds
// %d - day of the month
// %Y - year four digits
// %y - year two digits
// %m - month (as a number)
function convertIso8601StringToLocalTime(timestring, format){
//set default format
if((typeof(format) == "undefined") || (format == "")){
format = "%H:%M";
}
var date = new Date(timestring);
var replacements = {
"getMinutes": /\%M/g,
"getHours": /\%H/g,
"getDate": /\%d/g,
"getFullYear": /\%Y/g,
"getMonth": /\%m/g
}
for (const [function_name, regex] of Object.entries(replacements)) {
if(format.match(regex)){
format = format.replace(regex, date[function_name]());
}
}
// year two digits doesn't have a method we can call on a Date as far as I can see
// ('getYear()' returns '121' for me which is mysterious)
// so we do it 'manually' here by modding the full year by 100
if(format.match(/\%y/g)){
format = format.replace(/\%y/g, (date.getFullYear() % 100));
}
return format;
}

Related

Initializing time with its own timezone

I'm quite new (and confused) with time in JavaScript..
I currently have time data to work with, and they are in the format of DD-MMM-YYYY, meaning it would be 23-Feb-2021. This time is already in its own timezone, GMT-10. I'm trying to initialize it as GMT-10 so that I could get its appropriate epoch time.
I've done this:
date = new Date("23-Feb-2021") // This results in 2021-02-23T00:00:00.000Z
But what I'm trying to achieve is to get the time to be 2021-02-23T10:00:00.000Z, which I could then do a getTime() to get its epoch in ms. I understand I could probably hard code to +10 to the time I have, but the data I work with might vary so I'd like to figure a way to initialize the date with a specific timezone.
EDIT:
Here's an example of an outcome I'd want:
date = ("23-Feb-2021")
date = moment(date).format(); // 2021-02-23T00:00:00+00:00
date = date.replace("+00","+10");
date = new Date(msg.date); // 2021-02-22T14:00:00.000Z
date = date.getTime(); // 1614002400000 (2021-02-22T14:00:00.000Z)
In the end, 2021-02-22T14:00:00.000Z is what I'm trying to get, without having to iterate it a bunch of times like above and adding +10
You can add the timezone offset to your input string, and use an explicit string format to parse it:
let date = "23-Feb-2021"
date = moment(date + "-10:00", "D-MMM-YYYYZ")
console.log(date.format()) // 2021-02-23T11:00:00+01:00 (if local is GMT+1)
console.log(date.utc().format()) // 2021-02-23T10:00:00+00:00
It seems you're already using moment.js, so add moment–timezone so you can parse timestamps in whatever IANA timezone you want. You can either choose a location with the offset rules you want (e.g. Pacific/Honolulu or Pacific/Tahiti for -10) or just a fixed offset like etc/GMT+10.
You can then format the value in any timezone, as UTC, or as a time value, e.g.
// Timestamp
let d = "23-Feb-2021";
// Parse in specific IANA timezone
let m = moment.tz(d, 'D-MMM-YYYY', 'Pacific/Honolulu');
// Trigger UTC mode
m.utc()
// Show result
console.log(m.format())
// Get time value (ms since epoch)
console.log(m.valueOf());
// Parse using generic timezone/fixed offset
let g = moment.tz(d, 'D-MMM-YYYY', 'etc/GMT+10');
console.log(g.utc().format());
// Display timestamp for another timezone
console.log(moment.tz(g, 'Asia/Riyadh').format());
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.4/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.34/moment-timezone-with-data-10-year-range.js"></script>
Note that for fixed offset timezones like etc/GMT+10, the sign is the opposite of the common offset (e.g. etc/GMT+10 is UTC-10) to be consistent with POSIX notation. However, moment.tz only has limited POSIX support in that it only recognises one hour offsets, not the full POSIX timezone notation, so you can't do say "etc/GMT-530" instead of Asia/Kolkata.

Incorrect time while copy pasting a date time value using google apps script [duplicate]

I'm trying to get from a time formatted Cell (hh:mm:ss) the hour value, the values can be bigger 24:00:00 for example 20000:00:00 should give 20000:
Table:
if your read the Value of E1:
var total = sheet.getRange("E1").getValue();
Logger.log(total);
The result is:
Sat Apr 12 07:09:21 GMT+00:09 1902
Now I've tried to convert it to a Date object and get the Unix time stamp of it:
var date = new Date(total);
var milsec = date.getTime();
Logger.log(Utilities.formatString("%11.6f",milsec));
var hours = milsec / 1000 / 60 / 60;
Logger.log(hours)
1374127872020.000000
381702.1866722222
The question is how to get the correct value of 20000 ?
Expanding on what Serge did, I wrote some functions that should be a bit easier to read and take into account timezone differences between the spreadsheet and the script.
function getValueAsSeconds(range) {
var value = range.getValue();
// Get the date value in the spreadsheet's timezone.
var spreadsheetTimezone = range.getSheet().getParent().getSpreadsheetTimeZone();
var dateString = Utilities.formatDate(value, spreadsheetTimezone,
'EEE, d MMM yyyy HH:mm:ss');
var date = new Date(dateString);
// Initialize the date of the epoch.
var epoch = new Date('Dec 30, 1899 00:00:00');
// Calculate the number of milliseconds between the epoch and the value.
var diff = date.getTime() - epoch.getTime();
// Convert the milliseconds to seconds and return.
return Math.round(diff / 1000);
}
function getValueAsMinutes(range) {
return getValueAsSeconds(range) / 60;
}
function getValueAsHours(range) {
return getValueAsMinutes(range) / 60;
}
You can use these functions like so:
var range = SpreadsheetApp.getActiveSheet().getRange('A1');
Logger.log(getValueAsHours(range));
Needless to say, this is a lot of work to get the number of hours from a range. Please star Issue 402 which is a feature request to have the ability to get the literal string value from a cell.
There are two new functions getDisplayValue() and getDisplayValues() that returns the datetime or anything exactly the way it looks to you on a Spreadsheet. Check out the documentation here
The value you see (Sat Apr 12 07:09:21 GMT+00:09 1902) is the equivalent date in Javascript standard time that is 20000 hours later than ref date.
you should simply remove the spreadsheet reference value from your result to get what you want.
This code does the trick :
function getHours(){
var sh = SpreadsheetApp.getActiveSpreadsheet();
var cellValue = sh.getRange('E1').getValue();
var eqDate = new Date(cellValue);// this is the date object corresponding to your cell value in JS standard
Logger.log('Cell Date in JS format '+eqDate)
Logger.log('ref date in JS '+new Date(0,0,0,0,0,0));
var testOnZero = eqDate.getTime();Logger.log('Use this with a cell value = 0 to check the value to use in the next line of code '+testOnZero);
var hours = (eqDate.getTime()+ 2.2091616E12 )/3600000 ; // getTime retrieves the value in milliseconds, 2.2091616E12 is the difference between javascript ref and spreadsheet ref.
Logger.log('Value in hours with offset correction : '+hours); // show result in hours (obtained by dividing by 3600000)
}
note : this code gets only hours , if your going to have minutes and/or seconds then it should be developped to handle that too... let us know if you need it.
EDIT : a word of explanation...
Spreadsheets use a reference date of 12/30/1899 while Javascript is using 01/01/1970, that means there is a difference of 25568 days between both references. All this assuming we use the same time zone in both systems. When we convert a date value in a spreadsheet to a javascript date object the GAS engine automatically adds the difference to keep consistency between dates.
In this case we don't want to know the real date of something but rather an absolute hours value, ie a "duration", so we need to remove the 25568 day offset. This is done using the getTime() method that returns milliseconds counted from the JS reference date, the only thing we have to know is the value in milliseconds of the spreadsheet reference date and substract this value from the actual date object. Then a bit of maths to get hours instead of milliseconds and we're done.
I know this seems a bit complicated and I'm not sure my attempt to explain will really clarify the question but it's always worth trying isn't it ?
Anyway the result is what we needed as long as (as stated in the comments) one adjust the offset value according to the time zone settings of the spreadsheet. It would of course be possible to let the script handle that automatically but it would have make the script more complex, not sure it's really necessary.
For simple spreadsheets you may be able to change your spreadsheet timezone to GMT without daylight saving and use this short conversion function:
function durationToSeconds(value) {
var timezoneName = SpreadsheetApp.getActive().getSpreadsheetTimeZone();
if (timezoneName != "Etc/GMT") {
throw new Error("Timezone must be GMT to handle time durations, found " + timezoneName);
}
return (Number(value) + 2209161600000) / 1000;
}
Eric Koleda's answer is in many ways more general. I wrote this while trying to understand how it handles the corner cases with the spreadsheet timezone, browser timezone and the timezone changes in 1900 in Alaska and Stockholm.
Make a cell somewhere with a duration value of "00:00:00". This cell will be used as a reference. Could be a hidden cell, or a cell in a different sheet with config values. E.g. as below:
then write a function with two parameters - 1) value you want to process, and 2) reference value of "00:00:00". E.g.:
function gethours(val, ref) {
let dv = new Date(val)
let dr = new Date(ref)
return (dv.getTime() - dr.getTime())/(1000*60*60)
}
Since whatever Sheets are doing with the Duration type is exactly the same for both, we can now convert them to Dates and subtract, which gives correct value. In the code example above I used .getTime() which gives number of milliseconds since Jan 1, 1970, ... .
If we tried to compute what is exactly happening to the value, and make corrections, code gets too complicated.
One caveat: if the number of hours is very large say 200,000:00:00 there is substantial fractional value showing up since days/years are not exactly 24hrs/365days (? speculating here). Specifically, 200000:00:00 gives 200,000.16 as a result.

Parse JSON DateTime object to local time in JQuery by ignoring timezone

I am facing an issue while parsing JSON Date Time object using moment(of course I tried many approaches suggested in Stackoverflow but nothing worked in my case).
In my application, I'm storing a DateTime value as UTC DateTime. Now when I'm displaying I need to display it according to the browser timezone. After going through many StackOverflow questions, I used "moment.js" as below
//From server, the Date object looks like /Date(1506510057813)/
//The equivalent DateTime value stored in Database is 2017-09-27 13:00:57.813
fuction DateTimeFormatter(value)
{
if (value != undefined) {
var newValue = new Date(moment.utc(value));
//But at this line, even with just moment(value) all I am getting is DateTime which is not same as UTC time.
//I don't want any time zone to get appended all I want is just 13:00:57
var newHours = newValue.getHours() - newValue.getTimezoneOffset() / 60;
var newMinutes = (newHours + '.0').split('.')[1] * 6;
newValue.setHours(newHours);
newValue.setMinutes(newMinutes);
return moment(newValue).format(applicationTableDateFormat);
}
else
return "";
}
Please let me know what I am doing wrong or is there any other way I can display time as per browser time zone.
Once you have a UTC moment, you can convert it to local.
moment.utc(value).local().format(...)
https://momentjs.com/docs/#/manipulating/local/
But it sounds like maybe your real problem is when you store the date. If you're storing it as UTC, make sure you actually convert the local value to UTC before you store it. That way when you read it, you get a predictable value that you can safely convert to any locale.
Angularjs has its own mechanism to display formatted dates on views you just needs an absolute representation of a date and it takes care of the rest. And by absolute, I mean, a Date which is settled in a timezone whether it's utc or not, you need to know what timezone you are talking about.
The date filter
It's a filter from the core module of angularjs and it accepts:
"... either as Date object, milliseconds (string or number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.sssZ and its shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is specified in the string input, the time is considered to be in the local timezone." (Angularjs date filter)
The problem
Angularjs need a proper date input in order to display it correctly, in your case you seem to have the milliseconds format (sort of, /Date(1506510057813)/), you could use that and extract the numeric part and input that on the pipe, or you can change the server to send the ISO 8601 date (a.k.a., yyyy-MM-ddTHH:mm:ss.sssZ).
For example:
let rawDate = '/Date(1506510057813)/';
let re = /\/Date\((\d+)\)\//g; // regex to extract number from the string date
let myDate = new Date(Number(re.exec()[1])) // extract the milliseconds
Or
let rawDate = '2017-09-27T11:00:57.813Z';
let myDate = new Date(rawDate)// and you don't need to do anything else
Either way you'd end up with something like this:
<span> {{ myDate | date }}</span>

Dates in .net vs in javascript

I have a ASP.NET WEB.API 4 and a Controller returning the following json:
{ date: "2013-03-14T00:00:00" }
I parse it on the client (JavaScript):
date = new Date(json.date); // json.date being "2013-03-14T00:00:00"
Later I do a POST and in the body the date has changed format to
{ date: "2013-03-13T23:00:00.000Z" }
My guess was that some time zone stuff has been added by JavaScript or the browser?
Because of DB storing dates as int (yyyymmdd) I do the following convertion:
public static int ToInt(DateTime date)
{
return date.Year * 10000 + date.Month * 100 + date.Day;
}
The resulting int is then one day off :(
However if I do
date.ToLocalTime()
before calling the ToInt method, it looks fine.
Have I understood this correctly and is ToLocalTime() a sufficient solution or do I need to spend a day reading up on UTC dates in .NET and JavaScript?
Thanks!
In my opinion you will get better result if your controller will return date in ZULU format. So before sending use: ToUniversalTime (http://msdn.microsoft.com/en-us/library/system.datetime.touniversaltime.aspx) Than the result also will be in ZULU format, so you don't have to convert it to LocalTime
date = new Date(json.date); // json.date being "2013-03-14T00:00:00"
And what timezone is that json.date in? If there's no information about this, the JS Date constructor will assume it is the client's local timezone (and that is quite random). If you want it to be UTC, just add "Z" after your ISO-datestring:
date = new Date(json.date+"Z");
Also always use the [gs]etUTC(FullYear|Month|Date|…) methods then.

How to format date in correct format in JavaScript

I'm working on JavaScript and stuck in a small issue.
I am receiving this date in JSON response 1322919399447-0500
and I want to format this like: 6:50 PM, Dec 3rd 2011.
I'm not sure if this is the best way (I'm sure it's not, actually), but essentially you can make that datestring into a js Date object, then pull out the pieces to manipulate as you see fit:
var dateThing = new Date(1322919399447-0500);
dateThing.getFullYear(); // 2011
dateThing.getDay(); // 6
dateThing.getDate(); // 3
dateThing.getMonth(); // 11
dateThing.getHours(); // 8 (test for anything over 12, that indicates PM)
dateThing.getMinutes(); // 36
Then you can concatenate those pieces into your own format. Like I said, there's probably a better way, but this works in a pinch.
I used this handy little date format addon and it worked very well for me. Even took care of the pesky internet explorer quirks with the month.
This is a similar date format function I created that uses the same flags that PHP's date function uses.
PHP date function in Javascript
Here is the snippet with your example input. It is using script linked by Zoidberg.
This code returns formatted UTC date. If you want your local date then remove UTC: from the return statement.
function convertTime(dateString) {
// get ms part from the string
var milis = +dateString.substring(0, 13);
// get timezone part as "# of hours from UTC", e.g. "-0500" -> -5
var offset = +dateString.substring(13, 16);
// move the time for "offset" number of hours (to UTC time)
var date = new Date(milis - offset * 3600000);
// using http://stevenlevithan.com/assets/misc/date.format.js
return date.format("UTC:h:MM TT, mmm dS yyyy");
}
EDIT: Changed + offset * to - offset * as we want to normalize to UTC.

Categories