opening_hours 3.11.0 → 3.13.0

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.
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "type": "commonjs",
4
4
  "types": "./types/index.d.ts",
5
5
  "description": "Library to parse and process opening_hours tag from OpenStreetMap data",
6
- "version": "3.11.0",
6
+ "version": "3.13.0",
7
7
  "main": "./build/opening_hours.js",
8
8
  "module": "./build/opening_hours.esm.mjs",
9
9
  "sideEffects": false,
@@ -21,7 +21,10 @@
21
21
  "Robin Schneider <ypid@riseup.net>"
22
22
  ],
23
23
  "contributors": [
24
- "https://github.com/opening-hours/opening_hours.js/graphs/contributors"
24
+ {
25
+ "name": "Contributors",
26
+ "url": "https://github.com/opening-hours/opening_hours.js/graphs/contributors"
27
+ }
25
28
  ],
26
29
  "repository": {
27
30
  "type": "git",
@@ -60,47 +63,42 @@
60
63
  "regex_search": "make run-regex_search",
61
64
  "prepare": "husky",
62
65
  "readme": "doctoc README.md --title \"## Table of Contents\"",
63
- "check-updates": "npm-check-updates --reject colors --upgrade"
66
+ "check-updates": "npm-check-updates --upgrade"
64
67
  },
65
68
  "dependencies": {
66
- "i18next": "^25.3.2",
69
+ "i18next": "^26.1.0",
67
70
  "suncalc": "^1.9.0"
68
71
  },
69
72
  "devDependencies": {
70
- "@commitlint/cli": "^19.8.1",
71
- "@commitlint/config-conventional": "^19.8.1",
72
- "@eslint/js": "^9.32.0",
73
- "@eslint/markdown": "^7.1.0",
74
- "@rollup/plugin-commonjs": "^28.0.6",
73
+ "@commitlint/cli": "^21.0.0",
74
+ "@commitlint/config-conventional": "^21.0.0",
75
+ "@eslint/js": "^10.0.1",
76
+ "@eslint/markdown": "^8.0.1",
77
+ "@rollup/plugin-commonjs": "^29.0.2",
75
78
  "@rollup/plugin-json": "^6.1.0",
76
- "@rollup/plugin-node-resolve": "^16.0.1",
77
- "@rollup/plugin-terser": "^0.4.4",
79
+ "@rollup/plugin-node-resolve": "^16.0.3",
80
+ "@rollup/plugin-terser": "^1.0.0",
78
81
  "@rollup/plugin-yaml": "^4.1.2",
79
- "@stylistic/eslint-plugin": "^5.2.2",
80
- "colors": "^1.4.0",
81
- "commit-and-tag-version": "^12.5.2",
82
+ "@stylistic/eslint-plugin": "^5.10.0",
83
+ "commit-and-tag-version": "^12.7.3",
82
84
  "commitizen": "^4.3.1",
83
- "country-language": "^0.1.7",
84
85
  "cz-conventional-changelog": "^3.3.0",
85
- "doctoc": "^2.2.1",
86
- "eslint": "^9.32.0",
86
+ "doctoc": "^2.4.1",
87
+ "eslint": "^10.3.0",
87
88
  "full-icu": "^1.5.0",
88
- "glob": "^11.0.3",
89
- "globals": "^16.3.0",
89
+ "glob": "^13.0.6",
90
+ "globals": "^17.6.0",
90
91
  "husky": "^9.1.7",
91
- "line-reader": "^0.4.0",
92
- "lint-staged": "^16.2.6",
93
- "moment": "^2.30.1",
94
- "npm-check-updates": "^18.0.2",
95
- "package-json-validator": "^0.25.0",
96
- "rollup": "^4.46.2",
97
- "sprintf-js": "1.1.3",
98
- "terser": "^5.43.1",
92
+ "lint-staged": "^17.0.4",
93
+ "npm-check-updates": "^22.1.1",
94
+ "package-json-validator-cli": "^0.1.11",
95
+ "rollup": "^4.60.3",
96
+ "terser": "^5.47.1",
99
97
  "timekeeper": "^2.3.1",
100
- "typescript": "^5.8.3",
101
- "typescript-eslint": "^8.38.0",
102
- "yaml": "^2.8.0",
103
- "yargs": "^17.7.2"
98
+ "typescript": "^6.0.3",
99
+ "typescript-eslint": "^8.59.3",
100
+ "yaml": "^2.9.0",
101
+ "yargs": "^18.0.0"
104
102
  },
