-
Notifications
You must be signed in to change notification settings - Fork 0
/
Weather.js
185 lines (158 loc) · 5.82 KB
/
Weather.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
/**
* A JavaScript API for Environment Canada's Weather Data
* @module
*/
const { convert, normalize, simplify, restructure } = require("./parse");
const ForecastArray = require('./ForecastArray');
/**
* @classdesc A Weather instance has a number of methods for extracting specific types of data
* from the raw XML weather data fetched from Environment Canada.
* Thus, the XML data must first be fetched and then passed to the constructor.
*/
class Weather {
/**
* Parses and stores the XML data for use by the methods.
* Also, keeps a copy of the original data passed to it.
* @param {string} fetchedXMLWeatherData Raw XML weather data from Environment Canada.
*/
constructor(fetchedXMLWeatherData) {
if (!fetchedXMLWeatherData) throw new TypeError('Constructor must be passed weather data from Environment Canada.');
if (typeof fetchedXMLWeatherData !== 'string') throw new TypeError('Argument must be a string.');
this._originalXML = fetchedXMLWeatherData;
this._data = [
this._originalXML,
convert,
normalize,
simplify,
restructure
].reduce((previousResult, currentFunction) => currentFunction(previousResult));
}
/**
* @returns {Object} All weather data restructured into a javascript object.
*/
get all() {
return this._data;
}
/**
* @returns {string} Original XML weather data.
*/
get raw() {
return this._originalXML;
}
/**
* @returns {Object} All current conditions.
*/
get current() {
return this._data?.currentConditions;
}
/**
* @returns {<ForecastArray>} An array of the weekly forecast
*/
get weekly() {
if (!this._data?.forecast) return new ForecastArray();
return Array.from(this._data.forecast).reduce((accumulator, [key, value]) => {
const isDay = Number.isNaN(Number(key));
if (isDay) accumulator.push({ day: key, ...value });
return accumulator;
}, new ForecastArray());
}
/**
* @returns {<ForecastArray>} An array of the hourly forecast
*/
get hourly() {
if (!this._data?.forecast) return new ForecastArray();
return Array.from(this._data.forecast).reduce((accumulator, [key, value]) => {
const isHour = !Number.isNaN(Number(key));
if (isHour) accumulator.push({ hour: key, ...value });
return accumulator;
}, new ForecastArray());
}
/**
* @returns {<Date>} The date of the current forecast
*/
get date() {
const dateTime = this._data?.currentConditions?.dateTime?.[0] || {};
return new Date(Date.UTC(
Number(dateTime.year),
Number(dateTime.month?.value) - 1,
Number(dateTime.day?.value),
Number(dateTime.hour),
Number(dateTime.minute)
));
}
/**
* Retrieves all forecast data for a given date.
* The method accepts the name of a weekday (as a string),
* a UTC timestamp in the Environment Canada format (as a string),
* or a Date instance.
* Passing a UTC Date instance is recommended.
* The method will return undefined if no forecast is found.
* @param {string|<Date>} date The date to forecast
* @returns {Object}
*/
forecast(date) {
const isStringType = typeof date === 'string';
const isDateType = date instanceof Date;
if (!isStringType && !isDateType) throw new TypeError('Argument must be a string or Date instance');
const forecastData = this._data.forecast;
if (isStringType) return forecastData.get(date.toLowerCase());
// isDateType = true
const utcTimestamp = this._makeUTCTimestamp(date);
if (forecastData.has(utcTimestamp)) return forecastData.get(utcTimestamp);
// Since the UTC timestamp cannot be found in the forecast data,
// let's see if we can find the equivalent weekday.
// Is the date within the forecast range?
const startTime = this.date;
const endTime = new Date(startTime.getTime() + (1000 * 60 * 60 * 24 * 7));
const inRange = date.getTime() >= startTime.getTime() && date.getTime() <= endTime.getTime();
if (!inRange) return;
// Determine the local date.
const UTCOffset = Number(this._data.currentConditions.dateTime[1].UTCOffset);
const localDateValue = date.getTime() + (1000 * 60 * 60 * UTCOffset);
const localDate = new Date(localDateValue);
// Get the hour.
const localDateHour = localDate.getUTCHours();
// Get the name of the day of the week,
// and if the hour is at night, add 'night'.
const weekday = (localDateHour >= 6 && localDateHour < 18) ? this._getWeekDay(localDate) : this._getWeekDay(localDate) + ' night';
// Retrieve the forecast for that day of the week.
return forecastData.get(weekday);
}
/**
* Helper method.
* Turns a Date instance into a UTC Timestamp compatible with Environment Canada's weather data.
* @param {<Date>} date The date to forecast.
* @returns {string} The UTC Timestamp.
*/
_makeUTCTimestamp(date) {
const year = date.getUTCFullYear().toString();
let month = date.getUTCMonth() + 1;
month = month.toString();
if (month.length < 2) month = '0' + month;
let day = date.getUTCDate().toString();
if (day.length < 2) day = '0' + day;
let hour = date.getUTCHours().toString();
if (hour.length < 2) hour = '0' + hour;
return year + month + day + hour + '00';
}
/**
* Helper method.
* Expands the abbreviated weekday text returned from a Date object.
* @param {<Date>} date The localized date to forecast.
* @returns {string} Full weekday name.
*/
_getWeekDay(date) {
const partial = date.toUTCString().slice(0, 3);
const days = new Map([
[ 'sun', 'sunday' ],
[ 'mon', 'monday' ],
[ 'tue', 'tuesday' ],
[ 'wed', 'wednesday' ],
[ 'thu', 'thursday' ],
[ 'fri', 'friday' ],
[ 'sat', 'saturday' ]
]);
return days.get(partial.toLowerCase());
}
}
module.exports = Weather;