I remember being at an investor dinner when I worked for an up and coming tech startup. This was after our official presentation and we were kicking back and enjoying some casual conversation. One of the investors asked our CTO what the hardest technical challenge he found while working on software was. Without skipping a beat, our CTO responded “time zones”. 

Now, lest you think less of our CTO, he was a brilliant person who had built some very complex systems ground up, and at the time we had built a system that depended heavily on having to-the-second accurate information. He knew what was hard and what wasn’t.  

If you work on an application that deals with dates and times across time zones without a deliberate strategy behind it, you’re likely showing incorrect information.  

Time Zones 

Let’s cut to the chase.  

In a standard web application with persistence (a database) you will likely have an architecture simplified to this: 

Your web browser connects to a server somewhere and retrieves information from a database. Part of this information may include a date and time, such as 2022-03-22 12:00:00. The question is, what time does this represent?  

It’s impossible to know without knowing what the time zone of the database is assumed to be. Without explicitly storing the time zone with the datetime stamp, it’s meaningless. All database servers have an assumed time zone, though, and this is where our puzzle begins.  

Most of the time, your database server will be set to UTC, or Coordinated Universal Time. This is the primary standard time of the world, and all other time zones are described as being “offset” to this time. For instance, Eastern time in the United States can either be 4 or 5 hours behind UTC, depending on daylight savings time. You would represent this as UTC-4 or UTC-5. 

Now, in my case, the database server is set to represent Eastern time, which gives me a reference by which I, or any consuming application, can understand the dates and times retrieved from a database.  

Node to the Rescue? 

Node is a very popular runtime framework for servers these days, and when you work with MySql using the popular Node MySql adapter MySqlJS, it will convert a datetime stamp it finds to a native JavaScript Date object, unless you tell it not to. When it does this, it assumes a time zone of the incoming timestamp, and if it’s not given one it will use the time zone of the current runtime.  

In Steps AWS Lambda 

AWS Lambda runs in UTC which means that if you construct a new JavaScript Date object in Node, it assumes the time zone to be UTC. Let’s take our datetime stamp above and see what this would do: 

Database datetime stamp: 2022-03-22 12:00:00

If I took that date timestamp in an AWS Lambda function running Node and created a new Date from it like so: 

new Date(‘2022-03-22 12:00:00’);

You’ll end up with a datetime stamp in JavaScript like this: 


Looks great right? 

Here’s the problem, that Z at the end of the string means that this date and time is in UTC, which actually means that in Eastern time (my original time zone), my date time is this: 

2022-03-22 07:00:00

5 hours off. Ain’t it wacky? This is because MySQL knows the dates it holds are in Eastern, but we aren’t telling Node that, so when we query the database and Node becomes helpful and converts those times to a native Date within JavaScript, we’re distorting our data.  

There are lots of ways to handle this, but the key point is that it must be handled.  

The Solution 

In our situation, the most common scenario is this: 

In order to make sure the Client receives the right data, the server and database need to be talking the same time zone, or at least understand where the other stands. 

With the MySqlJS library above, this is pretty simple. When I establish a connection, I can give it the time zone to work in: 

Database Connection Example

That last property is the most important, because it will tell Node to use an offset of `-5` when establishing the connection.  

When you do this and you rerun our example from above you get the magical, correct, value: 

Database datetime stamp: 2022-03-22 12:00:00

new Date(‘2022-03-22 12:00:00’);

Annnd our datetime stamp in JavaScript: 


This may be confusing because this is a UTC timestamp, but that’s what we want. 

2022-03-22T17:00:00Z ==2022-03-22 12:00:00 EST



If you do date comparisons or casting on a server, make sure that you know the time zone of the server as well as the origin of the data.  

In addition: 

  1. Be specific about the time zones you’re using when passing around dates. 
  1. Understand the time zone of the runtimes you’re using. 
  1. Pass your dates and times around in consistent formats. 

Happy coding. 


We are honored to have these awards

Tech Industry
Disruptive Tech Fast 50 Award
Inc 5000 Award
75 WD

If you have worked in the software industry, chances are you’ve encountered a program that needs to migrate an application or feature. Often the term “migration” brings a sense of fear and foreboding, but it doesn’t have to. At Softrams, we have often taken ownership of applications that use legacy technologies and require some modernization to be sustainable for future use. We’re going to share some insights we’ve gained in doing this, as well as outlining some best practices that will help your organization if they find themselves needing to perform a migration themselves.  

What’s a migration? 

A standard software lifecycle is to define a product, build it, launch it, and then maintain it. Occasionally during the lifecycle of a software application it is found that the original way something was built isn’t adequate for the entire life of the application. There may be new requirements that the first build of the application aren’t well-positioned to support, or perhaps one of the platforms the application was built on will no longer be supported. In these cases, you may have to take your existing application and “migrate” the functionality in some way. This could mean: 

  • Your application is written in a particular language, and you need to move it to a new language  or runtime (perhaps ASP.NET to Node). 
  • The application needs to be moved from one platform to another (hosting using Heroku to AWS, for example). 
  • You may want to split your application from a monolith to a microservice approach, or the opposite.  