105
103
  "engines": {
106
104
  "node": ">=12"
@@ -1,4 +1,8 @@
1
1
  /*
2
+ * SPDX-FileCopyrightText: © 2015 Robin Schneider <ypid@riseup.net>
3
+ *
4
+ * SPDX-License-Identifier: LGPL-3.0-only
5
+ *
2
6
  * Upstream source: https://wiki.openstreetmap.org/wiki/Nominatim/Country_Codes
3
7
  */
4
8
  export const countryToLanguageMapping = {
@@ -1,5 +1,10 @@
1
+ /**
2
+ * SPDX-FileCopyrightText: © 2014 Robin Schneider <ypid@riseup.net>
3
+ *
4
+ * SPDX-License-Identifier: LGPL-3.0-only
5
+ */
1
6
  // Import dependencies
2
- import i18next from '../../node_modules/i18next/dist/esm/i18next.bundled.js';
7
+ import i18next from '../../node_modules/i18next/dist/esm/i18next.js';
3
8
  import { OpeningHoursTable } from './opening_hours_table.js';
4
9
  import { mapCountryToLanguage } from './countryToLanguageMapping.js';
5
10
  import { updateTimeButtonLabels } from './main.js';
@@ -46,15 +51,18 @@ export function josm(url_param) {
46
51
  }
47
52
  // }}}
48
53
 
49
- // add calculation for calendar week to date {{{
50
- export function dateAtWeek(date, week) {
51
- const minutes_in_day = 60 * 24;
52
- const msec_in_day = 1000 * 60 * minutes_in_day;
53
- const msec_in_week = msec_in_day * 7;
54
+ // ISO 8601 calendar week number {{{
55
+ export function getISOWeekNumber(date) {
56
+ const millisecondsPerDay = 24 * 60 * 60 * 1000;
57
+ const utcDate = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
58
+
59
+ // ISO week uses Monday=1..Sunday=7 and anchors weeks on Thursday.
60
+ const isoDay = utcDate.getUTCDay() || 7;
61
+ utcDate.setUTCDate(utcDate.getUTCDate() + 4 - isoDay);
54
62
 
55
- const tmpdate = new Date(date.getFullYear(), 0, 1);
56
- tmpdate.setDate(1 - (tmpdate.getDay() + 6) % 7 + week * 7); // start of week n where week starts on Monday
57
- return Math.floor((date - tmpdate) / msec_in_week);
63
+ const isoYearStart = new Date(Date.UTC(utcDate.getUTCFullYear(), 0, 1));
64
+ const dayOfYear = Math.floor((utcDate - isoYearStart) / millisecondsPerDay) + 1;
65
+ return Math.ceil(dayOfYear / 7);
58
66
  }
59
67
  // }}}
60
68
 
@@ -1,5 +1,10 @@
1
+ /**
2
+ * SPDX-FileCopyrightText: © 2013 Robin Schneider <ypid@riseup.net>
3
+ *
4
+ * SPDX-License-Identifier: LGPL-3.0-only
5
+ */
1
6
  // Import i18next for use in helper functions
2
- import i18next from '../../node_modules/i18next/dist/esm/i18next.bundled.js';
7
+ import i18next from '../../node_modules/i18next/dist/esm/i18next.js';
3
8
 
4
9
  // localization {{{
5
10
  export const resources = { // English is fallback language.
package/site/js/main.js CHANGED
@@ -1,7 +1,12 @@
1
+ /**
2
+ * SPDX-FileCopyrightText: © 2025 Kristjan ESPERANTO <https://github.com/KristjanESPERANTO>
3
+ *
4
+ * SPDX-License-Identifier: LGPL-3.0-only
5
+ */
1
6
  // Import all required modules
2
- import i18next from '../../node_modules/i18next/dist/esm/i18next.bundled.js';
7
+ import i18next from '../../node_modules/i18next/dist/esm/i18next.js';
3
8
  import { resources, detectLanguage, getUserSelectTranslateHTMLCode, changeLanguage } from './i18n-resources.js';
4
- import { Evaluate, EX, josm, toggle, dateAtWeek, newValue, currentDateTime } from './helpers.js';
9
+ import { Evaluate, EX, josm, toggle, getISOWeekNumber, newValue, currentDateTime } from './helpers.js';
5
10
 
6
11
  // Configuration constants
7
12
  window.default_lat = 48.7769;
@@ -72,7 +77,7 @@ export function updateTimeButtonLabels(date) {
72
77
  if (minuteLabel) minuteLabel.textContent = u2(currentDateTime.minute);
73
78
 
74
79
  if (weekLabel && date) {
75
- weekLabel.textContent = `W${u2(dateAtWeek(date, 0) + 1)}`;
80
+ weekLabel.textContent = `W${u2(getISOWeekNumber(date))}`;
76
81
  }
77
82
 
78
83
  if (wdayDisplay && date) {
@@ -1,5 +1,10 @@
1
+ /**
2
+ * SPDX-FileCopyrightText: © 2013 Robin Schneider <ypid@riseup.net>
3
+ *
4
+ * SPDX-License-Identifier: LGPL-3.0-only
5
+ */
1
6
  // Import dependencies
2
- import i18next from '../../node_modules/i18next/dist/esm/i18next.bundled.js';
7
+ import i18next from '../../node_modules/i18next/dist/esm/i18next.js';
3
8
 
4
9
  export const OpeningHoursTable = {
5
10
 
@@ -12,6 +17,16 @@ export const OpeningHoursTable = {
12
17
  return date.toLocaleString(i18next.language, { weekday: 'short' });
13
18
  },
14
19
 
20
+ // Returns a date's local time as a percentage of the day (0–100).
21
+ dayPercent(date) {
22
+ return (date.getHours() * 60 + date.getMinutes()) / 1440 * 100;
23
+ },
24
+
25
+ // Formats a date's local time as "HH:MM".
26
+ formatHM(date) {
27
+ return date.toLocaleString('en', { hourCycle: 'h23', hour: '2-digit', minute: '2-digit' });
28
+ },
29
+
15
30
  formatdate (now, nextchange, from) {
16
31
  const now_daystart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
17
32
  const nextdays = (nextchange.getTime() - now_daystart.getTime()) / 1000 / 60 / 60 / 24;
@@ -113,7 +128,7 @@ export const OpeningHoursTable = {
113
128
  // Calculate current time position for "now" marker (percentage of day)
114
129
  // Use evalDate instead of new Date() to show the evaluation time, not browser time
115
130
  const now = evalDate || new Date();
116
- const nowPercent = ((now.getHours() * 60 + now.getMinutes()) / (24 * 60)) * 100;
131
+ const nowPercent = this.dayPercent(now);
117
132
 
118
133
  const tableData = [];
119
134
 
@@ -137,30 +152,23 @@ export const OpeningHoursTable = {
137
152
  while (has_next_change && it.advance() && curdate.getTime() - date.getTime() < 24 * 60 * 60 * 1000) {
138
153
  curdate = it.getDate();
139
154
 
140
- let fr = prevdate.getTime() - date.getTime();
141
- let to = curdate.getTime() - date.getTime();
155
+ // Use local clock time (not elapsed ms) so bars stay aligned
156
+ // with labels on DST transition days.
157
+ const crossesMidnight = prevdate.getDay() !== curdate.getDay();
142
158
 
143
- if (to > 24 * 60 * 60 * 1000) {
144
- to = 24 * 60 * 60 * 1000;
145
- }
146
-
147
- fr *= 100 / 1000 / 60 / 60 / 24;
148
- to *= 100 / 1000 / 60 / 60 / 24;
159
+ const from = this.dayPercent(prevdate);
160
+ const to = crossesMidnight ? 100 : this.dayPercent(curdate);
149
161
 
150
162
  const stateClass = is_open ? 'open' : (unknown ? 'unknown' : 'closed');
151
- // Always use 24h format with HH:MM
152
- const timeFrom = `${String(prevdate.getHours()).padStart(2, '0')}:${String(prevdate.getMinutes()).padStart(2, '0')}`;
153
- const timeToDate = prevdate.getDay() !== curdate.getDay() ? null : curdate;
154
- const timeTo = timeToDate
155
- ? `${String(timeToDate.getHours()).padStart(2, '0')}:${String(timeToDate.getMinutes()).padStart(2, '0')}`
156
- : '24:00';
163
+ const timeFrom = this.formatHM(prevdate);
164
+ const timeTo = crossesMidnight ? '24:00' : this.formatHM(curdate);
157
165
 
158
166
  // Use current state_string for this period (before advancing)
159
167
  const currentStateString = state_string;
160
168
  const tooltip = `${i18next.t(`words.${currentStateString}`)}: ${timeFrom} - ${timeTo}`;
161
169
 
162
170
  rowData.times.push(
163
- `<div class="timebar ${stateClass}" style="width:${to - fr}%" title="${tooltip}"></div>`
171
+ `<div class="timebar ${stateClass}" style="width:${to - from}%" title="${tooltip}"></div>`
164
172
  );
165
173
 
166
174
  if (is_open || unknown) {
@@ -232,14 +240,6 @@ export const OpeningHoursTable = {
232
240
  return `<table class="opening-hours-table">${headerRow}${rows}</table>`;
233
241
  },
234
242
 
235
- getReadableState (startString, endString, oh, past) {
236
- if (past === true) past = 'd';
237
- else past = '';
238
-
239
- const output = '';
240
- return `${startString + output + endString}.`;
241
- },
242
-
243
243
  drawTableAndComments (oh, it, evalDate) {
244
244
  const prevdate = it.getDate();
245
245
  const unknown = it.getUnknown();
@@ -357,10 +357,7 @@ export const OpeningHoursTable = {
357
357
  const change_daystart = new Date(changeDate.getFullYear(), changeDate.getMonth(), changeDate.getDate());
358
358
  const daysDiff = Math.round((change_daystart.getTime() - now_daystart.getTime()) / (1000 * 60 * 60 * 24));
359
359
 
360
- // Always use 24h format with HH:MM
361
- const hours = String(changeDate.getHours()).padStart(2, '0');
362
- const minutes = String(changeDate.getMinutes()).padStart(2, '0');
363
- const timeStr = `${hours}:${minutes}`;
360
+ const timeStr = this.formatHM(changeDate);
364
361
 
365
362
  if (daysDiff === 0) {
366
363
  return `${i18next.t('words.today')} ${timeStr}`;
package/site/js/theme.js CHANGED
@@ -44,12 +44,8 @@
44
44
  const theme = getThemePreference();
45
45
  setTheme(theme);
46
46
 
47
- // Set up toggle button when DOM is ready
48
- if (document.readyState === 'loading') {
49
- document.addEventListener('DOMContentLoaded', initToggleButton);
50
- } else {
51
- initToggleButton();
52
- }
47
+ // Set up toggle button (script loads with defer, so DOM is ready)
48
+ initToggleButton();
53
49
 
54
50
  function initToggleButton() {
55
51
  const toggleBtn = document.getElementById('theme-toggle');
@@ -1,4 +1,7 @@
1
1
  /*
2
+ * SPDX-FileCopyrightText: Adrien PAVIE
3
+ * SPDX-License-Identifier: AGPL-3.0-or-later
4
+ *
2
5
  * This file is part of YoHours.
3
6
  *
4
7
  * YoHours is free software: you can redistribute it and/or modify
@@ -1,3 +1,8 @@
1
+ /**
2
+ * SPDX-FileCopyrightText: © 2025 Kristjan ESPERANTO <https://github.com/KristjanESPERANTO>
3
+ *
4
+ * SPDX-License-Identifier: LGPL-3.0-only
5
+ */
1
6
  import opening_hours_resources from './opening_hours_resources.yaml';
2
7
 
3
8
  const resources = opening_hours_resources;