local_time 2.1.0 → 3.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +50 -10
  4. data/app/assets/javascripts/local-time.es2017-esm.js +1 -0
  5. data/app/assets/javascripts/local-time.es2017-umd.js +1 -0
  6. data/app/helpers/local_time_helper.rb +72 -21
  7. data/lib/local_time/version.rb +3 -0
  8. data/test/helpers/local_time_helper_test.rb +60 -31
  9. data/test/javascripts/builds/index.js +26626 -0
  10. data/test/javascripts/fixtures/index.html +20 -0
  11. data/test/javascripts/fixtures/time_zone_check.html +20 -0
  12. data/test/javascripts/server.mjs +41 -0
  13. data/test/javascripts/src/format24_test.js +109 -0
  14. data/test/javascripts/src/i18n_test.js +55 -0
  15. data/test/javascripts/src/index.js +10 -0
  16. data/test/javascripts/src/local_time_test.js +54 -0
  17. data/test/javascripts/src/relative_date_test.js +121 -0
  18. data/test/javascripts/src/strftime_test.js +151 -0
  19. data/test/javascripts/src/test_helpers.js +66 -0
  20. data/test/javascripts/src/time_ago_test.js +72 -0
  21. data/test/javascripts/vendor/moment.js +5684 -2
  22. data/test/javascripts/vendor/sinon.js +20485 -0
  23. metadata +81 -28
  24. data/app/assets/javascripts/local-time.js +0 -1
  25. data/test/javascripts/fixtures/body.html +0 -8
  26. data/test/javascripts/src/i18n_test.coffee +0 -47
  27. data/test/javascripts/src/local_time_test.coffee +0 -37
  28. data/test/javascripts/src/relative_date_test.coffee +0 -89
  29. data/test/javascripts/src/strftime_test.coffee +0 -54
  30. data/test/javascripts/src/test.coffee +0 -43
  31. data/test/javascripts/src/time_ago_test.coffee +0 -56
  32. data/test/javascripts/vendor/sinon-timers.js +0 -385
