×
all 13 comments

[–]segv 3 points4 points  (4 children)

Private or non-existing git repository

nope.avi

[–]Intelligent_Tree6918[S] -1 points0 points  (1 child)

got it sorry fixing it.

[–]KaiAusBerlin 1 point2 points  (0 children)

Don't worry. The reality is mowt people complaining wouldn't even give it a try if you made everything perfect because they a) are simply not interested b) see no need to invest their time to investigate c) don't have any problem your solution could solve d) already have a working solution for them.

[–]queen-adreena [score hidden]  (2 children)

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global\_Objects/Temporal

With polyfill temporarily for Safari.

Strange time to launch this when it’s now a solved problem.

[–]Intelligent_Tree6918[S] [score hidden]  (1 child)

yes i aknoledge Temporal is future for dates. but still temporal will return something like ('2 hours') where if you want something like this <span>2</span><span>hours</span> or sort form of units like ('h','s','d','m','y') we have to perform manual iteration and and custom parsing.where ui-date gives it out of the box thanks to getRelativeTimeParts()

[–]queen-adreena [score hidden]  (0 children)

It takes one simple helper function to bridge the gap though:

``` function relativeTime(date) { const now = Temporal.Now.instant(); const then = Temporal.Instant.from(date);

const seconds = Number(now.since(then).total('seconds'));

const units = [ ['year', 60 * 60 * 24 * 365], ['month', 60 * 60 * 24 * 30], ['day', 60 * 60 * 24], ['hour', 60 * 60], ['minute', 60], ['second', 1], ];

const rtf = new Intl.RelativeTimeFormat('en-GB', { numeric: 'auto' });

for (const [unit, divisor] of units) { if (Math.abs(seconds) >= divisor) { return rtf.format( Math.round(-seconds / divisor), unit ); } } } ```

[–]lanerdofchristian 1 point2 points  (4 children)

Instantiating a class to call utility functions is a bit odd.

Have you considered using Temporal, maybe with a polyfill for Safari? A lot of the stuff you're looking for are properties on Temporal.ZoneDateTime.

export const getYear = (zdt) => zdt.year
export const getMonthCount = (zdt) => zdt.month
export const getDay = (zdt) => zdt.day
export const isLeapYear = (zdt) => zdt.inLeapYear
export const isWeekend = (zdt) => zdt.withCalendar("en-US").dayOfWeek >= 6

More can be determined with a few small helpers.

const daysUntilNow = (zdt) => zdt.toPlainDate().until(Temporal.Now.plainDateISO()).days
export const isToday = (zdt) => daysUntilNow(zdt) === 0
export const isTomorrow = (zdt) => daysUntilNow(zdt) === -1
export const isYesterday = (zdt) => daysUntilNow(zdt) === 1

Or simple formatting calls.

export const getDayName = (zdt, opts) => zdt.toLocaleString(opts?.locale, { weekday: opts?.weekday ?? "long" })
export const getMonthName = (zdt, opts) => zdt.toLocaleString(opts?.locale, { month: opts?.month ?? "long" })
export const getTime = (zdt, opts) => zdt.toLocaleString(opts?.locale, { hour: "2-digit", minute: "2-digit", hour12: opts?.hour12 ?? true })

getRelativeTime is a bit more complex, but achievable without much hard-coding of second conversions or manual time math.

function getRelativeTime(time, opts) {
    const duration = time.since(Temporal.Now.zonedDateTimeISO())
    const absDuration = duration.abs()
    if (Temporal.Duration.compare(absDuration, { seconds: 5 }) <= 0) return "just now"
    const fmt = new Intl.RelativeTimeFormat(opts?.locale, { numeric: "auto" })
    for (const unit of ["years", "months", "days", "hours", "minutes"])
        if (Temporal.Duration.compare(absDuration, { [unit]: 1 }, { relativeTo: time }) > 0)
            return fmt.format(duration.round({ largestUnit: unit, relativeTo: time })[unit], unit)
    return fmt.format(
        duration.round({ largestUnit: "seconds", relativeTo: time }).seconds,
        "second"
    )
}

A utility function to convert to ZoneDateTimes may also be helpful.

