How to convert timestamp, depends on user timezone? - javascript

From server I'm getting timestamp like this: " 2022-12-21 16:47:10 ". And I want to convert this time to local time zone, depends on client. E.g. 16:47:10 in Poland was 10am in US. Any ideas how to achieve that? I'm using Vue framework.

From server I'm getting timestamp like this: "2022-12-21 16:47:10"
That represents a date and a time without any time zone or offset. There's no way to tell that it is from Poland from this data alone.
Thus, the first part of your solution would be to change the server-side code to do one of the following:
Emit the time in terms of UTC. For example: "2022-12-21T15:47:10Z". In many cases this is the best choice, especially if your timestamps don't have any meaningful relationship to a local time zone.
Emit the time in terms of a local time, including the time zone offset for that point in time in that time zone. For example, if indeed the value is from Poland, then the server should emit "2022-12-21T16:47:10+01:00" because Poland is one hour ahead of UTC at that date and time.
Emit the time in terms of local time, but include a time zone identifier in a separate field. For example:
{
"datetime" : "2022-12-21T16:47:10",
"timezone" : "Europe/Warsaw",
}
However, this approach could have ambiguities during a backward transition, such as when daylight saving time ends.
Combine the previous two options to resolve ambiguities:
{
"datetime" : "2022-12-21T16:47:10+01:00",
"timezone" : "Europe/Warsaw",
}
This is the most complete form of the data, but generally should only be necessary if your use case is related to scheduling of future events.
For more on use cases for the options above, read DateTime vs DateTimeOffset, which was written for .NET but applies here as well.
As far as the client-side JavaScript goes, for either of the first two options, you can pass the inputs directly to the Date object's constructor, then use methods like toString or toLocaleString. You can also use that approach for the datetime portion of the fourth option.
For the third option though, you'll need to use a library such as Luxon to handle the input time zone identifier. The Date object cannot accept a time zone identifier as input presently. (There is a timeZone option on toLocaleString, but that is for output, not input.)
For example:
const dt1 = luxon.DateTime.fromISO("2022-12-21T16:47:10", {zone: "Europe/Warsaw"});
const dt2 = dt1.toLocal();
console.log(dt2.toString());
<script src="https://cdnjs.cloudflare.com/ajax/libs/luxon/3.1.1/luxon.min.js"></script>

Related

Moment.js to convert to correct timezone

I'm trying to convert my date to the correct time zone with moment.js. However, I always get the whole thing without a time specification.
Here is my program code:
console.log(von);
console.log(bis);
var nVon = moment.tz(von, "Europe/Berlin");
var nBis = moment.tz(bis, "Europe/Berlin");
console.log(nVon.format());
console.log(nBis.format());
This is what I see in the console:
2022-10-31T00:00:00+01:00
And here the original German format, which I want to save in the correct time zone in MongoDb.:
The problem is that it is saved in MongoDB with an hour loss of time like this, without UTC etc.: 2022-10-31T19:44:39.000+00:00
Date values in MongoDB are stored as UTC times - always and only!
If you need to preserve the client input time zone, then you must store it in a separate field. Usually the client takes responsibility to display the MongoDB UTC times as local times.
NB, you should never store date values as string, it's a design flaw. Store always proper Date objects. Thus store
moment.tz(von, "Europe/Berlin").toDate()

How to guess user's timezone using date-fns?

