# Date and time formatting

This guide shows how to customize date/time formatting in SDK components.

## Best Practices

- Centralize formatting in i18n JSON to keep UI consistent across components.
- Use `timestampTranslationKey` only when you need per-instance overrides.
- Prefer `calendar` formats for conversational timelines and `format` for precise timestamps.
- Ensure locale-specific formats are complete to avoid English fallbacks.
- Test formatting with long dates and different locales to catch layout issues.

## SDK components displaying date & time

These SDK components display date/time:

- `ChannelPreviewStatus` - Component showing last message date and time in `ChannelList`.
- `ImageGalleryHeader` - Component showing the header in the `ImageGallery`.
- `InlineDateSeparator` - Component separating groups of messages in `MessageList`.
- `MessageSystem` - Component showing system message.
- `MessageTimestamp` - Component showing message timestamp.
- `StickyHeader` - Component showing sticky header on the top of the `MessageList`/`Channel`.
- `ChannelMemberItem` - Member row in channel details; shows a presence line (`Online` / `Last seen ...` / `Offline`) via the `useUserActivityStatus` hook.

## Format Customization

Customize date/time formatting via the `i18n` JSON.

### Date & time formatting with i18n service

Formatting via i18n provides SDK-wide configuration stored in translation JSON. Advantages:

- It is centralized.
- It takes into consideration the locale out of the box.
- High granularity: per string, not per component.
- Reuse: apply the same configuration in multiple places via one translation key.
- Allows for custom formatting logic.

The default date time formatting configuration is stored in the JSON translation files. The default translation keys are namespaced with prefix `timestamp/` followed by the component name. For example, the message date formatting can be targeted via `timestamp/MessageTimestamp`, because the underlying component is called `MessageTimestamp`.

Override formatting in your translation JSON. Example:

```json
"timestamp/MessageSystem": "{{ timestamp | timestampFormatter(format: YYYY) }}",
```

You can also override the translation key via `timestampTranslationKey`. All listed components support it.

```tsx
import {
  MessageTimestampProps,
  MessageTimestamp,
} from "stream-chat-react-native";

const CustomMessageTimestamp = (props: MessageTimestampProps) => (
  <MessageTimestamp
    {...props}
    timestampTranslationKey="customTimestampTranslationKey"
  />
);
```

### Understanding the formatting syntax

Once default prop values are nulled, the JSON translation overrides formatting. Example:

```json
"timestamp/MessageSystem": "{{ timestamp | timestampFormatter(calendar: true) }}",
```

or with custom calendar formats:

```json
"timestamp/MessageSystem": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: {\"lastDay: \"[gestern um] LT\", \"lastWeek\": \"[letzten] dddd [um] LT\", \"nextDay\": \"[morgen um] LT\", \"nextWeek\": \"dddd [um] LT\", \"sameDay\": \"[heute um] LT\", \"sameElse\": \"L\"}) }}",
```

or with custom format:

```json
"timestamp/MessageTimestamp": "{{ timestamp | timestampFormatter(format: LT) }}",
```

Let's dissect the example:

- The curly brackets (`{{`, `}}`) indicate the place where a value will be interpolated (inserted) into the string.
- `timestamp` is the variable inserted into the string.
- The `|` character is a pipe that separates the variable from the formatting function.
- `timestampFormatter` converts the `timestamp` value into the desired format.
- `timestampFormatter` accepts the same parameters as the components (`calendar`, `calendarFormats`, `format`).

**Params**:

- `calendar` - This is a `Boolean` field to decide if the date format should be in calendar format or not. The default value is `false`.
- `calendarFormats` - This is an object that contains the formats for the calendar. The default is derived from the active locale (see the SDK's `calendarFormats` map). For example, the English (`en`) default is `{ lastDay: '[Yesterday]', lastWeek: 'dddd', nextDay: '[Tomorrow]', nextWeek: 'dddd [at] LT', sameDay: '[Today]', sameElse: 'L' }`.
- `format` - This is a string that contains the format of the date.

If `calendar` is enabled, dates use relative words ("yesterday at ...", "last ..."). Customize via `calendarFormats`, which should cover all cases:

```js
{
  lastDay: '[gestern um] LT',
  lastWeek: '[letzten] dddd [um] LT',
  nextDay: '[morgen um] LT',
  nextWeek: 'dddd [um] LT',
  sameDay: '[heute um] LT',
  sameElse: 'L',
}
```

>
> **Tip:** If any `calendarFormats` keys are missing, the library falls back to hard-coded English equivalents.
>

If `calendar` is enabled, `format` is ignored. Disable `calendar` to use `format`.

>
> **Note:** The described rules follow the formatting rules required by the i18n library used under the hood - `i18next`. You can learn more about the rules in [the formatting section of the `i18next` documentation](https://www.i18next.com/translation-function/formatting#basic-usage).
>

### Relative "time ago" formatting with `fromNowFormatter`

Besides `timestampFormatter`, the SDK ships a `fromNowFormatter` for relative, "X minutes ago" style output. It powers the channel details member presence line via the `timestamp/UserActivityStatus` key, which the `useUserActivityStatus` hook resolves for offline members with a known `last_active` time. The default configuration is:

```json
"timestamp/UserActivityStatus": "Last seen {{ timestamp | fromNowFormatter }}",
```

**Params**:

- `withSuffix` - This is a `Boolean` field that decides whether the relative suffix is included. When set to `false`, the suffix is dropped (e.g. "5 minutes" instead of "5 minutes ago"). The default value is `true`.

Override the format in your translation JSON. For example, to move the suffix into the surrounding string:

```json
"timestamp/UserActivityStatus": "Active {{ timestamp | fromNowFormatter(withSuffix: false) }} ago",
```

>
> **Note:** `Online` and `Offline` are plain translation strings, not timestamps, so only the offline "Last seen ..." branch is affected by this formatter. The relative time follows the configured locale because it is produced through `tDateTimeParser`.
>

### Custom date time formatter functions

You can also override the default `timestampFormatter` by providing a custom `Streami18n` instance:

```tsx
import { Chat, Streami18n } from "stream-chat-react-native";

const chatClient = "Your Chat client here";

const i18n = new Streami18n({
  formatters: {
    timestampFormatter: () => (val: string | Date) => {
      return new Date(val).getTime() + "";
    },
  },
});

export const ChatApp = ({ apiKey, userId, userToken }) => {
  return <Chat client={chatClient} i18nInstance={i18n}></Chat>;
};
```

---

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react-native/guides/date-time-formatting/](https://getstream.io/chat/docs/sdk/react-native/guides/date-time-formatting/).