Each of these scenarios will require a very different approach and bring their own challenges. For instance, changing languages depends greatly on 

  • Developer knowledge of both languages. If developers are weak in one or both languages, you may consider training to ensure that the migrated codebase is written with best practices. 
  • Developer knowledge of the business logic of the application. If developers do not understand the existing codebase, it is likely that subtle bugs will be introduced during the migration due to a misunderstanding. 

And each of the examples above will present their own gotchas.  

How do you approach a migration? 

When the challenge of migrating your code base is presented, it’s essential for the team to approach the migration intentionally. To have a high chance of a successful migration, ensure you: 

  • Understand why this migration is necessary 
  • Identify the specific goal of this migration 
  • Document all requirements necessary for the migration to occur 
  • Identify and communicate the risk associated with performing this migration

Let’s break each of those down a little further and understand why they’re crucial to a successful migration. 

Understanding why 

There are very few directives for a development team less motivating than being told to do a bunch of critical, risky work without adequate reason. There can be many real and important benefits to performing a migration, and any initiative needs to start with why. If the team isn’t motivated to achieve real benefits with a migration, you will find it takes longer and carries more risk. 

Identify the goal of this migration 

Though understanding “why” a migration is occurring is helpful, it should be taken one step further to also ensure that each migration has criteria for success. This could be as simple as specifying that “my application needs to work on AWS instead of Heroku” or it can be a specific metric such as “bringing the application launch time from 20 minutes to < 1 minute”. Either way, identify criteria for success and ensure the team understands how they succeed.  

Document all requirements  

The complexity of this documentation will depend on the type of migration. If you’re rewriting a codebase from one language to another, you would want to highlight any language specific considerations that may impact delivering comparable functionality. If you’re migrating platforms, you’ll want to ensure you understand exactly how you want your infrastructure to behave with robust analysis. This type of documentation also gives you a nice checklist to work through as the migration occurs and can provide visibility on how the migration is progressing. 

Identify and communicate risk 

Any changes to a software application carry risk. Whether you’re making small changes to a layout, or changing core aspects of your system, be assured that change brings the possibility of error and the larger the change, the greater the risk of error. Before you decide to migrate any part of your application identify what the major risks are and agree on mitigation strategies. For instance, if you are migrating the hosting of your website, the last step may be to route all traffic to your newly hosted site and a solid failsafe is to keep your existing site up and running in parallel for a while so that you can revert to the existing instance in cases of failure.  

Specific Scenarios/Gotchas 

As mentioned above, different migrations can have their own challenges. Below, we outline some specific things to keep in mind for each. 

Lift + shift 

A “lift and shift” migration is one where you take the exact functionality and code conventions of an existing codebase written in a specific programming language and rewrite it in another programming language by changing as few things as possible. Sometimes this means that you can copy an entire codebase with only some simple syntax changes. 

When to consider this: 

  • Your timeline is tight and you have a small amount to migrate 
  • You think there will be time in the future to revisit and refactor 
  • Your existing application architecture and structure is solid, with sound conventions 

What to examine before pursuing: 

  • Does the new language support all existing conventions of the current language? 
  • Are system requirements documented well enough to inform developers of how to make decisions when a clear “shift” is not possible? 
  • Are there any poor coding practices present that may be in need of a refactor? If so, this may not be the correct approach. 

Refactor + shift 

In contract to a “lift and shift” where you avoid changing code as you migrate it to a new language, a “refactor and shift” approach will require a more deliberate plan and coordination. This approach takes an existing codebase and rewrites in another language or framework, maintaining existing functionality and core structure while improving the structure and consistency of a codebase.  

When to consider this: 

  • You have an adequate timeline to complete the migration 
  • You have solid engineering leadership 
  • Your system could use a little TLC on the maintainability scale 

Some risks: 

  • Make sure you define how far to take the “refactor”. Some developers hear the word “refactor” and don’t know when enough is enough.  
  • This approach requires more robust testing and quality control, since a larger portion of the application will be changed and unintended side effects may creep in. 

Full re-write 

Sometimes you have an application that was either poorly written or written with requirements that are no longer relevant. In this case, you take the desired functionality and purpose of the system and rewrite it from ground up. This option provides an opportunity to change frameworks or languages, if desired. 

