Skip to content

How to Handle Time Zones using DateTime and Luxon

This article was written over 18 months ago and may contain information that is out of date. Some content may be relevant but please refer to the relevant official documentation or available resources for the latest information.

Raise your hand if you've ever had issues dealing with time zones, or even if you've asked, "How do I convert a Date object to another time zone in JavaScript?"

In my personal experience, this kind of requirement can become a big problem for developers if the date-handling-related concepts are not clearly understood, or the right tools are not used.

Using Dates and Time Zones in JavaScript

Let's suppose you have a date representation from an external API, and you need to convert the date to any desired time zone.

The best option for this is using a representation that meets the ISO 8601 standard. As an example of this, we can set a date like 2021/06/10 02:20:50 in UTC. Then, the standard notation for this date will be 2021-06-10T02:20:50+00:00.

On other hand, the JavaScript language provides a Date object that represents a single moment in time. You can create a Date object in different ways:

let date;
date = new Date(); // Get the current date

date = new Date("2021-06-10T02:20:50+00:00"); // An object representation of given string date

date = new Date(new Date()); // Creates an object representation from another one

Also, we can set a time zone value to any given Date object as follows:

let stringInput = "2021-06-10T02:20:50+00:00";
let timeZone = "America/Los_Angeles";
const dateObject = new Date(stringInput).toLocaleString("en-US", {
  timeZone,
});

console.log(dateObject); // Prints: 6/9/2021, 7:20:50 PM

The toLocaleString method returns a string with a language-sensitive representation of the Date object. At the same time, this method supports optional arguments where you can configure the time zone. Find more information about this method here.

As you can see, the output date matches the configured time zone (GMT-7). However, we have a string representation of the date, and it would be much better if we work with a JavaScript object instead.

Luxon

Luxon is considered an evolution of Moment.js- a very popular library for date handling in the JavaScript ecosystem.

As the Luxon project says:

Luxon is a powerful, modern, and friendly wrapper for JavaScript dates and times.

Indeed, this library solves most of the common problems related to Date handling:

  • Date internationalization
  • Time zones and Offsets
  • Calendars support
  • Dates Formatting
  • Dates Parsing
  • Dates Math (Add/Subtract days, months, etc)
  • Dates Validation
  • and More...

The DateTime Object

The most important part of the Luxon library is the DateTime object. It can be considered a wrapper of the native Date object along with a timezone, and a local configuration.

The simplest way of creating a DateTime object is as follows.

import { DateTime } from "luxon";

let dateTime = DateTime.local();
console.log("Current Date", dateTime.toISO()); // 2021-06-22T21:11:45.638-04:00

The method toISO() will return an ISO 8601-compliant string representation of the DateTime object.

Also, you can create a DateTime in a specific time zone.

// Create a DateTime in a Specific Timezone
let zone = "America/Denver";
let dateTime = DateTime.fromObject({
  zone,
});
console.log("Current Date", dateTime.toISO()); // 2021-06-22T19:11:45.640-06:00

As you can compare with the previous example, the time output is different because of the use of America/Denver as the time zone.

Of course, there is a way to create a custom date in a specific time zone:

let dateTime = DateTime.fromObject({
  'America/Denver',
}).set({
  day: 1,
  month: 5,
  year: 2021,
});
console.log("Custom date", dateTime.toISO()); //2021-05-01T19:11:45.641-06:00

The set method allows overriding specific properties such as year, month, day, etc.

Converting a DateTime to a different Time Zone

Now let's suppose we have a DateTime object, and we need to convert it to a different time zone.

let dateTime = DateTime.fromObject({
  'America/Denver',
}).set({
  day: 1,
  month: 5,
  year: 2021,
});

// Convert existing date to another Timezone
dateTime = dateTime.setZone("America/La_Paz");
console.log("Custom date, America/La_Paz", dateTime.toISO()); //2021-05-01T21:11:45.641-04:00

