diff --git a/package.json b/package.json index 6082d8b..4b6209a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hylo-shared", - "version": "5.1.3", + "version": "5.1.4", "description": "Code shared across Hylo applications for consistency of results", "repository": { "type": "git", @@ -46,6 +46,7 @@ }, "dependencies": { "coordinate-parser": "^1.0.7", + "get-user-locale": "^2.1.1", "html-to-text": "^8.1.0", "lodash": "~4.17.21", "marked": "^4.2.1", @@ -56,7 +57,7 @@ "validator": "^13.7.0" }, "peerDependencies": { - "moment-timezone": "^0.5.37" + "luxon": "^3.2.1" }, "devDependencies": { "@babel/core": "^7.17.0", @@ -67,9 +68,9 @@ "concurrently": "^7.0.0", "eslint": "^8.8.0", "jest": "^27.5.0", + "luxon": "^3.2.1", "microbundle": "^0.15.1", - "moment-timezone": "^0.5.37", "standard": "^16.0.4", "wml": "^0.0.83" } -} +} \ No newline at end of file diff --git a/src/TextHelpers.js b/src/TextHelpers.js index 24a53c5..c30975e 100644 --- a/src/TextHelpers.js +++ b/src/TextHelpers.js @@ -2,7 +2,7 @@ import { convert as convertHtmlToText } from 'html-to-text' import { isURL } from 'validator' import { marked } from 'marked' import merge from 'lodash/fp/merge' -import moment from 'moment-timezone' +import { DateTime } from 'luxon' import prettyDate from 'pretty-date' import truncHTML from 'trunc-html' import truncText from 'trunc-text' @@ -21,7 +21,7 @@ export function insaneOptions (providedInsaneOptions) { allowedAttributes: providedInsaneOptions?.allowedAttributes || { a: [ 'class', 'target', 'href', - 'data-type', 'data-id','data-label', + 'data-type', 'data-id', 'data-label', 'data-user-id', 'data-entity-type', 'data-search' ], span: [ @@ -153,38 +153,40 @@ export function humanDate (date, short) { .replace(/ month(s?)/, ' mo$1') } -export const formatDatePair = (startTime, endTime, returnAsObj) => { - const start = moment.tz(startTime, moment.tz.guess()) - const end = moment.tz(endTime, moment.tz.guess()) +export const formatDatePair = (locale = 'en', startTime, endTime, returnAsObj) => { + const dateWithTime = { ...DateTime.DATE_MED_WITH_WEEKDAY, hour: 'numeric', minute: 'numeric' } + const dateWithTimeAndOffset = { ...DateTime.DATE_MED_WITH_WEEKDAY, hour: 'numeric', minute: 'numeric', timeZoneName: 'short' } + const dateNoYearWithTime = { weekday: 'short', month: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric' } + const dateNoYearWithTimeAndOffset = { weekday: 'short', month: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric', timeZoneName: 'short' } + const start = startTime ? DateTime.fromISO(startTime, { locale }) : null + const end = endTime ? DateTime.fromISO(endTime, { locale }) : null - const now = moment() - const isThisYear = start.year() === now.year() && end.year() === now.year() + const now = DateTime.local({ locale }) + const isThisYear = start.year === now.year && end.year === now.year let to = '' let from = '' if (isThisYear) { - from = endTime ? start.format('ddd, MMM D [at] h:mmA') : start.format('ddd, MMM D [at] h:mmA z') + from = endTime ? start.toLocaleString(dateNoYearWithTime) : start.toLocaleString(dateNoYearWithTimeAndOffset) } else { - from = endTime ? start.format('ddd, MMM D, YYYY [at] h:mmA') : start.format('ddd, MMM D, YYYY [at] h:mmA z') + from = endTime ? start.toLocaleString(dateWithTime) : start.toLocaleString(dateWithTimeAndOffset) } - if (endTime) { - if (end.year() !== start.year()) { - to = end.format('ddd, MMM D, YYYY [at] h:mmA z') - } else if (end.month() !== start.month() || - end.day() !== start.day() || + if (end.year !== start.year && !isThisYear) { + to = end.toLocaleString(dateWithTimeAndOffset) + } else if (end.month !== start.month || + end.day !== start.day || end <= now) { - to = end.format('ddd, MMM D [at] h:mmA z') + to = end.toLocaleString(dateNoYearWithTimeAndOffset) } else { - to = end.format('h:mmA z') + to = end.toLocaleString(DateTime.TIME_WITH_SHORT_OFFSET) } to = returnAsObj ? to : ' - ' + to } - return returnAsObj ? { from, to } : from + to } export function isDateInTheFuture (date) { - return moment(date).isAfter(moment()) + return typeof date === 'number' ? DateTime.fromMillis(date) > DateTime.now() : DateTime.fromISO(date) > DateTime.now() } diff --git a/src/TextHelpers.test.js b/src/TextHelpers.test.js index 0e4bef5..c24cd89 100644 --- a/src/TextHelpers.test.js +++ b/src/TextHelpers.test.js @@ -1,5 +1,9 @@ import * as TextHelpers from '../src/TextHelpers' -import moment from 'moment-timezone' +import { DateTime, Settings } from 'luxon' + +beforeEach(() => { + Settings.now = () => new Date(2022, 6, 1).valueOf() +}) describe('presentHTMLToText', () => { it("shouldn't include text of href on links", () => { @@ -40,17 +44,23 @@ describe('markdown', () => { describe('formatDatePair', () => { it('displays differences of dates', () => { - const d1 = moment.tz(1551908483315, 'Etc/GMT').month(1).day(1).hour(18) - const d2 = moment.tz(d1, 'Etc/GMT').hour(21) - const d3 = moment.tz(d2, 'Etc/GMT').day(2) - const d4 = moment.tz(d3, 'Etc/GMT').month(2) - const d5 = moment.tz(d3, 'Etc/GMT').year(2050) - - expect(TextHelpers.formatDatePair(d1)).toMatchSnapshot() - expect(TextHelpers.formatDatePair(d1, d2)).toMatchSnapshot() - expect(TextHelpers.formatDatePair(d1, d3)).toMatchSnapshot() - expect(TextHelpers.formatDatePair(d1, d4)).toMatchSnapshot() - expect(TextHelpers.formatDatePair(d1, d5)).toMatchSnapshot() + const locale = 'en' + const d1 = DateTime.local(2019, 2, 4, 11, 41) + const d2 = DateTime.local(2019, 2, 4, 14, 41) + const d3 = DateTime.local(2019, 2, 5, 14, 41) + const d4 = DateTime.local(2019, 3, 5, 14, 41) + const d5 = DateTime.local(2050, 2, 5, 14, 41) + const d6 = DateTime.local(DateTime.now().year, 2, 5, 14, 41) + const d7 = DateTime.local(DateTime.now().year, 3, 5, 14, 41) + + expect(TextHelpers.formatDatePair(locale, d1)).toMatchSnapshot() + expect(TextHelpers.formatDatePair(locale, d1, d2)).toMatchSnapshot() + expect(TextHelpers.formatDatePair(locale, d1, d3)).toMatchSnapshot() + expect(TextHelpers.formatDatePair(locale, d1, d4)).toMatchSnapshot() + expect(TextHelpers.formatDatePair(locale, d1, d5)).toMatchSnapshot() + expect(TextHelpers.formatDatePair(locale, d1, d6)).toMatchSnapshot() + expect(TextHelpers.formatDatePair(locale, d6, d5)).toMatchSnapshot() + expect(TextHelpers.formatDatePair(locale, d6, d7)).toMatchSnapshot() }) }) diff --git a/src/__snapshots__/TextHelpers.test.js.snap b/src/__snapshots__/TextHelpers.test.js.snap index 32db0a3..18e11d1 100644 --- a/src/__snapshots__/TextHelpers.test.js.snap +++ b/src/__snapshots__/TextHelpers.test.js.snap @@ -1,11 +1,17 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`formatDatePair displays differences of dates 1`] = `"Mon, Feb 4, 2019 at 11:41AM MST"`; +exports[`formatDatePair displays differences of dates 1`] = `"Mon, Feb 4, 2019, 11:41 AM CST"`; -exports[`formatDatePair displays differences of dates 2`] = `"Mon, Feb 4, 2019 at 11:41AM - Mon, Feb 4 at 2:41PM MST"`; +exports[`formatDatePair displays differences of dates 2`] = `"Mon, Feb 4, 2019, 11:41 AM - Mon, Feb 4, 2:41 PM CST"`; -exports[`formatDatePair displays differences of dates 3`] = `"Mon, Feb 4, 2019 at 11:41AM - Tue, Feb 5 at 2:41PM MST"`; +exports[`formatDatePair displays differences of dates 3`] = `"Mon, Feb 4, 2019, 11:41 AM - Tue, Feb 5, 2:41 PM CST"`; -exports[`formatDatePair displays differences of dates 4`] = `"Mon, Feb 4, 2019 at 11:41AM - Tue, Mar 5 at 2:41PM MST"`; +exports[`formatDatePair displays differences of dates 4`] = `"Mon, Feb 4, 2019, 11:41 AM - Tue, Mar 5, 2:41 PM CST"`; -exports[`formatDatePair displays differences of dates 5`] = `"Mon, Feb 4, 2019 at 11:41AM - Sat, Feb 5, 2050 at 2:41PM MST"`; +exports[`formatDatePair displays differences of dates 5`] = `"Mon, Feb 4, 2019, 11:41 AM - Sat, Feb 5, 2050, 2:41 PM CST"`; + +exports[`formatDatePair displays differences of dates 6`] = `"Mon, Feb 4, 2019, 11:41 AM - Sat, Feb 5, 2022, 2:41 PM CST"`; + +exports[`formatDatePair displays differences of dates 7`] = `"Sat, Feb 5, 2022, 2:41 PM - Sat, Feb 5, 2050, 2:41 PM CST"`; + +exports[`formatDatePair displays differences of dates 8`] = `"Sat, Feb 5, 2:41 PM - Sat, Mar 5, 2:41 PM CST"`; diff --git a/yarn.lock b/yarn.lock index babd4f1..beddc67 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1513,6 +1513,18 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== +"@types/lodash.memoize@^4.1.7": + version "4.1.7" + resolved "https://registry.yarnpkg.com/@types/lodash.memoize/-/lodash.memoize-4.1.7.tgz#aff94ab32813c557cbc1104e127030e3d60a3b27" + integrity sha512-lGN7WeO4vO6sICVpf041Q7BX/9k1Y24Zo3FY0aUezr1QlKznpjzsDk3T3wvH8ofYzoK0QupN9TWcFAFZlyPwQQ== + dependencies: + "@types/lodash" "*" + +"@types/lodash@*": + version "4.14.191" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.191.tgz#09511e7f7cba275acd8b419ddac8da9a6a79e2fa" + integrity sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ== + "@types/node@*": version "18.8.3" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.8.3.tgz#ce750ab4017effa51aed6a7230651778d54e327c" @@ -3228,6 +3240,14 @@ get-symbol-description@^1.0.0: call-bind "^1.0.2" get-intrinsic "^1.1.1" +get-user-locale@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/get-user-locale/-/get-user-locale-2.1.1.tgz#fdd86a8391a761d0d2068bb1b0b319fb4a3505b3" + integrity sha512-xtAOGym5MxC379wYMeyheReND4tEWKDQfDM2ar4uX5g9x7sxl6AnB4yNzPNTP3BP0HQm4psDHtMuKjx7v3NXHw== + dependencies: + "@types/lodash.memoize" "^4.1.7" + lodash.memoize "^4.1.1" + glob-parent@^5.0.0, glob-parent@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" @@ -4467,7 +4487,7 @@ lodash.debounce@^4.0.8: resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== -lodash.memoize@^4.1.2: +lodash.memoize@^4.1.1, lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== @@ -4506,6 +4526,11 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +luxon@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.2.1.tgz#14f1af209188ad61212578ea7e3d518d18cee45f" + integrity sha512-QrwPArQCNLAKGO/C+ZIilgIuDnEnKx5QYODdDtbFaxzsbZcc/a7WFq7MhsVYgRlwawLtvOUESTlfJ+hc/USqPg== + magic-string@^0.25.0, magic-string@^0.25.7: version "0.25.9" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c" @@ -4649,18 +4674,6 @@ minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== -moment-timezone@^0.5.37: - version "0.5.37" - resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.37.tgz#adf97f719c4e458fdb12e2b4e87b8bec9f4eef1e" - integrity sha512-uEDzDNFhfaywRl+vwXxffjjq1q0Vzr+fcQpQ1bU0kbzorfS7zVtZnCnGc8mhWmF39d4g4YriF6kwA75mJKE/Zg== - dependencies: - moment ">= 2.9.0" - -"moment@>= 2.9.0": - version "2.29.4" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108" - integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w== - moo@^0.5.0, moo@^0.5.1: version "0.5.2" resolved "https://registry.yarnpkg.com/moo/-/moo-0.5.2.tgz#f9fe82473bc7c184b0d32e2215d3f6e67278733c"