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.
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.
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, acollection of node.js modules for interfacing with your PostgreSQL database
, translates theBIGINT
data type, which represents a number, to a string on the server. There is also a go consideration withBIGINT
s that I can't remember right now.
- When I say that it has no effect, it actually may have some effect when you retrieve the
- For this website, I don't care about the exact millisecond at which an event occurred, but since I store times in a
- I then store this time in a
BIGINT
column in a PostgreSQL database. - If you look at the PostgreSQL date and time types, you can see the minimum storage size of a date/time object that retains a resolution less than a day is 8 bytes, which is the same as the size of a
BIGINT
data type. (This shows that storage space is not conserved by storing the date in a different format). - Note: You could store the Unix time as an
INT
data type instead of aBIGINT
if you choose to not worry about the 2038 problem.
- Note: You could store the Unix time as an
- If you look at the PostgreSQL date and time types, you can see the minimum storage size of a date/time object that retains a resolution less than a day is 8 bytes, which is the same as the size of a
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 preferreddatestring
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.
- The
- 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).
- I try to always use a
<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 adata-date-format
attribute, and if it does, the JavaScript sets theinnerText
of the<span>
element inside the<time>
element to be the date/time formatted based on thedata-date-format
attribute and the user's local time zone. - The
data-date-format
attribute should contain a string that adheres to the dayjs Format function.
- The
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
- 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.
- (Possible) Keep track of the time zone in which an event happened for each user to better detect suspicious activity.
Comments
There are currently no comments to show for this article.