Configuring the Default Time Zone

What happens when the whole application needs to run every date in a specific time zone? Just suppose you have defined a configuration within your app to allow the selection of a time zone at any time.

To solve this problem, you don't need to use the time zone string here and there. The Settings class, instead, comes to the rescue:

import { Settings } from "luxon";

// Configure the time zone
Settings.defaultZoneName = "America/Denver";

console.log(Settings.defaultZoneName); // Reading the configured time zone.

The defaultZoneName can be used as a set or get method for the default time zone when you're working with the library.

In the same way, the Settings class contains other methods to configure Luxon's behavior.

Then, when you're creating a new DateTime object again, it will take the configured time zone by default.

dateTime = DateTime.local();
console.log("Configured defaultZoneName", dateTime.toISO()); //2021-06-22T19:21:54.362-06:00

Pay attention to the offset value, which corresponds now with America/Denver.

Validate a Time Zone

In case you define a user entry point to configure the time zone globally, it is important to validate the text before causing problems with the DateTime objects.

A useful way to do it is through, again, a DateTime object:

const timeZone = "America/Not_Defined_TZ";
const myDateTime = DateTime.local().setZone(timeZone);
console.log("timeZone valid", myDateTime.isValid); // Prints 'false'

Now try again with a valid time zone, for example, America/Los_Angeles.

Live Demo

Wanna play around with this code? Just open the embedded CodeSandbox editor:

Conclusion

In this article, I described a couple of useful methods for using Luxon for Time Zone handling using either JavaScript or TypeScript. Personally, I consider it a very useful library, and it also avoids rewriting and testing your own code for handling dates and time zones, which could save you a lot of time.

Feel free to reach out on Twitter if you have any questions. Follow me on GitHub to see more about my work.

This Dot is a consultancy dedicated to guiding companies through their modernization and digital transformation journeys. Specializing in replatforming, modernizing, and launching new initiatives, we stand out by taking true ownership of your engineering projects.

We love helping teams with projects that have missed their deadlines or helping keep your strategic digital initiatives on course. Check out our case studies and our clients that trust us with their engineering.

You might also like

Understanding Sourcemaps: From Development to Production cover image

Understanding Sourcemaps: From Development to Production

