使用date-fns格式化持续时间(以秒为单位)

54

给定int值1807,使用date-fns将其格式化为30:07

是的,我知道可以使用普通的js实现,但是否可以使用date-fns实现?

6个回答

64
根据date-fns/date-fns#229 (comment)中的示例代码,我们现在可以使用intervalToDuration将以秒为单位(作为Interval传递)转换为Duration,然后按照OP所需的方式简化格式:
import {  intervalToDuration } from 'date-fns'

const seconds = 10000

intervalToDuration({ start: 0, end: seconds * 1000 })
// { hours: 2, minutes: 46, seconds: 40 }

对于提问者的需求:

import {  intervalToDuration } from 'date-fns'

const seconds = 1807
const duration = intervalToDuration({ start: 0, end: seconds * 1000 })
// { minutes: 30, seconds: 7 }

const formatted = `${duration.minutes}:${duration.seconds}`
// 30:7

编辑(2022-08-04):有人指出上面的简单代码不会给数字补零,因此最终结果将是30:7而不是30:07。可以使用String.prototype.padStart()来实现这种填充,如下所示:

import {  intervalToDuration } from 'date-fns'

const seconds = 1807
const duration = intervalToDuration({ start: 0, end: seconds * 1000 })
// { minutes: 30, seconds: 7 }

const zeroPad = (num) => String(num).padStart(2, '0')

const formatted = `${zeroPad(duration.minutes)}:${zeroPad(duration.seconds)}`
// 30:07

还指出,如果Interval超过60分钟,它将开始在Duration内递增hours,而上面的代码不会显示这一点。因此,这里是另一个稍微复杂一些的示例,它处理了这个问题以及zeroPad的情况:
import {  intervalToDuration } from 'date-fns'

const seconds = 1807
const duration = intervalToDuration({ start: 0, end: seconds * 1000 })
// { minutes: 30, seconds: 7 }

const zeroPad = (num) => String(num).padStart(2, '0')

const formatted = [
  duration.hours,
  duration.minutes,
  duration.seconds,
]
.filter(Boolean)
.map(zeroPad)
.join(':')
// 30:07

GitHub上也有一个 问题,询问如何使用自定义格式与formatDuration一起使用,该问题表明目前唯一的方法是提供自定义Locale。GitHub用户@marselmustafin提供了使用此解决方法的示例。按照相同的模式,我们可以大致实现OP所需的功能,具体如下:

import { intervalToDuration, formatDuration } from "date-fns";

const seconds = 1807;
const duration = intervalToDuration({ start: 0, end: seconds * 1000 });
// { minutes: 30, seconds: 7 }

const zeroPad = (num) => String(num).padStart(2, "0");

const formatted = formatDuration(duration, {
  format: ["minutes", "seconds"],
  // format: ["hours", "minutes", "seconds"],
  zero: true,
  delimiter: ":",
  locale: {
    formatDistance: (_token, count) => zeroPad(count)
  }
});
// 30:07

6
请注意,如果您超过60分钟,它将开始递增小时,并且分钟计数将返回到0,因此如果您像在“格式化”中一样仅显示分钟和秒,则会出现错误。 - maxime1992
1
这是不正确的,它应该显示30:7而不是30:07。 - Boat
1
@Boat 如果你想要给数字补零,这里有一个简单的例子来自于另一个SO答案,它使用了String.prototype.padStart()String(num).padStart(2, '0') - Glenn 'devalias' Grant
1
这种方法还有一个小问题:如果你提供0秒,它将返回空字符串,但是我希望0秒被格式化为“00:00”。编辑:问题比这更深入。小时/分钟/秒钟中的任何部分为0都将从格式化的字符串中省略。 - nover
@nover 这是在使用 .filter(Boolean) 示例时吗?这会过滤掉任何虚假的结果(例如 0),因此如果您删除该行,则它们将被包括在内,并且将使用上面的 1807 秒作为格式化结果 00:30:07。然而,您最好使用下面的“自定义格式”示例,因为随着发现边缘情况,我的原始解决方案越来越像 hacky。在“自定义格式”示例中,zero:true 布尔值控制是否包括 0(例如,1800 秒将给出 30:0030 取决于此标志)。 - Glenn 'devalias' Grant

35

这是一个简单的实现:

import { formatDistance } from 'date-fns'

const duration = s => formatDistance(0, s * 1000, { includeSeconds: true })

duration(50) // 'less than a minute'
duration(1000) // '17 minutes'

这基本上与以下内容相同:

import moment from 'moment'    

const duration = s => moment.duration(s, 'seconds').humanize()

duration(50) // 'a minute'
duration(1000) // '17 minutes'

我喜欢这个解决方案,它的格式很清晰易读。 - Joseph Norman
1
我预计很多人都需要将日期与现在进行比较,并显示它们之间的持续时间,如下所示:duration(differenceInSeconds(new Date(), yourDate)) - Marc
1
@Marc 在这种情况下,您可以使用 date-fns 中已经存在的直接帮助程序之一:formatDistanceToNow / formatDistanceToNowStrict - Glenn 'devalias' Grant

30

