Format multiple dates to look good in text, using functional approach I had a piece of code that solved really simple problem  - it formated two dates to look better on a webpage. It looked like this:
function formatDateRange(
  days: { firstDay: string, lastDay: string }
): string {

  let first, last;
  first = dayjs(days.firstDay); //dayjs parses string that formatted like this &quot;YYYY-MM-DD&quot; 
  last = dayjs(days.lastDay);   //and returns a wrapper for Date object
  if (first.isSame(last, &quot;date&quot;)) {
    first = first.format(&quot;DD MMMM, YYYY&quot;)
    last = &quot;&quot;

  } else if (first.isSame(last, &quot;month&quot;)) {
    first = first.format(&quot;DD&quot;) + &quot; ... &quot;
    last = last.format(&quot;DD, MMMM YYYY&quot;)

  } else if (first.isSame(last, &quot;year&quot;)) {
    first = first.format(&quot;DD MMM&quot;) + &quot; ... &quot;
    last = last.format(&quot;DD MMM, YYYY&quot;)

  } else {
    first = first.format(&quot;DD MMM, YYYY&quot;) + &quot; ... &quot;
    last = last.format(&quot;DD MMM, YYYY&quot;)
  }
  return first + last
}

Than I decided to refactor it, and I got this:
//adapter that allows to use objects and arrays in function-composition
//EDIT: was called &quot;callable&quot;
function pluckFrom&lt;T extends {}&gt;(obj: T) {
  return (key: keyof T) =&gt; obj[key]
}

// a function for function-composition 
// (same as compose from redux but order is reversed)
function pipe(...funcs: Function[]) {
  return funcs.reduce((f1, f2) =&gt; (...args: any) =&gt; f2(f1(...args)))
}

//provides an interface to apply array of functions to array of data
function apply&lt;A&gt;(maps: Array&lt;(a: A) =&gt; any&gt;) {
  return {to: (arr: A[]) =&gt; arr.map((item, index) =&gt; maps[index](item))}
}

//takes array of dates, wrapped in Dayjs object and returns
//true if they're all the same in terms of unit of time 
function isSame([day1, ...rest]: Dayjs[]) {
  return (unit: UnitType) =&gt; rest.every(day =&gt; day.isSame(day1, unit))
}

interface DateRange {
  firstDay: string;
  lastDay: string;
}
//EDIT: removed redundant &quot;same_&quot; prefix
const case_to_format_map = {
  &quot;date&quot;: [&quot;DD MMMM, YYYY&quot;, &quot; &quot;],
  &quot;month&quot;: [&quot;DD ...&quot;, &quot;DD, MMMM YYYY&quot;],
  &quot;year&quot;: [&quot;DD MMM ... &quot;, &quot;DD MMM, YYYY&quot;],
  &quot; &quot;: [&quot;DD MMM, YYYY ... &quot;, &quot;DD MMM, YYYY&quot;]
}

type Case = keyof typeof case_to_format_map

//EDIT: slightly refactored 
function formatDateRange({firstDay, lastDay}: DateRange): string {

  const
    getCase = (days: Dayjs[]) =&gt; (
      ([&quot;date&quot;, &quot;month&quot;, &quot;year&quot;, &quot; &quot;] as UnitType[])
        .filter(isSame(days))[0]
    ) as Case
    ,
    makeFormatter = (f: string) =&gt; (d: Dayjs) =&gt; d.format(f)
    ,
    getFormatters = pipe(
      getCase,
      pluckFrom(case_to_format_map),
      formats =&gt; formats.map(makeFormatter)
    )
    ,
    days = [firstDay, lastDay].map(dayjs)

  return apply(getFormatters(days)).to(days).join(&quot; &quot;)
}

It's more code, but I like it because now I have 4 additional utility functions, it's more readable for me. Other than that, I'm not sure if I made better or worse.
And so I have these questions:
Is it readable to you?
Is it aligned with functional style and best practices?
I'm not sure if I will ever need to extend this code but how can I make it more extendable?
I personally prefer the first one, but this is very subjective. From a person reading the code for the first time, I can skim over the first version and have a good general idea of what the code is about. For the second version, I see a bunch of abstract functions and it takes me longer to see what the code actually does.
While the second version has some advantages, I don't believe it's worth it in this particular case unless you know something about the future of this code that I don't. I guess the point I'm trying to make is that, you should only reformat your code like that if you have a good reason for it. For example, you want to be able to change some part of the function behavior at runtime or when it's called from different parts of the codebase.