Making an allowance for UTC And Recording Process Streak Knowledge In ColdFusion

Making an allowance for UTC And Recording Process Streak Knowledge In ColdFusion

[ad_1]

For so long as I will take note, I have been storing Date/Time values in UTC (Coordinated Common Time) inside of my ColdFusion packages. UTC is “the main time usual during which the arena regulates clocks and time” (supply). Date/Time values get saved in UTC after which – as wanted – translated again right into a given person’s timezone right through the rendering procedure. That is why, after I added the concept that of “exercise streaks” in Dig Deep Health (my ColdFusion health tracker), my preliminary intuition was once to make use of UTC. Sadly, this method briefly broke down.

Believe a health fanatic dwelling within the Jap timezone of the USA. On the time of this writing, the Jap timezone is “GMT-4”; this is, we are 4-hours at the back of GMT. This health fanatic wakes up at 5am and will get their first exercise accomplished. Then, on the finish of the day, at 10pm, they get in a handy guide a rough 2nd exercise.

For them, 5am and 10pm are the identical day. However, if you happen to view those instances thru a UTC lens, those translated instances fall on two other days:

  • 5am EST + 4 => 9am UTC
  • 10pm EST + 4 => 2am UTC – the subsequent day

Now, if I used to be simply storing date/time values, this would not subject. However, on this case, since I’m seeking to retailer “job streak“https://www.bennadel.com/”habits streak” knowledge, it is vital that either one of those instances get categorised a the similar day from the person’s viewpoint.

With the intention to correctly retailer those date values, I’ve to maintain them within the person’s timezone – no longer the UTC timezone. And, since my ColdFusion server does not in reality know the rest concerning the person or the place they’re on the planet, I’ve to make use of a bit of JavaScript to sneak some user-related knowledge into my shape submissions.

Let’s check out a simplified model of my “Get started Exercise” shape. Since my “streak” knowledge pertains to exercises, the “Get started Exercise” workflow acts to each get started a brand new exercise and report a “streak access”. Within the following code, observe that I’m the use of JavaScript to replace a hidden shape box:

<cfscript>

	param title="shape.submitted" sort="boolean" default=false;
	param title="shape.offsetInMinutes" sort="numeric" default=0;

	if ( shape.submitted ) {

		// Get the UTC time at the server (which is all the time operating in UTC).
		serverNow = now();

		// The usage of the offset supplied by way of the person's browser (by the use of JavaScript), we will be able to
		// convert the UTC timestamp again to a user-local timestamp. This will likely let us
		// correctly classify / bucket their job streak knowledge.
		userNow = serverNow.upload( "n", shape.offsetInMinutes );

	}

</cfscript>
<cfoutput>

	<!doctype html>
	<html lang="en">
	<head>
		<meta charset="utf-8" />
	</head>
	<frame>

		<h1>
			Get started a New Exercise
		</h1>

		<shape manner="submit">
			<!--- This enter worth to by way of DYNAMICALLY CHANGED at runtime. --->
			<enter sort="hidden" title="offsetInMinutes" worth="0" />

			<enter sort="hidden" title="submitted" worth="true" />
			<button sort="post">
				Get Swole!
			</button>
		</shape>

		<cfif shape.submitted>
			<hr />
			<ul>
				<li>
					<robust>UTC Time:</robust>
					#dateTimeFormat( serverNow, "yyyy-mm-dd '... at' hh:nn TT" )#
				</li>
				<li>
					<robust>Native Time:</robust>
					#dateTimeFormat( userNow, "yyyy-mm-dd '... at' hh:nn TT" )#
				</li>
			</ul>
		</cfif>

		<script sort="textual content/javascript">

			var now = new Date();
			// NOTE: The timezone offset is the collection of mins we need to ADD TO LOCAL
			// with the intention to calculate UTC. Then again, the price we would like is in reality the
			// inverse: what do we need to ADD TO UTC with the intention to get again to the person's
			// native time? As such, we need to ship during the NEGATIVE worth in our shape
			// submission.
			var offsetInMinutes = - now.getTimezoneOffset();

			// INJECT the offset into the hidden shape box in order that we will be able to apply it to the
			// ColdFusion server to calculate the person's native time.
			report
				.querySelector( "enter[name="offsetInMinutes"]" )
				.worth = offsetInMinutes
			;

			// Debugging.
			console.staff( "Person's Time" );
			console.log( ...spotlight( "LOCAL:" ), now );
			console.log( ...spotlight( "UTC:" ), now.toUTCString() );
			console.log( ...spotlight( "Offset:" ), offsetInMinutes );
			console.groupEnd();

			// Logs the given worth with a amusing styled output.
			serve as spotlight( worth ) {

				go back([
					`%c${ value }`,
					"background-color: gold ; font-weight: bold ; padding: 2px 4px ; border-radius: 4px ;"
				]);

			}

		</script>

	</frame>
	</html>

