Skip to content

Commit

Permalink
Add date_relative and date_relative_short formatters (#1771)
Browse files Browse the repository at this point in the history
  • Loading branch information
rafalp authored Jul 27, 2024
1 parent 5c566d3 commit f4aaea5
Show file tree
Hide file tree
Showing 22 changed files with 375 additions and 72 deletions.
3 changes: 3 additions & 0 deletions devproject/test_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@
# Test assertions expect english locale
LANGUAGE_CODE = "en-us"

# Test assertions expect specific TZ
TIME_ZONE = "UTC"

# Register test post validator
MISAGO_POST_VALIDATORS = ["misago.core.testproject.validators.test_post_validator"]

Expand Down
10 changes: 5 additions & 5 deletions frontend/src/components/Timestamp/Timestamp.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React from "react"
import {
formatNarrow,
formatRelative,
dateRelative,
dateRelativeShort,
fullDateTime,
} from "../../datetimeFormats"
} from "../../formats"

class Timestamp extends React.Component {
constructor(props) {
Expand Down Expand Up @@ -48,8 +48,8 @@ class Timestamp extends React.Component {

render() {
const displayed = this.props.narrow
? formatNarrow(this.date)
: formatRelative(this.date)
? dateRelativeShort(this.date)
: dateRelative(this.date)

return (
<attr
Expand Down
94 changes: 47 additions & 47 deletions frontend/src/datetimeFormats.js → frontend/src/formats.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
export const locale = window.misago_locale || "en-us"

export const momentAgo = pgettext("time ago", "moment ago")
export const momentAgoNarrow = pgettext("time ago", "now")
export const momentAgoShort = pgettext("time ago", "now")
export const dayAt = pgettext("day at time", "%(day)s at %(time)s")
export const soonAt = pgettext("day at time", "at %(time)s")
export const soonAt = pgettext("day at time", "Today at %(time)s")
export const tomorrowAt = pgettext("day at time", "Tomorrow at %(time)s")
export const yesterdayAt = pgettext("day at time", "Yesterday at %(time)s")

export const minuteShort = pgettext("short minutes", "%(time)sm")
export const hourShort = pgettext("short hours", "%(time)sh")
export const dayShort = pgettext("short days", "%(time)sd")
export const minutesShort = pgettext("short minutes", "%(time)sm")
export const hoursShort = pgettext("short hours", "%(time)sh")
export const daysShort = pgettext("short days", "%(time)sd")
export const thisYearShort = pgettext("short month", "%(day)s %(month)s")
export const otherYearShort = pgettext("short month", "%(month)s %(year)s")

Expand Down Expand Up @@ -46,46 +46,7 @@ export const weekday = new Intl.DateTimeFormat(locale, {

export const shortTime = new Intl.DateTimeFormat(locale, { timeStyle: "short" })

export function formatShort(date) {
const now = new Date()
const absDiff = Math.abs(Math.round((date - now) / 1000))

if (absDiff < 60) {
return momentAgoNarrow
}

if (absDiff < 60 * 55) {
const minutes = Math.ceil(absDiff / 60)
return minuteShort.replace("%(time)s", minutes)
}

if (absDiff < 3600 * 24) {
const hours = Math.ceil(absDiff / 3600)
return hourShort.replace("%(time)s", hours)
}

if (absDiff < 86400 * 7) {
const days = Math.ceil(absDiff / 86400)
return dayShort.replace("%(time)s", days)
}

const parts = {}
short.formatToParts(date).forEach(function ({ type, value }) {
parts[type] = value
})

if (date.getFullYear() === now.getFullYear()) {
return thisYearShort
.replace("%(day)s", parts.day)
.replace("%(month)s", parts.month)
}

return otherYearShort
.replace("%(year)s", parts.year)
.replace("%(month)s", parts.month)
}

export function formatRelative(date) {
export function dateRelative(date) {
const now = new Date()
const diff = Math.round((date - now) / 1000)
const absDiff = Math.abs(diff)
Expand All @@ -96,12 +57,12 @@ export function formatRelative(date) {
}

if (absDiff < 60 * 47) {
const minutes = Math.ceil(absDiff / 60) * sign
const minutes = Math.round(absDiff / 60) * sign
return relativeNumeric.format(minutes, "minute")
}

if (absDiff < 3600 * 3) {
const hours = Math.ceil(absDiff / 3600) * sign
const hours = Math.round(absDiff / 3600) * sign
return relativeNumeric.format(hours, "hour")
}

Expand Down Expand Up @@ -158,3 +119,42 @@ export function formatDayAtTime(day, date) {
.replace("%(day)s", day)
.replace("%(time)s", shortTime.format(date))
}

export function dateRelativeShort(date) {
const now = new Date()
const absDiff = Math.abs(Math.round((date - now) / 1000))

if (absDiff < 60) {
return momentAgoShort
}

if (absDiff < 60 * 55) {
const minutes = Math.round(absDiff / 60)
return minutesShort.replace("%(time)s", minutes)
}

if (absDiff < 3600 * 24) {
const hours = Math.round(absDiff / 3600)
return hoursShort.replace("%(time)s", hours)
}

if (absDiff < 86400 * 7) {
const days = Math.round(absDiff / 86400)
return daysShort.replace("%(time)s", days)
}

const parts = {}
short.formatToParts(date).forEach(function ({ type, value }) {
parts[type] = value
})

if (date.getFullYear() === now.getFullYear()) {
return thisYearShort
.replace("%(day)s", parts.day)
.replace("%(month)s", parts.month)
}

return otherYearShort
.replace("%(year)s", parts.year)
.replace("%(month)s", parts.month)
}
9 changes: 5 additions & 4 deletions frontend/src/liveTimestamps.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import htmx from "htmx.org"

import { formatRelative, formatShort, fullDateTime } from "./datetimeFormats"
import { dateRelative, dateRelativeShort, fullDateTime } from "./formats"

const cache = {}

Expand All @@ -10,16 +10,17 @@ export function updateTimestamp(element) {
cache[timestamp] = new Date(timestamp)
}

if (!element.hasAttribute("title")) {
if (!element.hasAttribute("misago-timestamp-title")) {
element.setAttribute("misago-timestamp-title", "true")
element.setAttribute("title", fullDateTime.format(cache[timestamp]))
}

const format = element.getAttribute("misago-timestamp-format")

if (format == "short") {
element.textContent = formatShort(cache[timestamp])
element.textContent = dateRelativeShort(cache[timestamp])
} else {
element.textContent = formatRelative(cache[timestamp])
element.textContent = dateRelative(cache[timestamp])
}
}

Expand Down
Empty file added misago/formats/__init__.py
Empty file.
7 changes: 7 additions & 0 deletions misago/formats/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from django.apps import AppConfig


class MisagoFormatsConfig(AppConfig):
name = "misago.formats"
label = "misago_formats"
verbose_name = "Misago Formats"
128 changes: 128 additions & 0 deletions misago/formats/daterelative.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
from datetime import datetime, timedelta

from django.utils import timezone
from django.utils.formats import date_format
from django.utils.translation import npgettext, pgettext


def date_relative(value: datetime) -> str:
now = timezone.now()
delta = abs((now - value).total_seconds())
past = value < now

if delta < 60:
return pgettext("time ago", "moment ago")

if delta < 60 * 47:
minutes = round(delta / 60)
if past:
return npgettext(
"minutes ago",
"%(time)s minute ago",
"%(time)s minutes ago",
minutes,
) % {"time": minutes}

return npgettext(
"minutes in future",
"In %(time)s minute",
"In %(time)s minutes",
minutes,
) % {"time": minutes}

if delta < 3600 * 3:
hours = round(delta / 3600)
if past:
return npgettext(
"hours ago",
"%(time)s hour ago",
"%(time)s hours ago",
hours,
) % {"time": hours}

return npgettext(
"hours in future",
"In %(time)s hour",
"In %(time)s hours",
hours,
) % {"time": hours}

if is_same_day(now, value):
if past:
return time_short(value)

return pgettext("day at time", "Today at %(time)s") % {
"time": time_short(value)
}

if is_yesterday(now, value):
return pgettext("day at time", "Yesterday at %(time)s") % {
"time": time_short(value)
}

if is_tomorrow(now, value):
return pgettext("day at time", "Tomorrow at %(time)s") % {
"time": time_short(value)
}

if past and delta < 3600 * 24 * 6:
return pgettext("day at time", "%(day)s at %(time)s") % {
"day": date_format(value, "l"),
"time": time_short(value),
}

if is_same_year(now, value):
return date_format(value, pgettext("same year date", "F j"))

return date_format(value, pgettext("other year date", "F j, Y"))


def is_same_day(now: datetime, datetime_: datetime) -> bool:
return date_format(now, "dmY") == date_format(datetime_, "dmY")


def is_yesterday(now: datetime, datetime_: datetime) -> bool:
yesterday = now - timedelta(hours=24)
return date_format(yesterday, "dmY") == date_format(datetime_, "dmY")


def is_tomorrow(now: datetime, datetime_: datetime) -> bool:
yesterday = now + timedelta(hours=24)
return date_format(yesterday, "dmY") == date_format(datetime_, "dmY")


def is_same_year(now: datetime, datetime_: datetime) -> bool:
return date_format(now, "Y") == date_format(datetime_, "Y")


def date_relative_short(value: datetime) -> str:
now = timezone.now()
delta = abs((now - value).total_seconds())

if delta < 60:
return pgettext("time ago", "now")

if delta < 60 * 55:
minutes = round(delta / 60)
return pgettext("short minutes", "%(time)sm") % {"time": minutes}

if delta < 3600 * 24:
hours = round(delta / 3600)
return pgettext("short hours", "%(time)sh") % {"time": hours}

if delta < 86400 * 7:
days = round(delta / 86400)
return pgettext("short days", "%(time)sd") % {"time": days}

if now.year == value.year:
return date_format(value, pgettext("short this year", "j M"))

return date_format(value, pgettext("short other year", "M y"))


def time_short(value: datetime) -> str:
return date_format(
value,
pgettext("time short", "g:i A"),
use_l10n=False,
)
Empty file.
8 changes: 8 additions & 0 deletions misago/formats/templatetags/misago_formats.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from django import template

from ..daterelative import date_relative, date_relative_short

register = template.Library()

register.filter(is_safe=False, expects_localtime=True)(date_relative)
register.filter(is_safe=False, expects_localtime=True)(date_relative_short)
Empty file.
Loading

0 comments on commit f4aaea5

Please sign in to comment.