I have a requirement, where I need to show a user's time in their local time zone. This needs to be done using date-fns.
The following code checks the current time and then given a timezone converts it into local time as per the time zone.
const { formatToTimeZone } = require('date-fns-timezone')
let date = new Date()
const format = 'D.M.YYYY HH:mm:ss [GMT]Z (z)'
const output = formatToTimeZone(date, format, { timeZone: 'Asia/Calcutta' })
However, how do I guess the user's timezone on the fly?
In moment.js, you can get it with moment.tz.guess(). But how can I do it without moment.js and by using date-fns?
https://runkit.com/embed/835tsn9a87so
UPDATE: I will be using this inside a VueJS application. So, if there are any other related solutions, those are welcomed as well. Thanks.
To get the user's IANA time zone identifier, most modern browsers now support the following:
Intl.DateTimeFormat().resolvedOptions().timeZone
That said, the usual reason you would need to use formatToTimeZone from date-fns-timezone is when you need to use a different time zone other than the user's local zone. Otherwise, you can usually just use the format function from date-fns.
However, in your case, you are also trying to use the z format specifier to display the user's time zone abbreviation. This isn't provided by date-fns directly, so if that is critical then you will indeed need to get the user's time zone with the Intl api shown above and use formatToTimeZone.
Do keep in mind though that these abbreviations are whatever the IANA data has recorded, which are in English only, and it doesn't have abbreviations for every time zone. For those that don't, you will see a numeric value instead, such as -02.
Also, many abbreviations can be ambiguous (such as the I in IST possibly meaning India, Israel, or Ireland, and many others...). Thus, in most cases, if you don't need the abbreviation, you're often better off without it.
Just solved a similar problem myself. The trick is to use the format function from date-fns-tz instead of the one from date-fns.
import { format } from "date-fns";
console.log(format(new Date(), "yyyy-MM-dd HH:mm z"));
// 2021-11-29 13:55 GMT-8
import { format } from "date-fns-tz";
console.log(format(new Date(), "yyyy-MM-dd HH:mm z"));
// 2021-11-29 13:55 PST
See documentation here:
https://date-fns.org/v2.27.0/docs/Time-Zones

Using Moment.js like PHP's date and strtotime

