Compare commits

...

15 Commits

Author SHA1 Message Date
Matt Walsh
779d34a0a8 5.13.4 2024-11-25 09:40:50 -06:00
Matt Walsh
efeb45d3d0 fix travel forecast and hourly scrolling close #58 2024-11-25 09:40:36 -06:00
Matt Walsh
e2d7a96971 linting cleanup 2024-10-21 19:21:05 -05:00
Matt Walsh
487c83f664 update license 2024-10-21 19:16:38 -05:00
Matt Walsh
88b8b4a82e merge 2024-10-21 14:56:06 -05:00
Matt Walsh
13b77a0070 better filtering of null station data for current weather close #54
5.13.3
2024-10-19 13:47:22 -05:00
Matt Walsh
c9307768a4 fix radar timestamp
5.13.2
2024-10-19 13:39:34 -05:00
Matt Walsh
844544c364 night time hurricane icons
5.13.1
2024-10-19 13:39:25 -05:00
Matt Walsh
80a68caa27 hazards only on first run through after load/refresh
sorting of hazards based on type tornado, hurricane, etc

5.13.0
2024-10-19 13:39:16 -05:00
Matt Walsh
c7889eaa2c hazards only on first run through after load/refresh
sorting of hazards based on type tornado, hurricane, etc

5.13.0
2024-10-08 10:34:57 -05:00
Matt Walsh
019908684b update dependencies and clarify dev dependencies 2024-08-27 09:00:38 -05:00
Matt Walsh
e1a58b6548 Merge branch 'main' of https://github.com/netbymatt/ws4kp 2024-07-29 23:14:01 -05:00
Matt Walsh
e794976f4d 5.12.0 2024-07-29 23:13:12 -05:00
Matt Walsh
193d742aa3 add speed setting close #49 2024-07-29 23:12:47 -05:00
Matt Walsh
b62339af94 capture dist 2024-07-11 16:20:59 -05:00
26 changed files with 2283 additions and 2037 deletions

View File

@@ -8,7 +8,6 @@ module.exports = {
},
extends: [
'airbnb-base',
'plugin:sonarjs/recommended',
],
globals: {
Atomics: 'readonly',
@@ -25,13 +24,14 @@ module.exports = {
ecmaVersion: 2023,
},
plugins: [
'unicorn',
'sonarjs',
],
rules: {
indent: [
'error',
'tab',
{
SwitchCase: 1
},
],
'no-tabs': 0,
'no-console': 0,
@@ -67,24 +67,6 @@ module.exports = {
json: 'always',
},
],
// unicorn
'unicorn/numeric-separators-style': 'error',
'unicorn/prefer-query-selector': 'error',
'unicorn/catch-error-name': 'error',
'unicorn/no-negated-condition': 'error',
'unicorn/better-regex': 'error',
'unicorn/consistent-function-scoping': 'error',
'unicorn/prefer-array-flat-map': 'error',
'unicorn/prefer-array-find': 'error',
'unicorn/prefer-regexp-test': 'error',
'unicorn/consistent-destructuring': 'error',
'unicorn/prefer-date-now': 'error',
'unicorn/prefer-ternary': 'error',
'unicorn/prefer-dom-node-append': 'error',
'unicorn/explicit-length-check': 'error',
'unicorn/prefer-at': 'error',
// sonarjs
'sonarjs/cognitive-complexity': 0,
},
ignorePatterns: [
'*.min.js',

View File

@@ -7,8 +7,6 @@
"format": "compressed",
"extensionName": ".css",
"savePath": "/server/styles",
"savePathSegmentKeys": null,
"savePathReplaceSegmentsWith": null
}
],
"search.exclude": {
@@ -21,5 +19,8 @@
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
},
"eslint.validate": [
"javascript"
]
}

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2016-2017 Michael Battaglia
Copyright (c) 2020-2024 Matt Walsh
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

2
dist/index.html vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,4 @@
// express
// eslint-disable-next-line import/no-extraneous-dependencies
const express = require('express');
const app = express();

2948
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "ws4kp",
"version": "5.11.7",
"version": "5.13.4",
"description": "Welcome to the WeatherStar 4000+ project page!",
"main": "index.js",
"scripts": {
@@ -21,13 +21,18 @@
"homepage": "https://github.com/netbymatt/ws4kp#readme",
"devDependencies": {
"del": "^7.1.0",
"ejs": "^3.1.5",
"jquery": "^3.6.0",
"jquery-touchswipe": "^1.6.19",
"luxon": "^3.0.0",
"nosleep.js": "^0.12.0",
"suncalc": "^1.8.0",
"swiped-events": "^1.1.4",
"@aws-sdk/client-cloudfront": "^3.609.0",
"gulp-awspublish": "^8.0.0",
"gulp-s3-upload": "^1.7.3",
"eslint": "^8.2.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-plugin-import": "^2.10.0",
"eslint-plugin-sonarjs": "^0.25.1",
"eslint-plugin-unicorn": "^52.0.0",
"express": "^4.17.1",
"gulp": "^5.0.0",
"gulp-concat": "^2.6.1",
"gulp-ejs": "^5.1.0",
@@ -35,19 +40,12 @@
"gulp-rename": "^2.0.0",
"gulp-sass": "^5.1.0",
"gulp-terser": "^2.0.0",
"jquery": "^3.6.0",
"jquery-touchswipe": "^1.6.19",
"luxon": "^3.0.0",
"nosleep.js": "^0.12.0",
"sass": "^1.54.0",
"suncalc": "^1.8.0",
"swiped-events": "^1.1.4",
"terser-webpack-plugin": "^5.3.6",
"webpack-stream": "^7.0.0"
"webpack-stream": "^7.0.0",
"sass": "^1.54.0"
},
"dependencies": {
"@aws-sdk/client-cloudfront": "^3.609.0",
"gulp-awspublish": "^8.0.0",
"gulp-s3-upload": "^1.7.3"
"express": "^4.17.1",
"ejs": "^3.1.5"
}
}

View File