When to consider this: 

  • You find yourself consistently unable to meet new requirements due to prior (limiting) architecture decisions.  
  • The purpose or requirements of the system have changed dramatically since its original launch. 
  • You company can accommodate a timeline that prioritizes refactoring to new features. 
  • You have well defined product requirements. Scope creep can affect re-writing software just as much as a new product, and you don’t want your migration to be affected by ill-defined requirements. 


  • Having a working application brings stability and confidence that vanishes when you decide to launch fresh. The moment you decide to do a rewrite, you need to be prepared to treat the initiative as a new product launch. 
  • The only sure way to know how long it will take to develop software is to develop it and then measure the time it took. The larger the initiative, the larger the potential variance in timeline could take.  


Migrating a codebase, especially when it supports a live application, can be an intimidating task. There are times when it is the best course of action for the lifecycle of your application. In these cases make sure you set your program and team up for success by making good decisions and considering all your options.

We are honored to have these awards

Tech Industry
Disruptive Tech Fast 50 Award
Inc 5000 Award
75 WD


When Apple’s Don Norman became the first person granted a “User Experience Architect” title in the 1990s, the world did not quite understand the value of UX. Fast forward to 2021: Companies from all industries have invested in the development of UX teams, realizing the advantage of a human-centered design process. However, one thing that remains is the disconnect between stakeholders and users throughout the product building process. At Softrams, we constantly work to connect our clients’ goals with end user needs. In this blog, we outline three tried and tested methods to bridge the gap.

Proven methods to connect users and stakeholders:

  1. Summarize the HCD journey succinctly
  2. Align development timelines
  3. Deploy and test rapid prototypes

Articulating the Research Journey

The first step of the UX process is to align the product team and stakeholders with a clear and concise version of the research plan. Our HCD teams ensure the viability of this deliverable by condensing our research into a one-pager. If your stakeholder doesn’t understand your one-pager, they are less likely to engage in the journey.

Aligning Timelines

In the past, product developers often ignored the timing of the UX team’s process. We work closely with our product analysts, project managers, and lead developers to stay aligned by launching routine update meetings. These coworking sessions help build trust and credibility with key stakeholders.

Deploying Rapid Prototypes

Building and testing new ideas is an exciting part of our role. We work closely within our agile teams to build prototypes that help us validate our assumptions.


The most integral element in connecting clients with users is communication. Beyond defining their own goals, stakeholders must be actively looped into the research regarding their target audience and user needs. Alignment is integral to a successful product development process.


We are honored to have these awards

Tech Industry
Disruptive Tech Fast 50 Award
Inc 5000 Award
75 WD

Coauthored by John Marlett and Chris Hand

The best software solutions enable a user to accomplish the task they set out to do practically without noticing the software product. The more you have to think about what you’re doing when using software, the less you are thinking about solving hard problems, pushing the boundaries of creativity, or simply completing your routine tasks efficiently. The most popular software products reliably solve a problem with a foundation of quality that delights users. Think of your favorite mobile apps, computer software, or websites. When you use these, you’re not worried about whether you’ll find another bug or be unable to perform basic tasks. You perform your task seamlessly, as the application itself remains in the background enabling your workflow without intruding into your conscious thought process. 

At Softrams, we build quality into our most basic development processes, knowing that a good human experience with software cannot be achieved unless it’s foundational to how systems are built. Each product team discusses quality at the inception of feature requirements, allowing all members of the team to understand and have confidence in the solution they are building. how they will have confidence they are building a good solution. Testing is considered part of the Definition of Done for features and stories, and the team doesn’t consider them completed and delivered until they implement not only the desired functionality but also proper testing to guarantee code quality. 

Types of Testing 

In software there are many levels of testing that a team can use to build confidence that what they build is correct and will fulfill a user’s needs. Below are some of the many different types of testing you may hear about in software development. 

Unit tests, which are highly localized to validate that a small piece (a unit, such as a function or small cluster or functions) of code will behave properly when it is run.  

Integration tests are like unit tests, but will validate that multiple units, parts of a system, or entire systems will work correctly together under certain circumstances.  

End to end tests test a piece of functionality, or a system from a user’s perspective. These tools usually involve some program that interacts with a UI and lend tremendous value by being able to test every part of a system similar to how a user would.  

User acceptance testing is often a final step in software development where end users will use the system being developed to provide feedback before launch. 

Testing Code with Code 

Let’s examine two of the examples above, unit tests and integration tests. These are both examples of software developers writing code that tests their code. 

A common pitfall for developers is to receive requirements and start writing code specifically meant for the inputs and outputs discussed during product discovery without considering larger implementation impacts. This can lead to duplicate code and repository bloat. Conversely, the mental exercise of writing unit tests alongside functional code can help developers think more creatively about how the system, initiated by a user, will interact with the solution they’ve created. This results in a better, more robust solution ultimately giving a better experience for the user.  

