Skip to content

Instantly share code, notes, and snippets.

@yuheiy yuheiy/.gitignore
Last active Oct 31, 2018

Embed
What would you like to do?
formatToRelativeTime
node_modules

API

formatToRelativeTime(from, to) ⇒ string | undefined

相対日時表記形式に則った文字列に変換

Kind: global function
Returns: string | undefined - 相対日時表示形式に則った文字列を返す

Param Type Description
from Date 基準時刻(現在)
to Date 比較対象時刻(過去)

Example

formatToRelativeTime(new Date(), new Date('2000-01-01T00:00:00+09:00'))
/**
* @param {Date} value
* @param {Date} other
* @returns {boolean}
*/
const areDatesSameYear = (value, other) => {
return value.getFullYear() === other.getFullYear()
}
/**
* @param {Date} value
* @param {Date} other
* @returns {boolean}
*/
const areDatesSameMonth = (value, other) => {
return value.getMonth() === other.getMonth()
}
/**
* @param {Date} value
* @param {Date} other
* @returns {boolean}
*/
const areDatesSameDay = (value, other) => {
return value.getDate() === other.getDate()
}
/**
* @param {Date} value
* @param {Date} other
* @returns {boolean}
*/
const areDatesSameDate = (value, other) => {
return [areDatesSameYear, areDatesSameMonth, areDatesSameDay].every(
(areDatesSame) => {
return areDatesSame(value, other)
},
)
}
/**
* 相対日時表記形式に則った文字列に変換
*
* @param {Date} from - 基準時刻(現在)
* @param {Date} to - 比較対象時刻(過去)
* @example formatToRelativeTime(new Date(), new Date('2000-01-01T00:00:00+09:00'))
* @returns {(string|undefined)} 相対日時表示形式に則った文字列を返す
*/
const formatToRelativeTime = (from, to) => {
const diffMs = from.getTime() - to.getTime()
const diffSeconds = diffMs / 1000
const diffMinutes = diffSeconds / 60
const diffHours = diffMinutes / 60
const diffDays = diffHours / 24
const isFuture = diffMs < 0
if (isFuture) {
return
}
const isDiffLessThan10Min = diffMinutes < 10
if (isDiffLessThan10Min) {
const truncatedMinutes = Math.trunc(diffMinutes)
const displayMinutes = Math.max(truncatedMinutes, 1)
return `${displayMinutes}分前`
}
const isDiffLessThan60Min = diffMinutes < 60
if (isDiffLessThan60Min) {
const unit = 5
const truncatedMinutes = diffMinutes - (diffMinutes % unit)
return `${truncatedMinutes}分前`
}
const isDiffLessThan7Hours = diffHours < 7
if (isDiffLessThan7Hours) {
const isAfter6Hours = from.getHours() >= 6
const isToday = areDatesSameDay(from, to)
if (isAfter6Hours && !isToday) {
return 'きのう'
}
const truncatedHours = Math.trunc(diffHours)
return `${truncatedHours}時間前`
}
const isToday = areDatesSameDate(from, to)
if (isToday) {
return 'きょう'
}
const msPerDay = 1000 * 60 * 60 * 24
const yesterdayOfFrom = new Date(from.getTime() - msPerDay)
const isYesterday = areDatesSameDate(yesterdayOfFrom, to)
if (isYesterday) {
return 'きのう'
}
const isDiffLessThan4Days = diffDays < 4
if (isDiffLessThan4Days) {
const truncatedDays = Math.trunc(diffDays)
return `${truncatedDays}日前`
}
const displayYear = `${to.getFullYear()}`
const displayMonth = `${to.getMonth() + 1}`
const displayDay = `${to.getDate()}`
const isThisYear = areDatesSameYear(from, to)
if (isThisYear) {
return `${displayMonth}${displayDay}`
}
return `${displayYear}${displayMonth}${displayDay}`
}
module.exports = {
formatToRelativeTime,
}
const assert = require('assert')
const { formatToRelativeTime } = require('./formatToRelativeTime')
const now = () => {
return new Date()
}
const local = (
year,
month = 1,
date = 1,
hours = 0,
minutes = 0,
seconds = 0,
ms = 0,
) => {
return new Date(year, month - 1, date, hours, minutes, seconds, ms)
}
const plus = (date, amount) => {
return new Date(date.getTime() + amount)
}
describe('formatToRelativeTime', () => {
it('同時刻であれば"1分前"を返す', () => {
const date = now()
assert.strictEqual(formatToRelativeTime(date, date), '1分前')
})
it('`to`が`from`の未来であれば値を返さない', () => {
const from = now()
const to = plus(from, 1)
assert.strictEqual(formatToRelativeTime(from, to), undefined)
})
it('差が2分未満であれば"1分前"を返す', () => {
const from = now()
const diff1 = 1
assert.strictEqual(formatToRelativeTime(from, plus(from, -diff1)), '1分前')
const diff2 = 1000 * 60 * 2 - 1
assert.strictEqual(formatToRelativeTime(from, plus(from, -diff2)), '1分前')
})
it('差が10分未満であれば1分未満を切り捨てて"n分前"を返す', () => {
const from = now()
const diff1 = 1000 * 60 * 2
assert.strictEqual(formatToRelativeTime(from, plus(from, -diff1)), '2分前')
const diff2 = 1000 * 60 * 10 - 1
assert.strictEqual(formatToRelativeTime(from, plus(from, -diff2)), '9分前')
})
it('差が60分未満であれば5分未満を切り捨てて"n分前"を返す', () => {
const from = now()
const diff1 = 1000 * 60 * 10
assert.strictEqual(formatToRelativeTime(from, plus(from, -diff1)), '10分前')
const diff2 = 1000 * 60 * 15 - 1
assert.strictEqual(formatToRelativeTime(from, plus(from, -diff2)), '10分前')
const diff3 = 1000 * 60 * 15
assert.strictEqual(formatToRelativeTime(from, plus(from, -diff3)), '15分前')
const diff4 = 1000 * 60 * 60 - 1
assert.strictEqual(formatToRelativeTime(from, plus(from, -diff4)), '55分前')
})
it('差が7時間未満であれば1時間未満を切り捨てて"n時間前"を返す。`from`が午前6時以降かつ`to`と別日の場合を除く', () => {
{
const from = plus(local(2000, 1, 1, 6), -1)
const diff1 = 1000 * 60 * 60
assert.strictEqual(
formatToRelativeTime(from, plus(from, -diff1)),
'1時間前',
)
const diff2 = 1000 * 60 * 60 * 2 - 1
assert.strictEqual(
formatToRelativeTime(from, plus(from, -diff2)),
'1時間前',
)
const diff3 = 1000 * 60 * 60 * 2
assert.strictEqual(
formatToRelativeTime(from, plus(from, -diff3)),
'2時間前',
)
const diff4 = 1000 * 60 * 60 * 7 - 1
assert.strictEqual(
formatToRelativeTime(from, plus(from, -diff4)),
'6時間前',
)
}
{
const to = local(2000)
const from1 = local(2000, 1, 1, 6)
assert.strictEqual(formatToRelativeTime(from1, to), '6時間前')
const from2 = plus(local(2000, 1, 1, 7), -1)
assert.strictEqual(formatToRelativeTime(from2, to), '6時間前')
}
})
it('`from`が午前6時以降かつ`to`と別日の場合に"きのう"を返す', () => {
const from = local(2000, 1, 2, 6)
const to1 = plus(local(2000, 1, 1, 23), 1)
assert.strictEqual(formatToRelativeTime(from, to1), 'きのう')
const to2 = plus(local(2000, 1, 2), -1)
assert.strictEqual(formatToRelativeTime(from, to2), 'きのう')
})
it('`from`と`to`が同日であれば"きょう"を返す', () => {
const from = plus(local(2000), -1)
const diff1 = 1000 * 60 * 60 * 7
assert.strictEqual(formatToRelativeTime(from, plus(from, -diff1)), 'きょう')
const diff2 = 1000 * 60 * 60 * 24 - 1
assert.strictEqual(formatToRelativeTime(from, plus(from, -diff2)), 'きょう')
})
it('`to`が`from`の昨日であれば"きのう"を返す', () => {
const from = local(2000)
const diff1 = 1000 * 60 * 60 * 7
assert.strictEqual(formatToRelativeTime(from, plus(from, -diff1)), 'きのう')
const diff2 = 1000 * 60 * 60 * 24 - 1
assert.strictEqual(formatToRelativeTime(from, plus(from, -diff2)), 'きのう')
})
it('差が4日未満であれば1日未満を切り捨てて"n日前"を返す', () => {
const from = local(2000)
const diff1 = 1000 * 60 * 60 * 24 * 2
assert.strictEqual(formatToRelativeTime(from, plus(from, -diff1)), '2日前')
const diff2 = 1000 * 60 * 60 * 24 * 3 - 1
assert.strictEqual(formatToRelativeTime(from, plus(from, -diff2)), '2日前')
const diff3 = 1000 * 60 * 60 * 24 * 3
assert.strictEqual(formatToRelativeTime(from, plus(from, -diff3)), '3日前')
const diff4 = 1000 * 60 * 60 * 24 * 4 - 1
assert.strictEqual(formatToRelativeTime(from, plus(from, -diff4)), '3日前')
})
it('`from`と`to`が同年であれば"x月y日"を返す', () => {
const from = local(2000, 12, 31)
const to1 = local(2000, 12, 31 - 4)
assert.strictEqual(formatToRelativeTime(from, to1), '12月27日')
const to2 = local(2000)
assert.strictEqual(formatToRelativeTime(from, to2), '1月1日')
})
it('`from`と`to`の年が異なれば"x年y月z日"を返す', () => {
const from = local(2000, 1, 4)
const to = local(1999, 12, 31)
assert.strictEqual(formatToRelativeTime(from, to), '1999年12月31日')
})
})
{
"name": "format-to-relative-time",
"version": "1.0.0",
"main": "index.js",
"repository": "https://gist.github.com/d9c9fc4347ebe26cb85284240218cb14.git",
"author": "Yuhei Yasuda <yuhei.yasuda1003@gmail.com>",
"license": "MIT",
"scripts": {
"test": "mocha './*.spec.js'"
},
"devDependencies": {
"mocha": "^5.2.0"
}
}
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
balanced-match@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
dependencies:
balanced-match "^1.0.0"
concat-map "0.0.1"
browser-stdout@1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60"
integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==
commander@2.15.1:
version "2.15.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f"
integrity sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
debug@3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
dependencies:
ms "2.0.0"
diff@3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==
escape-string-regexp@1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
glob@7.1.2:
version "7.1.2"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
integrity sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
inherits "2"
minimatch "^3.0.4"
once "^1.3.0"
path-is-absolute "^1.0.0"
growl@1.10.5:
version "1.10.5"
resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e"
integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==
has-flag@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
he@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
integrity sha1-k0EP0hsAlzUVH4howvJx80J+I/0=
inflight@^1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
dependencies:
once "^1.3.0"
wrappy "1"
inherits@2:
version "2.0.3"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
minimatch@3.0.4, minimatch@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
dependencies:
brace-expansion "^1.1.7"
minimist@0.0.8:
version "0.0.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
mkdirp@0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=
dependencies:
minimist "0.0.8"
mocha@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/mocha/-/mocha-5.2.0.tgz#6d8ae508f59167f940f2b5b3c4a612ae50c90ae6"
integrity sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==
dependencies:
browser-stdout "1.3.1"
commander "2.15.1"
debug "3.1.0"
diff "3.5.0"
escape-string-regexp "1.0.5"
glob "7.1.2"
growl "1.10.5"
he "1.1.1"
minimatch "3.0.4"
mkdirp "0.5.1"
supports-color "5.4.0"
ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
once@^1.3.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
dependencies:
wrappy "1"
path-is-absolute@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
supports-color@5.4.0:
version "5.4.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54"
integrity sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==
dependencies:
has-flag "^3.0.0"
wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.