How I Handle Dates and Times on This Website

Handling times in web applications can be complex. I use dayjs to handle to dates and times on the backend and the frontend. I am mainly writing this blog post to remind myself of how I decided to handle dates and times.

Date Created:
Last Edited:

Note: I am writing this blog post to remind myself of how I handle dates on this website, because I forget sometimes.


The Problem


  • You want to display the time something happened to the user in their own time zone, which may be different from the time zone in which the event occurred or the time zone of the database.
  • You want to store the time eficiently.
    • There are storage space considerations when storing dates and times in a relational database.

Timezones of The World


References



The Solution


  • To record the time something happened, I use dayjs().utc().unix() function, which gives me the number of seconds since the Unix Epoch, on the server.
    • For this website, I don't care about the exact millisecond at which an event occurred, but since I store times in a BIGINT column in a PostgreSQL database, it wouldn't have any effect of whether I chose to store seconds or milliseconds.
      • When I say that it has no effect, it actually may have some effect when you retrieve the BIGINT data type from the database and interpret it on the server. For example, node-postgres, a collection of node.js modules for interfacing with your PostgreSQL database, translates the BIGINT data type, which represents a number, to a string on the server. There is also a go consideration with BIGINTs that I can't remember right now.
  • I then store this time in a BIGINT column in a PostgreSQL database.

Name

Storage Size

Description

Low Value

High Value

Resolution

timestamp [ (p) ] [ without time zone ]

8 bytes

both date and time (no time zone)

4713 BC

294276 AD

1 microsecond

timestamp [ (p) ] with time zone

8 bytes

both date and time, with time zone

4713 BC

294276 AD

1 microsecond

date

4 bytes

date (no time of day)

4713 BC

5874897 AD

1 day

time [ (p) ] [ without time zone ]

8 bytes

time of day (no date)

00:00:00

24:00:00

1 microsecond

time [ (p) ] with time zone

12 bytes

time of day (no date), with time zone

00:00:00+1559

24:00:00-1559

1 microsecond

interval [ fields ] [ (p) ]

16 bytes

time interval

-178000000 years

178000000 years

1 microsecond

  • After retrieving the date from the database with the purpose of rendering somewhere, I first translate the Unix Epoch Time to a datetime string that is still in Coordinated Universal Time (UTC). The preferred datestring looks like: YYYY-MM-DDTHH:mm:ss (for reasons described below).
    • The datestring should be in the format specified above because it is what can be read by the computer; in other words, it is in machine-readable format. This can be seen in the mdn web docs for the <time> element.
    • I am not sure why I made this decision really. I think I made it because it is easier to always render dates in UTC time on the server than it is to get the user's time zone from their session data, convert the date to their time zone, and then render it in the correct time zone.
    • If this website was something that I cared about a lot, then I would probably try to render the date correctly on the server to prevent any Cumulative Layout Shift.
  • I then render the datestring in a <time> element like seen below.
    • I try to always use a <time> element when representing dates and times because of adherence to semantic HTML and because it makes it easy to find elements that represent dates and times (elements that need to be handled when the user's preferred time zone changes).
<div>
<time data-date-format="MM/DD/YYYY" datetime="DATETIME_STRING">     
<svg>
<path></path>
</svg>       
<span></span>     
</time>   
</div>
  • After sending an HTML string like seen above to the client, the client looks for all <time> elements, checks each element to see if the <time> element has a data-date-format attribute, and if it does, the JavaScript sets the innerText of the <span> element inside the <time> element to be the date/time formatted based on the data-date-format attribute and the user's local time zone.
const dates = Array.from(document.querySelectorAll('time'));
dates.forEach((el) => {     
if (el.hasAttribute('data-date-format')) {
const dateTime = timeEl.getAttribute("datetime");
// The Timezone Input can be seen in the settings dialog -
// click the gear icon in the navbar
const timezoneInput = document.getElementById('settings-timezone');
// The INITIAL_TIME_ZONE is a guess of the user's timezone based on the
// dayjs.tz.guess() function
const d = dayjs
.utc(String(dateTime).replace('T',' '))
.tz(timezoneInput?.value || INITIAL_TIME_ZONE);
// Format the date according to the data-date-format attribute
const str = d.format(String(timeEl.getAttribute('data-date-format')));
// Put the formatted date into the <span> inside the <time> element
const span = timeEl.querySelector("span");
if (span) span.innerText = str;
}  
});
  • The client-side JavaScript listens for changes to the time zone input (input#settings-timezone)and re-renders the date/time strings in the <span>s inside the <time> elements whenever the user changes their time zone.


What I Would Do Differently


  1. Render the date string on the server.
    • The reason I didn't do this for this website is because it is not a critical application.
    • There is also a problem in the fact that no user really has a preferred time zone - they probably want the date to be represented based on the time zone that they are in, which may change occasionally.
      • I think storing in the user's time zone in the database doesn't really help with the initial render of the page, since they may currently be in a different time zone than they usually are, but storing the time zone in the session data should help with subsequent renders.
    • When rendering the date on the server and because of problems with rendering dates on initial load, you should probably include a data-timezone attribute on the <time> element which will help you determine on the client whether or not the date/time should be re-rendered.
  1. (Possible) Keep track of the time zone in which an event happened for each user to better detect suspicious activity.

Comments

You must be logged in to post a comment!

Insert Math Markup

ESC
About Inserting Math Content
Display Style:

Embed News Content

ESC
About Embedding News Content

Embed Youtube Video

ESC
Embedding Youtube Videos

Embed TikTok Video

ESC
Embedding TikTok Videos

Embed X Post

ESC
Embedding X Posts

Embed Instagram Post

ESC
Embedding Instagram Posts

Insert Details Element

ESC

Example Output:

Summary Title
You will be able to insert content here after confirming the title of the <details> element.

Insert Table

ESC
Customization
Align:
Preview:

Insert Horizontal Rule

#000000

Preview:


Insert Chart

ESC

View Content At Different Sizes

ESC

Edit Style of Block Nodes

ESC

Edit the background color, default text color, margin, padding, and border of block nodes. Editable block nodes include paragraphs, headers, and lists.

#ffffff
#000000

Edit Selected Cells

Change the background color, vertical align, and borders of the cells in the current selection.

#ffffff
Vertical Align:
Border
#000000
Border Style:

Edit Table

ESC
Customization:
Align:

Upload Lexical State

ESC

Upload a .lexical file. If the file type matches the type of the current editor, then a preview will be shown below the file input.

Upload 3D Object

ESC

Upload Jupyter Notebook

ESC

Upload a Jupyter notebook and embed the resulting HTML in the text editor.

Insert Custom HTML

ESC

Edit Image Background Color

ESC
#ffffff

Insert Columns Layout

ESC
Column Type:

Select Code Language

ESC
Select Coding Language