DEV Community

Chidera
Chidera

Posted on

1

Relative Time String

I recently came across this blog post by Steve Sewell and it really helped me in building a better time string function. Hopefully this can be a quick read for you.

Before I had this function getTimeText that was returning the time string relative to the current time:

export function getTimeText(current_date: Date, due_date: Date) {
    const _MS_PER_DAY = 1000 * 60 * 60 * 24
    // Discard the time and time-zone information.
    const utc1 = Date.UTC(current_date.getFullYear(), current_date.getMonth(), current_date.getDate())
    const utc2 = Date.UTC(due_date.getFullYear(), due_date.getMonth(), due_date.getDate())
    const days = Math.floor((utc2 - utc1) / _MS_PER_DAY)

    if (current_date.getFullYear() === due_date.getFullYear()) {
        if (current_date.getMonth() === due_date.getMonth()) {
            if (current_date.getDate() === due_date.getDate()) {
                if (current_date.getHours() === due_date.getHours()) {
                    if (current_date.getMinutes() === due_date.getMinutes()) {
                        return ['Right Now', false]
                    } else {
                        const diffInMinutes = due_date.getMinutes() - current_date.getMinutes()

                        if (diffInMinutes == 1) {
                            return ['In a Minute', false]
                        } else if (diffInMinutes == -1) {
                            return ['A Minute Ago', true]
                        } else if (diffInMinutes < 0) {
                            return [`${Math.abs(diffInMinutes)} Minutes Ago`, true]
                        } else return [`In ${diffInMinutes} Minutes`, false]
                    }
                } else {
                    const diffInHours = due_date.getHours() - current_date.getHours()

                    if (diffInHours == 1) {
                        return ['In an Hour', false]
                    } else if (diffInHours == -1) {
                        return ['An Hour Ago', true]
                    } else if (diffInHours < 0) {
                        return [`${Math.abs(diffInHours)} Hours Ago`, true]
                    } else return [`In ${diffInHours} Hours`, false]
                }
            } else {
                if (days == 1) {
                    return ['Tomorrow', false]
                } else if (days == -1) {
                    return ['Yesterday', true]
                } else if (days < 0) {
                    return [`${Math.abs(days)} Days Ago`, true]
                } else return [`In ${days} Days`, false]
            }
        } else {
            const diffInMonth = due_date.getMonth() - current_date.getMonth()

            if (diffInMonth == 1) {
                return ['Next Month', false]
            } else if (diffInMonth == -1) {
                return ['A Month Ago', true]
            } else if (diffInMonth < 0) {
                return [`${Math.abs(diffInMonth)} Months Ago`, true]
            } else return [`In ${diffInMonth} Months`, false]
        }
    } else {
        const diffInYear = due_date.getFullYear() - current_date.getFullYear()

        if (diffInYear == 1) {
            return ['Next Year', false]
        } else if (diffInYear == -1) {
            return ['A Year Ago', true]
        } else if (diffInYear < 0) {
            return [`${Math.abs(diffInYear)} Years Ago`, true]
        } else return [`In ${diffInYear} Years`, false]
    }
}
Enter fullscreen mode Exit fullscreen mode

Although this worked, I started finding some bugs.

For instance in a Todo List; if a todo was due at 11:59am and the current time is 12:05pm, the function returns 'an hour ago' instead of '6 minutes ago'. You see the problem. I eventually ignored it and said I'll fix it another time until I came across this blog post.

Basic Summary
I ended up changing my previous code to this and it fixed the edge cases.

/**
 * Convert a date to a relative time string, such as
 * "a minute ago", "in 2 hours", "yesterday", "3 months ago", etc.
 * using Intl.RelativeTimeFormat
 */
export function getRelativeTimeString(
  date: Date | number,
  lang = navigator.language
): string {
  // Allow dates or times to be passed
  const timeMs = typeof date === "number" ? date : date.getTime();

  // Get the amount of seconds between the given date and now
  const deltaSeconds = Math.round((timeMs - Date.now()) / 1000);

  // Array reprsenting one minute, hour, day, week, month, etc in seconds
  const cutoffs = [60, 3600, 86400, 86400 * 7, 86400 * 30, 86400 * 365, Infinity];

  // Array equivalent to the above but in the string representation of the units
  const units: Intl.RelativeTimeFormatUnit[] = ["second", "minute", "hour", "day", "week", "month", "year"];

  // Grab the ideal cutoff unit
  const unitIndex = cutoffs.findIndex(cutoff => cutoff > Math.abs(deltaSeconds));

  // Get the divisor to divide from the seconds. E.g. if our unit is "day" our divisor
  // is one day in seconds, so we can divide our seconds by this to get the # of days
  const divisor = unitIndex ? cutoffs[unitIndex - 1] : 1;

  // Intl.RelativeTimeFormat do its magic
  const rtf = new Intl.RelativeTimeFormat(lang, { numeric: "auto" });
  return rtf.format(Math.floor(deltaSeconds / divisor), units[unitIndex]);
}
Enter fullscreen mode Exit fullscreen mode

Let me know in the comments if this helped you in any way 👌🏾. And you can read the blog post to understand how this solution came about.

AWS GenAI LIVE image

Real challenges. Real solutions. Real talk.

From technical discussions to philosophical debates, AWS and AWS Partners examine the impact and evolution of gen AI.

Learn more

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay