Using javascript/react native + redux i need to save timestamps in a time format that is aware of local time when saving the date, but unaware when reading the same date in another timezone.
Take a hypothetical user that travels west around the world in one day, saving a time stamp every hour, all i different timezones, all at 2021-01-30 at 18:00 hours local time. Resulting in 24 timestamps at 18:00.
Using a function in the likes of isWithinInterval from date-fns in a similar fashion as below, all timestamps should return true.
isWithinInterval(
timestamp,
{start: new Date(2021,0,30,17,30}, end: new Date(2021,0,30,18,30)},
) // -> true
How do one go about doing this? Preferrably in a react-redux compatiable (serializable) way. By default, your date object will be saved in UTC based on the local time on your device.
I had such a hard time to wrap my head around the date objects that it took a long time for me to understand that this problem is actually what "date-fns-tz" package is all about. This is how i'm currently solving it:
Create a utc-time identical to the local time
using:
import {zonedTimeToUtc} from "date-fns-tz"
const myDate = new Date() // eg 2021-02-03 10:00 UTC +5
const localTimeIdenticalUtc = zonedTimeToUtc(localDateObject, localTimeZone) // 2021-02-03 10:00 UTC +-0
When i want to use the UTC-date, i transfer it back to an identical localDate for whatever time zone i am in.
import {utcToZonedTime } from "date-fns-tz"
const zonedTimeToUtc(localTimeIdenticalUtc, localTimeZone) // 2021-02-03 10:00 UTC +5
Related
I have a problem in my application. I display date in this format DD/MM/YYYY example 09/07/2020. But the problem is in my form if the user lives in reunion island enters the date 10/07/2020 (the date of today plus one day), the user lives in France sees 09/07/2020.
How can do in javascript to have the same date enters by the user who lives anywhere.
I want to have this : User lives in Reunion island enters 10/07/2020 and the user lives in France sees 10/07/2020
This is a recurrent issue in JS, and the solution is not easy as Date is stored as local time in JS.
You can use an ISO string to input your time: YYYY-MM-DDTHH:mm:ss.sssZ.
Executing this:
new Date('2020-01-01T00:00:00.000Z')
Will give the same results.
If you can, i'll advice you to use Moment.js, it will you save a lot of effort if you plan to do date operations.
If you want to save absolute dates i would recommend you to use a library like moment-timezone and save the dates in utc.
In order to do this you should do two things:
Set the date in utc before sending it to the server:
const parseDateBeforeSend = (selectedDate) => {
const parsedDate = moment(selectedDate).tz('utc', true).startOf('day').toDate()
return parsedDate
}
Transform the date to utc before showing it to the user:
const showDate = (date) => {
const parsedDate = moment(date).tz('utc').format("DD/MM/YYYY")
return parsedDate
}
For your information:
When using tz function in step 1 you can see that it has a second parameter set to true, what this does is to keep the original time and only update the timezone. This would keep the original date you want to show.
In the second step we omit this parameter since we want to show the actual date saved in utc.
This question already has answers here:
When is it ok to store datetimes as local time rathen than UTC?
(1 answer)
How to store repeating dates keeping in mind Daylight Savings Time
(1 answer)
Managing timezone & DST issue for javascript application
(1 answer)
java Calendar, Date, and Time management for a multi-timezone application
(1 answer)
Closed 3 years ago.
I have a scheduling application, with a calendar that I build from scratch.
As a lawyer, you should be able to configure your available times for booking, like below:
A lawyer's availabilities in Australia
:
1- 10/01/2020, from 07:00am to 08:am
...
Here's what I do :
1- Get the epoch number of the entered date in Javascript :
const dateFrom = new Date(firstOfJanSevenAm).getTime() // 1578600000000
// Fri Jan 10 2020 07:00:00 GMT+1100 (Australian Eastern Daylight Time)
const dateTo = new Date(firstOfJanEightAm).getTime() // 1578603600000
// Fri Jan 10 2020 08:00:00 GMT+1100 (Australian Eastern Daylight Time)
2- Send this to NodeJS server and save it MongoDB
Mongoose.save({
from:dateFrom, //1578600000000
from:dateTo //1578603600000
})
3- Represent it inside the Calendar :
<div>{format(from, 'HH:mm')}</div>
Everything is working as expected.
Now, this lawyer is traveling to the US and he's in a coffee shop using the US local time ( any city), he opens the Calendar, he wants to add some availability, but in Sydney time. I need to provide him with a timezone dropdown so he can tell me that he wants the new date to be based on his home, Syndey.
Question :
1- Do I save the date as I'm doing ( as a number ), and save the timeZone separately next to it, and when representing it, just apply the timeZone?
Mongoose.save({
from:dateFrom, //1578600000000
from:dateFrom //1578603600000
currentTimeZone : 'America/Costa_Rica',
desiredTimeZone: 'Australia/Sydney'
})
<div>{formatWithTimeZone(from, 'HH:mm',desiredTimeZone)}</div>
Is this all I have to do? Or am I naively missing something that is going to trip me down the road?
And back to my original questions, where do I do the whole "always store time as UTC" thing?
All I've realized is, when I use the library that I'm using date-fns-tz and try to convert the user entered date to UTC, I get exactly the same output :
const dateFrom = new Date(firstOfJanSevenAm).getTime() // 1578600000000
const dateFromUTC = zonedTimeToUtc(dateFrom,currentTimeZone) // 1578600000000
// currentTimeZone is America/Costa_Rica, when he is in Costa Rica's caffee shop.
enter code here
1578600000000 === 1578600000000 = true
So why do I get the same output when converting the entered date, to it's UTC date?
I am going to give you an answer decoupled from the technical implementation.
Let's think by contradiction, you have a lawyer living in Australia and another living in Switzerland, what happens if you decide to store time in their preferred location?
You then need to save two information: the time of course (11 am) but it's relative so you also need to store the timezone (11 am in Australia) or (1 pm in Switzerland)
Now what happens if your lawyer travels to France? Do you want to update all his calendar information? 11 am is not 11 am anymore.
UTC solves this problem, you just need to store 11 am or 1 pm. UTC is arbitrary absolute, universal by convention.
So your backend/database should store this kind of information in UTC, always. And you need a way to know your user's timezone, maybe it's possible to update it using a web interface and it could be stored in a user database or just cookies. Maybe you want to derive it yourself by using your user's location, IP, browser's language, whatever.
Now that you know his timezone, your server can send UTC (absolute) time to the client and the client can seamlessly display the correct (relative) time.
The client deals with relative time, the server always makes it absolute.
So why do I get the same output when converting the entered date, to it's utc date?
The value returned by getTime is the number of milliseconds since 1 January 1970 00:00:00 according to universal time.
Therefore, UTC and getTime both represent a moment in time using the same universal timezone.
Do I save the date as I'm doing, and save the timeZone separately next to it, and when representing it, just apply the timeZone?
Don't save the timezone with the date. Just save the date in universal time. If you need to save timezone. That should go in user settings. However, you can guess the timezone; therefore, you don't need to save this information.
Saving the date
When you do save the date to the database, it should be represented by a UTC time string or by a UNIX timestamp, both options demonstrated below.
UNIX timestamp
UNIX timestamp should be in seconds. getTime returns milliseconds. You can just divide by 1000 to get the UNIX timestamp.
const unixTimestamp = new Date('December 17, 1995 03:24:00').getTime() / 1000;
UTC Date string
const utcDate = new Date('December 17, 1995 03:24:00').toUTCString();
Displaying the date
When you get the date from the back-end, then, converted it to the correct timezone.
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone
const fromUnix = utcToZonedTime(1578423483, timezone)
const fromUtc = utcToZonedTime('Fri, 02 Feb 1996 03:04:05 GMT', timezone)
Closing thoughts
Dealing with timezones can be confusing. I am not to familiar with date-fns-tz. If you have the option, I would suggest migrating to Moment.js. Moment.js is the de facto standard JavaScript library these days - I highly recommend it.
I've got a form where I input an event that starts at a certain time. Let's say 9am.
To assign a date/time object I'm using MomentJs. The issue comes when displaying it in different time-zones.
In London will show up 9am as intended - in Kiev will show 11am.
How can I make MomentJS and the browser ignore which timezone is relevant for the user, and just displaying the time I'm giving?
Here's my code:
<p>
Start time:
{moment(event.startDate).format("HH:mm")}
</p>
Assuming you have stored the date as utc (which in this case you probably should have), you could use the following:
moment.utc(event.startDate).format("HH:mm")
Let me provide an alternative answer in Vanilla JavaScript. If you want to make it timezone 'neutral', you can first convert it to UTC using toISOString().
const current = new Date();
const utcCurrent = current.toISOString();
console.log(utcCurrent);
If you want to convert it to a specific timezone, such as London, you can use toLocaleString(). Do take note of the browser support for the timezone though.
const londonTime = new Date().toLocaleString('en-US', { timeZone: 'Europe/London' })
console.log(londonTime);
What you want is a normalized Datetime. This can get a little confusing since the concept of timezones is a rather arbitrary construct.
I like to think of Datetime values as "absolute" and "relative". An "absolute" Datetime is one that is true regardless of which timezone you're in. The most common example of these are UTC(+000) and UNIX Time (also known as Unix epoch, POSIX Time or Unix Timestampe).
UTC is pretty obvious. Its the current time at +000 timezone. UNIX time is a bit more interesting. It represents the number of seconds that have elapsed since January 1, 1970.
You should always store data, in both client and backend, as an "absolute" time. My preference is UNIX time since its represented as a single integer (nice and clean).
moment.js does this for you. When you instantiate your moment object, you can use:
var date = moment.utc(utcString)
or for Unix Time
var date = moment.unix(unixInt)
You can then use this object to display the date in any form you wish:
console.log(date.tz.("America/Toronto"))
The only way I could solve this is by removing the timezone and milliseconds info from the string. I used date-fns lib but I imagine moment will work the same way.
import { format } from 'date-fns'
const myDateTimeString = '2022-02-22T19:55:00.000+01:00'
const dateTimeWithoutTimezone = myDateTimeString.slice(0, 16) // <- 2022-02-22T19:55
format(new Date(dateTimeWithoutTimezone), 'HH:mm')
I use moment.js to display a UTC date in the users local timezone:
var date = new Date(Date.UTC(2016,03,30,0,0,0));
var now = new Date();
var diff = (date.getTime()/1000) - (now.getTime()/1000);
var textnode = document.createTextNode(moment(date).format('dddd, DD.MM.YYYY') + ' a las ' + moment(date).format('HH:mm A'));
document.getElementsByClassName("date")[0].appendChild(textnode.cloneNode(true));
I later use the diff variable to show a countdown timer.
I would like to show a different countdown timer to everyone in their local time zone. (Using the difference till its midnight in their time zone, not in UTC)
But I am struggeling to get it work. Instead of using var date = new Date(Date.UTC(2016,03,30,0,0,0)); I probably need to use some function of moment.js that gives me till midnight in the users time zone.
The best example would be new years eve. If I use UTC everyone would have the same counter (9 hours left) but in different parts of the world this wouldn't make sense. For someone in australia it should be 2 hours left, and for someone in the US 14 hours.
I'm not sure that I fully understand your question, but I'll give you some general advice and tips.
When using moment.js, there is very little need to ever use the Date object. Only use it for interacting with other APIs that expect a Date object.
To get a moment in UTC, just use moment.utc(...), passing the appropriate arguments, such as moment.utc([2016,3,30]) or moment.utc('2016-04-30') for midnight April 30th UTC.
If you want to convert that back to the user's local time, use the .local() function. For example, moment.utc('2016-04-30').local() will create a moment with the equivalent local time to the UTC time provided.
If you want a moment in the user's local time, then that would be moment(...), such as moment([2016,3,30]) or moment('2016-04-30') for midnight April 30th local time.
You can difference two moments using the diff function, which can give the answer in specific units, such as m1.diff(m2, 'seconds') where m1 and m2 are moment objects.
You don't need to call format twice. Just encapsulate any text you want outputed with square brackets. .format('dddd, DD.MM.YYYY [a las] HH:mm A')
You might look into moment's locale support. If I'm not mistaken, "a las" indicates Spanish, however it's not always "a las", but sometimes "a la", if the hour is 1. Also, moment only uses those words in its .calendar() function, such as when producing a phrase like "mañana a las 13:17". A regular date formatted with .format('LLLL') in the Spanish locale would be something like: "sábado, 19 de marzo de 2016 13:17". So, you might want to verify that "a las" is exactly what you want in every case.
The title to this question was how to set a date to midnight. For that, I recommend using moment's startOf function. m.startOf('day') will give set the moment m to the start of the day, which is usually midnight. Keep in mind that not every local day actually starts at midnight in every time zone. Due to anomalies like daylight saving time, some days might start at 1:00. For example, this occurs in Brazil on October 16th this year.
Also, if you created the moment in UTC mode, you may wish to convert it back to local mode first before setting it to the start of the day. If you don't want to change the original moment object, be sure to clone it first.
Putting this all together:
var m1 = moment.utc([2016,3,30]);
var m2 = m1.clone().local().startOf('day');
var now = moment();
var diff = m1.diff(now, 'seconds');
Sorry if the title is a little convoluted. I'm bashing my head against the floor with times in NodeJS / Javascript. I can get the current UTC time like this:
var currentTime = Date.now();
I can get the current time for a user who is, for example, in the -3 timezone like this:
var offsetTime = Date.now() + (numTimeZone * 3600000);
But how do I get the local user time at, say, 6am, converted to UTC?
Practical application:
What I'm trying to do is create an auto-emailer which sends an email to a user at 6am in their local time. My server is in one timezone and they will be in another, so I'm trying to standardise it against UTC so every minute I can set my server to check the currentUTC time, then check what the user's 6am time is converted to UTC (local6am), and if the currentUTC > local6am then an email should be sent.
What's the best way to achieve this? Preferably without using a library if possible.
Utc to Local
moment.utc('2014-02-19 05:24:32 AM').toDate();
Local to utc
Read this documentation.
MomentJS is parsing the date as a locale date-time. If no hour is given, it is assuming midnight.
Then, you convert it to UTC, so it is shifted, according to your local time, forward or backwards. If your are in UTC+N, then you will get the previous date.
moment(new Date('02-19-2014')).utc().format("YYYY-MM-DD HH:mm").toString()
moment(new Date('02-19-2014 12:00')).utc().format("YYYY-MM-DD HH:mm").toString()
(or)
You can try this:
moment.utc('07-18-2013', 'MM-DD-YYYY')
moment.utc('07-18-2013', 'MM-DD-YYYY').format('YYYY-MM-DD')
You do not need to call toString explicitly.