What Are Sourcemaps? Modern web development involves transforming your source code before deploying it. We minify JavaScript to reduce file sizes, bundle multiple files together, transpile TypeScript to JavaScript, and convert modern syntax into browser-compatible code. These optimizations are essential for performance, but they create a significant problem: the code running in production does not look like the original code you wrote. Here's a simple example. Your original code might look like this: ` After minification, it becomes something like this: ` Now imagine trying to debug an error in that minified code. Which line threw the exception? What was the value of variable d? This is where sourcemaps come in. A sourcemap is a JSON file that contains a mapping between your transformed code and your original source files. When you open browser DevTools, the browser reads these mappings and reconstructs your original code, allowing you to debug with variable names, comments, and proper formatting intact. How Sourcemaps Work When you build your application with tools like Webpack, Vite, or Rollup, they can generate sourcemap files alongside your production bundles. A minified file references its sourcemap using a special comment at the end: ` The sourcemap file itself contains a JSON structure with several key fields: ` The mappings field uses an encoding format called VLQ (Variable Length Quantity) to map each position in the minified code back to its original location. The browser's DevTools use this information to show you the original code while you're debugging. Types of Sourcemaps Build tools support several variations of sourcemaps, each with different trade-offs: Inline sourcemaps: The entire mapping is embedded directly in your JavaScript file as a base64 encoded data URL. This increases file size significantly but simplifies deployment during development. ` External sourcemaps: A separate .map file that's referenced by the JavaScript bundle. This is the most common approach, as it keeps your production bundles lean since sourcemaps are only downloaded when DevTools is open. Hidden sourcemaps: External sourcemap files without any reference in the JavaScript bundle. These are useful when you want sourcemaps available for error tracking services like Sentry, but don't want to expose them to end users. Why Sourcemaps During development, sourcemaps are absolutely critical. They will help avoid having to guess where errors occur, making debugging much easier. Most modern build tools enable sourcemaps by default in development mode. Sourcemaps in Production Should you ship sourcemaps to production? It depends. While security by making your code more difficult to read is not real security, there's a legitimate argument that exposing your source code makes it easier for attackers to understand your application's internals. Sourcemaps can reveal internal API endpoints and routing logic, business logic, and algorithmic implementations, code comments that might contain developer notes or TODO items. Anyone with basic developer tools can reconstruct your entire codebase when sourcemaps are publicly accessible. While the Apple leak contained no credentials or secrets, it did expose their component architecture and implementation patterns. Additionally, code comments can inadvertently contain internal URLs, developer names, or company-specific information that could potentially be exploited by attackers. But that’s not all of it. On the other hand, services like Sentry can provide much more actionable error reports when they have access to sourcemaps. So you can understand exactly where errors happened. If a customer reports an issue, being able to see the actual error with proper context makes diagnosis significantly faster. If your security depends on keeping your frontend code secret, you have bigger problems. Any determined attacker can reverse engineer minified JavaScript. It just takes more time. Sourcemaps are only downloaded when DevTools is open, so shipping them to production doesn't affect load times or performance for end users. How to manage sourcemaps in production You don't have to choose between no sourcemaps and publicly accessible ones. For example, you can restrict access to sourcemaps with server configuration. You can make .map accessible from specific IP addresses. Additionally, tools like Sentry allow you to upload sourcemaps during your build process without making them publicly accessible. Then configure your build to generate sourcemaps without the reference comment, or use hidden sourcemaps. Sentry gets the mapping information it needs, but end users can't access the files. Learning from Apple's Incident Apple's sourcemap incident is a valuable reminder that even the largest tech companies can make deployment oversights. But it also highlights something important: the presence of sourcemaps wasn't actually a security vulnerability. This can be achieved by following good security practices. Never include sensitive data in client code. Developers got an interesting look at how Apple structures its Svelte codebase. The lesson is that you must be intentional about your deployment configuration. If you're going to include sourcemaps in production, make that decision deliberately after considering the trade-offs. And if you decide against using public sourcemaps, verify that your build process actually removes them. In this case, the public repo was quickly removed after Apple filed a DMCA takedown. (https://github.com/github/dmca/blob/master/2025/11/2025-11-05-apple.md) Making the Right Choice So what should you do with sourcemaps in your projects? For development: Always enable them. Use fast options, such as eval-source-map in Webpack or the default configuration in Vite. The debugging benefits far outweigh any downsides. For production: Consider your specific situation. But most importantly, make sure your sourcemaps don't accidentally expose secrets. Review your build output, check for hardcoded credentials, and ensure sensitive configurations stay on the backend where they belong. Conclusion Sourcemaps are powerful development tools that bridge the gap between the optimized code your users download and the readable code you write. They're essential for debugging and make error tracking more effective. The question of whether to include them in production doesn't have a unique answer. Whatever you decide, make it a deliberate choice. Review your build configuration. Verify that sourcemaps are handled the way you expect. And remember that proper frontend security doesn't come from hiding your code. Useful Resources * Source map specification - https://tc39.es/ecma426/ * What are sourcemaps - https://web.dev/articles/source-maps * VLQ implementation - https://github.com/Rich-Harris/vlq * Sentry sourcemaps - https://docs.sentry.io/platforms/javascript/sourcemaps/ * Apple DMCA takedown - https://github.com/github/dmca/blob/master/2025/11/2025-11-05-apple.md...

The Future of Dates in JavaScript: Introducing Temporal cover image

The Future of Dates in JavaScript: Introducing Temporal

The Future of Dates in JavaScript: Introducing Temporal What is Temporaal? Temporal is a proposal currently at stage 3 of the TC39 process. It's expected to revolutionize how we handle dates in JavaScript, which has always been a challenging aspect of the language. But what does it mean that it's at stage 3 of the process? * The specification is complete * It has been reviewed * It's unlikely to change significantly at this point Key Features of Temporal Temporal introduces a new global object with a fresh API. Here are some important things to know about Temporal: 1. All Temporal objects are immutable 2. They're represented in local calendar systems, but can be converted 3. Time values use 24-hour clocks 4. Leap seconds aren't represented Why Do We Need Temporal? The current Date object in JavaScript has several limitations: * No support for time zones other than the user's local time and UTC * Date objects can be mutated * Unpredictable behavior * No support for calendars other than Gregorian * Daylight savings time issues While some of these have workarounds, not all can be fixed with the current Date implementation. Let's see some useful examples where Temporal will improve our lives: Some Examples Creating a day without a time zone is impossible using Date, it also adds time beyond the date. Temporal introduces PlainDate to overcome this. ` But what if we want to add timezone information? Then we have ZonedDateTime for this purpose. The timezone must be added in this case, as it also allows a lot of flexibility when creating dates. ` Temporal is very useful when manipulating and displaying the dates in different time zones. ` Let's try some more things that are currently difficult or lead to unexpected behavior using the Date object. Operations like adding days or minutes can lead to inconsistent results. However, Temporal makes these operations easier and consistent. ` Another interesting feature of Temporal is the concept of Duration, which is the difference between two time points. We can use these durations, along with dates, for arithmetic operations involving dates and times. Note that Durations are serialized using the ISO 8601 duration format ` Temporal Objects We've already seen some of the objects that Temporal exposes. Here's a more comprehensive list. * Temporal * Temporal.Duration` * Temporal.Instant * Temporal.Now * Temporal.PlainDate * Temporal.PlainDateTime * Temporal.PlainMonthDay * Temporal.PlainTime * Temporal.PlainYearMonth * Temporal.ZonedDateTime Try Temporal Today If you want to test Temporal now, there's a polyfill available. You can install it using: ` Note that this doesn't install a global Temporal object as expected in the final release, but it provides most of the Temporal implementation for testing purposes. Conclusion Working with dates in JavaScript has always been a bit of a mess. Between weird quirks in the Date object, juggling time zones, and trying to do simple things like “add a day,” it’s way too easy to introduce bugs. Temporal is finally fixing that. It gives us a clear, consistent, and powerful way to work with dates and times. If you’ve ever struggled with JavaScript dates (and who hasn’t?), Temporal is definitely worth checking out....

How to Manage Breakpoints using BreakpointObserver in Angular cover image

How to Manage Breakpoints using BreakpointObserver in Angular

Defining Breakpoints is important when you start working with Responsive Design and most of the time they're created using CSS code. For example: ` By default, the text size value will be 12px, and this value will be changed to 14px when the viewport gets changed to a smaller screen (a maximum width of 600px). That solution works. However, what about if you need to _listen_ for certain breakpoints to perform changes in your application? This may be needed to configure third-party components, processing events, or any other. Luckily, Angular comes with a handy solution for these scenarios: the BreakpointObserver. Which is a utility for checking the matching state of @media queries. In this post, we will build a sample application to add the ability to configure certain breakpoints, and being able to _listen_ to them. Project Setup Prerequisites You'll need to have installed the following tools in your local environment: - Node.js. Preferably the latest LTS version. - A package manager. You can use either NPM or Yarn. This tutorial will use NPM. Creating the Angular Project Let's start creating a project from scratch using the Angular CLI tool. ` This command will initialize a base project using some configuration options: - --routing. It will create a routing module. - --prefix corp. It defines a prefix to be applied to the selectors for created components(corp in this case). The default value is app. - --style scss. The file extension for the styling files. - --skip-tests. it avoids the generations of the .spec.ts files, which are used for testing Adding Angular Material and Angular CDK Before creating the breakpoints, let's add the Angular Material components, which will install the Angular CDK library under the hood. ` Creating the Home Component We can create a brand new component to handle a couple of views to be updated while the breakpoints are changing. We can do that using the ng generate command. ` Pay attention to the output of the previous command since it will show you the auto-generated files. Update the Routing Configuration Remember we used the flag --routing while creating the project? That parameter has created the main routing configuration file for the application: app-routing.module.ts. Let's update it to be able to render the home component by default. ` Update the App Component template Remove all code except the router-outlet placeholder: ` This will allow rendering the home component by default once the routing configuration is running. Using the BreakpointObserver The application has the Angular CDK installed already, which has a layout package with some utilities to build responsive UIs that _react_ to screen-size changes. Let's update the HomeComponent, and inject the BreakpointObserver as follows. ` Once the BreakpointObserver is injected, we'll be able to evaluate media queries to determine the current screen size, and perform changes accordingly. Then, a breakpoint$ variable references an _observable_ object after a call to the observe method. The observe method gets an observable of results for the given queries, and can be used along with predetermined values defined on Breakpoints as a constant. Also, it's possible to use custom breakpoints such as (min-width: 500px). Please refer to the documentation to find more details about this. Next, you may need to _subscribe_ to the breakpoint$ observable to see the emitted values after matching the given queries. Again, let's update the home.component.ts file to do that. ` In the above code, the ngOnInit method is used to perform a _subscription_ to the breakpoint$ observable and the method breakpointChanged will be invoked every time a breakpoint match occurs. As you may note, the breakpointChanged method verifies what Breakpoint value has a match through isMatched method. In that way, the current component can perform changes after a match happened (in this case, it just updates the value for the currentBreakpoint attribute). Using Breakpoint values on the Template Now, we can set a custom template in the home.component.html file and be able to render a square according to the currentBreakpoint value. ` The previous template will render the current media query value at the top along with a rectangle according to the size: Large, Medium, Small or Custom. Live Demo and Source Code Want to play around with this code? Just open the Stackblitz editor or the preview mode in fullscreen. Find the complete angular project in this GitHub repository: breakpointobserver-example-angular. Do not forget to give it a star ⭐️ and play around with the code. Feel free to reach out on Twitter if you have any questions. Follow me on GitHub to see more about my work....

Implementing Dynamic Types in Docusign Extension Apps cover image

Implementing Dynamic Types in Docusign Extension Apps

Implementing Dynamic Types in Docusign Extension Apps In our previous blog post about Docusign Extension Apps, Advanced Authentication and Onboarding Workflows with Docusign Extension Apps, we touched on how you can extend the OAuth 2 flow to build a more powerful onboarding flow for your Extension Apps. In this blog post, we will continue explaining more advanced patterns in developing Extension Apps. For that reason, we assume at least basic familiarity with how Extension Apps work and ideally some experience developing them. To give a brief recap, Docusign Extension Apps are a powerful way to embed custom logic into Docusign agreement workflows. These apps are lightweight services, typically cloud-hosted, that integrate at specific workflow extension points to perform custom actions, such as data validation, participant input collection, or interaction with third-party services. Each Extension App is configured using a manifest file. This manifest defines metadata such as the app's author, support links, and the list of extension points it uses (these are the locations in the workflow where your app's logic will be executed). The extension points that are relevant for us in the context of this blog post are GetTypeNames and GetTypeDefinitions. These are used by Docusign to retrieve the types supported by the Extension App and their definitions, and to show them in the Maestro UI. In most apps, these types are static and rarely change. However, they don't have to be. They can also be dynamic and change based on certain configurations in the target system that the Extension App is integrating with, or based on the user role assigned to the Maestro administrator on the target system. Static vs. Dynamic Types To explain the difference between static and dynamic types, we'll use the example from our previous blog post, where we integrated with an imaginary task management system called TaskVibe. In the example, our Extension App enabled agreement workflows to communicate with TaskVibe, allowing tasks to be read, created, and updated. Our first approach to implementing the GetTypeNames and GetTypeDefinitions endpoints for the TaskVibe Extension App might look like the following. The GetTypeNames endpoint returns a single record named task: ` Given the type name task, the GetTypeDefinitions endpoint would return the following definition for that type: ` As noted in the Docusign documentation, this endpoint must return a Concerto schema representing the type. For clarity, we've omitted most of the Concerto-specific properties. The above declaration states that we have a task type, and this type has properties that correspond to task fields in TaskVibe, such as record ID, title, description, assignee, and so on. The type definition and its properties, as described above, are static and they never change. A TaskVibe task will always have the same properties, and these are essentially set in stone. Now, imagine a scenario where TaskVibe supports custom properties that are also project-dependent. One project in TaskVibe might follow a typical agile workflow with sprints, and the project manager might want a "Sprint" field in every task within that project. Another project might use a Kanban workflow, where the project manager wants a status field with values like "Backlog," "ToDo," and so on. With static types, we would need to return every possible field from any project as part of the GetTypeDefinitions response, and this introduces new challenges. For example, we might be dealing with hundreds of custom field types, and showing them in the Maestro UI might be too overwhelming for the Maestro administrator. Or we might be returning fields that are simply not usable by the Maestro administrator because they relate to projects the administrator doesn't have access to in TaskVibe. With dynamic types, however, we can support this level of customization. Implementing Dynamic Types When Docusign sends a request to the GetTypeNames endpoint and the types are dynamic, the Extension App has a bit more work than before. As we've mentioned earlier, we can no longer return a generic task type. Instead, we need to look into each of the TaskVibe projects the user has access to, and return the tasks as they are represented under each project, with all the custom fields. (Determining access can usually be done by making a query to a user information endpoint on the target system using the same OAuth 2 token used for other calls.) Once we find the task definitions on TaskVibe, we then need to return them in the response of GetTypeNames, where each type corresponds to a task for the given project. This is a big difference from static types, where we would only return a single, generic task. For example: ` The key point here is that we are now returning one type per task in a TaskVibe project. You can think of this as having a separate class for each type of task, in object-oriented lingo. The type name can be any string you choose, but it needs to be unique in the list, and it needs to contain the minimum information necessary to be able to distinguish it from other task definitions in the list. In our case, we've decided to form the ID by concatenating the string "task_" with the ID of the project on TaskVibe. The implementation of the GetTypeDefinitions endpoint needs to: 1. Extract the project ID from the requested type name. 1. Using the project ID, retrieve the task definition from TaskVibe for that project. This definition specifies which fields are present on the project's tasks, including all custom fields. 1. Once the fields are retrieved, map them to the properties of the Concerto schema. The resulting JSON could look like this (again, many of the Concerto properties have been omitted for clarity): ` Now, type definitions are fully dynamic and project-dependent. Caching of Type Definitions on Docusign Docusign maintains a cache of type definitions after an initial connection. This means that changes made to your integration (particularly when using dynamic types) might not be immediately visible in the Maestro UI. To ensure users see the latest data, it's useful to inform them that they may need to refresh their Docusign connection in the App Center UI if new fields are added to their integrated system (like TaskVibe). As an example, a newly added custom field on a TaskVibe project wouldn't be reflected until this refresh occurs. Conclusion In this blog post, we've explored how to leverage dynamic types within Docusign Extension Apps to create more flexible integrations with external systems. While static types offer simplicity, they can be constraining when working with external systems that offer a high level of customization. We hope that this blog post provides you with some ideas on how you can tackle similar problems in your Extension Apps....

Let's innovate together!

We're ready to be your trusted technical partners in your digital innovation journey.

Whether it's modernization or custom software solutions, our team of experts can guide you through best practices and how to build scalable, performant software that lasts.

Prefer email? hi@thisdot.co