I am using JavaScript and a PostgreSQL database, I would like to store birthdays and notify users at 12pm in their own timezone, currently I am converting dates from their timezone to my local server time and check every hour to see if a date and time matches
import { parseFromTimeZone } from "date-fns-timezone";
const userInput = "08-11" // day/month
const timeZone = "Europe/Amsterdam"
const date = parseFromTimeZone(`2000-${userInput} 00:00:00`, { timeZone });
// This is what I store in my database
const dateToStore = date.toISOString().slice("2000:".length).split(":")[0];
// This is what I run every hour
await Birthday.find({
where: {
date: new Date().toISOString().slice("year:".length).split(":")[0],
},
});
The problem is that this solution is not very dynamic because if I migrate my server it breaks, my questions are:
How can I store the birthdays? Assume users provide the month, day and time zone
In what interval can / should I check to see if a birthday message should be sent? (00:00) in the user's time zone and specified date
What would that check look like?
I have date-fns available but I do not mind using other libraries
I'd recommend a solution with a account table containing three fields:
birthday, which is of Postgres type date.
timezone, of Postgres type text. Here you'd store something like Europe/Amsterdam, with the important part is that it's something Postgres and your date libraries can all recognize as a time zone.
last_birthday_wish_sent_at of type timestamptz (shorthand for timestamp with time zone, which stores everything internally as UTC).
I've decoupled the birthday date from its timezone because remember that a user's birthday is always the same day anywhere in the world, even if they move around. So if my birthday is August 11th in Amsterdam, it's still August 11th if I move to San Francisco. Storing these components separately would allow you to reconfigure their timezone if they move.
I'd run a cron on the 0th minute of each hour that ran logic something like this (pseudocode, sorry):
for timezone in all timezones:
if > 12 PM in timezone:
for account in accounts in timezone:
if birthday <= today AND (last_birthday_wish_sent_at IS NULL OR last_birthday_wish_sent_at < now() - '1 year):
send birthday wish
set last_birthday_wish_sent_at = now()
The purpose of last_birthday_wish_sent_at is so that you can write an algorithm that's a bit dumber and more resilient (i.e. birthday wishes still get sent even if the cron fails one hour), but still make sure to never double-send a birthday wish for any given year.
It might also be safer to model this as a separate table where you track every birthday wish you've ever sent and the user and year you sent it for. This eliminates any potential for time bugs across year boundaries.
You'd want to model the account selection and filtering in the pseudocode above as SQL so that you're not returning result sets larger than necessary. Something like:
SELECT *
FROM account
WHERE timezone IN ('Europe/Amsterdam', ...)
-- note: actual date comparison a little more complicated than this
-- because you should make sure to compare the month and day components
-- only (probably with the `EXTRACT` function)
AND birthday <= NOW()
AND (
last_birthday_wish_sent IS NULL
OR last_birthday_wish_sent < NOW() - '1 year'::interval
);
And make sure there's appropriate indexes on timezone, birthday, and last_birthday_wish_sent.
You could also tighten up the logic around time zone checks: it's always turning 12 PM somewhere, but it's perfectly predictable as to where that's happening so it's not necessary to check every time zone every time. You could also potentially push this into Postgres and get the whole selection logic packaged up into a single query.
Related
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
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.
Here's the scenario:
1- User opens the website, and enters 07:00 am using a dropdown field, which will give me this :
// 1578600000000 => Save to DB
// Fri Jan 10 2020 07:00:00 GMT+1100 (Australian Eastern Daylight Time)
The user himself is in Sydney, which means his local clock is on GMT+1100
However, he wants to represent this time as Asia/Tehran time, because that's where he's going to be tomorrow. So essentially, he wants me to completely ignore his local time and see him as if he's in Asia/Tehran. So when he's in Tehran tomorrow, he can see his calendar has 07:00am.
On the other hand, are the people all over the world who will see his available time, let's say from Australia/Perth.
I thought something like the below work, as per momentJS documentation, but it doesn't.
First, convert the timezone to Asia/Tehran, which is the user's desired place:
const desiredTimeZone = 'Asia/Tehran';
let OriginalDesired = moment.tz(1578600000000,desiredTimeZone);
Then, when representing it to the people in Australia/Perth make sure it's in their timezone
const PerthTimeZone = 'Australia/Perth`; // this is dynamic, can be anything
OriginalDesired.clone().tz(PerthTimeZone);
I naively thought this should work. But I noticed the original timestamp 1578600000000 is a UTC timestamp, meaning it's not really 07:00am, it's actually 20:00pm, because Javascript
has subtracted 11 hours, which is the very original user's local timezone's offset, from the user entry.
I managed to work around it by adding and subtracting the offset in a dramatic way, but it only works in one scenario.
const originalTime = 1578600000000;
const LocalAdjustment = moment(originalTime).tz("Australia/Sydney").utcOffset() * 60000;
const DesiredAdjustment = moment(originalTime).tz("Asia/Tehran").utcOffset() * 60000;
const newUTC = originalTime + LocalAdjustment - DesiredAdjustment;
And when representing this to the user's in Tehran
moment(newUTC).tz("Asia/Tehran").format('hh:mma'); // 07:00am.
I know this is probably stupid and obviously only works in one scenario, but is this the right path that I'm going? or is there an easier way?
By the way, all the calculations are on my server, which is 'UTC'.
You said:
1- User opens the website, and enters 07:00 am using a dropdown field, which will give me this : // 1578600000000 ...
You've already lost. If the local time zone is not relevant, then don't write code that assumes that it is.
In other words, you probably have something along the lines of:
moment("2020-01-10 07:00")
Instead you should have something like:
moment.tz("2020-01-10 07:00", "Asia/Tehran")
Or rather, you should simply send "2020-01-10 07:00" and "Asia/Tehran" to your database, then later retrieve them and pass them to moment-timezone when you need to know what moment that represents.
As to your other approach, it's generally not a good idea to add or subtract time zone offsets from timestamps. Unix timestamps are inherently UTC based. Adding or subtracting will produce a different moment in time, not adjust for time zone.
Consider that there's also a slim (but not impossible) chance that the offsets returned by your code are incorrect, as they would have to be shifted before being looked up. In other words, Tehran was at UTC+3:30 on the date in question, so the timestamp passed would have to be adjusted by 3 hours 3 minutes before being passed to the moment constructor. This leads to circular logic, and is difficult to resolve. It will show up for timestamps near transitions (either for DST, or for changes to standard time for a particular time zone).
I'm working on a scheduling system for music venues. The basic idea is that there's an "Create new schedule" page, on which there is a DatePicker calendar (using AngularUI Bootstrap). The user selects a Date, then adds performers into timeslots. The built object looks something like this:
{
date: 2017-6-22 00:00:00.000-5:00
venue: VenueID
performances: [
{
performer: performerID,
time: 2017-06-22 22:00:23.231-5:00
},{
perfomer: performer2ID,
time: 2017-06-22 23:00:42.523-5:00
}
]
}
There's a couple of problems here. For the original date selection, I set the time (using myDate.setHours(0,0,0,0)) to midnight because the time doesn't really matter, I only care about the actual date. Likewise for the timeslots, their date doesn't matter (since they belong to the schedule for that day), so I only care about the time. Then in another project, we have a node/mongo app that saves these schedules, and returns them to a page in the angular project that lets you select a schedule for editing/etc. It selects which ones to return by grabbing all the schedules for a specific venue, and doing "if (schedule.date >= new Date().setHours(0,0,0,0)) { add schedule to return list }"
Anyway, on to the actual problem. The angular app does all of the date calculations client side. What I mean is, I'm in CST. If I select a Date on the calendar and save a schedule for that date, then someone in EST selects the same day on the calendar and saves a schedule, they have different dates in the database. For example, when I make the schedule, the date in the DB is "2017-06-22 00:00:00.000-5:00". When the EST friend makes a schedule on the same date, it gets saved as "2017-06-22 00:00:00.000-4:00".
In the "Select a schedule to view/edit" page, I do something like this:
<select ng-model="schedule" ng-options="s.date|date:'fullDate' for s in schedules" ng-show="schedules.length>=1"></select>
Of course this doesn't work because when my EST friend looks at the list, he sees the correct date. But when I look at one that he created, the date is one day off because "2017-06-22 00:00:00.000-4:00" converted to local timezone is "2017-06-21 23:00:00.000-5:00".
I guess TL;DR is I'm not sure how to handle it since the venue and anyone creating/editing the schedules may not share the same time zone. I want all of the dates/times to show up in the timezone of the venue (which I have the address for. I guess I could geolocate to find timezone?). I'm just not sure how to go about it.
The DatePicker gives you a date object. Instead of storing the entire value string just grab the day month and year Date(value).getYear() + '-' + Date(value).getMonth() + '-' + Date(value).getDate(). As for the times do the same as the dates. Store those values in the DB and then when you get them back you will have to convert them back to a date object so that the date picker can understand them.
Ultimately with this solution your just trying to store dates without the timezones. Make sure to state in your app that the times are for those areas.
You have to distinguish between the format the date/time is transported, saved vs. how the date will be shown to the user.
For transportation and saving use UTC in a format that is easy computable (eg. ISO8601).
For visualization to the user convert this value to the timezone and desired user format by using some helper library.
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.