An "Easy" Way to Convert Bubble Date/Times to the Same Date, but in a Different Timezone

I know there’s lots and lots and lots of threads discussing the various head-banging difficulties involved in manipulating date/time objects.

This is not an attempt to solve every single-dingle issue involved with dealing with date/times, but a lot of these discussions are about calendars and displaying dates and just wanting to be able to treat a DATE (such as "August 10, 2018) as the SAME date but in a different timezone.

Like,

"I’m trying to display a date on [insert your fave calendar solution here], but it’s not working right because the date in question was created with timezone X, but the person viewing my calendar is in timezone Y.

This would kind of be easy if I could just turn the date part in timezone X into the same date part in timezone Y, essentially throwing away the time part, but ‘transmuting’ the date into the same date in timezone Y. Why is it so hard to do that?"

So, here’s a single line of JavaScript that does just that (note: it depends on moment.js / moment-timezone scripts to do this):

Let’s say you have a Bubble date object (or a JavaScript date object). We’ll call it myDate. Here, myDate is July 29, 2018 at 00:00:00 in Pacific Daylight Time (that is, it indicates the start of that particular day):

console.log(myDate); // "Sun Jul 29 2018 00:00:00 GMT-0700 (Pacific Daylight Time)"

If this date is displayed in a calendar where the viewer is ALSO in PDT, they will see the date as blocked (or whatever) just fine. However, if the viewer is SOMEWHERE else, this date may show up funky, right? (Or not show up at all. Totally depends on how your calendar figures this out.)

Let’s say you want to take this date which is currently in the “America/Los Angeles” timezone ID / Pacific Daylight Time timezone and construct the same date, but in the “America/New York” timezone ID / Eastern Daylight Time timezone.

Here’s the one line of code that will do just that:

myDateInAnotherZone = moment.tz(String(moment.utc(myDate).format('YYYY-MM-DD')), 'YYYY-MM-DD', zone).toDate();

where myDate is a JavaScript date and zone is a string that represents what Bubble calls the timezone ID (and what moment calls the zone’s “name”).

In this case zone would be:

console.log(zone); // "America/New York"

The output, myDateInAnotherZone is a JavaScript date object that now looks like this:

console.log(myDateInAnotherZone); // "Sat Jul 28 2018 21:00:00 GMT-0700 (Pacific Daylight Time)"

Of course, JavaScript objects can only be shown in the browser’s timezone. So you can’t immediately see that this date is, in fact “Sun Jul 29 2018 00:00:00” in Eastern Daylight Time. But it is.

(Do it in your head: 21:00 hours in Pacific on Saturday night is in fact the first moment of Sunday morning in Eastern.)

Anyway, hooray!

So, to make this work (like in a Toolbox “Run JavaScript” workflow action or an “Expression” element), you need moment-timezone loaded. This requires moment.js AND moment-timezone, so you need to load both in an HTML element like this (both are available in cdnjs and at present these are the latest versions, but ‘duh’ this stuff changes so don’t just blindly go grabbing this code):

<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.2/moment.min.js
"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.21/moment-timezone-with-data.js"></script>

Once those scripts are loaded, you can access moment-timezone like I did in the single line of code above.

BONUS BEATS:

How does this work? There’s kind of a lot going on in that one line of code, yeah?

Well, the general technique here is this:

  1. You have a JavaScript date (Sun Jul 29 2018 at 00:00:00, in Pacific).

  2. You turn that JavaScript date into an unambiguous text (string) version of the date. The format “YYYY-MM-DD” will suffice. (“2018-07-29”)

  3. Now, you have the date as a string that is set free from the time element, timezones and all of that (important but annoying) nonsense.

  4. We take that string and turn it into a moment (which is like a date but fancier). Anyway, using moment-timezone, we can construct the moment and ASSIGN IT THE TIMEZONE indicated by zone. (So now we have it’s like: Sun Jul 29 2018 00:00:00 but in Eastern Time.)

  5. Now moment’s .toDate(); method is used to turn the moment back into a JavaScript date (but now that date has Eastern timezone [or whatever the string in zone represents]).

So, some variations: Let’s say you have a string date already (not a date object) like myStringDate is “2018-07-29”. The expression becomes:

myDateInAnotherZone = moment.tz(myStringDate, 'YYYY-MM-DD', zone).toDate();

This gets a little easier to understand. It’s a little more obvious that what we’re doing is telling moment, “Hey, here’s the string representation of a date. It’s formatted as YYYY-MM-DD and I want you to slap a zone on it and then turn it back into a JavaScript date. Thanks!”

The ‘YYYY-MM-DD’ part can be changed to other things of course. Like, if your date is like “07/29/2018” you could change that to ‘MM/DD/YYYY’. Moment is pretty flexible like that.

Anyway, I just wanted to leave this here in the hopes it’ll help someone.

-K-

12 Likes

Hey Keith, considering Grupz has launched and you have likely tested this out more since the original post, I am curious if you are using this for Grupz and if you think it is the best method for this?

Thanks!

Hey @gf_wolfer: yes, this is used constantly in Grupz. Mainly, when my calendars build their array of dates (which is done using moment/moment-timezone as events in my calendars are timezone-aware), I use this transform on every single date.

So, for example, in the loop that builds the calendar’s value list (in an Expression element), we do:

D.push(moment.tz(String(moment.utc(aMom.add(1, 'days')).format('YYYY-MM-DD')), 'YYYY-MM-DD', zone).toDate());

Which, you can see is literally this:

myDateInAnotherZone = moment.tz(myStringDate, 'YYYY-MM-DD', zone).toDate();

where “myStringDate” is replaced by:

String(moment.utc(aMom.add(1, 'days')).format('YYYY-MM-DD'))

In case you’re curious, variable aMom is “a moment” and it begins as the starting
date for the calendar (the date displayed in the upper left cell of the calendar).

The .add method in moment is a mutator, so what’s going on here is that I am taking that moment, adding one day to it and pushing that onto the calendar’s array of dates. aMom stays incremented to that date value and so the next time through the loop, it will increment by another day and that will get pushed onto the list of dates. And the process repeats until we have built the 42 dates in the 7 x 6 calendar repeating group.

For a small-ish array like this, this doesn’t take much longer than the original version (which would just make a JavaScript date) and is performant enough.

(It’s not the bottleneck anyway in terms of displaying an updated calendar as the user clicks the next/prev month buttons. What is the bottleneck is the browser building/updating the elements of the RG itself and Bubble’s evaluation of various conditions that determine if a date should be shown as “blocked” or “available”.

In my calendars, this is done with rangewise comparisons, which are unfortunately a bit slow compared to if one has a simple array of dates that one knows should be blocked. So, performance is not exactly optimal on lower/mid-range mobile devices. My lowest powered target device would be like an iPhone 6 generation where performance is just “acceptable” and certainly not “sprightly”. :P)

1 Like

Thanks!

Dear Father Time (@keith I’m looking at you. :slight_smile: )

What about dealing with a date that should always ignore time zone, such as a date of birth? While a date of birth is sort-of a moment in time, we don’t want to convert it based on time zone because across the date line, the date changes, but really, my date of birth never should change.

For example, a user wants to search for a list of users by their date of birth. These DOB’s are created by users across the globe and thus go into bubble from various time zones, and similarly are searched across the globe from various time zones.

First thought is to simply store as text because in this scenario, it is more of a label than an actual moment in time. This is fine and works for an exact match search, but what if the user wants to find all people with a date of birth across a range of time? Greater/Less than comparisons aren’t logical on text fields.

1 Like

You can store both the actual date as a date type and also the text representation.
Then when performing some operation like searching you use the date field then display the corresponding text field

2 Likes

Thanks @seanhoots…Was hoping to avoid the duplicative storage approach, but I guess I should just move on and go with that.

EDIT: But wait…will this actually work?

“Tom” puts his DOB (2/14/1972) in using an input field with date as the content format from London. If Tom could look at his data in the bubble db he would see 2/14/1972 (and 12:00am for time)

Then “Sue” comes along from Los Angeles and searches for users where DOB = 2/14/1972. If Sue were able to look at the database, she would see Tom’s DOB as 2/13/1972 because of the offset difference, no? Sue’s search will therefore not return Tom as a user with this DOB.

@seanhoots – What am I missing here?

Remember that searches are done server-side.
So when performing a search, no matter where the search was initiated from the date being used is the date stored in the server.

It’s only when dates are “displayed” that the user’s timezone is used (which you can even control).

The best way do deal with date and time ambiguities is to always let the user know or specify exactly the timezone a date is in.

Date of birth is an interesting case because normally you don’t specify the time and timezone.
So lets say you were born in Toronto (GMT -5) on 3rd Dec, 1980 at 11:30pm.
This time in London (GMT + 0) will be 4th Dec 1980 4:30am.

So if you ask a user to enter their date of birth, because they were born in Toronto on 3rd Dec, no matter where they’re are currently (say in London), they’re going to enter it as 3rd Dec 1980.

So if you really need minute accuracy the only way to avoid this will have been to let the user specify their country of birth. Then you could use that information to know the timezone and store the date accurately.

1 Like

Right. You’d just store the date object, @mebeingken. You can set the time parts to 0 if you want before storage or as part of retrieval. If you really want to do this absolutely “correctly”, you would want to normalize birthdates to some arbitrary timezone (like UTC is often used for this purpose but in fact you could pick ANY zone).

Moment has a UTC mode for things like this.

And then, when a searching user is specifying start and end dates for which to find (for example) all users with birthdays between those two dates, you’d convert the start and end points of the search to the chosen arbitrary zone (in the way I describe in the original post).

Essentially the above is what I do in my vacation rental calendars. And it’s more-or-less what things of that nature do. For example, an iCal URL from HomeAway or VRBO lists start and end dates for a reservation where the start and end dates are assumed to be the first moment of the day in question. And there’s no timezone metadata attached.

Here’s an example of a typical iCal VEVENT like this:

BEGIN:VEVENT
UID:e6c722b0-a388-4018-93e6-04b0414e84be
DTSTAMP:20181203T231539Z
DTSTART;VALUE=DATE:20131219
DTEND;VALUE=DATE:20131222
SUMMARY:Reserved - mary & millard anderson
END:VEVENT

See how there’s no timezone info except in the datestamp field? (that’s when the file was generated)

So what do you do? Well, it depends on how and when/where you turn those text values into real date objects. If you’re doing it in the browser, the start and end dates are going to appear as though they are in the browser’s timezone.

If those texts representing dates are brought into Bubble via the api connector (and you tell bubble “hey, these are dates”), they’ll get turned into dates at start time 0 on that day in UTC.

What I actually do is I have an API that does the conversion before passing back the dates. And it passes them as texts (of course), but they are fully specified simplified extended ISO date texts and so they have all the timezone info in them. And my API actually takes an input for timezone – the dates that get passed back are transmuted to that target timezone.

This is a little bit fancier (but not much!) than just assuming UTC. (You still have the challenge of presenting the information in the user’s browser in such a way that it appears that the UTC dates actually start and end at the correct time when shown in a calendar.)

Long long long story short, if you have a datewise comparison and all of the dates under consideration are from the same timezone, you don’t have any of the weird issues that you’re anticipating.

But to @seanhoots more recent point: Birth dates are like the dates I showed you in that iCal feed. A user is born somewhere and likely does not currently reside where they were born, but of course just enters their birthday as the date upon which they were born, without consideration of timezone issues.

Basically, when we ask the question: “Out of a batch of people, which ones were born between date X and date Y?,” what we are doing is saying/asking, “Assuming that everyone’s birthday, date X and date Y are in the same timezone, which birthdays are in that range defined by X and Y?”

“Ignoring” the timezone aspect is assuming some sort of unified timezone (which we might call a “null” timezone) shared by all of the dates that we are considering. We are also at the same time ignoring time components and setting them to 0 in our heads.

You can pick whatever timezone you want to represent this shared or null timezone.

Hey all, I have a working custom plugin that solves this exact issue if anyone is interested. It’s currently in Private Beta only and is plug and play

It takes into account the User’s timezone (via browser) and converts the date into another date based on what timezone is chosen so that the date you choose is the date you get at the timezone

That may be useful to some folks. My take on this type of thing is that it would be most useful if available both server and client side. At the present time, one can’t install arbitrary NPM packages for Bubble server-side plugins, so Moment/Moment-range/Moment Timezone support there just yet.

On the browser/client side, I’m a big advocate for NOT relying on plugins in the vast majority of cases. Folks need to just learn the basics of installing useful packages, calling the parts of them that they find helpful via JavaScript (using Toolbox, which should really be a native part of Bubble if you ask me), etc.

The “you don’t have to be a coder” positioning aside, learning a few simple things like this greatly expands one’s capabilities.

I do wish that Bubble would implement moment and moment timezone inside of Bubble and just make some cool operators available in the native platform. They’re pretty essential functions and would make a nice complement to the already pretty powerful date/time features.

1 Like

@keith @seanhoots Thanks for all that – I’m no longer wrapped around the axle. :slight_smile:

What I heard was: For dates that people don’t associate with a time zone (like a birthdate,) save them as UTC, search for them in UTC, and format them UTC. In doing so, you can just use a single date field.

More testing to be had, but this seems fine:
Load the scripts in html element

Entering the birthdate:

Convert DOB to UTC before adding to database

Then for the search:
Convert search date to UTC

Reference the UTC search date in the search and set the time ranges

For testing, this was helpful:

Thanks again Gents!

3 Likes

Thanks @peng.o. I’ve got it working, but glad you jumped in with another option!

Yes, you understood what I was getting at! (sorry for the long note with lots of words) Again, if you wanted to compare vs. some other date, date range or whatnot in a datewise way, you can do a similar transform on the comparison date objects and remove any doubt about differences due to timezones.

Alternatively, you can use the unix value. The number of seconds passed since 1-1-1970

Add this in the API connector:

Then store a date, passed through this API, as a number in the DB

image

For display:

image

2 Likes

I am reading this topic because of the same problem that the rest of you have had and it seems ridiculously complicated. When a user enters the date of birth surely it could be as simple as a setting to “Change timezone to” and set the timezone to UTC. We already can change the hours, minutes etc. Am I missing something here?

@keith made a plugin…I haven’t looked yet, but maybe it will help you.

Hi @arnold.smyth, I built an explainer app with an easier to follow method as well. See my replies and links in this thread:

How To Schedule API Workflows Based On A Date In A Different Timezone Than Current Users Timezone

I also have a forthcoming suite of server side actions that also help with this sort of transformation (and lots else).

1 Like

I don’t think it will - what we need to do is take an input like: 13/02/2019 and save it as 13/02/2019 00:00 UTC. What is happening is that when I update the ‘thing’ in the DB it converts the date from whatever timezone I am in to the equivalent time in UTC and then saves it. There is no easy way around this behavior in Bubble because when you format that date first and then try to save it Bubble sees it as a text.