Moment.js is one of the most popular JavaScript libraries for parsing and formatting dates.  WhereTo uses Node.js, so for them, using this library was a completely natural move.  Problems with server usage of Moment.js were not expected.  In the end, from the very beginning they used this library in the frontend to display dates and were pleased with its work.  However, the fact that the library performed well on the client did not mean that there would be no problems with the server either. 
      
        
        
        
      
    
      
        
        
        
      
      
 
      
        
        
        
      
    
      
        
        
        
      
      The material, the translation of which we publish today, is dedicated to the story of solving the performance problem Moment.js. 
      
        
        
        
      
    
      
        
        
        
      
      Project growth and productivity decline 
      
        
        
        
      
      Recently, the number of airline flight records returned by the WhereTo system has grown approximately tenfold.  Then we faced a very strong drop in performance.  It turned out that the rendering cycle, which took less than 100 milliseconds, now takes more than 3 seconds to display about 5,000 search results.  Our team has undertaken research.  After several profiling sessions, we noticed that more than 99% of this time is spent in a single function called 
createInZone
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     . 
      
        
        
        
      
    
      
        
        
        
      
    
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    
      
        
        
        
      
      The createInZone function takes about 3.3 seconds to complete. 
      
        
        
        
      
    
      
        
        
        
      
      Continuing our investigation of the situation, we found that this function is called by the Moment.js 
parseZone
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     function.  Why is she so slow?  We had the feeling that the Moment.js library was designed for common use cases, and as a result, it will try to process the input string in various ways.  Maybe you should limit it?  After we read the documentation, we found out that the 
parseZone
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     function accepts an optional argument specifying the date format: 
      
        
        
        
      
    
      
        
        
        
      
     moment.parseZone(input, [format])
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     
 
      
        
        
        
      
      The first thing we did was try to use the 
parseZone
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     function with passing information about the date format to it, but this, as the performance tests showed, did not lead to anything: 
      
        
        
        
      
    
      
        
        
        
      
     $ node bench.js moment#parseZone x 22,999 ops/sec ±7.57% (68 runs sampled) moment#parseZone (with format) x 30,010 ops/sec ±8.09% (77 runs sampled)
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     
 
      
        
        
        
      
      Although now the 
