Related
Let's say we're in London at midnight on 2020-01-01 and make an entry into an app that stores the datetime as an ISO-8601 string like this.
2020-01-01T00:00:00-00:00
Later, I am in Los Angeles and want to view this date on a chart that requires a javascript date object.
Getting the localized date object is easy.
const iso8601Date = '2020-01-01T00:00:00+00:00';
const theDate = new Date(iso8601Date);
console.log(typeOf(theDate)); // date
console.log(theDate); // Tue Dec 31 2019 16:00:00 GMT-0800 (PST)
But, sometimes we want to "ignore" the timezone offset and analyze the data as if it happened in the current timezone.
This is the result I'm looking for but don't know how to accomplish.
const iso8601Date = '2020-01-01T00:00:00+00:00';
const theRepositionedDate = someMagic(iso8601Date);
console.log(typeOf(theRepositionedDate)); // date
console.log(theRepositionedDate); // Wed Jan 01 2020 00:00:00 GMT-0800 (PST)
How do you reposition the date and return a date object?
/* Helper function
Returns the object type
https://stackoverflow.com/a/28475133/25197
typeOf(); //undefined
typeOf(null); //null
typeOf(NaN); //number
typeOf(5); //number
typeOf({}); //object
typeOf([]); //array
typeOf(''); //string
typeOf(function () {}); //function
typeOf(/a/) //regexp
typeOf(new Date()) //date
*/
function typeOf(obj) {
return {}.toString
.call(obj)
.split(' ')[1]
.slice(0, -1)
.toLowerCase();
}
This is really a duplicate of Why does Date.parse give incorrect results?, but that may not seem apparent at first glance.
The first rule of parsing timestamps is "do not use the built–in parser", even for the 2 or 3 formats supported by ECMA-262.
To reliably parse a timestamp, you must know the format. Built–in parsers try and work it out, so there are differences between them that may well produce unexpected results. It just happens that '2020-01-01T00:00:00+00:00' is probably the only supported format that is actually reliably parsed. But it does differ slightly from strict ISO 8601, and different browsers differ in how strictly they apply the ECMAScript parsing rules so again, very easy to get wrong.
You can convert it to a "local" timestamp by just trimming the offset information, i.e. '2020-01-01T00:00:00', however Safari at least gets it wrong and treats it as UTC anyway. ECMAScrip itself is inconsistent with ISO 8601 by treating date–only forms of ISO 8601 as UTC (i.e. '2020-01-01' as UTC when ISO 8601 says to treat it as local).
So just write your own parser or use a library, there are plenty to choose from. If you're only looking for parsing and formatting, there are some that are less than 2k minified (and there are examples on SO).
Writing your own is not that challenging if you just want to support straight forward ISO 8601 like formats, e.g.
// Parse ISO 8601 timestamps in YYYY-MM-DDTHH:mm:ss±HH:mm format
// Optional "T" date time separator and
// Optional ":" offset hour minute separator
function parseIso(s, local) {
let offset = (s.match(/[+-]\d\d:?\d\d$/) || [])[0];
let b = s.split(/\D/g);
// By default create a "local" date
let d = new Date(
b[0],
b[1]-1,
b[2] || 1,
b[3] || 0,
b[4] || 0,
b[5] || 0
);
// Use offset if present and not told to ignore it
if (offset && !local){
let sign = /^\+/.test(offset)? 1 : -1;
let [h, m] = offset.match(/\d\d/g);
d.setMinutes(d.getMinutes() - sign * (h*60 + m*1) - d.getTimezoneOffset());
}
return d;
}
// Samples
['2020-01-01T00:00:00+00:00', // UTC, ISO 8601 standard
'2020-01-01 00:00:00+05:30', // IST, missing T
'2020-01-01T00:00:00-0400', // US EST, missing T and :
'2020-01-01 00:00:00', // No timezone, local always
'2020-01-01' // Date-only as local (differs from ECMA-262)
].forEach(s => {
console.log(s);
console.log('Using offset\n' + parseIso(s).toString());
console.log('Ignoring offset\n' + parseIso(s, true).toString());
});
Building off of #RobG's answer I was able to speed this one up a little by using a single regex. Posting here for posterity.
const isoToDate = (iso8601, ignoreTimezone = false) => {
// Differences from default `new Date()` are...
// - Returns a local datetime for all without-timezone inputs, including date-only strings.
// - ignoreTimezone processes datetimes-with-timezones as if they are without-timezones.
// - Accurate across all mobile browsers. https://stackoverflow.com/a/61242262/25197
const dateTimeParts = iso8601.match(
/(\d{4})-(\d{2})-(\d{2})(?:[T ](\d{2}):(\d{2}):(\d{2})(?:\.(\d{0,7}))?(?:([+-])(\d{2}):(\d{2}))?)?/,
);
// Create a "localized" Date by always specifying a time. If you create a date without specifying
// time a date set at midnight in UTC Zulu is returned. https://www.diigo.com/0hc3eb
const date = new Date(
dateTimeParts[1], // year
dateTimeParts[2] - 1, // month (0-indexed)
dateTimeParts[3] || 1, // day
dateTimeParts[4] || 0, // hours
dateTimeParts[5] || 0, // minutes
dateTimeParts[6] || 0, // seconds
dateTimeParts[7] || 0, // milliseconds
);
const sign = dateTimeParts[8];
if (sign && ignoreTimezone === false) {
const direction = sign === '+' ? 1 : -1;
const hoursOffset = dateTimeParts[9] || 0;
const minutesOffset = dateTimeParts[10] || 0;
const offset = direction * (hoursOffset * 60 + minutesOffset * 1);
date.setMinutes(date.getMinutes() - offset - date.getTimezoneOffset());
}
return date;
};
The key difference is a single regex that returns all the matching groups at once.
Here's a regex101 with some examples of it matching/grouping.
It's about double the speed of the #RobG's awesome accepted answer and 4-6x faster than moment.js and date-fns packages. 👍
const createDate = (isoDate) => {
isoDate = new Date(isoDate)
return new Date(Date.UTC(
isoDate.getUTCFullYear(),
isoDate.getUTCMonth(),
isoDate.getUTCDate(),
isoDate.getUTCMinutes(),
isoDate.getUTCSeconds(),
isoDate.getUTCMilliseconds()
));
}
const iso8601Date = '2020-01-01T00:00:00+00:00';
const theRepositionedDate = createDate(iso8601Date);
console.log(theRepositionedDate instanceof Date); // true
console.log(theRepositionedDate);
But, sometimes we want to "ignore" the timezone offset and analyze the data as if it happened in the current timezone.
Ok, then ignore it.
const iso8601Date = '2020-01-01T00:00:00+00:00';
const theDate = new Date(iso8601Date.substring(0, 19));
This works because you're creating a Date object from 2020-01-01T00:00:00 - an ISO 8601 date-time without offset.
ECMAScript section 20.3.1.15 - Date Time String Format says:
When the time zone offset is absent, date-only forms are interpreted as a UTC time and date-time forms are interpreted as a local time.
I have a date object that's created by the user, with the timezone filled in by the browser, like so:
var date = new Date(2011, 05, 07, 04, 0, 0);
> Tue Jun 07 2011 04:00:00 GMT+1000 (E. Australia Standard Time)
When I stringify it, though, the timezone goes bye-bye
JSON.stringify(date);
> "2011-06-06T18:00:00.000Z"
The best way I can get a ISO8601 string while preserving the browser's timezone is by using moment.js and using moment.format(), but of course that won't work if I'm serializing a whole command via something that uses JSON.stringify internally (in this case, AngularJS)
var command = { time: date, contents: 'foo' };
$http.post('/Notes/Add', command);
For completeness, my domain does need both the local time and the offset.
Assuming you have some kind of object that contains a Date:
var o = { d : new Date() };
You can override the toJSON function of the Date prototype. Here I use moment.js to create a moment object from the date, then use moment's format function without parameters, which emits the ISO8601 extended format including the offset.
Date.prototype.toJSON = function(){ return moment(this).format(); }
Now when you serialize the object, it will use the date format you asked for:
var json = JSON.stringify(o); // '{"d":"2015-06-28T13:51:13-07:00"}'
Of course, that will affect all Date objects. If you want to change the behavior of only the specific date object, you can override just that particular object's toJSON function, like this:
o.d.toJSON = function(){ return moment(this).format(); }
I'd always be inclined to not mess with functions in the prototype of system objects like the date, you never know when that's going to bite you in some unexpected way later on in your code.
Instead, the JSON.stringify method accepts a "replacer" function (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#The_replacer_parameter) which you can supply, allowing you to override the innards of how JSON.stringify performs its "stringification"; so you could do something like this;
var replacer = function(key, value) {
if (this[key] instanceof Date) {
return this[key].toUTCString();
}
return value;
}
console.log(JSON.stringify(new Date(), replacer));
console.log(JSON.stringify({ myProperty: new Date()}, replacer));
console.log(JSON.stringify({ myProperty: new Date(), notADate: "I'm really not", trueOrFalse: true}, replacer));
Based on Matt Johnsons 's answer, I re-implemented toJSON without having to depend on moment (which I think is a splendid library, but a dependency in such a low level method like toJSON bothers me).
Date.prototype.toJSON = function () {
var timezoneOffsetInHours = -(this.getTimezoneOffset() / 60); //UTC minus local time
var sign = timezoneOffsetInHours >= 0 ? '+' : '-';
var leadingZero = (Math.abs(timezoneOffsetInHours) < 10) ? '0' : '';
//It's a bit unfortunate that we need to construct a new Date instance
//(we don't want _this_ Date instance to be modified)
var correctedDate = new Date(this.getFullYear(), this.getMonth(),
this.getDate(), this.getHours(), this.getMinutes(), this.getSeconds(),
this.getMilliseconds());
correctedDate.setHours(this.getHours() + timezoneOffsetInHours);
var iso = correctedDate.toISOString().replace('Z', '');
return iso + sign + leadingZero + Math.abs(timezoneOffsetInHours).toString() + ':00';
}
The setHours method will adjust other parts of the date object when the provided value would "overflow". From MDN:
If a parameter you specify is outside of the expected range, setHours() attempts to update the date information in the Date object accordingly. For example, if you use 100 for secondsValue, the minutes will be incremented by 1 (minutesValue + 1), and 40 will be used for seconds.
When I stringify it, though, the timezone goes bye-bye
That’s because Tue Jun 07 2011 04:00:00 GMT+1000 (E. Australia Standard Time) is actually the result of the toString method of the Date object, whereas stringify seems to call the toISOString method instead.
So if the toString format is what you want, then simply stringify that:
JSON.stringify(date.toString());
Or, since you want to stringify your “command” later on, put that value in there in the first place:
var command = { time: date.toString(), contents: 'foo' };
If you have a JS Date Object and want to stringify it to preserve the timezone, then you should definitely use toLocaleDateString().
It is a very powerful helper function that can help you format your Date object in every way possible.
For example, if you want to print "Friday, February 1, 2019, Pacific Standard Time",
const formatDate = (dateObject : Date) => {
const options: any = {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
timeZoneName: 'long'
};
return dateObject.toLocaleDateString('en-CA', options);
};
Thus, by modifying the options object, you can achieve different styles of formatting for your Date Object.
For more information regarding the ways of formatting, refer to this Medium article: https://medium.com/swlh/use-tolocaledatestring-to-format-javascript-dates-2959108ea020
let date = new Date(JSON.parse(JSON.stringify(new Date(2011, 05, 07, 04, 0, 0))));
I've created a small library that preserves the timezone with ISO8601 string after JSON.stringify. The library lets you easily alter the behavior of the native Date.prototype.toJSON method.
npm: https://www.npmjs.com/package/lbdate
Example:
lbDate().init();
const myObj = {
date: new Date(),
};
const myStringObj = JSON.stringify(myObj);
console.log(myStringObj);
// {"date":"2020-04-01T03:00:00.000+03:00"}
The library also gives you options to customize the serialization result if necessary.
I am trying to deserialize a json object that has a javascript date in it. When JSON.stringify is called on the object, dates are serialized to strings that are not properly deserialized back to dates. I have attempted to deserialize the object using both the native browser implementation with chrome, IE, and FF and using jquery. Both give the some results. Here is the snippet:
var obj = {Date: new Date()};
var objSer = JSON.stringify(obj);
var objDeser = JSON.parse(objSer);
var objJqDeser = $.parseJSON(objSer);
function getYear(value){
try{
return value.getYear();
}
catch(err){
return err;
}
}
$("#orig").text("Orig Year: " + getYear(obj.Date));
$("#deser").text("Deser Year: " + getYear(objDeser.Date));
$("#jqDeser").text("JqDeser Year: " + getYear(objJqDeser.Date));
I want objDeser.Date to be a js date not a string. You can see this problem in action here: http://jsbin.com/unijud/24/edit. Is there any js libraries that can properly deserialize the dates when building the javascript object?
JSON.parse has a little-known second parameter: the 'reviver' function. This is used for precisely this purpose: to revive a date string into a Date object (or, hypothetically, any other kind of object you wanted to convert from string) during the initial parse.
There's an SO post about this, and here's a blog post that includes an implementation example and a function that will do property checking for a couple common date encodings (ISO & that weird .NET AJAX format), before parsing to a Date.
Here's the key function from that blog post, fwiw:
// JSON date deserializer
// use as the second, 'reviver' argument to JSON.parse();
if (window.JSON && !window.JSON.dateParser) {
var reISO = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*))(?:Z|(\+|-)([\d|:]*))?$/;
var reMsAjax = /^\/Date\((d|-|.*)\)[\/|\\]$/;
JSON.dateParser = function (key, value) {
// first, just make sure the property is a string:
if (typeof value === 'string') {
// then, use regex to see if it's an ISO-formatted string
var a = reISO.exec(value);
if (a) {
// if so, Date() can parse it:
return new Date(value);
}
// otherwise, see if it's a wacky Microsoft-format string:
a = reMsAjax.exec(value);
if (a) {
// and perform some jujitsu to make use of it:
var b = a[1].split(/[-+,.]/);
return new Date(b[0] ? +b[0] : 0 - +b[1]);
}
// here, you could insert any additional tests and parse instructions you like, for other date syntaxes...
}
// important: you need to return any values you're not parsing, or they die...
return value;
};
}
// use: JSON.parse(json,JSON.dateParser);
(There are lots of opinions about proper regexes for ISO 8601 dates. YMMV. Also, there's no particular reason to punch the function onto the global JSON object. You could store/reference it anywhere you like. )
I took #LastCoder advice and wrote a simple implementation. It seems to be doing what I wanted it to.
var jsonDates = {
dtrx2: /\d{4}-\d{2}-\d{2}/,
parse: function(obj){
var parsedObj = JSON.parse(obj);
return this.parseDates(parsedObj);
},
parseDates: function(obj){
// iterate properties
for(pName in obj){
// make sure the property is 'truthy'
if (obj[pName]){
var value = obj[pName];
// determine if the property is an array
if (Array.isArray(value)){
for(var ii = 0; ii < value.length; ii++){
this.parseDates(value[ii]);
}
}
// determine if the property is an object
else if (typeof(value) == "object"){
this.parseDates(value);
}
// determine if the property is a string containing a date
else if (typeof(value) == "string" && this.dtrx2.test(value)){
// parse and replace
obj[pName] = new Date(obj[pName]);
}
}
}
return obj;
}
};
A live example is available on jsbin. A reference is available on gist.
In order to represent dates using JavaScript, I found that JSON uses ISO 8601, a specific string format to encode dates as string. When I last checked though, there is not an official standard for what the date format should look like. The major browsers use ISO 8601 as the JSON Date encoding format.
So, dates are encoded as ISO 8601 strings and then used just like a regular strings when the JSON is serialized and deserialized.
That being said, ISO dates can be converted into JavaScript dates by use of the JavaScript Date constructor, which accepts a wide variety of inputs to construct a date, ISO 8601 being one of them.
Get todays date:
var curDate = new Date();
document.write(curDate); //Mon Feb 01 2016 12:57:12 GMT-0600 (Central Standard Time)
Parse it into a string:
var dateStr = JSON.parse(JSON.stringify(curDate));
document.write(dateStr);//2016-02-01T18:59:35.375Z
Then convert it back to a javascript date, using the constructor:
var date = new Date(dateStr);
document.write(date); //Mon Feb 01 2016 12:59:35 GMT-0600 (Central Standard Time)
The JSON spec does not include special formatting for dates. As such they are often serialized as a string, sometimes with special markings to indicate it should be treated as a Date object if the language supports them. As such, most (all?) browser-native JSON parsers can not round-trip a Date object properly.
There are several good libraries that help with this - I very much like MomentJS though I have used datejs in the past as well. You would just need to iterate over your objects and convert the proper fields to Date objects after they have been parsed.
I find it helpful to remember that the JSON format is much more restrictive than JavaScript object literal notation.
The JavaScript es5 is able to parse the date like 2018-04-03T22:00:00... in its default Date constructor. (e.g. new Date("2018-04-03T22:00:00...");.
For someone like me, who is searching for automatic date deserialization from JSON web responses, this could be handy.
/**
* Iterates over all entries of the input object and replace the string dates with the objects of {#link Date}.
*/
function fixDateObjects(responseBody) {
if (responseBody) {
const regex = /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}/;
for (const [key, value] of Object.entries(responseBody)) {
const val = String(value);
if (val.startsWith('[object Object]')) {
fixDateObjects(value);
}
if (val.match(regex)) {
responseBody[key] = new Date(val);
}
}
}
}
Explanation: It iterates over all object entries of the JSON(responseBody) and replaces the string dates (matched by the given regex) with the new Date(str) objects.
Result: This brings you freedom of further processing. Now you have all date strings fully deserialized.
//usage
function parseBody(response) {
fixDateObjects(response);
console.log(response.someDate); // Mon Aug 30 2021 22:45:59 GMT+0200 (...)
// further processing
}
You could manually add all of the Date functions you require to the String.prototype.
String.prototype.getYear = function() {
return Date.parse(this).getYear();
};
var obj = {date: new Date()};
var dtObj = JSON.parse(JSON.stringify(obj));
console.log(dtObj.date.getYear());
Or you could override JSON.parse and have it loop through the result object looking for strings that match the time stamp regex and then convert them to Date objects.
var JSON_parse = JSON.parse;
JSON.parse = function(str) {
var res = JSON_parse(str);
findAndConvertStringsToDates(res);
return res;
}
EDIT Here's what I'd throw together for an implementation
(function() {
var jsonParse = JSON.parse;
var reDate = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/i;
function jsonDate(obj) {
var type = typeof(obj);
if(type == 'object') {
for(var p in obj)
if(obj.hasOwnProperty(p))
obj[p] = jsonDate(obj[p]);
return obj;
} else if(type == 'string' && reDate.test(obj)) {
return new Date(obj);
}
return obj;
}
JSON.parse = function(str) { return jsonDate(jsonParse(str)); }
})();
/*
* Tests
*/
var dt = JSON.parse(JSON.stringify({date: new Date()}));
console.log(typeof(dt.date));
console.log(JSON.parse(JSON.stringify(null)));
console.log(JSON.parse(JSON.stringify(123)));
console.log(JSON.parse(JSON.stringify("test")));
console.log(JSON.parse(JSON.stringify(new Date())));
console.log(JSON.parse(JSON.stringify([1,new Date(),2])));
console.log(JSON.parse(JSON.stringify({d: new Date(), d2: {d3: new Date(), d4: [0,new Date(),4]}})));
Is there a way to take a date object from a HTML object in the format of ####-##-## and convert it to epoch time. For example, the user inputs the value of August 12, 2012 which shows as 2012-08-12 when I print out the .val() of it, and I need to get this in Epoch time.
EDIT
Code to date:
if (hvStartDate == "") {
hvStartDate = "start"
}
else {
console.log($("#hv-start-date").val()); // => 2012-08-20
hvStartDate = new Date($("#hv-start-date").val()).getTime(); // => NaN
}
if (hvEndDate == "") {
hvEndDate = "end"
}
else {
hvEndDate = new Date($("#hv-end-date").val()).getTime(); // => NaN
}
var myTmp = new Date("2012-08-20");
console.log(myTmp.getTime()); // => NaN
Javascript's Date built-in allows you to pass a date string into its constructor, giving you a Date based on that string. From there, calling getTime( ) will give you the epoch time.
new Date($('.user-value').val()).getTime(); // => epoch time
new Date('2012-08-12').getTime(); // 1344729600000
Caveat: Beware of locale strings and locale-specific date formatting (for example, the position of days and months switch depending on locale).
EDIT: Based on your code in the comment below, here's what you need to do. Notice that you have to instantiate a new Date Object before calling getTime():
if (hvStartDate == "") {
hvStartDate = "start"
}
else {
hvStartDate = new Date($("#hv-start-date").val()).getTime();
}
Simply use the getTime() function. It returns the number of milliseconds since Epoch :
var msSinceEpoch = myDate.getTime();
Complete Date reference at MDN : https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date
EDIT : if you have to parse it too, you may :
use new Date(theString) if it has the good format
set yourself the different date fields (see reference) after having parsed it
use a date parsing library. I use this one : http://www.datejs.com/ which is very powerful for all date parsing, computing and formating.
I have two dates 18-Aug-2010 and 19-Aug-2010 of this format. How to find whether which date is greater?
You will need to create a custom parsing function to handle the format you want, and get date objects to compare, for example:
function customParse(str) {
var months = ['Jan','Feb','Mar','Apr','May','Jun',
'Jul','Aug','Sep','Oct','Nov','Dec'],
n = months.length, re = /(\d{2})-([a-z]{3})-(\d{4})/i, matches;
while(n--) { months[months[n]]=n; } // map month names to their index :)
matches = str.match(re); // extract date parts from string
return new Date(matches[3], months[matches[2]], matches[1]);
}
customParse("18-Aug-2010");
// "Wed Aug 18 2010 00:00:00"
customParse("19-Aug-2010") > customParse("18-Aug-2010");
// true
You can do the parsing manually, for your given format, but I'd suggest you use the date.js library to parse the dates to Date objects and then compare.
Check it out, its awesome!
And moreover, its a great addition to your js utility toolbox.
The native Date can parse "MMM+ dd yyyy", which gives:
function parseDMY(s){
return new Date(s.replace(/^(\d+)\W+(\w+)\W+/, '$2 $1 '));
}
+parseDMY('19-August-2010') == +new Date(2010, 7, 19) // true
parseDMY('18-Aug-2010') < parseDMY('19-Aug-2010') // true
Firstly, the 'dd-MMM-yyyy' format isn't an accepted input format of the Date constructor (it returns an "invalid date" object) so we need to parse this ourselves. Let's write a function to return a Date object from a string in this format.
function parseMyDate(s) {
var m = ['jan','feb','mar','apr','may','jun','jul','aug','sep','oct','nov','dec'];
var match = s.match(/(\d+)-([^.]+)-(\d+)/);
var date = match[1];
var monthText = match[2];
var year = match[3];
var month = m.indexOf(monthText.toLowerCase());
return new Date(year, month, date);
}
Date objects implicitly typecast to a number (milliseconds since 1970; epoch time) so you can compare using normal comparison operators:
if (parseMyDate(date1) > parseMyDate(date2)) ...
Update: IE10, FX30 (and likely more) will understand "18 Aug 2010" without the dashes - Chrome handles either
so Date.parse("18-Aug-2010".replace("/-/g," ")) works in these browsers (and more)
Live Demo
Hence
function compareDates(str1,str2) {
var d1 = Date.parse(str1.replace("/-/g," ")),
d2 = Date.parse(str2.replace("/-/g," "));
return d1<d2;
}