Additionally, testing the features being written encourages developers to think more creatively about the code itself. Examples of this are unit tests and integration tests.  In the most basic sense both a unit test and integration test are code that developers write to test their code. For instance, if a developer writes some code to add two numbers together, a series of unit tests may validate that if you pass the code certain values, it will return the correct sum. Very often if a function is overextended and confusing, the exercise of figuring out what it does and why in order to test it will give the developer key insight into how to split the function up into smaller ones with clear uses. For many teams, the greatest value from developer led unit testing is found in productivity and code quality gains for the developers themselves. Well tested code tends to be higher quality and better documented, making it easier to maintain. Additionally, when developers extend heavily tested code, they can make isolated changes and include relevant tests with the confidence that existing functionality is stable if the tests pass. Integration tests are similar, but test multiple parts of a system interacting together, which can lend a tremendous amount of value in catching bugs early in the product development process.  

These types of tests can be written and run very quickly, which means that you are able to change existing functionality and verify it in a short amount of time. Those times when a developer runs a test to find that something broke can save an entire team time in investigation, troubleshooting, and fixing code in the future, making even these simple tests a development time power up that contribute to higher confidence across the team in delivery estimates and functional stability. 

End to End Testing 

While it’s extremely valuable to increase confidence in e code written by developers via unit and integration testing, the end user’s experience is the final goalpost in producing a superior product. End to end testing gives a team or customer ultimate confidence that a user is able to complete the desired workflow by running through the flows themselves and validating the results 

There are many kinds of end-to-end tests, but here we will focus on UI end to end tests, which interact with the user interface of an application and are meant to replicate user workflows and validate certain steps that work the way you expect. These tests typically require tooling and frameworks to help interact with an application, which have advanced dramatically over the past decade. At Softrams, we prefer tools that allow us to write and run tests mirroring expected user workflows as closely as possible. A primitive script for a test may look something like this: 

Test Scenario: Validate Monthly Report Shows Expected Values 

  • User logs in 
  • Navigate to reports page 
  • Access monthly report 
  • Run report for August 2021,  
  • Validate total sales revenue equals $21,000 

I can then schedule these tests to be run at arbitrary intervals, such as nightly, or when I make changes to the system. In doing so, I can confidently enhance this feature, develop new ones, or upgrade dependent services knowing that existing functionality is being tested reliably and consistently, and if a bug is introduced, or a dependency is broken it will be caught quickly.  

While it’s obvious this type of testing can be extremely valuable, they can also have a high cost of development and maintenance. Because of this, it’s advisable to be very targeted with the tests you write in this manner. Focus on the core workflows of your application. It is usually not worthwhile to write end to end tests for flows or even whole products that are in a state of flux. 

These types of tests don’t replace the need for manual testing, but significantly reduce the effort needed to have a high degree of confidence that your product will continue allowing users to perform their needed functions.  

Lastly, in writing these tests, the entire team can understand how users will interact with the system. This, in turn, will allow the team to build better solutions and think creatively about how to constantly improve the experience offered.  

User Acceptance Testing 

As helpful as all the above types of tests are (and we strongly encourage teams to adopt writing all of them in different scenarios) having users interacting with your application directly will always be more valuable than the tests you write yourself. A user acceptance test allows your end users to interact with functionality before they are officially released. There are many benefits to a user acceptance test that we would recommend taking advantage of. 

  • They allow you to get direct feedback from your users. 
  • You will get the opportunity to understand how a user will interact with the application, validating or challenging the assumptions made while building it. 
  • Bugs or failures in workflows will be discovered in a safe environment, before getting to a production environment. 

Just to name a few. Be sure to give yourself plenty of time to incorporate feedback from these tests into your product! Ideally, this is not the first time your users have seen the application they will be interacting with. Make sure you have validated your development with the use of wireframes and mockups well before you start developing, so this stage of the process is validating what you already know, as opposed to discovering new needs.  

Building Quality into Process 

In order to realize the benefits of writing tests that verify a small section, or your entire system works correctly, your team has to understand and buy into the value of each type of test. An engineer who is forced to write unit tests just to meet certain coverage guidelines will do just that and find little to no improvement in having written them. Similarly, integration or end to end tests that are written just for the sake of appearances will still add value but fall short of delivering a massively better experience for end users.  

To start building these types of tests into your own process, start by focusing on quality at every step of the product life cycle. Ensure your teams understand the need to have confidence in what they build. Push your team to deliver a better product with every iteration, and don’t settle for testing the system manually to be the only answer to ensuring quality.  

For those teams that want to move faster without breaking things, having quality built into each step of the process will allow you to ship exponentially faster without the worry of breaking your system.  


Building quality into your development processes will produce a better product and encourage your entire team to think differently about how to deliver value to end users. There are many ways you can build confidence that a product being built will not only fulfill a need and delight a user but will do so without several iterations of finding and fixing bugs. Take advantage of some industry accepted practices to build this into your own process!  

We are honored to have these awards

Tech Industry
Disruptive Tech Fast 50 Award
Inc 5000 Award
75 WD