I am working on a very small JavaScript library that allows users to retrieve content based on date. For this content, the date is just an identifier and timezones are completely irrelevant (think along the lines of a Dilbert flip calendar). The "May 14th" content is the same whether you are in the United States or Australia.
The function for retrieving data currently takes a Date object as a parameter. Within the function, the timezone is ignored. Does this approach make sense, or would it be better to take a timezone-independent identifier (like 2012-01-01) as a parameter instead? With the Date object approach, do I run the risk of returning the wrong data as a result of timezone adjustments browsers make?
How about using Date.getUTC*() functions? UTC time is the same for everyone.
After doing some research, it appears that simply ignoring the timezone information is the best approach. Why? This will always preserve the date and time that were provided to the Date constructor (which is my goal), whereas the getUTC* methods will return altered versions of the date and time. For example, take a look at this node REPL session I ran on a computer in the Eastern Time zone.
> d = new Date(2013, 03, 27, 23, 00, 00)
Sat Apr 27 2013 23:00:00 GMT-0400 (EDT)
> d.getDate() // The same date provided in the constructor. Woo!
27
> d.getUTCDate() // A different date. Boo!
28
Long story short, if you want to read the exact date and time that were provided in the Date constructor, using the normal get* methods (like getDate) will do that. If you use the getUTC* methods (like getUTCDate) modified versions of the date and time will be returned.
I know this may sound rudimentary to some of the more experienced programmers out there, but this really helped me make sense of things. I hope it helps others who come along.
The only problem with the approach in your own answer is that it doesn't account for ambiguous times. This happens during daylight savings fall-back transitions.
For example, set your computer's time zone to US Mountain Time ("Mountain Time (US & Canada)" on windows, or "America/Denver" on mac/linux). Then restart your browser and run the following javascript:
var dt = new Date(2013,10,3,1,0);
alert(dt);
This is November 3rd, 2013 at 1:00 AM. But you don't know which 1:00 AM it is representing. Is it in Mountain Daylight Time (UTC-6) before the transition, or Mountain Standard Time (UTC-7) after? There's no way to tell, and JavaScript will just use the standard time.
Now if all you need is 2013-11-03 01:00 - then you are correct. You can just ignore the offset and be done with it. But if you are going to use that value for anything meaningful - such as recording a point in time, or subtracting from another point in time for duration between them, then you have a problem that you can't resolve without the offset.
Unfortunately, there is no great solution for this problem in JavaScript. The closest thing is Moment.js, but it is still not perfect yet. Still, it is better than the Date object by itself, because it gets around browser inconsistencies and provides better parsing and formatting.
Related
I had an issue where dates were out by one day when displaying on an asp.net web form. These dates are only used for display so I can pass them as strings to resolved the issue, but I'm curious about why I'm seeing this behaviour.
I'm in Ireland, and while Ireland is more or less in line with GMT, we use IST (Irish Standard Time) during summer instead of DST and then revert to GMT for Winter. This has the same effect as being on GMT, but "officially" is slightly different.
As we're not on GMT, in the past, IST and DST didn't always line up.
For example, in 1958, IST started in the 20th April and ended on the 5th October whereas, DST started on the 27th of April and ended on 26th of October.
So if a date between the 5th and 26th of October 1958 is passed to JS, JS will display it as the previous day.
I wrote this this code to try and understand what's going on:
DateTime date = new DateTime(1958, 10, 4);
while (date <= new DateTime(1958, 10, 30))
{
Console.WriteLine($"normal : {date} | isDst? : {date.IsDaylightSavingTime()}");
Console.WriteLine($"universal: {date.ToUniversalTime()} | isDst? : {date.ToUniversalTime().IsDaylightSavingTime()}");
Console.WriteLine($"local : {date.ToLocalTime()} | isDst? : {date.ToLocalTime().IsDaylightSavingTime()}");
Console.WriteLine("-------------------------");
date = date.AddDays(1);
}
Which produced this output (truncated):
So I can see there are a number of days being mis identified as DST days, but it doesn't seem like that would cause this? If both .Net and JS though they were DST days, then surely the end result should be correct?
Additionally, why is there a 2 hour difference between the output of ToUniversalTime and ToLocalTime during DST?
Here's a screenshot of JS processing a few dates duirng this problematic window
You can see that JS (or chrome?) is aware that during the 5th to the 27th of that year, Ireland is no longer on GMT+1 (even though it still says it's IST) so why is the date passed from VB an incorrect date? I though they both got their datetime information from the same source i.e. the host pc?
You appear to be running .NET on Windows, in which case .NET is using the Windows time zone data for your local time zone.
The Windows time zone data does not have the full history of time zone changes from all time. Microsoft's policy only guarantees historical data is present for dates from 2010 forward, though some time zones have historical data before then.
Conversely, Chrome is using time zone data from ICU, which uses IANA time zone data. IANA time zones have historical data since at least 1970, though many time zones have historical data before then.
With specific regard to Ireland, IANA has Irish time zone data going back to 1880. Windows has no history for Ireland at all, so it assumes the current rule has always been in effect. In reality, the current rule has been in effect since Oct 1968, so any dates before then will only have accurate time zone information in the IANA data.
If you run the same .NET code you showed above on Linux or MacOS, you'll see that .NET will use IANA time zone data on those platforms and your results will match up for 1958. Or, if you pick a more recent date your results will match on Windows too.
In short - don't run these sorts of tests on old dates and expect to get the same fidelity that you'll get with modern dates.
You also asked:
Additionally, why is there a 2 hour difference between the output of ToUniversalTime and ToLocalTime during DST?
Your date variables are all DateTime where .Kind is DateTimeKind.Unspecified. The IsDaylightSavingTime method will treat such values as if they belonged to the local time zone, as if they actually had DateTimeKind.Local. The same is true for the ToUniversalTime method, however, the ToLocalTime method will assume that DateTime values with DateTimeKind.Unspecified kind are actually in terms of UTC - as if they were DateTimeKind.Utc. Thus, when DST is in effect, date.ToUniversalTime() shifts an hour backward, and date.ToLocalTime() shifts an hour forward.
You can avoid such ambiguities by using DateTimeOffset instead of DateTime.
Take the date '2022-04-01' and another date '2022-05-15' for example. When I calculated their deviation in Chrome devtools, what I got is:
The result is 3801600000. But when my friend did the same thing in another device, what he got is:
The result is 3798000000. The difference between 3801600000 and 3798000000 is exactly one hour. What may causes this result? How can I eliminate this difference?
You lack the zone data:
UTC datetime: new Date("2021-01-01T12:00:00Z");
UTC-4 datetime: new Date("2021-01-01T12:00:00-04:00");
The main issue you are experiencing is because your input string is being interpreted as assigned to the local time zone, and you have different time zones on the two machines you've been testing with. One of them has a DST transition between the two dates, and the other does not - which explains your one hour difference.
The examples you show also reveal your possible time zones:
Your machine is showing 8 hours ahead of UTC (UTC+8) for both timestamps. There are several parts of the world that use UTC+8 without DST, including China, Western Australia, Irkutsk Russia, and many others. There are no places on earth that use UTC+8 in conjunction with DST.
Your friend's machine is a different story. The timestamps are showing 2 hours ahead of UTC (UTC+2) on 2022-04-01, and 3 hours ahead of UTC (UTC+3) on 2022-05-15. While many countries use those offsets (such as those that are in Eastern Europe that use EET/EEST), none of those areas have a DST transition between those two dates. See the bottom of this table of DST transitions. All of the +2/+3 areas of the world transitioned in March. I can only conclude that your friend's machine has some non-standard time zone setting, or they are significantly behind on time zone data updates, or both. (Please reply in comments if I am incorrect on this!)
Also, your input string format, 2022-04-01 00:00:00 is not in the standard date time string format defined by the ECMAScript specification. Thus, it is not guaranteed to be parsed consistently by all browsers. Current versions of Chrome, Edge, and Firefox will interpret it as a local date and time, but the current version of Safari will fail with "Invalid Date".
If you want it interpreted as local time correctly in all browsers, you would need to specify it as 2044-04-01T00:00:00.
If you want it interpreted as UTC then specify as 2044-04-01T00:00:00Z.
If you want it interpreted with a specific time zone offset, then append that instead, as in: 2044-04-01T00:00:00+08:00.
If you must parse the string in the original format, then don't do it using the Date object. Either use a library (such as Luxon or date-fns), or parse it yourself with regex and/or string manipulation techniques.
I'm working on a React.js project that handles lots of comparisons related to DateTime (comparing the hour, the month, the year and so forth to dates retrieved from an API). For this specific React.js application, I would like to always consider DateTimes (from new Date()) as if the user was in the server timezone. Assuming that the server is in "Europe/Berlin" timezone, I would like that at any point in the application, calling new Date('2019-01-01') would retrieve me a DateTime that refers to this time in the "Europe/Berlin" timezone, that would be Tue Jan 01 2019 01:00:00 GMT+0100 (Central European Standard Time). If I set my computer Date and Time as if I was in Brazil, for instance, I get Mon Dec 31 2018 22:00:00 GMT-0200 (Brasilia Summer Time), but would like to get the same as before. The main reason why this is a problem is that we extract data from these DateTimes such as .getHours(), .getDate(), etc.
This would fit this specific application because it's really important for this project that we only support the server Timezone, no matter where the user is. So to keep consistent with the server time, the call new Date('2019-01-01').getDate() should return 1, since it will be 1st January in Berlin. However, if the user is in Brazil, this same call will return 31, as Brazil is some hours before GMT, when it's midnight in GMT time, it will be still the previous day in Brazil.
I tried first to use the date-fns along with date-fns-timezone to set the DateTime to display the dates and times to the client considering the server timezone. That worked fine to display the right data but didn't solve some issues that are caused due to these attributes extraction from the date and that will vary depending on where the user is.
So that's why what I'm trying to do now is override the new Date() method in a way that it will always retrieve the date as if the user was in the server time. I haven't managed to get how it can be done. Whenever I change the Date and Time from my computer, the output for the new Date() already takes into account this new date and time settings.
So how can I force the new Date() to always give back the DateTime with a hardcoded timezone? Also, it would be really good if I could do it without using external libs (like moment.js, for instance), and do it only with plain Javascript.
I was taking a look into the window.navigator variable to see if I could set this one to force the Date to the server timezone, but it doesn't look like that will be the way to solve my issue.
I looked a lot for this answer and didn't find any question that was really close to this one. I'll just list here some of the questions I looked before and why my case differs from them.
1- new Date() for a specific timezone in JavaScript: In this case, the moment is used, and there was no way to accomplish this overriding for the new Date() itself.
2- Convert string to date without considering timezone: In this one, the answer gives back a string with the date formatted to the desired timezone, but not a Date object itself.
3- javascript date considering Request.UserLanguages[0]: also in this one, the question/answer is about formatting the date output rather than retrieving the Date object itself with the new timezone.
4- How do I combine moment.js timezone with toDate to build a new date object?: in this one the answer also is about using moment.js, what I would like to avoid since what I really want to achieve is overriding the new Date() method.
A few things:
Generally speaking, one should try to design their applications such that the server's time zone is not relevant at all. This means only relying on the server's UTC functionality, and working with specific named time zones. Asking for the local time of a server tends to become problematic, especially when dealing with environments where you may not have full control of the server.
Keep in mind that the Date object in JavaScript is misnamed. It is really a timestamp. Essentially it is just an object wrapper around the value you get with .valueOf() or .getTime() or when you coerce it to a number. Everything function on the Date object simply reads this value, applies some logic (which may or may not use the local time zone depending on the function), and emits some result. Similarly, the constructors of the Date object interpret your input and then assign this number in the resulting object. In other words, the Date object does not keep track of a time zone at all. It simply applies it when it is called for. Thus, one cannot change it.
When you pass a string in yyyy-MM-dd format to the Date constructor, per the ECMAScript specification it is interpreted as midnight UTC, not as midnight local time. This is a deviation from ISO 8601, and such often confuses people.
The strings you showed as output like Tue Jan 01 2019 01:00:00 GMT+0100 (Central European Standard Time) are emitted in local time, and are the result of either calling the .toString() function, or by passing a Date object to console.log in some environments. Other environments show the result of .toISOString(), which is emitted in UTC.
There's no global in window.navigator or elsewhere that can change the local time zone. In Node.js apps, if you really need to set the server's time zone globally, you can set the TZ environment variable before launching Node. However, this doesn't work in Windows environments, and doesn't help for browsers.
You're on track with using a library such as date-fns with date-fns-timezone. There are other libraries as well, which are listed in this answer. Alternatively, you could call .toLocaleString with the timeZone option, such as:
new Date().toLocaleString("en-US", {timeZone: "America/New_York"})
This API provides functionality for emitting a string in a particular time zone, but it does not do anything for applying a time zone to an input string.
So ultimately, to answer your question:
So how can I force the new Date() to always give back the DateTime with a hardcoded timezone?
Sorry, you can't. You can either use a different object to track the time zone statefully, or you can use functions that take the time zone as a parameter. Both are things offered by existing library, but only the one function I showed above is currently built in to JavaScript.
In case someone else has this same problem, here is the approach that I followed to solve the issue:
As in this specific case I really need to compare and group some entities based on date, among other operations (always considering the server timezone), I ended up choosing moment.js along with moment-timezone. The native Date class would not solve it since it has no way to handle different timezones. Then, I tried to use first date-fns and date-fsn-timezone, but as these libs always use the standard Date class, it gets back to the problem that a Date in javascript is only a timestamp and has no clue about other timezones other than the one in the client (the application is a React.js one).
Just to show one example about the issue, consider that the server is placed in Europe/Berlin timezone, and one stamp is retrieved as 2019-04-30T02:00:00+02:00. On the React.js application, I want to group this stamp to all the stamps that are also from 2019-04-30. However, if the browser is in Brazil/Brasilia time, the call new Date('2019-04-30T02:00:00+02:00').getDate() will give me 29 as return, since in the client timezone, this same timestamp will represent 2019-04-29T09:00:00-03:00.
Using moment and moment-timezone it could be fixed by defining in which timezone I want to retrieve the values, such as:
moment('2019-04-30T02:00:00+02:00').tz('Europe/Berlin').date()
// retrieves 30 independent on the client timezone.
Thank you very much to all who contributed with answers and suggestions. You all helped me to find the path to solve this issue.
This is the strangest behavior I have witnessed in JavaScript.
Under a very specific set of circumstances, this code will yield a time that's 40 minutes and 28 seconds earlier:
var jsDate = new Date('01/01/1900 11:00:00');
jsDate.setSeconds(0);
var dateString = jsDate.toLocaleTimeString("en", {
hour12 : false
});
alert(dateString); //dateString will be 10:19:32
This happens for one site in the Netherlands but not for our developer there. It happens on Firefox and Chrome. The workstation is Windows 7.
Testing it, I found that the broken result happens for any year prior to 1942.
For 1943 and 1944, it adds an hour.
Every year after that works fine, regardless of the date format: 01/01/1900 and 1900-01-01.
Background for the curious:
We have a date time widget and are only interested in the time portion.
The fix is to set the dummy date to 2000. But we are boggled about the "why".
Link to jsFiddle
This issue is not related to UTC vs Localized time. The strangest feature is that it alters the time by minutes and seconds, not hours.
I'll stick to your Netherlands example that was returning 10:19:32, and address the why part of this question.
The time zone database entry for Europe/Amsterdam is here, and looks like this:
# Zone NAME GMTOFF RULES FORMAT [UNTIL]
Zone Europe/Amsterdam 0:19:32 - LMT 1835
0:19:32 Neth %s 1937 Jul 1
0:20 Neth +0020/+0120 1940 May 16 0:00
1:00 C-Eur CE%sT 1945 Apr 2 2:00
1:00 Neth CE%sT 1977
1:00 EU CE%sT
Since you passed a date in 1900, the second line applies, which has an offset from UTC of 0:19:32, which happens to coincide with the Local Mean Time (LMT) entry above it.
As Chris Pearce explains in The Great Daylight Saving Time Controversy:
... All this was in a country that hadn't yet adopted standard time. Controversy had raged in the Netherlands over whether it should use GMT and turn clocks back about 20 minutes or use GMT+1 and go forward 40 minutes. Agreement couldn't be reached, so they stayed on local time long after all other European countries had moved to standard time. Local mean time of capital city Amsterdam, GMT+0:19:32, had been used nationwide for decades and this became the legal time ...
With regard to other time zones, you're likely hitting on their own interesting bits of history, or just on the Local Mean Time entry in the TZDB, which is usually the earliest entry and is used when there is no other history known about timekeeping in the region.
The lesson here is that time zones are a relatively modern invention, and have often been a point of political controversy. Don't assume that because you know how they work today that your knowledge will apply to the past. Keep in mind also that as various tidbits of history are uncovered, the TZDB is updated accordingly, so indeed history can change!
In general, expect to encounter oddities for any dates before 1970.
As to why you don't get this same result in every browser, see my oldish blog post JavaScript Date type is horribly broken, which among other things describes that ECMAScript 5.1 and prior required TZ/DST rules to ignore the history of timekeeping and assume the current rule always existed. This was fixed in ECMAScript 6, and in the ECMA Internationalization API - so most modern browsers will give you the correct result.
However, it also depends on where they source their time zone data. Windows computers don't have the full history of the tzdb, unless the browser ships their own copy of it. Browsers that use ICU for this functionality do ship their own copy of the TZDB, so those would have the historical data, but not every browser does this.
Am seeking confirmation if this is a bona fide documentation and/or implementation bug with Javascript's Date.parse method.
The docs I'm referring to are at https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Date/parse and they say 'If you do not specify a time zone, the local time zone is assumed.'
But the following code shows that, despite not specifying a time zone, local time is not being assumed (but rather my timezone offset is being applied), if the string passed to Date.parse begins with the 4-digit year representation, and is dash-delimited.
var euroStyleDate = '2011-10-04';
var amerStyleDate = '10/04/2011';
var euroStyleParsed = Date.parse(euroStyleDate);
var amerStyleParsed = Date.parse(amerStyleDate);
console.log(euroStyleParsed); //1317686400000
console.log(amerStyleParsed); //1317700800000
console.log(new Date(euroStyleParsed));
//Date {Mon Oct 03 2011 20:00:00 GMT-0400 (Eastern Daylight Time)}
console.log(new Date(amerStyleParsed));
//Date {Tue Oct 04 2011 00:00:00 GMT-0400 (Eastern Daylight Time)}
There may even be other cases, and I'm sure I'm not the first to discover this if I am incorrect. So beyond confirmation, I'd surely love to be pointed at more in-depth information on this if anybody knows of pertinent links.
I'm experiencing this in FF3, Chrome for Windows and of course just to be special IE8 doesn't even seem to able to perform the conversion on 2011-10-04 whatsoever: I'm just getting an empty string in my application
Thanks in advance for any further insight or resources.
I ran into this concept, too. For anyone googling "Javascript dates dashes slashes" like I was, this is the clearest demonstration that I can think of as to what's going on here.
In short, slashes means local time zone, and dashes means UTC. Other answers has explanations regarding why.
<script type="text/javascript">
var
testB = new Date("2012/02/09"),
testC = new Date("2012-02-09");
alert(testB.toString());
alert(testC.toString());
alert(testC.toUTCString());
</script>
**Update:**It looks like there are several different standards at work here:
The EMCAScript < 5 standard allowed for dates in the standard IETF format, e.g. Sun Oct 03 2010. With these dates, the local timezone is assumed.
In ECMAScript 5, a limited version of the ISO 8601 standard is also allowed, e.g. 2010-10-03. The spec seems to say (perhaps following ISO 8601?) that in this case, the UTC timezone is assumed if one is not specified.
I haven't found a spec that says Date.parse can handle mm/dd/yyyy dates, but my browser (Chrome 14) clearly can, and probably other browsers can too. This appears to follow standard 1 above, assuming the local timezone. Given that it's not in the spec, however, I would recommend not using this version, as it's likely to be browser-dependent (and I have no idea whether 10-03-2010 would result in a different date if I had a European locale set on my browser).
There are a few issues with the native Date.parse function in most interpreters - I have often had timezone issues like the one you describe. So in general, I either use a library like Datejs or I write my own parsing functions. The DateTime module of the SIMILE AJAX library has a pretty good example function for parsing ISO-8601 dates (what you're referring to as euroStyleDate, plus an optional time component).
When setting dates, I generally use new Date() and then use the setUTC*() functions to set the different date elements to my desired precision. It's not perfect, but at least you're dealing with a clear timezone.