I'm a typically server side developer feeling a bit like a fish out of water trying to display time values on the front end. How can I get behavior like PHP's date() and strtotime() functions out of moment.js? I just want a unix timestamp to appear in H:i:s format, and vice versa.
So far I've tried the following, from existing example code and the documentation:
moment(timestamp).format(H:i:s);
moment().duration(timestamp).format(H:i:s);
moment.unix(timestamp).format(h:mm:ss);
moment(formatted,'H:i:s');
Not a SINGLE one of which has worked properly. This may get flagged as duplicate since there are plenty of moment.js questions out there, but I don't know whether it's been updates to the library itself or slightly different context, I have not found one existing solution that has worked for me.
Anybody have any suggestions for these two simple tasks?
EDIT:
I've distilled two different problems out of this. One is that functions the moment docs say should work are giving weird values:
moment(1437462000).format('h:mm:ss')
for instance, which should return 7:00:00 utc, returns 10:17:42. This can be fixed in this case by using moment.unix(1437462000).utc().format('h:mm:ss') instead, but this leads into the second problem - the .utc() function seems to get ignored when converting back from a date into a timestamp:
timestamp = moment(formatted,'DD/MM/YYYY H:m:s').utc().unix();
will still return a timezone corrected value (in my case this is incorrect by several hours since the formatted time in question has nothing to do with the client computer) regardless of whether the .utc() function is included or not.
A few things you should realize:
Unix timestamps should always in terms of UTC. They are never adjusted for time zone in numerical form. If they're adjusted for time zone, that's done during the interpretation of the number, not in its representation.
While traditionally a "Unix Timestamp" is in terms of seconds, many environments use milliseconds instead. PHP's date timestamps are based on seconds, while moment and JavaScript's Date object both use milliseconds by default. Using the moment.unix function will let you pass seconds, and is identical to just multiplying the timestamp by 1000.
Moment has two built-in modes, local and UTC. The default mode is local. It doesn't matter what input you provide, if you don't specify UTC, the moment is adjusted to local. To specify UTC, you use the utc function. There are two forms of the function:
moment.utc(input) // parsing form
moment(input).utc() // conversion form
Both forms take some input and result in a moment in UTC mode. The difference is in how the input is interpreted. In either case, if the input value is unambiguous, the result is the same. For strings, that means the input would contain either a Z (from ISO8601), or a UTC-based offset. All other forms are ambiguous. For example, if I pass "2015-11-08 01:23:45", I will get different results depending on whether I interpret that string as local time or as UTC.
For numbers, they are always interpreted as milliseconds in UTC. However, if you use moment(number) without then calling .utc() then the moment is left in local mode, so any output will display as local time.
When you call moment.unix(input), the input is a number of seconds, but the moment is left in local mode. So to display the UTC time, you would use moment.unix(input).utc().
If your pre-recorded timestamps from your other system are in numeric form, but have been adjusted away from UTC, then they are incorrect. You have bad data, and Moment can't help you unless you know specifically how they have deviated and you write code to counteract that.
Moment's formatters are case sensitive. M is months, m is minutes. H is hours on a 24-hour clock, h is hours on a 12-hour clock. Use two consecutive letters when you want to include zero-padding. Example, HH:mm:ss for 13:02:03 vs. h:m:s for 1:2:3.
Moment's X formatter does not care which mode the moment is in. It will always emit seconds in UTC. Likewise, the x formatter returns milliseconds in UTC, as does moment.valueOf().
Also, your last example:
moment.unix(1437462000).utc().format()
Returns "2015-07-21T07:00:00+00:00" - which I believe is the value you expected.
You also get the same original timestamp regardless of which of these you try:
moment.unix(1437462000).utc().format("X") // "1437462000"
moment.unix(1437462000).format("X") // "1437462000"
moment.unix(1437462000).utc().unix() // 1437462000
moment.unix(1437462000).unix() // 1437462000
For anyone who comes in and is still looking for direct PHP equivalents for date() and strtotime(), here are the ones I ended up using. Matching up to php basically means just completely ignoring any kind of local time information by making sure everything is in UTC. That task is a little different between the timestamp->date and date->timestamp cases, though, so you have to be careful.
date()
Converting a timestamp to formatted date without any client timezone correction
var formatted = moment.unix(timestamp).utc().format('h:mm:ss');
strtotime()
Converting a UTC formatted date back to a timestamp without correcting it to local time:
var new_timestamp = moment.utc(formatted_utc,'DD/MM/YYYY H:m:s').format('X')
//where 'DD/MM/YYYY H:m:s' is the formatted date's format, and
//'X' outputs a unix timestamp without milliseconds.
Notes:
Do not use moment() with parenthesis in the calls:
moment().utc(date,format) will return local time values, not your
input.
Moment.js does not like the use of 'i' for minutes in the formatting,
unlike php.

Unit testing handling of date inputs in JavaScript regardless of time zone

I have a form where a user can enter a date, i.e. <input type="date"> the value is submitted in yyyy-MM-dd format. When I create a Date object with the string it assumes the time zone is the one the user's browser is set to – this is the behavior I want.
I'm then using the date value to make queries against a REST API that expects ISO date/time strings. That's no problem as the toISOString function on the Date object handles everything correctly.
However, when I'm unit testing this code – setting my input to a yyyy-MM-dd string then asserting that the output is an expected ISO timestamp string the tests can only work in a particular time zone. Is there a way I can force the time zone in the test?
I've tried using Jasmine spies to do something like:
var fixedTime = moment().zone(60).toDate()
spyOn(window, 'Date').andCallFake(function() {
return fixedTime;
});
But given there are so many variants of the constructor and so many ways it gets called by moment.js this is pretty impractical and is getting me into infinite loops.
A JavaScript Date cannot be set to a particular time zone. It only knows about UTC and the computer's local time from the environment it is running on.
There are time zone libraries for javascript, but I don't think that will help you here.
First, understand that "ISO" refers to ISO8601, which is a specification that defines a collection of related formats, such as YYYY-MM-DDTHH:MM:SS.
It is a separate concept from UTC, which refers to Universal Coordinated Time. UTC is the timekeeping system that we all synchronize our clocks to, which uses GMT as its basis - that is, the time in effect at the prime meridian not adjusted for daylight saving time.
Put together, the Date.toISOString() method will return the UTC form of an ISO8601 formatted timestamp, such as 2013-09-20T01:23:45Z. The Z at the end indicates that the time is in UTC.
But a value such as 2013-09-20 is still ISO formatted - it's just that it only has precision to the whole day, which means that it can't carry any time zone information.
When you use <input type="date">, the resulting value is not a Date class. It's a string containing the ISO formatted YYYY-MM-DD. You should just pass this directly to your application.
Now if what you are looking for is the full date and time, at midnight in the local time zone, of the date selected, and adjusted to UTC, well that's a different story. It is certainly doable but you have to understand that it is not the same as just passing the calendar date.
The easiest way to do that would be with moment.js as follows:
var s = "2013-09-20"; // from your input's value property
var m = moment(s);
var result = m.toISOString(); // "2013-09-20T07:00:00.000Z"
The value is adjusted because my time zone offset is -07:00.
You can do it without moment, but you have to replace dashes with slashes or the original value will be interpreted as if it is already in UTC.
new Date(s.replace('-','/')).toISOString()