parseZone
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     function worked a little faster, for our needs this speed was clearly not enough. 
      
        
        
        
      
    
      
        
        
        
      
      Project-specific optimization 
      
        
        
        
      
      We used Moment.js to parse dates retrieved from our provider’s API (Travelport).  We realized that it always returns data in the same format: 
      
        
        
        
      
    
      
        
        
        
      
     "2019-12-03T14:05:00.000-07:00"
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     
 
      
        
        
        
      
      Knowing this, we began to understand the internal structure of Moment.js in order to (as we hoped) write a much more efficient function that produces the same results. 
      
        
        
        
      
    
      
        
        
        
      
      Creating a faster alternative to parseZone 
      
        
        
        
      
      First, we needed to figure out how the Moment.js objects look.  This was pretty easy to understand: 
      
        
        
        
      
    
      
        
        
        
      
     > const m = moment() > console.log(m) Moment {  _isAMomentObject: true,  _i: '2019-12-03T14:05:00.000-07:00',  _f: 'YYYY-MM-DDTHH:mm:ss.SSSSZ',  _tzm: -420,  _isUTC: true,  _pf: { ...snip },  _locale: [object Locale],  _d: 2019-12-03T14:05:00.000Z,  _isValid: true,  _offset: -420 }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     
 
      
        
        
        
      
      The next step was to instantiate Moment without using a constructor: 
      
        
        
        
      
    
      
        
        
        
      
     export function parseTravelportTimestamp(input: string) {  const m = {}  
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     
 
      
        
        
        
      
      Now it seemed like we had a lot of Moment instance properties that we could just set (I don’t go into the details of how we found out about this, but if you look at the source code of Moment.js you will understand): 
      
        
        
        
      
    
      
        
        
        
      
     const FAKE = moment() const TRAVELPORT_FORMAT = 'YYYY-MM-DDTHH:mm:ss.SSSSZ' export function parseTravelportTimestamp(input: string) {  const m = {}  
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     
 
      
        
        
        
      
      The last step of our work was to find out how to parse the 
offset
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     value of the timestamp.  It turned out that this is always the same position in the line.  As a result, we were able to optimize this: 
      
        
        
        
      
    
      
        
        
        
      
     function parseTravelportDateOffset(input: string) {  const hrs = +input.slice(23, 26)  const mins = +input.slice(27, 29)  return hrs * 60 + (hrs < 0 ? -mins : mins) }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     
 
      
        
        
        
      
      Here's what happened after we put it all together: 
      
        
        
        
      
    
      
        
        
        
      
     const FAKE = moment() const TRAVELPORT_FORMAT = 'YYYY-MM-DDTHH:mm:ss.SSSSZ' function parseTravelportDateOffset(input: string) {  const hrs = +input.slice(23, 26)  const mins = +input.slice(27, 29)  return hrs * 60 + (hrs < 0 ? -mins : mins) }  export function parseTravelportTimestamp(input: string): moment {  const m = {}  
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     
 
      
        
        
        
      
      Performance tests 
      
        
        
        
      
      We tested the performance of the resulting solution using the benchmark npm module.  Here is the benchmark code: 
      
        
        
        
      
    
      
        
        
        
      
     const FAKE = moment() const TRAVELPORT_FORMAT = 'YYYY-MM-DDTHH:mm:ss.SSSSZ' function parseTravelportDateOffset(input: string) {  const hrs = +input.slice(23, 26)  const mins = +input.slice(27, 29)  return hrs * 60 + (hrs < 0 ? -mins : mins) }  export function parseTravelportTimestamp(input: string): moment {  const m = {}  
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     
 
      
        
        
        
      
      Here are the results of our performance research: 
      
        
        
        
      
    
      
        
        
        
      
     $ node fastMoment.bench.js moment#parseZone x 21,063 ops/sec ±7.62% (73 runs sampled) moment#parseZone (with format) x 24,620 ops/sec ±6.11% (71 runs sampled) fast#parseTravelportTimestamp x 1,357,870 ops/sec ±5.24% (79 runs sampled) Fastest is fast#parseTravelportTimestamp
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     
 
      
        
        
        
      
      As it turned out, we managed to speed up the analysis of timestamps by about 64 times.  But how did this affect the actual operation of the system?  Here's what happened as a result of profiling. 
      
        
        
        
      
    
      
        
        
        
      
    
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    
      
        
        
        
      
      The total runtime of parseTravelportTimestamp is less than 40 ms. 
      
        
        
        
      
    
      
        
        
        
      
      The results were simply amazing: we started with 3.3 seconds, going into parsing dates, and came to less than 40 milliseconds. 
      
        
        
        
      
    
      
        
        
        
      
      Summary 
      
        
        
        
      
      When we started working on our platform, we had to solve just a terrible amount of problems.  We only knew that we were repeating to ourselves: “Let it work first, but you can do optimization later”. 
      
        
        
        
      
    
      
        
        
        
      
      Over the past few years, the complexity of our project has grown tremendously.  Fortunately, now we have come to the place where we can move on to the second part of our “mantra” - optimization. 
      
        
        
        
      
    
      
        
        
        
      
      Library solutions helped the project get to where it is today.  But we are faced with a "library" problem.  Solving it, we learned that by creating our own mechanism focused on our needs, we can make the code “easier” in terms of consuming system resources and save valuable time for our users. 
      
        
        
        
      
    
      
        
        
        
      
      Dear readers!  Have you encountered problems similar to the one discussed in this article? 
      
        
        
        
      
    
      
        
        
        
      
    
      
        
        
        
      
     