@@ -0,0 +1,20 @@
1
+ <!DOCTYPE html>
2
+ <head>
3
+ <meta charset="utf-8">
4
+ <title>Test Suite</title>
5
+ <link rel="stylesheet" href="https://code.jquery.com/qunit/qunit-2.19.4.css">
6
+ </head>
7
+ <body>
8
+ <div id="qunit"></div>
9
+ <div id="qunit-fixture"></div>
10
+
11
+ <script src="https://code.jquery.com/qunit/qunit-2.19.4.js"></script>
12
+ <script src="/test-build"></script>
13
+
14
+ <time id="one" data-format="%B %e, %Y %l:%M%P" data-local="time" datetime="2013-11-12T12:13:00Z"></time>
15
+ <time id="two" data-format="%B %e, %Y %l:%M%P" data-local="time" datetime="2013-01-02T02:04:00Z"></time>
16
+ <time id="past" data-format="%B %e, %Y %l:%M%P" data-local="time" datetime="1805-05-15T01:01:00Z"></time>
17
+ <time id="future" data-format="%B %e, %Y %l:%M%P" data-local="time" datetime="3333-02-14T23:55:00Z"></time>
18
+ <time id="date" data-format="%B %e, %Y" data-local="time" datetime="2013-11-12T12:13:00Z"></time>
19
+ <time id="ago"></time>
20
+ </body>
@@ -0,0 +1,20 @@
1
+ <!DOCTYPE html>
2
+ <head>
3
+ <meta charset="utf-8">
4
+ <title>Time Zone Check</title>
5
+ <script src="/current-build"></script>
6
+ </head>
7
+ <body>
8
+ <script>
9
+ LocalTime.start()
10
+ </script>
11
+
12
+ <p><strong>Standard Time (Northern Hemisphere) | DST Time (Southern Hemisphere)</strong></p>
13
+ <time data-format="%B %e, %Y %l:%M%P (%Z)" data-local="time" datetime="2023-12-12T12:13:00Z"></time>
14
+
15
+ <br>
16
+ <br>
17
+
18
+ <p><strong>DST Time (Northern Hemisphere) | Standard Time (Southern Hemisphere)</strong></p>
19
+ <time data-format="%B %e, %Y %l:%M%P (%Z)" data-local="time" datetime="2023-07-12T12:13:00Z"></time>
20
+ </body>
@@ -0,0 +1,41 @@
1
+ import { Router } from "express"
2
+ import express from "express"
3
+ import path from "path"
4
+ import * as url from 'url'
5
+
6
+ const __dirname = url.fileURLToPath(new URL('.', import.meta.url));
7
+ const router = Router()
8
+
9
+ router.get("/", (_request, response) => {
10
+ const fixture = path.join(__dirname, "./fixtures/index.html")
11
+ response.status(200).sendFile(fixture)
12
+ })
13
+
14
+ router.get("/time-zone-check", (_request, response) => {
15
+ const fixture = path.join(__dirname, "./fixtures/time_zone_check.html")
16
+ response.status(200).sendFile(fixture)
17
+ })
18
+
19
+ router.get("/test-build", (_request, response) => {
20
+ const file = path.join(__dirname, "./builds/index.js")
21
+ response.status(200).sendFile(file)
22
+ })
23
+
24
+ router.get("/current-build", (_request, response) => {
25
+ const file = path.join(__dirname, "./../../app/assets/javascripts/local-time.es2017-umd.js")
26
+ response.status(200).sendFile(file)
27
+ })
28
+
29
+ const app = express()
30
+ app.use(express.static("."))
31
+ app.use(router)
32
+
33
+ const port = parseInt(process.env.PORT || "9000")
34
+ app.listen(port, () => {
35
+ console.log(`
36
+ /*
37
+ Please go to http://localhost:${port}/ to run JS tests now.
38
+ Or go to http://localhost:${port}/time-zone-check to manually test a time zone.
39
+ */
40
+ `)
41
+ })
@@ -0,0 +1,109 @@
1
+ import LocalTime from "local_time"
2
+
3
+ const { addTimeEl, assert, defer, getText, testAsync, testGroup, getTitle } = LocalTime.TestHelpers
4
+ const { config } = LocalTime
5
+
6
+ testGroup("format24 (24h time)", () => {
7
+ testAsync("default behavior", (done) => {
8
+ const now = moment()
9
+
10
+ const el = addTimeEl({ format: "%-l:%M%P", format24: "%H:%M", datetime: now.toISOString() })
11
+ defer(() => {
12
+ assert.equal(getText(el), now.format("h:mma"))
13
+ done()
14
+ })
15
+ })
16
+
17
+ testAsync("turned off", (done) => {
18
+ const now = moment()
19
+ const originalFormat24 = config.useFormat24
20
+ config.useFormat24 = false
21
+
22
+ const el = addTimeEl({ format: "%-l:%M%P", format24: "%H:%M", datetime: now.toISOString() })
23
+ defer(() => {
24
+ assert.equal(getText(el), now.format("h:mma"))
25
+ config.useFormat24 = originalFormat24
26
+ done()
27
+ })
28
+ })
29
+
30
+ testAsync("turned on", (done) => {
31
+ const now = moment()
32
+ const originalFormat24 = config.useFormat24
33
+ config.useFormat24 = true
34
+
35
+ const el = addTimeEl({ format: "%-l:%M%P", format24: "%H:%M", datetime: now.toISOString() })
36
+ defer(() => {
37
+ assert.equal(getText(el), now.format("HH:mm"))
38
+ config.useFormat24 = originalFormat24
39
+ done()
40
+ })
41
+ })
42
+
43
+ testAsync("fallback for missing data-format24 values", (done) => {
44
+ const now = moment()
45
+ const originalFormat24 = config.useFormat24
46
+ config.useFormat24 = true
47
+
48
+ const el = addTimeEl({ format: "%-l:%M%P", datetime: now.toISOString() })
49
+ defer(() => {
50
+ assert.equal(getText(el), now.format("h:mma"))
51
+ config.useFormat24 = originalFormat24
52
+ done()
53
+ })
54
+ })
55
+
56
+ testAsync("turned off for relative time elements", (done) => {
57
+ const ago = moment().subtract("days", 5)
58
+ const originalFormat24 = config.useFormat24
59
+ config.useFormat24 = false
60
+
61
+ const el = addTimeEl({ type: "time-ago", datetime: ago.toISOString() })
62
+ defer(() => {
63
+ assert.equal(getText(el), `${ago.format("dddd")} at ${ago.format("h:mma")}`)
64
+ config.useFormat24 = originalFormat24
65
+ done()
66
+ })
67
+ })
68
+
69
+ testAsync("turned on for relative time elements", (done) => {
70
+ const ago = moment().subtract("days", 5)
71
+ const originalFormat24 = config.useFormat24
72
+ config.useFormat24 = true
73
+
74
+ const el = addTimeEl({ type: "time-ago", datetime: ago.toISOString() })
75
+ defer(() => {
76
+ assert.equal(getText(el), `${ago.format("dddd")} at ${ago.format("HH:mm")}`)
77
+ config.useFormat24 = originalFormat24
78
+ done()
79
+ })
80
+ })
81
+
82
+ testAsync("element title when turned off", (done) => {
83
+ const ago = moment().subtract("days", 5)
84
+ const originalFormat24 = config.useFormat24
85
+ config.useFormat24 = false
86
+
87
+ const el = addTimeEl({ type: "time-ago", datetime: ago.toISOString() })
88
+ defer(() => {
89
+ const regex = new RegExp(ago.format('MMMM D, YYYY [at] h:mma') + " (\\w{3,4}|UTC[+-]d+)")
90
+ assert.ok(regex.test(getTitle(el)), `'${getTitle(el)}' doesn't look correct, it should match regex ${regex}`)
91
+ config.useFormat24 = originalFormat24
92
+ done()
93
+ })
94
+ })
95
+
96
+ testAsync("element title when turned on", (done) => {
97
+ const ago = moment().subtract("days", 5)
98
+ const originalFormat24 = config.useFormat24
99
+ config.useFormat24 = true
100
+
101
+ const el = addTimeEl({ type: "time-ago", datetime: ago.toISOString() })
102
+ defer(() => {
103
+ const regex = new RegExp(ago.format('MMMM D, YYYY [at] HH:mm') + " (\\w{3,4}|UTC[+-]d+)")
104
+ assert.ok(regex.test(getTitle(el)), `'${getTitle(el)}' doesn't look correct, it should match regex ${regex}`)
105
+ config.useFormat24 = originalFormat24
106
+ done()
107
+ })
108
+ })
109
+ })
@@ -0,0 +1,55 @@
1
+ import LocalTime from "local_time"
2
+
3
+ const { addTimeEl, assert, defer, getText, setText, test, testAsync, testGroup, triggerEvent } = LocalTime.TestHelpers
4
+ const { config } = LocalTime
5
+ const { i18n } = config
6
+
7
+ testGroup("i18n", () => {
8
+ testAsync("updating a value", (done) => {
9
+ const now = moment()
10
+ const values = i18n[config.defaultLocale].date
11
+
12
+ const originalValue = values.today
13
+ values.today = "2day"
14
+
15
+ const el = addTimeEl({ type: "weekday", datetime: now.toISOString() })
16
+ defer(() => {
17
+ assert.equal(getText(el), "2day")
18
+ values.today = originalValue
19
+ done()
20
+ })
21
+ })
22
+
23
+ testAsync("adding a new locale", (done) => {
24
+ const now = moment()
25
+
26
+ const originalLocale = config.locale
27
+ config.locale = "es"
28
+ i18n.es = { date: { today: "hoy" } }
29
+
30
+ const el = addTimeEl({ type: "weekday", datetime: now.toISOString() })
31
+ defer(() => {
32
+ assert.equal(getText(el), "hoy")
33
+ config.locale = originalLocale
34
+ done()
35
+ })
36
+ })
37
+
38
+ testAsync("falling back to the default locale", (done) => {
39
+ const now = moment()
40
+ const yesterday = moment().subtract("days", 1)
41
+
42
+ const originalLocale = config.locale
43
+ config.locale = "es"
44
+ i18n.es = { date: { yesterday: "ayer" } }
45
+
46
+ const elWithTranslation = addTimeEl({ type: "weekday", datetime: yesterday.toISOString() })
47
+ const elWithoutTranslation = addTimeEl({ type: "weekday", datetime: now.toISOString() })
48
+ defer(() => {
49
+ assert.equal(getText(elWithTranslation), "ayer")
50
+ assert.equal(getText(elWithoutTranslation), "today")
51
+ config.locale = originalLocale
52
+ done()
53
+ })
54
+ })
55
+ })
@@ -0,0 +1,10 @@
1
+ import "moment"
2
+ import "sinon"
3
+
4
+ import "./test_helpers"
5
+ import "./format24_test"
6
+ import "./i18n_test"
7
+ import "./local_time_test"
8
+ import "./relative_date_test"
9
+ import "./strftime_test"
10
+ import "./time_ago_test"
@@ -0,0 +1,54 @@
1
+ import LocalTime from "local_time"
2
+
3
+ const { addTimeEl, assert, defer, getText, setText, test, testAsync, testGroup, triggerEvent } = LocalTime.TestHelpers
4
+
5
+ testGroup("localized", () => {
6
+ for (var id of [ "one", "two", "past", "future" ]) {
7
+ test(id, () => {
8
+ assertLocalized(id)
9
+ })
10
+ }
11
+
12
+ test("date", () => {
13
+ assertLocalized("date", "date")
14
+ })
15
+
16
+ test("unparseable time", () => {
17
+ const el = addTimeEl({ format: "%Y", datetime: ":(" })
18
+ setText(el, "2013")
19
+ assert.equal(getText(el), "2013")
20
+ })
21
+ })
22
+
23
+ test("processed timestamp", () => {
24
+ const el = addTimeEl({ type: "time-or-date", datetime: moment().toISOString() })
25
+ assert.notOk(el.getAttribute("data-processed-at"))
26
+ LocalTime.run()
27
+ assert.ok(el.getAttribute("data-processed-at"))
28
+ })
29
+
30
+ function assertLocalized(id, type = "time") {
31
+ let compare, datetime, local, momentFormat
32
+ switch (type) {
33
+ case "time":
34
+ momentFormat = "MMMM D, YYYY h:mma"
35
+ compare = "toString"
36
+ break
37
+ case "date":
38
+ momentFormat = "MMMM D, YYYY"
39
+ compare = "dayOfYear"
40
+ break
41
+ }
42
+
43
+ const el = document.getElementById(id)
44
+
45
+ assert.ok(datetime = el.getAttribute("datetime"))
46
+ assert.ok(local = getText(el))
47
+
48
+ const datetimeParsed = moment(datetime)
49
+ const localParsed = moment(local, momentFormat)
50
+
51
+ assert.ok(datetimeParsed.isValid())
52
+ assert.ok(localParsed.isValid())
53
+ assert.equal(datetimeParsed[compare](), localParsed[compare]())
54
+ }
@@ -0,0 +1,121 @@
1
+ import LocalTime from "local_time"
2
+
3
+ const { addTimeEl, assert, defer, getText, testAsync, testGroup, stubNow } = LocalTime.TestHelpers
4
+
5
+ testGroup("relative date", () => {
6
+ testAsync("this year", (done) => {
7
+ const now = moment()
8
+ const el = addTimeEl({ type: "date", datetime: now.toISOString() })
9
+ defer(() => {
10
+ assert.equal(getText(el), now.format("MMM D"))
11
+ done()
12
+ })
13
+ })
14
+
15
+ testAsync("last year", (done) => {
16
+ const before = moment().subtract("years", 1).subtract("days", 1)
17
+ const el = addTimeEl({ type: "date", datetime: before.toISOString() })
18
+ defer(() => {
19
+ assert.equal(getText(el), before.format("MMM D, YYYY"))
20
+ done()
21
+ })
22
+ })
23
+ })
24
+
25
+ testGroup("relative time or date", () => {
26
+ testAsync("today", (done) => {
27
+ const now = moment()
28
+ const el = addTimeEl({ type: "time-or-date", datetime: now.toISOString() })
29
+ defer(() => {
30
+ assert.equal(getText(el), now.format("h:mma"))
31
+ done()
32
+ })
33
+ })
34
+
35
+ testAsync("before today", (done) => {
36
+ const before = moment().subtract("days", 1)
37
+ const el = addTimeEl({ type: "time-or-date", datetime: before.toISOString() })
38
+ defer(() => {
39
+ assert.equal(getText(el), before.format("MMM D"))
40
+ done()
41
+ })
42
+ })
43
+ })
44
+
45
+ testGroup("relative weekday", () => {
46
+ testAsync("today", (done) => {
47
+ const now = moment()
48
+ const el = addTimeEl({ type: "weekday", datetime: now.toISOString() })
49
+ defer(() => {
50
+ assert.equal(getText(el), "today")
51
+ done()
52
+ })
53
+ })
54
+
55
+ testAsync("yesterday", (done) => {
56
+ const yesterday = moment().subtract("days", 1)
57
+ const el = addTimeEl({ type: "weekday", datetime: yesterday.toISOString() })
58
+ defer(() => {
59
+ assert.equal(getText(el), "yesterday")
60
+ done()
61
+ })
62
+ })
63
+
64
+ testAsync("this week", (done) => {
65
+ const recent = moment().subtract("days", 3)
66
+ const el = addTimeEl({ type: "weekday", datetime: recent.toISOString() })
67
+ defer(() => {
68
+ assert.equal(getText(el), recent.format("dddd"))
69
+ done()
70
+ })
71
+ })
72
+
73
+ testAsync("before this week", (done) => {
74
+ const before = moment().subtract("days", 8)
75
+ const el = addTimeEl({ type: "weekday", datetime: before.toISOString() })
76
+ defer(() => {
77
+ assert.equal(getText(el), "")
78
+ done()
79
+ })
80
+ })
81
+ })
82
+
83
+ testGroup("relative weekday or date", () => {
84
+ testAsync("today", (done) => {
85
+ const now = moment()
86
+ const el = addTimeEl({ type: "weekday-or-date", datetime: now.toISOString() })
87
+ defer(() => {
88
+ assert.equal(getText(el), "today")
89
+ done()
90
+ })
91
+ })
92
+
93
+ testAsync("yesterday", (done) => {
94
+ const yesterday = moment().subtract("days", 1)
95
+ const el = addTimeEl({ type: "weekday-or-date", datetime: yesterday.toISOString() })
96
+ defer(() => {
97
+ assert.equal(getText(el), "yesterday")
98
+ done()
99
+ })
100
+ })
101
+
102
+ testAsync("this week", (done) => {
103
+ const recent = moment().subtract("days", 3)
104
+ const el = addTimeEl({ type: "weekday-or-date", datetime: recent.toISOString() })
105
+ defer(() => {
106
+ assert.equal(getText(el), recent.format("dddd"))
107
+ done()
108
+ })
109
+ })
110
+
111
+ testAsync("before this week", (done) => {
112
+ stubNow(`${moment().year()}-03-20`, () => {
113
+ const before = moment().subtract("days", 8)
114
+ const el = addTimeEl({ type: "weekday-or-date", datetime: before.toISOString() })
115
+ defer(() => {
116
+ assert.equal(getText(el), before.format("MMM D"))
117
+ done()
118
+ })
119
+ })
120
+ })
121
+ })
@@ -0,0 +1,151 @@
1
+ import LocalTime from "local_time"
2
+
3
+ const { addTimeEl, assert, defer, getText, setText, test, testAsync, testGroup, triggerEvent } = LocalTime.TestHelpers
4
+
5
+ const momentMap = {
6
+ "%a": "ddd",
7
+ "%A": "dddd",
8
+ "%b": "MMM",
9
+ "%B": "MMMM",
10
+ "%c": "toString()",
11
+ "%d": "DD",
12
+ "%-d": "D",
13
+ "%e": "D",
14
+ "%H": "HH",
15
+ "%-H": "H",
16
+ "%I": "hh",
17
+ "%-I": "h",
18
+ "%l": "h",
19
+ "%m": "MM",
20
+ "%-m": "M",
21
+ "%M": "mm",
22
+ "%-M": "m",
23
+ "%p": "A",
24
+ "%P": "a",
25
+ "%S": "ss",
26
+ "%-S": "s",
27
+ "%w": "e",
28
+ "%y": "YY",
29
+ "%Y": "YYYY"
30
+ }
31
+
32
+ function stubDateToLocaleString(stubImplementation, callback) {
33
+ const original = Date.prototype.toLocaleString
34
+ Date.prototype.toLocaleString = stubImplementation
35
+ try {
36
+ return callback()
37
+ } finally {
38
+ Date.prototype.toLocaleString = original
39
+ }
40
+ }
41
+
42
+ function stubDateToString(stubImplementation, callback) {
43
+ const original = Date.prototype.toString
44
+ Date.prototype.toString = stubImplementation
45
+ try {
46
+ return callback()
47
+ } finally {
48
+ Date.prototype.toString = original
49
+ }
50
+ }
51
+
52
+ testGroup("strftime", () => {
53
+ for (let day = 0; day <= 30; day += 6) {
54
+ for (let hour = 0; hour <= 24; hour += 6) {
55
+ for (const format in momentMap) {
56
+ if (Object.prototype.hasOwnProperty.call(momentMap, format)) {
57
+ const momentFormat = momentMap[ format ]
58
+
59
+ test(`${format} (+${day} days, ${hour} hours)`, () => {
60
+ const now = moment().add("days", day).add("hours", hour)
61
+ const el = addTimeEl({ format, datetime: now.toISOString() })
62
+ LocalTime.process(el)
63
+
64
+ if (momentFormat.includes("toString()")) {
65
+ assert.equal(getText(el), now.toDate().toString())
66
+ } else {
67
+ assert.equal(getText(el), now.format(momentFormat))
68
+ }
69
+ })
70
+ }
71
+ }
72
+
73
+ test(`%Z Timezone (+${day} days, ${hour} hours)`, () => {
74
+ const now = moment().add("days", day).add("hours", hour)
75
+ const el = addTimeEl({ format: "%Z", datetime: now.toISOString() })
76
+ LocalTime.process(el)
77
+
78
+ const text = getText(el)
79
+ assert.ok(/^(\w{3,4}|UTC[\+\-]\d+)$/.test(text), `'${text}' doesn't look like a timezone. System date: '${new Date}'`)
80
+ })
81
+ }
82
+ }
83
+ })
84
+
85
+ testGroup("strftime time zones", () => {
86
+ for (var timeZone of Array.from(Object.keys(LocalTime.knownEdgeCaseTimeZones))) {
87
+ ((timeZone => test(`edge-case time zone ${timeZone}`, () => {
88
+ const stub = () => `Thu Nov 30 2023 14:22:57 GMT-0000 (${timeZone})`
89
+
90
+ stubDateToLocaleString(stub, () => {
91
+ const el = addTimeEl({ format: "%Z", datetime: "2023-11-30T14:22:57Z" })
92
+ LocalTime.process(el)
93
+
94
+ assert.equal(getText(el), LocalTime.knownEdgeCaseTimeZones[timeZone])
95
+ })
96
+ })))(timeZone)
97
+ }
98
+
99
+ test("time zones Intl can abbreviate are parsed correctly", () => {
100
+ const stub = (_, options) => {
101
+ if (options.timeZoneName === "long") {
102
+ return "Thu Nov 30 2023 14:22:57 GMT-0800 (Alaska Daylight Time)" // not a known edge-case
103
+ } else if (options.timeZoneName === "short") {
104
+ return "11/30/2023, 2:22:57 PM AKDT" // possible to abbreviate
105
+ }
106
+ }
107
+
108
+ stubDateToLocaleString(stub, () => {
109
+ const el = addTimeEl({ format: "%Z", datetime: "2023-11-30T14:22:57Z" })
110
+ LocalTime.process(el)
111
+
112
+ assert.equal(getText(el), "AKDT")
113
+ })
114
+ })
115
+
116
+ test("time zones Intl can't abbreviate are parsed by our heuristic", () => {
117
+ const dateToStringStub = () => "Sat Dec 02 2023 17:20:26 GMT-0600 (Central Standard Time)"
118
+ const dateToLocaleStringStub = (_, options) => {
119
+ if (options.timeZoneName === "long") {
120
+ return "Thu Nov 30 2023 14:22:57 GMT+0700 (Central Twilight Time)" // not a known edge-case
121
+ } else if (options.timeZoneName === "short") {
122
+ return "11/30/2023, 2:22:57 PM GMT+7" // not possible to abbreviate
123
+ }
124
+ }
125
+
126
+ stubDateToString(dateToStringStub, () => stubDateToLocaleString(dateToLocaleStringStub, () => {
127
+ const el = addTimeEl({ format: "%Z", datetime: "2023-11-30T14:22:57Z" })
128
+ LocalTime.process(el)
129
+
130
+ assert.equal(getText(el), "CST")
131
+ }))
132
+ })
133
+
134
+ test("time zones Intl can't abbreviate and our heuristic can't parse display GMT offset", () => {
135
+ const dateToStringStub = () => ""
136
+ const dateToLocaleStringStub = (_, options) => {
137
+ if (options.timeZoneName === "long") {
138
+ return "Thu Nov 30 2023 14:22:57 GMT+0700 (Central Twilight Time)" // not a known edge-case
139
+ } else if (options.timeZoneName === "short") {
140
+ return "11/30/2023, 2:22:57 PM GMT+7" // not possible to abbreviate
141
+ }
142
+ }
143
+
144
+ stubDateToString(dateToStringStub, () => stubDateToLocaleString(dateToLocaleStringStub, () => {
145
+ const el = addTimeEl({ format: "%Z", datetime: "2023-11-30T14:22:57Z" })
146
+ LocalTime.process(el)
147
+
148
+ assert.equal(getText(el), "GMT+7")
149
+ }))
150
+ })
151
+ })
@@ -0,0 +1,66 @@
1
+ import LocalTime from "local_time"
2
+
3
+ LocalTime.start()
4
+
5
+ LocalTime.TestHelpers = {
6
+ assert: QUnit.assert,
7
+ testGroup: QUnit.module,
8
+ test: QUnit.test,
9
+
10
+ testAsync(name, callback) {
11
+ QUnit.test(name, (assert) => {
12
+ const done = assert.async()
13
+ callback(done)
14
+ })
15
+ },
16
+
17
+ addTimeEl(param = {}) {
18
+ let { format, type, datetime, format24 } = param
19
+ if (!format) format = "%Y"
20
+ if (!type) type = "time"
21
+ if (!datetime) datetime = "2013-11-12T12:13:00Z"
22
+
23
+ const el = document.createElement("time")
24
+ el.setAttribute("data-local", type)
25
+ el.setAttribute("data-format", format)
26
+ el.setAttribute("datetime", datetime)
27
+ if (format24) el.setAttribute("data-format24", format24)
28
+
29
+ document.body.appendChild(el)
30
+ return el
31
+ },
32
+
33
+ setText(el, text) {
34
+ el.textContent = text
35
+ },
36
+
37
+ getText(el) {
38
+ // innerHTML works in all browsers so using it ensures we're
39
+ // reading the text content, not a potentially arbitrary property.
40
+ return el.innerHTML
41
+ },
42
+
43
+ getTitle(el) {
44
+ return el.getAttribute("title")
45
+ },
46
+
47
+ triggerEvent(name, el = document) {
48
+ const event = document.createEvent("Events")
49
+ event.initEvent(name, true, true)
50
+ el.dispatchEvent(event)
51
+ },
52
+
53
+ defer(callback) {
54
+ setTimeout(callback, 1)
55
+ },
56
+
57
+ stubNow(dateString, callback) {
58
+ const originalNow = moment.now
59
+ try {
60
+ moment.now = () => new Date(dateString).getTime()
61
+ callback()
62
+ } finally {
63
+ moment.now = originalNow
64
+ }
65
+ }
66
+ }