我正在寻找一个不错的JS代码片段,将时间戳(例如来自Twitter API)转换为友好的相对时间(例如2秒前,一周前等)。 有人愿意分享他们最喜欢的方法吗?最好不要使用插件。
如果你不是过于关注准确性,那么很容易做到。普通方法有什么问题吗?
function timeDifference(current, previous) {
var msPerMinute = 60 * 1000;
var msPerHour = msPerMinute * 60;
var msPerDay = msPerHour * 24;
var msPerMonth = msPerDay * 30;
var msPerYear = msPerDay * 365;
var elapsed = current - previous;
if (elapsed < msPerMinute) {
return Math.round(elapsed/1000) + ' seconds ago';
}
else if (elapsed < msPerHour) {
return Math.round(elapsed/msPerMinute) + ' minutes ago';
}
else if (elapsed < msPerDay ) {
return Math.round(elapsed/msPerHour ) + ' hours ago';
}
else if (elapsed < msPerMonth) {
return 'approximately ' + Math.round(elapsed/msPerDay) + ' days ago';
}
else if (elapsed < msPerYear) {
return 'approximately ' + Math.round(elapsed/msPerMonth) + ' months ago';
}
else {
return 'approximately ' + Math.round(elapsed/msPerYear ) + ' years ago';
}
}
这里提供了一个工作示例,点击这里查看。
如果您对单数值的处理不满意(例如使用“1天”而不是“1天”),则可以进行微调。
[✔] (2018年12月) 第三阶段提案,并已在Chrome 71中实现
[✔] (2020年10月) 第四阶段(完成),准备纳入正式的ECMAScript标准中
// in miliseconds
var units = {
year : 24 * 60 * 60 * 1000 * 365,
month : 24 * 60 * 60 * 1000 * 365/12,
day : 24 * 60 * 60 * 1000,
hour : 60 * 60 * 1000,
minute: 60 * 1000,
second: 1000
}
var rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' })
var getRelativeTime = (d1, d2 = new Date()) => {
var elapsed = d1 - d2
// "Math.abs" accounts for both "past" & "future" scenarios
for (var u in units)
if (Math.abs(elapsed) > units[u] || u == 'second')
return rtf.format(Math.round(elapsed/units[u]), u)
}
// test-list of dates to compare with current date
[
'10/20/1984',
'10/20/2015',
+new Date() - units.year,
+new Date() - units.month,
+new Date() - units.day,
+new Date() - units.hour,
+new Date() - units.minute,
+new Date() + units.minute*2,
+new Date() + units.day*7,
]
.forEach(d => console.log(
new Date(d).toLocaleDateString(),
new Date(d).toLocaleTimeString(),
'(Relative to now) →',
getRelativeTime(+new Date(d))
))
小时
、天
、年
等。然后只需将该结果传递给Intl.RelativeTimeFormat
方法即可。您所问的是一个完全不同的主题。 - vsync2020
?你在代码中看到了 2020
吗?请再次查看,一旦发现错误,请删除您的评论。 - vsyncIntl.RelativeTimeFormat
可能会四舍五入日期 / 生成近似日期(例如,36 和 11 个月会被四舍五入为 37 年)。现在我说对不起,你的代码没有错误。 - Daryl Malibiranfor...in
遍历对象的键可能是不可预测的。由于您的逻辑依赖于从最大单位(“年”)开始并按特定顺序迭代,因此最好将单位存储在数组中。 - Shaun Scovilfunction timeSince(timeStamp) {
var now = new Date(),
secondsPast = (now.getTime() - timeStamp) / 1000;
if (secondsPast < 60) {
return parseInt(secondsPast) + 's';
}
if (secondsPast < 3600) {
return parseInt(secondsPast / 60) + 'm';
}
if (secondsPast <= 86400) {
return parseInt(secondsPast / 3600) + 'h';
}
if (secondsPast > 86400) {
day = timeStamp.getDate();
month = timeStamp.toDateString().match(/ [a-zA-Z]*/)[0].replace(" ", "");
year = timeStamp.getFullYear() == now.getFullYear() ? "" : " " + timeStamp.getFullYear();
return day + " " + month + year;
}
}
const currentTimeStamp = new Date().getTime();
console.log(timeSince(currentTimeStamp));
Gist https://gist.github.com/timuric/11386129
Fiddle http://jsfiddle.net/qE8Lu/1/
希望对你有所帮助。
综合了@vsync和@kigiri的方法,使用Web APIRelativeTimeFormat实现的Typescript。
const units: {unit: Intl.RelativeTimeFormatUnit; ms: number}[] = [
{unit: "year", ms: 31536000000},
{unit: "month", ms: 2628000000},
{unit: "day", ms: 86400000},
{unit: "hour", ms: 3600000},
{unit: "minute", ms: 60000},
{unit: "second", ms: 1000},
];
const rtf = new Intl.RelativeTimeFormat("en", {numeric: "auto"});
/**
* Get language-sensitive relative time message from Dates.
* @param relative - the relative dateTime, generally is in the past or future
* @param pivot - the dateTime of reference, generally is the current time
*/
export function relativeTimeFromDates(relative: Date | null, pivot: Date = new Date()): string {
if (!relative) return "";
const elapsed = relative.getTime() - pivot.getTime();
return relativeTimeFromElapsed(elapsed);
}
/**
* Get language-sensitive relative time message from elapsed time.
* @param elapsed - the elapsed time in milliseconds
*/
export function relativeTimeFromElapsed(elapsed: number): string {
for (const {unit, ms} of units) {
if (Math.abs(elapsed) >= ms || unit === "second") {
return rtf.format(Math.round(elapsed / ms), unit);
}
}
return "";
}
Math.abs(elasped) > ms
更改为 Math.abs(elapsed) >= ms
。前者会显示像“24小时前”/“365天前”这样的内容。后者(即编辑中反映的内容)则会显示“一天前”/“一年前”。 - Hooray Im Helping灵感来自Diego Castillo的回答和timeago.js插件,我为此编写了自己的原始插件。
var timeElement = document.querySelector('time'),
time = new Date(timeElement.getAttribute('datetime'));
timeElement.innerText = TimeAgo.inWords(time.getTime());
var TimeAgo = (function() {
var self = {};
// Public Methods
self.locales = {
prefix: '',
sufix: 'ago',
seconds: 'less than a minute',
minute: 'about a minute',
minutes: '%d minutes',
hour: 'about an hour',
hours: 'about %d hours',
day: 'a day',
days: '%d days',
month: 'about a month',
months: '%d months',
year: 'about a year',
years: '%d years'
};
self.inWords = function(timeAgo) {
var seconds = Math.floor((new Date() - parseInt(timeAgo)) / 1000),
separator = this.locales.separator || ' ',
words = this.locales.prefix + separator,
interval = 0,
intervals = {
year: seconds / 31536000,
month: seconds / 2592000,
day: seconds / 86400,
hour: seconds / 3600,
minute: seconds / 60
};
var distance = this.locales.seconds;
for (var key in intervals) {
interval = Math.floor(intervals[key]);
if (interval > 1) {
distance = this.locales[key + 's'];
break;
} else if (interval === 1) {
distance = this.locales[key];
break;
}
}
distance = distance.replace(/%d/i, interval);
words += distance + separator + this.locales.sufix;
return words.trim();
};
return self;
}());
// USAGE
var timeElement = document.querySelector('time'),
time = new Date(timeElement.getAttribute('datetime'));
timeElement.innerText = TimeAgo.inWords(time.getTime());
<time datetime="2016-06-13"></time>
const units = [
['year', 31536000000],
['month', 2628000000],
['day', 86400000],
['hour', 3600000],
['minute', 60000],
['second', 1000],
]
const rtf = new Intl.RelativeTimeFormat('en', { style:'narrow'})
const relatime = elapsed => {
for (const [unit, amount] of units) {
if (Math.abs(elapsed) > amount || unit === 'second') {
return rtf.format(Math.round(elapsed/amount), unit)
}
}
}
我打了一些有趣的高尔夫 192b
呵呵
const relatime = e=>{for(let[u,a]of Object.entries({year:31536e6,month:2628e6,day:864e5,hour:36e5,minute:6e4,second:1e3})){if(Math.abs(e)>a||a===1e3){return new Intl.RelativeTimeFormat('en',{style:'narrow'}).format(~~(e/a),u)}}}
我在打高尔夫球的时候也测试了一个功能版本:
const rtf = new Intl.RelativeTimeFormat('en', { style:'narrow'})
const relatime = Object.entries({year:31536e6,month:2628e6,day:864e5,hour:36e5,minute:6e4,second:1e3})
.reduce((f, [unit, amount]) => amount === 1e3
? f(elapsed => rtf.format(Math.round(elapsed/amount), unit))
: next => f(e => Math.abs(e) < amount
? next(elapsed)
: rtf.format(Math.round(elapsed/amount), unit)), _=>_)
好的,我现在必须回到工作中了...
MomentJS答案
对于Moment.js的用户,它提供了fromNow()函数,可以从当前日期/时间返回“x天前”或“x小时前”的信息。
moment([2007, 0, 29]).fromNow(); // 4 years ago
moment([2007, 0, 29]).fromNow(true); // 4 years
export default class RTF {
formatters;
options;
/**
* @param options {{localeMatcher: string?, numeric: string?, style: string?}} Intl.RelativeTimeFormat() options
*/
constructor(options = RTF.defaultOptions) {
this.options = options;
this.formatters = { auto: new Intl.RelativeTimeFormat(undefined, this.options) };
}
/**
* Add a formatter for a given locale.
*
* @param locale {string} A string with a BCP 47 language tag, or an array of such strings
* @returns {boolean} True if locale is supported; otherwise false
*/
addLocale(locale) {
if (!Intl.RelativeTimeFormat.supportedLocalesOf(locale).includes(locale)) {
return false;
}
if (!this.formatters.hasOwnProperty(locale)) {
this.formatters[locale] = new Intl.RelativeTimeFormat(locale, this.options);
}
return true;
}
/**
* Format a given date as a relative time string, with support for i18n.
*
* @param date {Date|number|string} Date object (or timestamp, or valid string representation of a date) to format
* @param locale {string?} i18n code to use (e.g. 'en', 'fr', 'zh'); if omitted, default locale of runtime is used
* @returns {string} Localized relative time string (e.g. '1 minute ago', '12 hours ago', '3 days ago')
*/
format(date, locale = "auto") {
if (!(date instanceof Date)) {
date = new Date(Number.isNaN(date) ? Date.parse(date) : date);
}
if (!this.formatters.hasOwnProperty(locale) && !this.addLocale(locale)) {
locale = "auto";
}
const elapsed = date - Date.now();
for (let i = 0; i < RTF.units.length; i++) {
const { unit, value } = RTF.units[i];
if (unit === 'second' || Math.abs(elapsed) >= value) {
return this.formatters[locale].format(Math.round(elapsed/value), unit);
}
}
}
/**
* Generate HTTP middleware that works with popular frameworks and i18n tools like Express and i18next.
*
* @param rtf {RTF?} Instance of RTF to use; defaults to a new instance with default options
* @param reqProp {string?} Property name to add to the HTTP request context; defaults to `rtf`
* @param langProp {string?} Property of HTTP request context where language is stored; defaults to `language`
* @returns {function(*, *, *): *} HTTP middleware function
*/
static httpMiddleware(rtf = new RTF(), reqProp = "rtf", langProp = "language") {
return (req, res, next) => {
req[reqProp] = (date) => rtf.format(date, req[langProp]);
next();
};
}
/**
* Default options object used by Intl.RelativeTimeFormat() constructor.
*
* @type {{localeMatcher: string, numeric: string, style: string}}
*/
static defaultOptions = {
localeMatcher: "best fit",
numeric: "auto", // this intentionally differs from Intl.RelativeTimeFormat(), because "always" is dumb
style: "long",
};
/**
* Used to determine the arguments to pass to Intl.RelativeTimeFormat.prototype.format().
*/
static units = [
{ unit: "year", value: 365 * 24 * 60 * 60 * 1000 },
{ unit: "month", value: 365 / 12 * 24 * 60 * 60 * 1000 },
{ unit: "week", value: 7 * 24 * 60 * 60 * 1000 },
{ unit: "day", value: 24 * 60 * 60 * 1000 },
{ unit: "hour", value: 60 * 60 * 1000 },
{ unit: "minute", value: 60 * 1000 },
{ unit: "second", value: 1000 },
];
/**
* Enumerated values for options object used by Intl.RelativeTimeFormat() constructor.
*
* @type {{localeMatcher: {lookup: string, default: string, bestFit: string}, numeric: {always: string, default: string, auto: string}, style: {default: string, short: string, narrow: string, long: string}}}
*/
static opt = {
localeMatcher: {
bestFit: "best fit",
lookup: "lookup",
},
numeric: {
always: "always",
auto: "auto",
},
style: {
long: "long",
narrow: "narrow",
short: "short",
},
};
}
为什么要使用它而不是 Intl.RelativeTimeFormat.prototype.format()?
Intl.RelativeTimeFormat.prototype.format() 接受两个参数:值和单位。
const rtf = new Intl.RelativeTimeFormat("en", { style: "narrow" });
expect(rtf.format(-1, "day")).toBe("1 day ago");
expect(rtf.format(10, "seconds")).toBe("in 10 sec.");
var rf = new IntlRelativeFormat('en-US');
var posts = [
{
id : 1,
title: 'Some Blog Post',
date : new Date(1426271670524)
},
{
id : 2,
title: 'Another Blog Post',
date : new Date(1426278870524)
}
];
posts.forEach(function (post) {
console.log(rf.format(post.date));
});
// => "3 hours ago"
// => "1 hour ago"
如果有人感兴趣,我最终创建了一个Handlebars助手来完成这个任务。 用法:
{{#beautify_date}}
{{timestamp_ms}}
{{/beautify_date}}
助手:
Handlebars.registerHelper('beautify_date', function(options) {
var timeAgo = new Date(parseInt(options.fn(this)));
if (Object.prototype.toString.call(timeAgo) === "[object Date]") {
if (isNaN(timeAgo.getTime())) {
return 'Not Valid';
} else {
var seconds = Math.floor((new Date() - timeAgo) / 1000),
intervals = [
Math.floor(seconds / 31536000),
Math.floor(seconds / 2592000),
Math.floor(seconds / 86400),
Math.floor(seconds / 3600),
Math.floor(seconds / 60)
],
times = [
'year',
'month',
'day',
'hour',
'minute'
];
var key;
for(key in intervals) {
if (intervals[key] > 1)
return intervals[key] + ' ' + times[key] + 's ago';
else if (intervals[key] === 1)
return intervals[key] + ' ' + times[key] + ' ago';
}
return Math.floor(seconds) + ' seconds ago';
}
} else {
return 'Not Valid';
}
});