Using timezones with moment.js fromNow() or from()

I want to show users how long has been elapsed since they performed an action.
The date+time of the action happening is stored on the server, in the server's timezone. That's what's causing the trouble, since if the user's computer's timezone is 12 hours ahead of the server's timezone, then if the user adds something right now, moment.js will show '12 hours ago' as the output of fromNow() rather than just now.
To try to solve this, I'm trying the following method:
var actionTime = moment( action.timeStamp);//time of when user performed action
var serverTime = moment().zone('-07:00'); //current server time
console.debug( serverTime);//outputs Wed Sep 11 2013 15:19:51 GMT-0700
var timeAgo = serverTime.from( actionTime);
But despite of all this, timeAgo still shows the difference between the client's timezone and the server's timezone (i.e showing '12 hours ago' instead of 'now');
Anyone know how to fix this or what I'm doing wrong?
Ideally, you would want to pass a UTC timestamp from your server to the client. That doesn't mean you have to switch your whole server over to UTC, it just means that you would convert from the time in your database to UTC on the server before sending it over the web. Sure, it would be even better if you actually stored times in UTC, but you said you aren't in a position to make that sort of change right now. But let's just work off the assumption that you can't change anything at all on the server.
We'll also assume that your server is fixed to the UTC-07:00 offset. In real life, this would only be true for places like Arizona that don't follow daylight saving time. So if you are in Los Angeles and are in Pacific Time, then some of your data is based on UTC-07:00, but some of it is based on UTC-08:00. That requires a lot more work if you want to do it in JavaScript.
Let's also assume that the input is already a string in ISO8601 format. (If it's not, then let me know and I will adjust this code.)
var s = "2013-09-11 18:00:00"; // from action.timeStamp
var actionTime = moment(s + "-07:00", "YYYY-MM-DD HH:mm:ssZ");
var timeAgo = actionTime.fromNow();
The reason your other code didn't work is because in the first line, you are affected by the time zone of the browser. The zone setter in the second line just changes the zone for formatting, not changing the actual moment in time.
Also, when you dump a moment to the console for debugging, make sure you format it for output. Otherwise you are just looking at its internal property values, which may or may not make sense directly.
I have solved it in a different way, maybe this option was not possible back when the question was asked, but might be easier now.
I used moment-timezone.js (which requires moment.js 2.6.0+).
I set the default timezone to my server's timezone like this:
moment.tz.setDefault("America/New_York"); // "America/New_York" in my case
and then just use it normally. fromNow() will use the timezone in the client to calculate the time that has passed since that moment.
moment(myDatetime).fromNow();
i had the same issue and used the above comments to modify my code. I did the following to get it resolved:
transform(value: string) {
var time = moment(value).utc();
return moment(time, "YYYYMMDD").fromNow();
}
I was missing the .utc() to convert it before I applied the .fromNow()
Things to note this is being used within a Pipe for Ionic 3 and the above code is from the pipe logic.

Categories

Resources