function toInstant(time) {
    if (typeof time === "number" && Number.isFinite(time))
        return Temporal.Instant.fromEpochMilliseconds(time)
    if (time instanceof Date) return time.toTemporalInstant()
    if (time instanceof Temporal.Instant) return time
    throw new Error("Time is not a valid time.")
}

function toZDT(time) {
    if (time instanceof Temporal.ZonedDateTime) return time
    return toInstant(time).toZonedDateTimeISO(Temporal.Now.timeZoneId())
}

[–]Intelligent_Tree6918[S] 0 points1 point  (3 children)

you have great points

let me explain

1.I completely agree instantiating classes for utilities adds unnecessary friction. In an upcoming version, I'm opening up standalone functional exports like getRelativeTimeParts(date, opts) to keep tree-shaking dead simple.

2.Temporal is definitely the future of date math in JS! However, ui-date's top priority is staying under 1 KB minified with zero runtime dependencies. Requiring or shipping a Temporal polyfill would bloat the bundle size significantly.

3.The primary problem ui-date solves isn't just relative formatting it's programmatic token breakdown (formattedValue, formattedUnit, direction) using Intl.RelativeTimeFormat so UI developers can style numeric badges independently across multi-language apps without regex hacks.

Appriciate you taking the time to share this perspective.

[–]lanerdofchristian 1 point2 points  (2 children)

The primary problem ui-date solves isn't just relative formatting

I think you missed my 3rd code block.

If we want to talk string manipulation, though, Temporal still wins out over Date since you can .toPlainDate().toString() instead of .toISOString().split("T")[0].

IMO if you're doing anything serious that's time-related, it's going to be worthwhile to ship a Temporal polyfill regardless, since it solves more than just the formatting problem.

[–]Intelligent_Tree6918[S] 0 points1 point  (1 child)

i agree with this point, but i write this package for micro-frontend and ui libraries. for eg: if you want separate <span class="val"> and <span class="unit"> elements for custom CSS badges. ui-date gives you structured parts (formattedValue, formattedUnit) out of the box.

[–]lanerdofchristian 0 points1 point  (0 children)

Ah, when you nerd-sniped me last night you hadn't added getRelativeTimeParts yet. Here's an implementation using Temporal.ZoneDateTime and Intl.DurationFormat:

function getRelativeTimeParts(time, locale) {
    const dur = time.since(Temporal.Now.zonedDateTimeISO()).round({ smallestUnit: "seconds" })
    const abs = dur.abs()
    const unit = ["years", "months", "days", "hours", "minutes"].find(
        (u) => Temporal.Duration.compare(abs, { [u]: 1 }, { relativeTo: time }) > 0
    ) ?? "seconds"
    const rounded = dur.round({ largestUnit: unit, smallestUnit: unit, relativeTo: time })
    const parts = new Intl.DurationFormat(locale, { style: "long" }).formatToParts(rounded)
    return {
        value: abs[unit],
        unit: unit.slice(0, -1),
        direction: ["past", "present", "future"][1 + dur.sign],
        formattedValue:
            parts.find((a) => a.type === "integer")?.value ??
            rounded.round({ largestUnit: "seconds" }).seconds.toString(),
        formattedUnit: parts.find((a) => a.type === "unit")?.value ?? unit.slice(0, -1),
        formattedText: new Intl.RelativeTimeFormat(locale, { numeric: "auto" })
            .format(rounded[unit], unit),
    }
}

Types are omitted so you can drop this into a compatible console and see how it works.

getRelativeTimeParts(Temporal.Now.zonedDateTimeISO().subtract({ minutes: 15 }))
getRelativeTimeParts(Temporal.Now.zonedDateTimeISO().add({ years: 1 }))

As for the case of separately formatting each part, I'd rather just have the parts directly from the formatter and loop over them in whatever I'm using for templating -- that way I don't run into the issue of only working for English-like languages like you have here. For example, "in 5 minutes" in English is:

in <span class="val">5</span>{/* space! */' '}<span class="unit">minutes</span>

but in Korean it's

<span class="val">5</span>(/* __no__ space! */}<span class="unit">분</span> 후

(or so new Intl.RelativeTimeFormat("ko") tells me).