你可以使用date-fns通过简单修改一个中间帮助日期来实现此操作。使用new Date(0),您将获得设置为1970年1月1日00:00:00 UTC的日期。然后,您可以使用date-fns的addSeconds添加相关秒数(实际上,您也可以使用本地日期的setTime(1000 * seconds))。对此进行分钟和秒钟的格式化即可得到您想要的结果。

var input = document.getElementById('seconds');
var output = document.getElementById('out');

output.innerText = formattedTime(input.value);
input.addEventListener('input', function() {
  output.innerText = formattedTime(input.value);
});

function formattedTime(seconds) {
  var helperDate = dateFns.addSeconds(new Date(0), seconds);
  return dateFns.format(helperDate, 'mm:ss');
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/date-fns/1.26.0/date_fns.min.js"></script>

<input type="number" id="seconds" value="1807">
<pre id="out"></pre>


4
它有效,但可能不是你预期的方式。我只是假设他只想显示较长时间中的分钟/秒部分,因为他只要求了这个。如果时间较大,你需要提取小时或天数并决定如何显示。所以它适合@Simon的用例,但不是每个人的;-) - alex3683
6
我认为 var helperDate = dateFns.addSeconds(new Date(0), seconds) 这段代码会将你的时区偏移量加到结果日期上,例如,如果你使用 hh:mm:ss 格式来格式化结果,那么 180 秒将产生 10:03:00,而 240 秒将产生 10:04:00,前提是你在一个 +10 的时区偏移量下。 - user1063287
6
如@user1063287所指出的,您的时间设置将会有时区。因此,如果您想显示“HH:mm:ss”,则在使用“new Date(0)”时,对于“Europe/Berlin”来说,您将始终看到“01:00:00”。我通过使用“date-fns-tz”设置一个已知偏移量的固定时区来解决这个问题(例如,“Europe/Berlin == GMT+1小时”),然后使用“date-fns”的“addHour”函数将其减去该值。 const dateHelper: Date = addSeconds(new Date(0), duration); const utcDate = zonedTimeToUtc(dateHelper, TIME_ZONE); this.durationDisplay = format(addHours(utcDate, -1), 'HH:mm:ss'); - HasBert
请注意,根据您的用户所在的时区不同,甚至分钟也可能会有偏差,例如持续时间为5秒的情况下可能会显示为“30:05”。了解更多信息,请访问https://www.timeanddate.com/time/time-zones-interesting.html。 - Alex Wally

11

此函数将把秒转换成持续时间格式 hh:mm:ss,与 moment.js 中的持续时间相似。

import { addHours, getMinutes, getHours, getSeconds } from 'date-fns';

export const convertToDuration = (secondsAmount: number) => {
    const normalizeTime = (time: string): string =>
    time.length === 1 ? `0${time}` : time;

    const SECONDS_TO_MILLISECONDS_COEFF = 1000;
    const MINUTES_IN_HOUR = 60;

    const milliseconds = secondsAmount * SECONDS_TO_MILLISECONDS_COEFF;

    const date = new Date(milliseconds);
    const timezoneDiff = date.getTimezoneOffset() / MINUTES_IN_HOUR;
    const dateWithoutTimezoneDiff = addHours(date, timezoneDiff);

    const hours = normalizeTime(String(getHours(dateWithoutTimezoneDiff)));
    const minutes = normalizeTime(String(getMinutes(dateWithoutTimezoneDiff)));
    const seconds = normalizeTime(String(getSeconds(dateWithoutTimezoneDiff)));

    const hoursOutput = hours !== '00' ? `${hours}:` : '';

    return `${hoursOutput}${minutes}:${seconds}`;
};

写得很好。命名很棒。 - moto

10

只需像这样使用date-fns中的format

format((seconds * 1000), 'mm:ss')

如果需要同时去除时区小时偏移量,可以使用getTimezoneOffset(以分钟为单位)

let dt = new Date((seconds * 1000));
dt = addMinutes(dt, dt.getTimezoneOffset());
return format(dt, 'mm:ss');

2
虽然对于将 1807 秒转换成 mm:ss 的精确示例而言,这种方法可行,但如果您想要处理小时数的话,它就无法胜任了。例如,将格式字符串更改为 hh:mm:ss 将会导致输出为 12:30:07,即使在这种情况下小时数实际上应该是 0 - Glenn 'devalias' Grant
我收到了RangeError:无效的时间值。 - Or Nakash

3

在date-fns中似乎没有直接相当于moment.duration的功能...

我写了一个函数,可能对某些人有帮助。使用Date-fns.differenceInSeconds函数可以获取总秒数的差异。还有等效方法可用于毫秒、分钟等。 然后我使用原生JavaScript数学格式化这个值。

/**
* calculates the duration between two dates.
* @param {date object} start The start date.
* @param {date object} finish The finish date.
* @return {string} a string of the duration with format 'hh:mm'
*/
export const formatDuration = (start, finish) => {
    const diffTime = differenceInSeconds(finish, start);
    if (!diffTime) return '00:00'; // divide by 0 protection
    const minutes = Math.abs(Math.floor(diffTime / 60) % 60).toString();
    const hours = Math.abs(Math.floor(diffTime / 60 / 60)).toString();
    return `${hours.length < 2 ? 0 + hours : hours}:${minutes.length < 2 ? 0 + minutes : minutes}`;
};

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接