Skip to content

Time column helpers ​

Eight helpers that build a time picker's columns. They decide whether the reader sees a 12- or 24-hour clock, and in which order.

Open in demo

Import ​

ts
import {
  dayPeriodLabel,
  formatHourCell,
  formatMinuteCell,
  formatTime,
  hourCells,
  periodOf,
  usesHour12,
  withPeriod,
} from '@shared/utils/timeColumns'

Every one takes a BCP-47 tag as its first argument, except periodOf and withPeriod, which are pure arithmetic.

usesHour12 ​

ts
function usesHour12(tag: string): boolean

Whether this locale writes a 12-hour clock. Every shipped locale resolves to h12 or h23, so the answer is never ambiguous.

hourCells ​

ts
function hourCells(tag: string, period: DayPeriod): number[]

type DayPeriod = 'am' | 'pm'

The 24-hour hours the column lists, in display order.

A 12-hour locale gets 1…11 and then 12 — the order on a clock face, so midnight and noon land on the "12" row where a reader expects them. A 24-hour locale gets 0…23 and ignores period.

The values are always 24-hour, so nothing downstream has to convert; only the labels change.

formatHourCell and formatMinuteCell ​

ts
function formatHourCell(tag: string, hour: number): string
function formatMinuteCell(tag: string, minute: number): string

One cell's label, in the locale's own digits and padding.

formatTime ​

ts
function formatTime(tag: string, hour: number, minute: number): string

The whole value, as the locale writes it.

dayPeriodLabel ​

ts
function dayPeriodLabel(tag: string, period: DayPeriod): string

"AM" and "PM" in the locale's own words — ص and م in Arabic.

It is read off 09:00 and 21:00, not off midnight and noon. Some locales name those two hours specifically, and a label meaning "noon" is wrong on a button that selects an entire half-day.

periodOf and withPeriod ​

ts
function periodOf(hour: number): DayPeriod
function withPeriod(hour: number, period: DayPeriod): number

periodOf(14) is 'pm'. withPeriod(2, 'pm') is 14. The pair that moves a selection between the two columns without either one having to know the clock.

Building a column ​

vue
<script setup lang="ts">
import type { DayPeriod } from '@shared/utils/timeColumns'
import { localeTag } from '@shared/utils/intl'
import {
  dayPeriodLabel,
  formatHourCell,
  hourCells,
  periodOf,
  usesHour12,
  withPeriod,
} from '@shared/utils/timeColumns'
import { computed, ref } from 'vue'

const tag = computed(() => localeTag())
const hour = ref(14)
const period = computed<DayPeriod>(() => periodOf(hour.value))

const cells = computed(() => hourCells(tag.value, period.value))

function setPeriod(next: DayPeriod) {
  hour.value = withPeriod(hour.value % 12, next)
}
</script>

<template>
  <div class="flex gap-4">
    <ul>
      <li v-for="cell in cells" :key="cell">
        <button type="button" :aria-pressed="hour === cell" @click="hour = cell">
          {{ formatHourCell(tag, cell) }}
        </button>
      </li>
    </ul>

    <div v-if="usesHour12(tag)" class="flex flex-col">
      <button type="button" @click="setPeriod('am')">
        {{ dayPeriodLabel(tag, 'am') }}
      </button>
      <button type="button" @click="setPeriod('pm')">
        {{ dayPeriodLabel(tag, 'pm') }}
      </button>
    </div>
  </div>
</template>