</cfoutput>

The Date.prototype.getTimezoneOffset() manner, in JavaScript, offers us the collection of mins that we must upload to the native time with the intention to calculate UTC time. Then again, we in reality need to cross the opposite route: calculating the person’s native time from the UTC time. As such, the price that we need to post with the shape is the inverse of this.

And, once we render this ColdFusion web page for the primary time, you’ll be able to see that we’re injecting the inverse worth into the hidden shape enter:

Screenshot of browser showing that -240 minutes have been injected into the hidden form field (allowing UTC to be converted back to EDT).

As you’ll be able to see, after I run this ColdFusion web page in EDT (Jap Sunlight Time), we are injecting a price of -240. Which means that, we will must subtract 240-minutes from UTC with the intention to generate a date/time worth within the person’s native timezone.

Now, after I post this ColdFusion shape, together with the hidden offset-in-minutes, you’ll be able to see that I’m able to generate each the UTC time and the person’s native time:

Screenshot of browser showing that the user's local timezone was successfully calculated on the server using the offset-in-minutes form value.

Now that I’ve the person’s native timezone offset at the server, I will correctly report their job streak knowledge. My function this is to bucket each and every exercise right into a date (no time) that marks an access within the streak. For this, I’ve a quite simple MySQL database desk that incorporates the userID and the createdAt date:

CREATE TABLE `workout_streak` (
	`userID` bigint unsigned NOT NULL,
	`createdAt` date NOT NULL,
	PRIMARY KEY (`userID`,`createdAt`)
) ENGINE=InnoDB;

Realize that the createdAt column is a dateno longer a datetime. It’s because the time is inappropriate for streak calculations. Additionally, not like maximum different dates in my database, this createdAt column represents the person’s native time, no longer UTC time.

And, because the number one secret is the mix of each related columns (a compound key), I will safely name this as time and again as I wish to the use of, INSERT IGNORE INTO:

<cfscript>

	param title="shape.submitted" sort="boolean" default=false;
	param title="shape.offsetInMinutes" sort="numeric" default=0;

	if ( shape.submitted ) {

		// Get the UTC time at the server (which is all the time operating in UTC).
		serverNow = now();

		// The usage of the offset supplied by way of the person's browser (by the use of JavaScript), we will be able to
		// convert the UTC timestamp again to a user-local timestamp. This will likely let us
		// correctly classify / bucket their job streak knowledge.
		userNow = serverNow.upload( "n", shape.offsetInMinutes );

		// File this new exercise within the job streak log.
		createStreakEntry( 123, userNow );

	}

</cfscript>

<cffunction title="createStreakEntry" returnType="void">

	<cfargument title="userID" sort="numeric" required="true" />
	<cfargument title="createdAt" sort="date" required="true" />

	<cfquery title="native.effects" consequence="native.metaResults">
		INSERT IGNORE INTO
			workout_streak
		SET
			userID = <cfqueryparam worth="#userID#" cfsqltype="cf_sql_bigint" />,
			createdAt = <cfqueryparam worth="#createdAt#" cfsqltype="cf_sql_date" />
		;
	</cfquery>

</cffunction>

<!--- ... truncated ... --->

For the reason that MySQL column for our createdAt knowledge is date, I do not also have to fret about stripping-off the time portion of the enter – the database will do this for me robotically. Which has the additional advantage of naturally collating a number of exercises in the similar day down into the identical job streak bucket.

It feels peculiar to retailer a date this is no longer in UTC. However, for job streak / behavioral streak monitoring, this makes essentially the most sense. Particular shout-out to my co-hosts at the Running Code Podcast who helped me suppose this thru (on subsequent week’s episode).

Wish to use code from this submit?
Take a look at the license.



[ad_2]

0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back To Top
0
Would love your thoughts, please comment.x
()
x