@@ -254,14 +254,14 @@ const loadData = (_latLon, haveDataCallback) => {
const swipeCallBack = (direction) => {
switch (direction) {
case 'left':
btnNavigateNextClick();
break;
case 'left':
btnNavigateNextClick();
break;
case 'right':
default:
btnNavigatePreviousClick();
break;
case 'right':
default:
btnNavigatePreviousClick();
break;
}
};
@@ -312,41 +312,41 @@ const documentKeydown = (e) => {
if (document.fullscreenElement || document.activeElement === document.body) {
switch (key) {
case ' ': // Space
// don't scroll
e.preventDefault();
btnNavigatePlayClick();
return false;
case ' ': // Space
// don't scroll
e.preventDefault();
btnNavigatePlayClick();
return false;
case 'ArrowRight':
case 'PageDown':
// don't scroll
e.preventDefault();
btnNavigateNextClick();
return false;
case 'ArrowRight':
case 'PageDown':
// don't scroll
e.preventDefault();
btnNavigateNextClick();
return false;
case 'ArrowLeft':
case 'PageUp':
// don't scroll
e.preventDefault();
btnNavigatePreviousClick();
return false;
case 'ArrowLeft':
case 'PageUp':
// don't scroll
e.preventDefault();
btnNavigatePreviousClick();
return false;
case 'ArrowUp': // Home
e.preventDefault();
btnNavigateMenuClick();
return false;
case 'ArrowUp': // Home
e.preventDefault();
btnNavigateMenuClick();
return false;
case '0': // "O" Restart
btnNavigateRefreshClick();
return false;
case '0': // "O" Restart
btnNavigateRefreshClick();
return false;
case 'F':
case 'f':
btnFullScreenClick();
return false;
case 'F':
case 'f':
btnFullScreenClick();
return false;
default:
default:
}
}
return false;

View File

@@ -94,7 +94,7 @@ class Almanac extends WeatherDisplay {
if (iteration % 2 === 0) test = (lastPhase, testPhase) => lastPhase > threshold && testPhase <= threshold;
do {
// store last phase
// store last phase
const lastPhase = phase;
// calculate new phase after step
moonDate = moonDate.plus(step);
@@ -103,7 +103,7 @@ class Almanac extends WeatherDisplay {
if (phase > 0.9) phase -= 1.0;
// compare
if (test(lastPhase, phase)) {
// last iteration is three, return value
// last iteration is three, return value
if (iteration >= 3) break;
// iterate recursively
return this.getMoonTransition(threshold, phaseName, moonDate, iteration + 1);
@@ -160,15 +160,15 @@ class Almanac extends WeatherDisplay {
const imageName = (type) => {
switch (type) {
case 'Full':
return 'images/2/Full-Moon.gif';
case 'Last':
return 'images/2/Last-Quarter.gif';
case 'New':
return 'images/2/New-Moon.gif';
case 'First':
default:
return 'images/2/First-Quarter.gif';
case 'Full':
return 'images/2/Full-Moon.gif';
case 'Last':
return 'images/2/Last-Quarter.gif';
case 'New':
return 'images/2/New-Moon.gif';
case 'First':
default:
return 'images/2/First-Quarter.gif';
}
};

View File

@@ -56,7 +56,9 @@ class CurrentWeather extends WeatherDisplay {
|| observations.features[0].properties.windSpeed.value === null
|| observations.features[0].properties.textDescription === null
|| observations.features[0].properties.textDescription === ''
|| observations.features[0].properties.icon === null) {
|| observations.features[0].properties.icon === null
|| observations.features[0].properties.dewpoint.value === null
|| observations.features[0].properties.barometricPressure.value === null) {
observations = undefined;
throw new Error(`Unable to get observations: ${station.properties.stationIdentifier}, trying next station`);
}

View File

@@ -10,6 +10,12 @@ const hazardLevels = {
Severe: 5,
};
const hazardModifiers = {
'Hurricane Warning': 2,
'Tornado Warning': 3,
'Severe Thunderstorm Warning': 1,
};
class Hazards extends WeatherDisplay {
constructor(navId, elemId, defaultActive) {
// special height and width for scrolling
@@ -34,8 +40,9 @@ class Hazards extends WeatherDisplay {
url.searchParams.append('limit', 5);
const alerts = await json(url, { retryCount: 3, stillWaiting: () => this.stillWaiting() });
const unsortedAlerts = alerts.features ?? [];
const sortedAlerts = unsortedAlerts.sort((a, b) => (hazardLevels[b.properties.severity] ?? 0) - (hazardLevels[a.properties.severity] ?? 0));
const filteredAlerts = sortedAlerts.filter((hazard) => hazard.properties.severity !== 'Unknown');
const hasImmediate = unsortedAlerts.reduce((acc, hazard) => acc || hazard.properties.urgency === 'Immediate', false);
const sortedAlerts = unsortedAlerts.sort((a, b) => (calcSeverity(b.properties.severity, b.properties.event)) - (calcSeverity(a.properties.severity, a.properties.event)));
const filteredAlerts = sortedAlerts.filter((hazard) => hazard.properties.severity !== 'Unknown' && (!hasImmediate || (hazard.properties.urgency === 'Immediate')));
this.data = filteredAlerts;
// show alert indicator
@@ -134,7 +141,26 @@ class Hazards extends WeatherDisplay {
this.getDataCallbacks.push(() => resolve(this.data));
});
}
// after we roll through the hazards once, don't display again until the next refresh (10 minutes)
screenIndexFromBaseCount() {
const superValue = super.screenIndexFromBaseCount();
// false is returned when we reach the end of the scroll
if (superValue === false) {
// set total screens to zero to take this out of the rotation
this.timing.totalScreens = 0;
}
// return the value as expected
return superValue;
}
}
const calcSeverity = (severity, event) => {
// base severity plus some modifiers for specific types of warnings
const baseSeverity = hazardLevels[severity] ?? 0;
const modifiedSeverity = hazardModifiers[event] ?? 0;
return baseSeverity + modifiedSeverity;
};
// register display
registerDisplay(new Hazards(0, 'hazards', true));

View File

@@ -109,7 +109,7 @@ class Hourly extends WeatherDisplay {
// base count change callback
baseCountChange(count) {
// calculate scroll offset and don't go past end
let offsetY = Math.min(this.elem.querySelector('.hourly-lines').getBoundingClientRect().height - 289, (count - 150));
let offsetY = Math.min(this.elem.querySelector('.hourly-lines').offsetHeight - 289, (count - 150));
// don't let offset go negative
if (offsetY < 0) offsetY = 0;

View File

@@ -1,4 +1,3 @@
/* eslint-disable unicorn/consistent-function-scoping */
/* spell-checker: disable */
const getWeatherRegionalIconFromIconLink = (link, _isNightTime) => {
@@ -21,136 +20,138 @@ const getWeatherRegionalIconFromIconLink = (link, _isNightTime) => {
// find the icon
switch (conditionName + (isNightTime ? '-n' : '')) {
case 'skc':
case 'hot':
case 'haze':
return addPath('Sunny.gif');
case 'skc':
case 'hot':
case 'haze':
return addPath('Sunny.gif');
case 'skc-n':
case 'nskc':
case 'nskc-n':
case 'cold-n':
return addPath('Clear-1992.gif');
case 'skc-n':
case 'nskc':
case 'nskc-n':
case 'cold-n':
return addPath('Clear-1992.gif');
case 'bkn':
return addPath('Mostly-Cloudy-1994-2.gif');
case 'bkn':
return addPath('Mostly-Cloudy-1994-2.gif');
case 'bkn-n':
case 'few-n':
case 'nfew-n':
case 'nfew':
return addPath('Partly-Clear-1994-2.gif');
case 'bkn-n':
case 'few-n':
case 'nfew-n':
case 'nfew':
return addPath('Partly-Clear-1994-2.gif');
case 'sct':
case 'few':
return addPath('Partly-Cloudy.gif');
case 'sct':
case 'few':
return addPath('Partly-Cloudy.gif');
case 'sct-n':
case 'nsct':
case 'nsct-n':
return addPath('Mostly-Clear.gif');
case 'sct-n':
case 'nsct':
case 'nsct-n':
return addPath('Mostly-Clear.gif');
case 'ovc':
case 'ovc-n':
return addPath('Cloudy.gif');
case 'ovc':
case 'ovc-n':
return addPath('Cloudy.gif');
case 'fog':
case 'fog-n':
return addPath('Fog.gif');
case 'fog':
case 'fog-n':
return addPath('Fog.gif');
case 'rain_sleet':
return addPath('Sleet.gif');
case 'rain_sleet':
return addPath('Sleet.gif');
case 'rain_showers':
case 'rain_showers_high':
return addPath('Scattered-Showers-1994-2.gif');
case 'rain_showers':
case 'rain_showers_high':
return addPath('Scattered-Showers-1994-2.gif');
case 'rain_showers-n':
case 'rain_showers_high-n':
return addPath('Scattered-Showers-Night-1994-2.gif');
case 'rain_showers-n':
case 'rain_showers_high-n':
return addPath('Scattered-Showers-Night-1994-2.gif');
case 'rain':
case 'rain-n':
return addPath('Rain-1992.gif');
case 'rain':
case 'rain-n':
return addPath('Rain-1992.gif');
// case 'snow':
// return addPath('Light-Snow.gif');
// break;
// case 'snow':
// return addPath('Light-Snow.gif');
// break;
// case 'cc_snowshowers.gif':
// //case "heavy-snow.gif":
// return addPath('AM-Snow-1994.gif');
// break;
// case 'cc_snowshowers.gif':
// //case "heavy-snow.gif":
// return addPath('AM-Snow-1994.gif');
// break;
case 'snow':
case 'snow-n':
if (value > 50) return addPath('Heavy-Snow-1994-2.gif');
return addPath('Light-Snow.gif');
case 'snow':
case 'snow-n':
if (value > 50) return addPath('Heavy-Snow-1994-2.gif');
return addPath('Light-Snow.gif');
case 'rain_snow':
case 'rain_snow-n':
return addPath('Rain-Snow-1992.gif');
case 'rain_snow':
case 'rain_snow-n':
return addPath('Rain-Snow-1992.gif');
case 'snow_fzra':
case 'snow_fzra-n':
return addPath('Freezing-Rain-Snow-1992.gif');
case 'snow_fzra':
case 'snow_fzra-n':
return addPath('Freezing-Rain-Snow-1992.gif');
case 'fzra':
case 'fzra-n':
case 'rain_fzra':
case 'rain_fzra-n':
return addPath('Freezing-Rain-1992.gif');
case 'fzra':
case 'fzra-n':
case 'rain_fzra':
case 'rain_fzra-n':
return addPath('Freezing-Rain-1992.gif');
case 'snow_sleet':
case 'snow_sleet-n':
return addPath('Snow and Sleet.gif');
case 'snow_sleet':
case 'snow_sleet-n':
return addPath('Snow and Sleet.gif');
case 'sleet':
case 'sleet-n':
return addPath('Sleet.gif');
case 'sleet':
case 'sleet-n':
return addPath('Sleet.gif');
case 'tsra_sct':
case 'tsra':
return addPath('Scattered-Tstorms-1994-2.gif');
case 'tsra_sct':
case 'tsra':
return addPath('Scattered-Tstorms-1994-2.gif');
case 'tsra_sct-n':
case 'tsra-n':
return addPath('Scattered-Tstorms-Night-1994-2.gif');
case 'tsra_sct-n':
case 'tsra-n':
return addPath('Scattered-Tstorms-Night-1994-2.gif');
case 'tsra_hi':
case 'tsra_hi-n':
case 'hurricane':
case 'tropical_storm':
return addPath('Thunderstorm.gif');
case 'tsra_hi':
case 'tsra_hi-n':
case 'hurricane':
case 'tropical_storm':
case 'hurricane-n':
case 'tropical_storm-n':
return addPath('Thunderstorm.gif');
case 'wind':
case 'wind_few':
case 'wind_sct':
case 'wind_bkn':
case 'wind_ovc':
case 'wind-n':
case 'wind_few-n':
case 'wind_bkn-n':
case 'wind_ovc-n':
return addPath('Wind.gif');
case 'wind':
case 'wind_few':
case 'wind_sct':
case 'wind_bkn':
case 'wind_ovc':
case 'wind-n':
case 'wind_few-n':
case 'wind_bkn-n':
case 'wind_ovc-n':
return addPath('Wind.gif');
case 'wind_skc':
return addPath('Sunny-Wind-1994.gif');
case 'wind_skc':
return addPath('Sunny-Wind-1994.gif');
case 'wind_skc-n':
case 'wind_sct-n':
return addPath('Clear-Wind-1994.gif');
case 'wind_skc-n':
case 'wind_sct-n':
return addPath('Clear-Wind-1994.gif');
case 'blizzard':
case 'blizzard-n':
return addPath('Blowing Snow.gif');
case 'blizzard':
case 'blizzard-n':
return addPath('Blowing Snow.gif');
case 'cold':
return addPath('cold.gif');
case 'cold':
return addPath('cold.gif');
default:
console.log(`Unable to locate regional icon for ${conditionName} ${link} ${isNightTime}`);
return false;
default:
console.log(`Unable to locate regional icon for ${conditionName} ${link} ${isNightTime}`);
return false;
}
};
@@ -176,120 +177,122 @@ const getWeatherIconFromIconLink = (link, _isNightTime) => {
// find the icon
switch (conditionName + (isNightTime ? '-n' : '')) {
case 'skc':
case 'hot':
case 'haze':
case 'cold':
return addPath('CC_Clear1.gif');
case 'skc':
case 'hot':
case 'haze':
case 'cold':
return addPath('CC_Clear1.gif');
case 'skc-n':
case 'nskc':
case 'nskc-n':
case 'cold-n':
return addPath('CC_Clear0.gif');
case 'skc-n':
case 'nskc':
case 'nskc-n':
case 'cold-n':
return addPath('CC_Clear0.gif');
case 'sct':
case 'few':
case 'bkn':
return addPath('CC_PartlyCloudy1.gif');
case 'sct':
case 'few':
case 'bkn':
return addPath('CC_PartlyCloudy1.gif');
case 'bkn-n':
case 'few-n':
case 'nfew-n':
case 'nfew':
case 'sct-n':
case 'nsct':
case 'nsct-n':
return addPath('CC_PartlyCloudy0.gif');
case 'bkn-n':
case 'few-n':
case 'nfew-n':
case 'nfew':
case 'sct-n':
case 'nsct':
case 'nsct-n':
return addPath('CC_PartlyCloudy0.gif');
case 'ovc':
case 'novc':
case 'ovc-n':
return addPath('CC_Cloudy.gif');
case 'ovc':
case 'novc':
case 'ovc-n':
return addPath('CC_Cloudy.gif');
case 'fog':
case 'fog-n':
return addPath('CC_Fog.gif');
case 'fog':
case 'fog-n':
return addPath('CC_Fog.gif');
case 'rain_sleet':
case 'rain_sleet-n':
case 'sleet':
case 'sleet-n':
return addPath('Sleet.gif');
case 'rain_sleet':
case 'rain_sleet-n':
case 'sleet':
case 'sleet-n':
return addPath('Sleet.gif');
case 'rain_showers':
case 'rain_showers_high':
return addPath('CC_Showers.gif');
case 'rain_showers':
case 'rain_showers_high':
return addPath('CC_Showers.gif');
case 'rain_showers-n':
case 'rain_showers_high-n':
return addPath('CC_Showers.gif');
case 'rain_showers-n':
case 'rain_showers_high-n':
return addPath('CC_Showers.gif');
case 'rain':
case 'rain-n':
return addPath('CC_Rain.gif');
case 'rain':
case 'rain-n':
return addPath('CC_Rain.gif');
// case 'snow':
// return addPath('Light-Snow.gif');
// break;
// case 'snow':
// return addPath('Light-Snow.gif');
// break;
// case 'cc_snowshowers.gif':
// //case "heavy-snow.gif":
// return addPath('AM-Snow-1994.gif');
// break;
// case 'cc_snowshowers.gif':
// //case "heavy-snow.gif":
// return addPath('AM-Snow-1994.gif');
// break;
case 'snow':
case 'snow-n':
if (value > 50) return addPath('CC_Snow.gif');
return addPath('CC_SnowShowers.gif');
case 'snow':
case 'snow-n':
if (value > 50) return addPath('CC_Snow.gif');
return addPath('CC_SnowShowers.gif');
case 'rain_snow':
return addPath('CC_RainSnow.gif');
case 'rain_snow':
return addPath('CC_RainSnow.gif');
case 'snow_fzra':
case 'snow_fzra-n':
case 'fzra':
case 'fzra-n':
case 'rain_fzra':
case 'rain_fzra-n':
return addPath('CC_FreezingRain.gif');
case 'snow_fzra':
case 'snow_fzra-n':
case 'fzra':
case 'fzra-n':
case 'rain_fzra':
case 'rain_fzra-n':
return addPath('CC_FreezingRain.gif');
case 'snow_sleet':
return addPath('Snow-Sleet.gif');
case 'snow_sleet':
return addPath('Snow-Sleet.gif');
case 'tsra_sct':
case 'tsra':
return addPath('EF_ScatTstorms.gif');
case 'tsra_sct':
case 'tsra':
return addPath('EF_ScatTstorms.gif');
case 'tsra_sct-n':
case 'tsra-n':
return addPath('CC_TStorm.gif');
case 'tsra_sct-n':
case 'tsra-n':
return addPath('CC_TStorm.gif');
case 'tsra_hi':
case 'tsra_hi-n':
case 'hurricane':
case 'tropical_storm':
return addPath('CC_TStorm.gif');
case 'tsra_hi':
case 'tsra_hi-n':
case 'hurricane':
case 'tropical_storm':
case 'hurricane-n':
case 'tropical_storm-n':
return addPath('CC_TStorm.gif');
case 'wind_few':
case 'wind_sct':
case 'wind_bkn':
case 'wind_ovc':
case 'wind_skc':
case 'wind_few-n':
case 'wind_bkn-n':
case 'wind_ovc-n':
case 'wind_skc-n':
case 'wind_sct-n':
return addPath('CC_Windy.gif');
case 'wind_few':
case 'wind_sct':
case 'wind_bkn':
case 'wind_ovc':
case 'wind_skc':
case 'wind_few-n':
case 'wind_bkn-n':
case 'wind_ovc-n':
case 'wind_skc-n':
case 'wind_sct-n':
return addPath('CC_Windy.gif');
case 'blizzard':
case 'blizzard-n':
return addPath('Blowing-Snow.gif');
case 'blizzard':
case 'blizzard-n':
return addPath('Blowing-Snow.gif');
default:
console.log(`Unable to locate icon for ${conditionName} ${link} ${isNightTime}`);
return false;
default:
console.log(`Unable to locate icon for ${conditionName} ${link} ${isNightTime}`);
return false;
}
};

View File

@@ -238,30 +238,30 @@ const setPlaying = (newValue) => {
// handle all navigation buttons
const handleNavButton = (button) => {
switch (button) {
case 'play':
setPlaying(true);
break;
case 'playToggle':
setPlaying(!playing);
break;
case 'stop':
setPlaying(false);
break;
case 'next':
setPlaying(false);
navTo(msg.command.nextFrame);
break;
case 'previous':
setPlaying(false);
navTo(msg.command.previousFrame);
break;
case 'menu':
setPlaying(false);
progress.showCanvas();
hideAllCanvases();
break;
default:
console.error(`Unknown navButton ${button}`);
case 'play':
setPlaying(true);
break;
case 'playToggle':
setPlaying(!playing);
break;
case 'stop':
setPlaying(false);
break;
case 'next':
setPlaying(false);
navTo(msg.command.nextFrame);
break;
case 'previous':
setPlaying(false);
navTo(msg.command.previousFrame);
break;
case 'menu':
setPlaying(false);
progress.showCanvas();
hideAllCanvases();
break;
default:
console.error(`Unknown navButton ${button}`);
}
};

View File

@@ -157,7 +157,7 @@ class Radar extends WeatherDisplay {
minute,
}, {
zone: 'UTC',
}).setZone();
}).setZone(timeZone());
} else {
time = DateTime.fromHTTP(response.headers.get('last-modified')).setZone(timeZone());
}

View File

@@ -4,20 +4,28 @@ document.addEventListener('DOMContentLoaded', () => {
init();
});
const settings = {};
// default speed
const settings = { speed: { value: 1.0 } };
const init = () => {
// create settings
settings.wide = new Setting('wide', 'Widescreen', 'boolean', false, wideScreenChange, true);
settings.kiosk = new Setting('kiosk', 'Kiosk', 'boolean', false, kioskChange, false);
settings.speed = new Setting('speed', 'Speed', 'select', 1.0, null, true, [
[0.5, 'Very Fast'],
[0.75, 'Fast'],
[1.0, 'Normal'],
[1.25, 'Slow'],
[1.5, 'Very Slow'],
]);
// generate checkboxes
const checkboxes = Object.values(settings).map((d) => d.generateCheckbox());
// generate html objects
const settingHtml = Object.values(settings).map((d) => d.generate());
// write to page
const settingsSection = document.querySelector('#settings');
settingsSection.innerHTML = '';
settingsSection.append(...checkboxes);
settingsSection.append(...settingHtml);
};
const wideScreenChange = (value) => {

View File

@@ -31,6 +31,14 @@ const createLink = async (e) => {
}
});
// get all select boxes
const selects = document.querySelectorAll('select');
[...selects].forEach((elem) => {
if (elem?.id) {
queryStringElements[elem.id] = elem?.value ?? 0;
}
});
// add the location string
queryStringElements.latLonQuery = localStorage.getItem('latLonQuery');
queryStringElements.latLon = localStorage.getItem('latLon');

View File

@@ -9,20 +9,20 @@ const STATUS = {
const calcStatusClass = (statusCode) => {
switch (statusCode) {
case STATUS.loading:
return 'loading';
case STATUS.loaded:
return 'press-here';
case STATUS.failed:
return 'failed';
case STATUS.noData:
return 'no-data';
case STATUS.disabled:
return 'disabled';
case STATUS.retrying:
return 'retrying';
default:
return '';
case STATUS.loading:
return 'loading';
case STATUS.loaded:
return 'press-here';
case STATUS.failed:
return 'failed';
case STATUS.noData:
return 'no-data';
case STATUS.disabled:
return 'disabled';
case STATUS.retrying:
return 'retrying';
default:
return '';
}
};

View File

@@ -131,7 +131,7 @@ class TravelForecast extends WeatherDisplay {
// base count change callback
baseCountChange(count) {
// calculate scroll offset and don't go past end
let offsetY = Math.min(this.elem.querySelector('.travel-lines').getBoundingClientRect().height - 289, (count - 150));
let offsetY = Math.min(this.elem.querySelector('.travel-lines').offsetHeight - 289, (count - 150));
// don't let offset go negative
if (offsetY < 0) offsetY = 0;

View File

@@ -39,14 +39,14 @@ const fetchAsync = async (_url, responseType, _params = {}) => {
if (!response.ok) throw new Error(`Fetch error ${response.status} ${response.statusText} while fetching ${response.url}`);
// return the requested response
switch (responseType) {
case 'json':
return response.json();
case 'text':
return response.text();
case 'blob':
return response.blob();
default:
return response;
case 'json':
return response.json();
case 'text':
return response.text();
case 'blob':
return response.blob();
default:
return response;
}
};
@@ -84,11 +84,11 @@ const delay = (time, func, ...args) => new Promise((resolve) => {
const retryDelay = (retryNumber) => {
switch (retryNumber) {
case 1: return 1000;
case 2: return 2000;
case 3: return 5000;
case 4: return 10_000;
default: return 30_000;
case 1: return 1000;
case 2: return 2000;
case 3: return 5000;
case 4: return 10_000;
default: return 30_000;
}
};

View File

@@ -3,23 +3,27 @@ import { parseQueryString } from '../share.mjs';
const SETTINGS_KEY = 'Settings';
class Setting {
constructor(shortName, name, type, defaultValue, changeAction, sticky) {
constructor(shortName, name, type, defaultValue, changeAction, sticky, values) {
// store values
this.shortName = shortName;
this.name = name;
this.defaultValue = defaultValue;
this.myValue = defaultValue;
this.type = type;
this.type = type ?? 'checkbox';
this.sticky = sticky;
this.values = values;
// a default blank change function is provided
this.changeAction = changeAction ?? (() => { });
// get value from url
const urlValue = parseQueryString()?.[`settings-${shortName}-checkbox`];
const urlValue = parseQueryString()?.[`settings-${shortName}-${type}`];
let urlState;
if (urlValue !== undefined) {
if (type === 'checkbox' && urlValue !== undefined) {
urlState = urlValue === 'true';
}
if (type === 'select' && urlValue !== undefined) {
urlState = parseFloat(urlValue);
}
// get existing value if present
const storedValue = urlState ?? this.getFromLocalStorage();
@@ -28,7 +32,46 @@ class Setting {
}
// call the change function on startup
this.checkboxChange({ target: { checked: this.myValue } });
switch (type) {
case 'select':
this.selectChange({ target: { value: this.myValue } });
break;
case 'checkbox':
default:
this.checkboxChange({ target: { checked: this.myValue } });
}
}
generateSelect() {
// create a radio button set in the selected displays area
const label = document.createElement('label');
label.for = `settings-${this.shortName}-select`;
label.id = `settings-${this.shortName}-label`;
const span = document.createElement('span');
span.innerHTML = `${this.name} `;
label.append(span);
const select = document.createElement('select');
select.id = `settings-${this.shortName}-select`;
select.name = `settings-${this.shortName}-select`;
select.addEventListener('change', (e) => this.selectChange(e));
this.values.forEach(([value, text]) => {
const option = document.createElement('option');
option.value = value.toFixed(2);
option.innerHTML = text;
select.append(option);
});
label.append(select);
this.element = label;
// set the initial value
this.selectHighlight(this.myValue);
return label;
}
generateCheckbox() {
@@ -48,7 +91,7 @@ class Setting {
label.append(checkbox, span);
this.checkbox = label;
this.element = label;
return label;
}
@@ -62,6 +105,15 @@ class Setting {
this.changeAction(this.myValue);
}
selectChange(e) {
// update the value
this.myValue = parseFloat(e.target.value);
this.storeToLocalStorage(this.myValue);
// call the change action
this.changeAction(this.myValue);
}
storeToLocalStorage(value) {
if (!this.sticky) return;
const allSettingsString = localStorage?.getItem(SETTINGS_KEY) ?? '{}';
@@ -77,12 +129,12 @@ class Setting {
const storedValue = JSON.parse(allSettings)?.[this.shortName];
if (storedValue !== undefined) {
switch (this.type) {
case 'boolean':
return storedValue;
case 'int':
return storedValue;
default:
return null;
case 'boolean':
return storedValue;
case 'select':
return storedValue;
default:
return null;
}
}
}
@@ -99,12 +151,36 @@ class Setting {
set value(newValue) {
// update the state
this.myValue = newValue;
this.checkbox.checked = newValue;
switch (this.type) {
case 'select':
this.selectHighlight(newValue);
break;
case 'checkbox':
default:
this.element.checked = newValue;
}
this.storeToLocalStorage(this.myValue);
// call change action
this.changeAction(this.myValue);
}
selectHighlight(newValue) {
// set the dropdown to the provided value
this.element.querySelectorAll('option').forEach((elem) => {
elem.selected = newValue.toFixed(2) === elem.value;
});
}
generate() {
switch (this.type) {
case 'select':
return this.generateSelect();
case 'checkbox':
default:
return this.generateCheckbox();
}
}
}
export default Setting;

View File

@@ -6,6 +6,7 @@ import {
msg, displayNavMessage, isPlaying, updateStatus, timeZone,
} from './navigation.mjs';
import { parseQueryString } from './share.mjs';
import settings from './settings.mjs';
class WeatherDisplay {
constructor(navId, elemId, name, defaultEnabled) {
@@ -358,7 +359,7 @@ class WeatherDisplay {
// start and stop base counter
startNavCount() {
if (!this.navInterval) this.navInterval = setInterval(() => this.navBaseTime(), this.timing.baseDelay);
if (!this.navInterval) this.navInterval = setInterval(() => this.navBaseTime(), this.timing.baseDelay * settings.speed.value);
}
resetNavBaseCount() {

File diff suppressed because one or more lines are too long

View File

@@ -259,6 +259,12 @@ class Zone {
throw new ZoneIsAbstractError();
}
/**
* The IANA name of this zone.
* Defaults to `name` if not overwritten by a subclass.
* @abstract
* @type {string}
*/
get ianaName() {
return this.name;
}
@@ -468,7 +474,7 @@ class IANAZone extends Zone {
* @param {string} s - The string to check validity on
* @example IANAZone.isValidSpecifier("America/New_York") //=> true
* @example IANAZone.isValidSpecifier("Sport~~blorp") //=> false
* @deprecated This method returns false for some valid IANA names. Use isValidZone instead.
* @deprecated For backward compatibility, this forwards to isValidZone, better use `isValidZone()` directly instead.
* @return {boolean}
*/
static isValidSpecifier(s) {
@@ -503,32 +509,65 @@ class IANAZone extends Zone {
this.valid = IANAZone.isValidZone(name);
}
/** @override **/
/**
* The type of zone. `iana` for all instances of `IANAZone`.
* @override
* @type {string}
*/
get type() {
return "iana";
}
/** @override **/
/**
* The name of this zone (i.e. the IANA zone name).
* @override
* @type {string}
*/
get name() {
return this.zoneName;
}
/** @override **/
/**
* Returns whether the offset is known to be fixed for the whole year:
* Always returns false for all IANA zones.
* @override
* @type {boolean}
*/
get isUniversal() {
return false;
}
/** @override **/
/**
* Returns the offset's common name (such as EST) at the specified timestamp
* @override
* @param {number} ts - Epoch milliseconds for which to get the name
* @param {Object} opts - Options to affect the format
* @param {string} opts.format - What style of offset to return. Accepts 'long' or 'short'.
* @param {string} opts.locale - What locale to return the offset name in.
* @return {string}
*/
offsetName(ts, { format, locale }) {
return parseZoneInfo(ts, format, locale, this.name);
}
/** @override **/
/**
* Returns the offset's value as a string
* @override
* @param {number} ts - Epoch milliseconds for which to get the offset
* @param {string} format - What style of offset to return.
* Accepts 'narrow', 'short', or 'techie'. Returning '+6', '+06:00', or '+0600' respectively
* @return {string}
*/
formatOffset(ts, format) {
return formatOffset(this.offset(ts), format);
}
/** @override **/
/**
* Return the offset in minutes for this zone at the specified timestamp.
* @override
* @param {number} ts - Epoch milliseconds for which to compute the offset
* @return {number}
*/
offset(ts) {
const date = new Date(ts);
@@ -562,12 +601,21 @@ class IANAZone extends Zone {
return (asUTC - asTS) / (60 * 1000);
}
/** @override **/
/**
* Return whether this Zone is equal to another zone
* @override
* @param {Zone} otherZone - the zone to compare
* @return {boolean}
*/
equals(otherZone) {
return otherZone.type === "iana" && otherZone.name === this.name;
}
/** @override **/
/**
* Return whether this Zone is valid.
* @override
* @type {boolean}
*/
get isValid() {
return this.valid;
}
@@ -909,7 +957,7 @@ class Locale {
static create(locale, numberingSystem, outputCalendar, weekSettings, defaultToEN = false) {
const specifiedLocale = locale || Settings.defaultLocale;
// the system locale is useful for human readable strings but annoying for parsing/formatting known formats
// the system locale is useful for human-readable strings but annoying for parsing/formatting known formats
const localeR = specifiedLocale || (defaultToEN ? "en-US" : systemLocale());
const numberingSystemR = numberingSystem || Settings.defaultNumberingSystem;
const outputCalendarR = outputCalendar || Settings.defaultOutputCalendar;
@@ -1108,6 +1156,10 @@ class Locale {
this.outputCalendar === other.outputCalendar
);
}
toString() {
return `Locale(${this.locale}, ${this.numberingSystem}, ${this.outputCalendar})`;
}
}
let singleton = null;
@@ -1161,16 +1213,31 @@ class FixedOffsetZone extends Zone {
this.fixed = offset;
}
/** @override **/
/**
* The type of zone. `fixed` for all instances of `FixedOffsetZone`.
* @override
* @type {string}
*/
get type() {
return "fixed";
}
/** @override **/
/**
* The name of this zone.
* All fixed zones' names always start with "UTC" (plus optional offset)
* @override
* @type {string}
*/
get name() {
return this.fixed === 0 ? "UTC" : `UTC${formatOffset(this.fixed, "narrow")}`;
}
/**
* The IANA name of this zone, i.e. `Etc/UTC` or `Etc/GMT+/-nn`
*
* @override
* @type {string}
*/
get ianaName() {
if (this.fixed === 0) {
return "Etc/UTC";
@@ -1179,32 +1246,65 @@ class FixedOffsetZone extends Zone {
}
}
/** @override **/
/**
* Returns the offset's common name at the specified timestamp.
*
* For fixed offset zones this equals to the zone name.
* @override
*/
offsetName() {
return this.name;
}
/** @override **/
/**
* Returns the offset's value as a string
* @override
* @param {number} ts - Epoch milliseconds for which to get the offset
* @param {string} format - What style of offset to return.
* Accepts 'narrow', 'short', or 'techie'. Returning '+6', '+06:00', or '+0600' respectively
* @return {string}
*/
formatOffset(ts, format) {
return formatOffset(this.fixed, format);
}
/** @override **/
/**
* Returns whether the offset is known to be fixed for the whole year:
* Always returns true for all fixed offset zones.
* @override
* @type {boolean}
*/
get isUniversal() {
return true;
}
/** @override **/
/**
* Return the offset in minutes for this zone at the specified timestamp.
*
* For fixed offset zones, this is constant and does not depend on a timestamp.
* @override
* @return {number}
*/
offset() {
return this.fixed;
}
/** @override **/
/**
* Return whether this Zone is equal to another zone (i.e. also fixed and same offset)
* @override
* @param {Zone} otherZone - the zone to compare
* @return {boolean}
*/
equals(otherZone) {
return otherZone.type === "fixed" && otherZone.fixed === this.fixed;
}
/** @override **/
/**
* Return whether this Zone is valid:
* All fixed offset zones are valid.
* @override
* @type {boolean}
*/
get isValid() {
return true;
}
@@ -1288,6 +1388,97 @@ function normalizeZone(input, defaultZone) {
}
}
const numberingSystems = {
arab: "[\u0660-\u0669]",
arabext: "[\u06F0-\u06F9]",
bali: "[\u1B50-\u1B59]",
beng: "[\u09E6-\u09EF]",
deva: "[\u0966-\u096F]",
fullwide: "[\uFF10-\uFF19]",
gujr: "[\u0AE6-\u0AEF]",
hanidec: "[|一|二|三|四|五|六|七|八|九]",
khmr: "[\u17E0-\u17E9]",
knda: "[\u0CE6-\u0CEF]",
laoo: "[\u0ED0-\u0ED9]",
limb: "[\u1946-\u194F]",
mlym: "[\u0D66-\u0D6F]",
mong: "[\u1810-\u1819]",
mymr: "[\u1040-\u1049]",
orya: "[\u0B66-\u0B6F]",
tamldec: "[\u0BE6-\u0BEF]",
telu: "[\u0C66-\u0C6F]",
thai: "[\u0E50-\u0E59]",
tibt: "[\u0F20-\u0F29]",
latn: "\\d",
};
const numberingSystemsUTF16 = {
arab: [1632, 1641],
arabext: [1776, 1785],
bali: [6992, 7001],
beng: [2534, 2543],
deva: [2406, 2415],
fullwide: [65296, 65303],
gujr: [2790, 2799],
khmr: [6112, 6121],
knda: [3302, 3311],
laoo: [3792, 3801],
limb: [6470, 6479],
mlym: [3430, 3439],
mong: [6160, 6169],
mymr: [4160, 4169],
orya: [2918, 2927],
tamldec: [3046, 3055],
telu: [3174, 3183],
thai: [3664, 3673],
tibt: [3872, 3881],
};
const hanidecChars = numberingSystems.hanidec.replace(/[\[|\]]/g, "").split("");
function parseDigits(str) {
let value = parseInt(str, 10);
if (isNaN(value)) {
value = "";
for (let i = 0; i < str.length; i++) {
const code = str.charCodeAt(i);
if (str[i].search(numberingSystems.hanidec) !== -1) {
value += hanidecChars.indexOf(str[i]);
} else {
for (const key in numberingSystemsUTF16) {
const [min, max] = numberingSystemsUTF16[key];
if (code >= min && code <= max) {
value += code - min;
}
}
}
}
return parseInt(value, 10);
} else {
return value;
}
}
// cache of {numberingSystem: {append: regex}}
let digitRegexCache = {};
function resetDigitRegexCache() {
digitRegexCache = {};
}
function digitRegex({ numberingSystem }, append = "") {
const ns = numberingSystem || "latn";
if (!digitRegexCache[ns]) {
digitRegexCache[ns] = {};
}
if (!digitRegexCache[ns][append]) {
digitRegexCache[ns][append] = new RegExp(`${numberingSystems[ns]}${append}`);
}
return digitRegexCache[ns][append];
}
let now = () => Date.now(),
defaultZone = "system",
defaultLocale = null,
@@ -1412,7 +1603,7 @@ class Settings {
}
/**
* Get the cutoff year after which a string encoding a year as two digits is interpreted to occur in the current century.
* Get the cutoff year for whether a 2-digit year string is interpreted in the current or previous century. Numbers higher than the cutoff will be considered to mean 19xx and numbers lower or equal to the cutoff will be considered 20xx.
* @type {number}
*/
static get twoDigitCutoffYear() {
@@ -1420,10 +1611,11 @@ class Settings {
}
/**
* Set the cutoff year after which a string encoding a year as two digits is interpreted to occur in the current century.
* Set the cutoff year for whether a 2-digit year string is interpreted in the current or previous century. Numbers higher than the cutoff will be considered to mean 19xx and numbers lower or equal to the cutoff will be considered 20xx.
* @type {number}
* @example Settings.twoDigitCutoffYear = 0 // cut-off year is 0, so all 'yy' are interpreted as current century
* @example Settings.twoDigitCutoffYear = 50 // '49' -> 1949; '50' -> 2050
* @example Settings.twoDigitCutoffYear = 0 // all 'yy' are interpreted as 20th century
* @example Settings.twoDigitCutoffYear = 99 // all 'yy' are interpreted as 21st century
* @example Settings.twoDigitCutoffYear = 50 // '49' -> 2049; '50' -> 1950
* @example Settings.twoDigitCutoffYear = 1950 // interpreted as 50
* @example Settings.twoDigitCutoffYear = 2050 // ALSO interpreted as 50
*/
@@ -1454,6 +1646,8 @@ class Settings {
static resetCaches() {
Locale.resetCache();
IANAZone.resetCache();
DateTime.resetCache();
resetDigitRegexCache();
}
}
@@ -1951,6 +2145,13 @@ function normalizeObject(obj, normalizer) {
return normalized;
}
/**
* Returns the offset's value as a string
* @param {number} ts - Epoch milliseconds for which to get the offset
* @param {string} format - What style of offset to return.
* Accepts 'narrow', 'short', or 'techie'. Returning '+6', '+06:00', or '+0600' respectively
* @return {string}
*/
function formatOffset(offset, format) {
const hours = Math.trunc(Math.abs(offset / 60)),
minutes = Math.trunc(Math.abs(offset % 60)),
@@ -4235,7 +4436,7 @@ class Interval {
}
/**
* Return whether this Interval engulfs the start and end of the specified Interval.
* Returns true if this Interval fully contains the specified Interval, specifically if the intersect (of this Interval and the other Interval) is equal to the other Interval; false otherwise.
* @param {Interval} other
* @return {boolean}
*/
@@ -4778,82 +4979,6 @@ function diff (earlier, later, units, opts) {
}
}
const numberingSystems = {
arab: "[\u0660-\u0669]",
arabext: "[\u06F0-\u06F9]",
bali: "[\u1B50-\u1B59]",
beng: "[\u09E6-\u09EF]",
deva: "[\u0966-\u096F]",
fullwide: "[\uFF10-\uFF19]",
gujr: "[\u0AE6-\u0AEF]",
hanidec: "[|一|二|三|四|五|六|七|八|九]",
khmr: "[\u17E0-\u17E9]",
knda: "[\u0CE6-\u0CEF]",
laoo: "[\u0ED0-\u0ED9]",
limb: "[\u1946-\u194F]",
mlym: "[\u0D66-\u0D6F]",
mong: "[\u1810-\u1819]",
mymr: "[\u1040-\u1049]",
orya: "[\u0B66-\u0B6F]",
tamldec: "[\u0BE6-\u0BEF]",
telu: "[\u0C66-\u0C6F]",
thai: "[\u0E50-\u0E59]",
tibt: "[\u0F20-\u0F29]",
latn: "\\d",
};
const numberingSystemsUTF16 = {
arab: [1632, 1641],
arabext: [1776, 1785],
bali: [6992, 7001],
beng: [2534, 2543],
deva: [2406, 2415],
fullwide: [65296, 65303],
gujr: [2790, 2799],
khmr: [6112, 6121],
knda: [3302, 3311],
laoo: [3792, 3801],
limb: [6470, 6479],
mlym: [3430, 3439],
mong: [6160, 6169],
mymr: [4160, 4169],
orya: [2918, 2927],
tamldec: [3046, 3055],
telu: [3174, 3183],
thai: [3664, 3673],
tibt: [3872, 3881],
};
const hanidecChars = numberingSystems.hanidec.replace(/[\[|\]]/g, "").split("");
function parseDigits(str) {
let value = parseInt(str, 10);
if (isNaN(value)) {
value = "";
for (let i = 0; i < str.length; i++) {
const code = str.charCodeAt(i);
if (str[i].search(numberingSystems.hanidec) !== -1) {
value += hanidecChars.indexOf(str[i]);
} else {
for (const key in numberingSystemsUTF16) {
const [min, max] = numberingSystemsUTF16[key];
if (code >= min && code <= max) {
value += code - min;
}
}
}
}
return parseInt(value, 10);
} else {
return value;
}
}
function digitRegex({ numberingSystem }, append = "") {
return new RegExp(`${numberingSystems[numberingSystem || "latn"]}${append}`);
}
const MISSING_FTP = "missing Intl.DateTimeFormat.formatToParts support";
function intUnit(regex, post = (i) => i) {
@@ -5280,27 +5405,59 @@ function expandMacroTokens(tokens, locale) {
* @private
*/
function explainFromTokens(locale, input, format) {
const tokens = expandMacroTokens(Formatter.parseFormat(format), locale),
units = tokens.map((t) => unitForToken(t, locale)),
disqualifyingUnit = units.find((t) => t.invalidReason);
class TokenParser {
constructor(locale, format) {
this.locale = locale;
this.format = format;
this.tokens = expandMacroTokens(Formatter.parseFormat(format), locale);
this.units = this.tokens.map((t) => unitForToken(t, locale));
this.disqualifyingUnit = this.units.find((t) => t.invalidReason);
if (disqualifyingUnit) {
return { input, tokens, invalidReason: disqualifyingUnit.invalidReason };
} else {
const [regexString, handlers] = buildRegex(units),
regex = RegExp(regexString, "i"),
[rawMatches, matches] = match(input, regex, handlers),
[result, zone, specificOffset] = matches
? dateTimeFromMatches(matches)
: [null, null, undefined];
if (hasOwnProperty(matches, "a") && hasOwnProperty(matches, "H")) {
throw new ConflictingSpecificationError(
"Can't include meridiem when specifying 24-hour format"
);
if (!this.disqualifyingUnit) {
const [regexString, handlers] = buildRegex(this.units);
this.regex = RegExp(regexString, "i");
this.handlers = handlers;
}
return { input, tokens, regex, rawMatches, matches, result, zone, specificOffset };
}
explainFromTokens(input) {
if (!this.isValid) {
return { input, tokens: this.tokens, invalidReason: this.invalidReason };
} else {
const [rawMatches, matches] = match(input, this.regex, this.handlers),
[result, zone, specificOffset] = matches
? dateTimeFromMatches(matches)
: [null, null, undefined];
if (hasOwnProperty(matches, "a") && hasOwnProperty(matches, "H")) {
throw new ConflictingSpecificationError(
"Can't include meridiem when specifying 24-hour format"
);
}
return {
input,
tokens: this.tokens,
regex: this.regex,
rawMatches,
matches,
result,
zone,
specificOffset,
};
}
}
get isValid() {
return !this.disqualifyingUnit;
}
get invalidReason() {
return this.disqualifyingUnit ? this.disqualifyingUnit.invalidReason : null;
}
}
function explainFromTokens(locale, input, format) {
const parser = new TokenParser(locale, format);
return parser.explainFromTokens(input);
}
function parseFromTokens(locale, input, format) {
@@ -5639,13 +5796,46 @@ function normalizeUnitWithLocalWeeks(unit) {
}
}
// cache offsets for zones based on the current timestamp when this function is
// first called. When we are handling a datetime from components like (year,
// month, day, hour) in a time zone, we need a guess about what the timezone
// offset is so that we can convert into a UTC timestamp. One way is to find the
// offset of now in the zone. The actual date may have a different offset (for
// example, if we handle a date in June while we're in December in a zone that
// observes DST), but we can check and adjust that.
//
// When handling many dates, calculating the offset for now every time is
// expensive. It's just a guess, so we can cache the offset to use even if we
// are right on a time change boundary (we'll just correct in the other
// direction). Using a timestamp from first read is a slight optimization for
// handling dates close to the current date, since those dates will usually be
// in the same offset (we could set the timestamp statically, instead). We use a
// single timestamp for all zones to make things a bit more predictable.
//
// This is safe for quickDT (used by local() and utc()) because we don't fill in
// higher-order units from tsNow (as we do in fromObject, this requires that
// offset is calculated from tsNow).
function guessOffsetForZone(zone) {
if (!zoneOffsetGuessCache[zone]) {
if (zoneOffsetTs === undefined) {
zoneOffsetTs = Settings.now();
}
zoneOffsetGuessCache[zone] = zone.offset(zoneOffsetTs);
}
return zoneOffsetGuessCache[zone];
}
// this is a dumbed down version of fromObject() that runs about 60% faster
// but doesn't do any validation, makes a bunch of assumptions about what units
// are present, and so on.
function quickDT(obj, opts) {
const zone = normalizeZone(opts.zone, Settings.defaultZone),
loc = Locale.fromObject(opts),
tsNow = Settings.now();
const zone = normalizeZone(opts.zone, Settings.defaultZone);
if (!zone.isValid) {
return DateTime.invalid(unsupportedZone(zone));
}
const loc = Locale.fromObject(opts);
let ts, o;
@@ -5662,10 +5852,10 @@ function quickDT(obj, opts) {
return DateTime.invalid(invalid);
}
const offsetProvis = zone.offset(tsNow);
const offsetProvis = guessOffsetForZone(zone);
[ts, o] = objToTS(obj, offsetProvis, zone);
} else {
ts = tsNow;
ts = Settings.now();
}
return new DateTime({ ts, zone, loc, o });
@@ -5713,6 +5903,18 @@ function lastOpts(argList) {
return [opts, args];
}
/**
* Timestamp to use for cached zone offset guesses (exposed for test)
*/
let zoneOffsetTs;
/**
* Cache for zone offset guesses (exposed for test).
*
* This optimizes quickDT via guessOffsetForZone to avoid repeated calls of
* zone.offset().
*/
let zoneOffsetGuessCache = {};
/**
* A DateTime is an immutable data structure representing a specific date and time and accompanying methods. It contains class and instance methods for creating, parsing, interrogating, transforming, and formatting them.
*
@@ -5757,7 +5959,9 @@ class DateTime {
if (unchanged) {
[c, o] = [config.old.c, config.old.o];
} else {
const ot = zone.offset(this.ts);
// If an offset has been passed and we have not been called from
// clone(), we can trust it and avoid the offset calculation.
const ot = isNumber(config.o) && !config.old ? config.o : zone.offset(this.ts);
c = tsToObj(this.ts, ot);
invalid = Number.isNaN(c.year) ? new Invalid("invalid input") : null;
c = invalid ? null : c;
@@ -5852,6 +6056,7 @@ class DateTime {
* @param {string} [options.locale] - a locale to set on the resulting DateTime instance
* @param {string} [options.outputCalendar] - the output calendar to set on the resulting DateTime instance
* @param {string} [options.numberingSystem] - the numbering system to set on the resulting DateTime instance
* @param {string} [options.weekSettings] - the week settings to set on the resulting DateTime instance
* @example DateTime.utc() //~> now
* @example DateTime.utc(2017) //~> 2017-01-01T00:00:00Z
* @example DateTime.utc(2017, 3) //~> 2017-03-01T00:00:00Z
@@ -5904,6 +6109,7 @@ class DateTime {
* @param {string} [options.locale] - a locale to set on the resulting DateTime instance
* @param {string} options.outputCalendar - the output calendar to set on the resulting DateTime instance
* @param {string} options.numberingSystem - the numbering system to set on the resulting DateTime instance
* @param {string} options.weekSettings - the week settings to set on the resulting DateTime instance
* @return {DateTime}
*/
static fromMillis(milliseconds, options = {}) {
@@ -5912,7 +6118,7 @@ class DateTime {
`fromMillis requires a numerical input, but received a ${typeof milliseconds} with value ${milliseconds}`
);
} else if (milliseconds < -MAX_DATE || milliseconds > MAX_DATE) {
// this isn't perfect because because we can still end up out of range because of additional shifting, but it's a start
// this isn't perfect because we can still end up out of range because of additional shifting, but it's a start
return DateTime.invalid("Timestamp out of range");
} else {
return new DateTime({
@@ -5931,6 +6137,7 @@ class DateTime {
* @param {string} [options.locale] - a locale to set on the resulting DateTime instance
* @param {string} options.outputCalendar - the output calendar to set on the resulting DateTime instance
* @param {string} options.numberingSystem - the numbering system to set on the resulting DateTime instance
* @param {string} options.weekSettings - the week settings to set on the resulting DateTime instance
* @return {DateTime}
*/
static fromSeconds(seconds, options = {}) {
@@ -5967,6 +6174,7 @@ class DateTime {
* @param {string} [opts.locale='system\'s locale'] - a locale to set on the resulting DateTime instance
* @param {string} opts.outputCalendar - the output calendar to set on the resulting DateTime instance
* @param {string} opts.numberingSystem - the numbering system to set on the resulting DateTime instance
* @param {string} opts.weekSettings - the week settings to set on the resulting DateTime instance
* @example DateTime.fromObject({ year: 1982, month: 5, day: 25}).toISODate() //=> '1982-05-25'
* @example DateTime.fromObject({ year: 1982 }).toISODate() //=> '1982-01-01'
* @example DateTime.fromObject({ hour: 10, minute: 26, second: 6 }) //~> today at 10:26:06
@@ -6080,6 +6288,10 @@ class DateTime {
);
}
if (!inst.isValid) {
return DateTime.invalid(inst.invalid);
}
return inst;
}
@@ -6092,6 +6304,7 @@ class DateTime {
* @param {string} [opts.locale='system's locale'] - a locale to set on the resulting DateTime instance
* @param {string} [opts.outputCalendar] - the output calendar to set on the resulting DateTime instance
* @param {string} [opts.numberingSystem] - the numbering system to set on the resulting DateTime instance
* @param {string} [opts.weekSettings] - the week settings to set on the resulting DateTime instance
* @example DateTime.fromISO('2016-05-25T09:08:34.123')
* @example DateTime.fromISO('2016-05-25T09:08:34.123+06:00')
* @example DateTime.fromISO('2016-05-25T09:08:34.123+06:00', {setZone: true})
@@ -6113,6 +6326,7 @@ class DateTime {
* @param {string} [opts.locale='system's locale'] - a locale to set on the resulting DateTime instance
* @param {string} opts.outputCalendar - the output calendar to set on the resulting DateTime instance
* @param {string} opts.numberingSystem - the numbering system to set on the resulting DateTime instance
* @param {string} opts.weekSettings - the week settings to set on the resulting DateTime instance
* @example DateTime.fromRFC2822('25 Nov 2016 13:23:12 GMT')
* @example DateTime.fromRFC2822('Fri, 25 Nov 2016 13:23:12 +0600')
* @example DateTime.fromRFC2822('25 Nov 2016 13:23 Z')
@@ -6133,6 +6347,7 @@ class DateTime {
* @param {string} [opts.locale='system's locale'] - a locale to set on the resulting DateTime instance
* @param {string} opts.outputCalendar - the output calendar to set on the resulting DateTime instance
* @param {string} opts.numberingSystem - the numbering system to set on the resulting DateTime instance
* @param {string} opts.weekSettings - the week settings to set on the resulting DateTime instance
* @example DateTime.fromHTTP('Sun, 06 Nov 1994 08:49:37 GMT')
* @example DateTime.fromHTTP('Sunday, 06-Nov-94 08:49:37 GMT')
* @example DateTime.fromHTTP('Sun Nov 6 08:49:37 1994')
@@ -6153,6 +6368,7 @@ class DateTime {
* @param {boolean} [opts.setZone=false] - override the zone with a zone specified in the string itself, if it specifies one
* @param {string} [opts.locale='en-US'] - a locale string to use when parsing. Will also set the DateTime to this locale
* @param {string} opts.numberingSystem - the numbering system to use when parsing. Will also set the resulting DateTime to this numbering system
* @param {string} opts.weekSettings - the week settings to set on the resulting DateTime instance
* @param {string} opts.outputCalendar - the output calendar to set on the resulting DateTime instance
* @return {DateTime}
*/
@@ -6191,6 +6407,7 @@ class DateTime {
* @param {boolean} [opts.setZone=false] - override the zone with a zone specified in the string itself, if it specifies one
* @param {string} [opts.locale='en-US'] - a locale string to use when parsing. Will also set the DateTime to this locale
* @param {string} opts.numberingSystem - the numbering system to use when parsing. Will also set the resulting DateTime to this numbering system
* @param {string} opts.weekSettings - the week settings to set on the resulting DateTime instance
* @param {string} opts.outputCalendar - the output calendar to set on the resulting DateTime instance
* @example DateTime.fromSQL('2017-05-15')
* @example DateTime.fromSQL('2017-05-15 09:12:34')
@@ -6259,6 +6476,11 @@ class DateTime {
return expanded.map((t) => t.val).join("");
}
static resetCache() {
zoneOffsetTs = undefined;
zoneOffsetGuessCache = {};
}
// INFO
/**
@@ -7493,6 +7715,74 @@ class DateTime {
return DateTime.fromFormatExplain(text, fmt, options);
}
/**
* Build a parser for `fmt` using the given locale. This parser can be passed
* to {@link DateTime.fromFormatParser} to a parse a date in this format. This
* can be used to optimize cases where many dates need to be parsed in a
* specific format.
*
* @param {String} fmt - the format the string is expected to be in (see
* description)
* @param {Object} options - options used to set locale and numberingSystem
* for parser
* @returns {TokenParser} - opaque object to be used
*/
static buildFormatParser(fmt, options = {}) {
const { locale = null, numberingSystem = null } = options,
localeToUse = Locale.fromOpts({
locale,
numberingSystem,
defaultToEN: true,
});
return new TokenParser(localeToUse, fmt);
}
/**
* Create a DateTime from an input string and format parser.
*
* The format parser must have been created with the same locale as this call.
*
* @param {String} text - the string to parse
* @param {TokenParser} formatParser - parser from {@link DateTime.buildFormatParser}
* @param {Object} opts - options taken by fromFormat()
* @returns {DateTime}
*/
static fromFormatParser(text, formatParser, opts = {}) {
if (isUndefined(text) || isUndefined(formatParser)) {
throw new InvalidArgumentError(
"fromFormatParser requires an input string and a format parser"
);
}
const { locale = null, numberingSystem = null } = opts,
localeToUse = Locale.fromOpts({
locale,
numberingSystem,
defaultToEN: true,
});
if (!localeToUse.equals(formatParser.locale)) {
throw new InvalidArgumentError(
`fromFormatParser called with a locale of ${localeToUse}, ` +
`but the format parser was created for ${formatParser.locale}`
);
}
const { result, zone, specificOffset, invalidReason } = formatParser.explainFromTokens(text);
if (invalidReason) {
return DateTime.invalid(invalidReason);
} else {
return parseDataToDateTime(
result,
zone,
opts,
`format ${formatParser.format}`,
text,
specificOffset
);
}
}
// FORMAT PRESETS
/**
@@ -7689,7 +7979,7 @@ function friendlyDateTime(dateTimeish) {
}
}
const VERSION = "3.4.4";
const VERSION = "3.5.0";
export { DateTime, Duration, FixedOffsetZone, IANAZone, Info, Interval, InvalidZone, Settings, SystemZone, VERSION, Zone };
//# sourceMappingURL=luxon.js.map