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.
Related
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>
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()
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.
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.
I'm wondering if it's possible to use AngularStrap's datepicker without it keeping the user's locale's timezone information. In our application we want to handle Contract objects that have an expiration date.
When adding or editing the contract object, there is a datepicker field for selecting the date. The following thing happens:
The user selects the date (e.g. 2013-10-24)
Angular binds the javascript date object to the ng-model field
The binded date object is in the user's timezone (e.g. GMT+3)
The user submits the form
The date gets sent to the server using Angular's $http service
In step 5 the date is converted to UTC format. The selected date was GMT+3 2013-10-24 at midnight, but the UTC conversion changes the date to 2013-10-23 at 9pm.
How could we prevent the conversion, or use UTC dates during the whole process? We don't want the contract's date to change based on the user's local timezone. Instead, we want the date to be always 2013-10-24, no matter what timezone.
Our current solution was to make small changes to the AngularStrap library so that the date won't change when sent to the server.
If we could get the user's selected timezone in the server, we could make another conversion there, but the server doesn't have that information.
All ideas are appreciated!
The issue isn't AngularStrap. Its just how javascript dates work and how JSON formats them for transmission. When you turn a javascript date object into a JSON string, it formats the string as UTC.
For example, I'm in Utah and it is now 07:41 on 2013-10-24. If I create a new javascript date and print it to the console it will say:
Thu Oct 24 2013 07:41:19 GMT-0600 (MDT)
If I stringify that same date (using JSON.stringify(date), I get:
"2013-10-24T13:41:47.656Z"
which you can see is not in my current timezone, but is in UTC. So the conversion is happening just before the form gets sent to the server when it gets converted from a javascript object to a JSON string.
The easiest way to do it would be to just change the date to a string of your own choosing prior to sending the date to the server. So instead of letting JSON change the date to UTC, (assuming you don't care about the time of day) you could just do something like this:
var dateStrToSend = $scope.date.getUTCFullYear() + '-' + ($scope.date.getUTCMonth() + 1) + '-' + $scope.date.getUTCDate();
That will give you a UTC-based string that looks like '2013-10-24' and then you can send that to the server, instead of the JSON format which includes the time info. Hopefully that helps.
UPDATE: As #Matt Johnson said, there are two ways to do it. You said: How could we prevent the conversion, or use UTC dates during the whole process?. If you want to use UTC, then use my above explanation. If you want to just "prevent the conversion", you could use the following:
var dateStrToSend = $scope.date.getFullYear() + '-' + ($scope.date.getMonth() + 1) + '-' + $scope.date.getDate();
A bit late but I spent my afternoon on this and someone might find it useful.
Another way to do this declaratively is to use the dateType, dateFormat and modelDateFormat attributes. Set these in either the config or the HTML e.g
angular.module('app').config(function ($datepickerProvider) {
angular.extend($datepickerProvider.defaults, {
dateFormat: 'dd-MMMM-yyyy',
modelDateFormat: "yyyy-MM-ddTHH:mm:ss",
dateType: "string"
});
});
DateFormat is the format the date will be displayed to the user in the date picker while modelDateFormat is the format it will be converted to before being bound to your model.
I also had default values coming from the server which I needed to be bound to the datepicker on page load. I therefore had to update the format the server serialized dates in JSON to match the modelDateFormat. I am using Web API so I used the below.
var jsonSettings = Formatters.JsonFormatter.SerializerSettings;
jsonSettings.DateFormatString = "yyyy-MM-ddTHH:mm:ss";
The "Angular way" is to use the $filter service to format the date returned by the datepicker.
Example (HTML):
{{inpDate | date: 'dd-MM-yyyy'}}
Example (JS):
$scope.processDate = function(dt) {
return $filter('date')(dt, 'dd-MM-yyyy');
}
Plunker here