The rampart-almanac module

Preface

Acknowledgment

The rampart-almanac module uses astronomy.c library and the date-holidays JavaScript module The authors of Rampart extend our thanks to authors of these libraries.

License

The astronomy.c library is licensed under the MIT license. The date-holidays Javascript module is licensed under an ISC license and includes other licenses therein.

What does it do?

The rampart-astronomy module calculates Sun, Moon and Planet rise, set and positions given a date and time, and latitude and longitude, and seasons given a year. It also includes rampart-date-holidays.js which calculates holidays for a particular locale and year.

Loading and Using the Module

Loading

Loading the module is a simple matter of using the require() function:

var almanac = require("rampart-almanac");

Celestial Positions

almanac.suntimes()

Calculate sun position times for a given day, longitude and latitude.

Usage:

var almanac = require("rampart-almanac");

var times = almanac.suntimes(date, latitude, longitude);

Where:

  • date is a JavaScript Date or a String (which is converted using autoScanDate).
  • latitude is a Number (negative for south).
  • longitude is a Number (negative for west).

Return Value: An Object with the relevant information.

Example:

var almanac = require("rampart-almanac");

var times = almanac.suntimes("2024-01-01 -0700", 37.77, -122.42);
/* times =
   {
       "daylightHours": 9.605555555555556,
       "civilTwilightHours": 10.580555555555556,
       "nauticalTwilightHours": 11.674444444444445,
       "astronomicalTwilightHours": 12.7375,
       "solarNoon": "2024-01-01T20:13:08.000Z",
       "sunrise": "2024-01-01T15:25:03.000Z",
       "sunset": "2024-01-02T01:01:23.000Z",
       "civilTwilightStart": "2024-01-01T14:55:47.000Z",
       "civilTwilightEnd": "2024-01-02T01:30:37.000Z",
       "nauticalTwilightStart": "2024-01-01T14:22:59.000Z",
       "nauticalTwilightEnd": "2024-01-02T02:03:27.000Z",
       "astronomicalTwilightStart": "2024-01-01T13:51:05.000Z",
       "astronomicalTwilightEnd": "2024-01-02T02:35:18.000Z",
       "sunriseAzimuth": 118.89538324193013,
       "sunsetAzimuth": 241.1489255497421
   }
*/
/* get sunrise as a local time by using '%z' */
var sunrise = rampart.utils.dateFmt("%c %z\n", times.sunrise);
/* sunrise = "Mon Jan  1 07:25:03 2024 -0800" */

almanac.moontimes()

Calculate the next moon rise and set times from the given date/time, for a given longitude and latitude. Also return dates of the next moon phases.

Usage:

var almanac = require("rampart-almanac");

var times = almanac.moontimes(date, latitude, longitude);

Where:

  • date is a JavaScript Date or a String (which is converted using autoScanDate). The next moonrise and moonset occurring after this date/time are returned. To get times for a specific day, pass local midnight (e.g. "2024-01-01 00:00:00 -0700").
  • latitude is a Number (negative for south).
  • longitude is a Number (negative for west).

Return Value: An Object with the relevant information.

Example:

var almanac = require("rampart-almanac");

var times = almanac.moontimes("2024-01-01 00:00:00 -0700", 37.77, -122.42);
/*  times =
    {
       "moonrise": "2024-01-02T06:37:25.000Z",
       "moonset": "2024-01-01T19:02:43.000Z",
       "newMoon": "2024-01-11T11:57:55.000Z",
       "firstQuarter": "2024-01-18T03:53:10.000Z",
       "fullMoon": "2024-01-25T17:54:42.000Z",
       "lastQuarter": "2024-01-04T03:31:09.000Z",
       "moonriseAzimuth": 82.64960233180054,
       "moonsetAzimuth": 280.67746349893633,
       "moonPhase": 0.6641758156156009,
       "moonIllumination": 0.7567459628710032
    }
*/
/* Note: moonset may be before moonrise if the moon is
   already up at the given time. */

almanac.celestials()

Calculate the next rise and set times for the sun, moon and planets from the given date/time, for a given longitude and latitude. Also return current position data (right ascension, declination, azimuth and altitude).

Usage:

var almanac = require("rampart-almanac");

var times = almanac.celestials(date, latitude, longitude);

Where:

  • date is a JavaScript Date or a String (which is converted using autoScanDate).
  • latitude is a Number (negative for south).
  • longitude is a Number (negative for west).

Return Value: An Object with the relevant information.

Example:

var almanac = require("rampart-almanac");

var times = almanac.celestials("2024-01-01", 37.77, -122.42);
/*  times =
    {
       "sun": {
          "currentRightAscension": 18.70381774294387,
          "currentDeclination": -23.082972736894366,
          "currentAzimuth": 231.2447789386162,
          "currentAltitude": 9.200955835755252,
          "nextRise": "2024-01-01T15:25:03.000Z",
          "nextSet": "2024-01-02T01:01:23.000Z"
       },
       "moon": {
          "currentRightAscension": 10.585655630489418,
          "currentDeclination": 12.06431630189065,
          "currentAzimuth": 358.2427660196457,
          "currentAltitude": -39.911128386544675,
          "nextRise": "2024-01-01T05:39:21.000Z",
          "nextSet": "2024-01-01T19:02:42.000Z"
       },
       "mercury": {
          "currentRightAscension": 17.42857713438222,
          "currentDeclination": -20.134230004864982,
          "currentAzimuth": 245.65820797921364,
          "currentAltitude": -1.1285066854169088,
          "nextRise": "2024-01-01T13:56:35.000Z",
          "nextSet": "2024-01-01T23:48:43.000Z"
       },
       "venus": {
          "currentRightAscension": 16.04024593585745,
          "currentDeclination": -18.703696660954833,
          "currentAzimuth": 258.8170836590166,
          "currentAltitude": -16.054068589788287,
          "nextRise": "2024-01-01T12:31:52.000Z",
          "nextSet": "2024-01-01T22:35:04.000Z"
       },
       "mars": {
          "currentRightAscension": 17.77925369534947,
          "currentDeclination": -23.951999465047503,
          "currentAzimuth": 239.58535957726625,
          "currentAltitude": 0.013586371580359469,
          "nextRise": "2024-01-01T14:34:09.000Z",
          "nextSet": "2024-01-01T23:59:15.000Z"
       },
       "jupiter": {
          "currentRightAscension": 2.224206624688085,
          "currentDeclination": 12.150270854695547,
          "currentAzimuth": 101.66782757086347,
          "currentAltitude": 34.25711749414994,
          "nextRise": "2024-01-01T20:58:35.000Z",
          "nextSet": "2024-01-02T10:19:59.000Z"
       },
       "saturn": {
          "currentRightAscension": 22.363992244102082,
          "currentDeclination": -11.961562816580198,
          "currentAzimuth": 182.50892699669285,
          "currentAltitude": 40.37470253011248,
          "nextRise": "2024-01-01T18:23:44.000Z",
          "nextSet": "2024-01-02T05:13:29.000Z"
       },
       "uranus": {
          "currentRightAscension": 3.1122566235436486,
          "currentDeclination": 17.187443780343074,
          "currentAzimuth": 88.20667075806635,
          "currentAltitude": 26.70533930046701,
          "nextRise": "2024-01-01T21:34:37.000Z",
          "nextSet": "2024-01-02T11:30:03.000Z"
       },
       "neptune": {
          "currentRightAscension": 23.711146313700397,
          "currentDeclination": -3.221011807290628,
          "currentAzimuth": 153.3716388125294,
          "currentAltitude": 45.795412016162715,
          "nextRise": "2024-01-01T19:16:31.000Z",
          "nextSet": "2024-01-02T07:01:13.000Z"
       },
       "pluto": {
          "currentRightAscension": 20.118773598789403,
          "currentDeclination": -23.058689775023026,
          "currentAzimuth": 214.92988354299945,
          "currentAltitude": 20.68436892218172,
          "nextRise": "2024-01-01T16:48:25.000Z",
          "nextSet": "2024-01-02T02:19:56.000Z"
       }
    }
*/

almanac.seasons()

Calculate the start of seasons for a given year.

Usage:

var almanac = require("rampart-almanac");

var times = almanac.seasons(year);

Where:

  • date is a Number, the year to calculate.

Return Value: An Object with dates for each season.

Example:

var almanac = require("rampart-almanac");

var seasons = almanac.seasons(2025);
/*  seasons =
    {
       "spring": "2025-03-20T09:01:26.000Z",
       "summer": "2025-06-21T02:42:17.000Z",
       "autumn": "2025-09-22T18:19:33.000Z",
       "winter": "2025-12-21T15:03:03.000Z"
    }
*/
console.log( dateFmt("Happy Nowruz! %c %z", seasons.spring) );
    /* Happy Nowruz! Thu Mar 20 02:01:26 2025 -0700 */

Holidays

new almanac.Holiday()

Create a new Holidays object.

Usage:

var almanac = require("rampart-almanac");

var hd = new almanac.Holidays();

/* or */

var hd = new almanac.Holidays(countryCode [, LocaleCode [, LocaleCode]]);

hd.getHolidays(year);

More information, and this example (slightly modified) can be found here:

var almanac = require('rampart-almanac');
var Holidays = almanac.holidays;
var printf = rampart.utils.printf;

var hd = new Holidays();

// get supported countries
var res = hd.getCountries();
printf("%3J\n", res);
/*  res =
    {
       "AD": "Andorra",
       "AE": "دولة الإمارات العربية المتحدة",
       "AG": "Antigua & Barbuda",
        ...
       "ZM": "Zambia",
       "ZW": "Zimbabwe"
    }
*/

// get supported states e.g. for US
var res = hd.getStates('US');
printf("%3J\n", res);
/*  res =
    {
       AL: 'Alabama',
       ...
       WY: 'Wyoming'
    }
*/

// get supported regions e.g. for US, Louisiana
res = hd.getRegions('US', 'LA');
printf("%3J\n", res);
/*  res =
    {
       "NO": "New Orleans"
    }
*/

// initialize holidays for US, Louisiana, New Orleans
hd.init('US', 'LA', 'NO');

/* or using a new instance */
//hd = new Holidays('US', 'la', 'no');

// get all holidays for the year 2016
res = hd.getHolidays(2016);
printf("%3J\n", res);
/*  res =
    {
       "date": "2016-01-01 00:00:00",
       "start": "2016-01-01T06:00:00.000Z",
       "end": "2016-01-02T06:00:00.000Z",
       "name": "New Year's Day",
       "type": "public",
       "rule": "01-01 and if sunday then next monday if saturday then previous friday"
    },
    {
       "date": "2016-01-18 00:00:00",
       "start": "2016-01-18T06:00:00.000Z",
       "end": "2016-01-19T06:00:00.000Z",
       "name": "Martin Luther King Jr. Day",
       "type": "public",
       "rule": "3rd monday in January"
    },
    ...
    {
       "date": "2016-12-31 00:00:00",
       "start": "2016-12-31T06:00:00.000Z",
       "end": "2017-01-01T06:00:00.000Z",
       "name": "New Year's Eve",
       "type": "observance",
       "rule": "12-31"
    }
*/

// check if date is a holiday while respecting timezones
res = hd.isHoliday(new Date('2016-02-09 00:00:00'));
printf("%3J\n", res);
// res = false

res = hd.isHoliday(rampart.utils.autoScanDate('2016-02-09 00:00:00 -0600').date);
printf("%3J\n", res);
/*  res =
    [
       {
          "date": "2016-02-09 00:00:00",
          "start": "2016-02-09T06:00:00.000Z",
          "end": "2016-02-10T06:00:00.000Z",
          "name": "Mardi Gras",
          "type": "public"
       }
    ]
*/

Weather

The weather sub-module provides access to weather data from Open-Meteo, a free weather API for non-commercial use. Commercial use requires a paid API subscription. It is available as almanac.weather and includes geocoding, current conditions, forecasts, historical data, air quality and marine weather. Results are cached in an LMDB database to minimize API calls.

Weather data from Open-Meteo is licensed under CC BY 4.0, which requires attribution. Any application that displays Open-Meteo data should include an attribution notice such as:

Weather data by Open-Meteo.com (CC BY 4.0)

almanac.weather.configure()

Configure the weather module.

Usage:

var almanac = require("rampart-almanac");
var weather = almanac.weather;

weather.configure(options);

Where options is an Object which may contain any of the following:

  • lmdbCache - String or null. Path to the LMDB cache directory. If not set, defaults to /tmp/rampart-open-meteo-cache. If set to null, caching is disabled entirely.
  • units - String. Set to "imperial" for Fahrenheit, mph and inches, or "metric" for Celsius, km/h and mm.
  • temperature_unit - String. "celsius" (default) or "fahrenheit".
  • wind_speed_unit - String. "kmh" (default), "mph", "ms" or "kn".
  • precipitation_unit - String. "mm" (default) or "inch".
  • timezone - String. IANA timezone or "auto" (default).
  • maxTime - Number. Maximum seconds per HTTP request (default 10).
  • retries - Number. Number of retries on failure (default 2).

Example:

weather.configure({ units: 'imperial', lmdbCache: '/var/data/weather-cache' });

almanac.weather.current()

Get current weather conditions for a location.

Usage:

var res = weather.current(latitude, longitude [, options]);

/* async version */
weather.currentAsync(latitude, longitude [, options], callback);

Where:

  • latitude is a Number.

  • longitude is a Number.

  • options is an optional Object with:

    • variables - Array of Strings. Variables to request. Default:

      ['temperature_2m', 'relative_humidity_2m', 'apparent_temperature',
       'weather_code', 'wind_speed_10m', 'wind_direction_10m',
       'precipitation', 'cloud_cover', 'cloud_cover_low',
       'cloud_cover_mid', 'cloud_cover_high', 'surface_pressure']
      
    • Unit overrides (see almanac.weather.configure()).

Return Value: An Object containing current (with the requested variables), current_units and a weather_code_description String.

Results are cached for 15 minutes.

Example:

var res = weather.current(37.77, -122.42);
printf("Temperature: %s %s\n", res.current.temperature_2m, res.current_units.temperature_2m);
printf("Conditions: %s\n", res.current.weather_code_description);
/* Temperature: 18.5 °C */
/* Conditions: Partly cloudy */

almanac.weather.forecast()

Get weather forecast (current conditions, hourly and daily data).

Usage:

var res = weather.forecast(latitude, longitude [, options]);

/* async version */
weather.forecastAsync(latitude, longitude [, options], callback);

Where:

  • latitude is a Number.

  • longitude is a Number.

  • options is an optional Object with:

    • days - Number (default 7, max 16). Forecast days.

    • hourly - Array of Strings or false. Hourly variables to request. Set to false to omit hourly data. Default:

      ['temperature_2m', 'precipitation', 'relative_humidity_2m',
       'wind_speed_10m', 'cloud_cover', 'weather_code']
      
    • daily - Array of Strings or false. Daily variables to request. Set to false to omit daily data. Default:

      ['temperature_2m_max', 'temperature_2m_min', 'precipitation_sum',
       'sunrise', 'sunset', 'wind_speed_10m_max', 'weather_code']
      
    • current - Array of Strings or false. Current condition variables. Set to false to omit. Default:

      ['temperature_2m', 'relative_humidity_2m', 'apparent_temperature',
       'weather_code', 'wind_speed_10m', 'wind_direction_10m',
       'precipitation', 'cloud_cover', 'cloud_cover_low',
       'cloud_cover_mid', 'cloud_cover_high', 'surface_pressure']
      
    • past_days - Number (0-92). Include past days.

    • timezone - String. IANA timezone or "auto".

    • Unit overrides (see almanac.weather.configure()).

    The full list of available variables can be found in the Open-Meteo forecast API documentation.

Return Value: An Object containing current, hourly, daily, their corresponding *_units Objects, and weather_code_description Arrays for weather codes.

Results are cached for 1 hour.

Example:

var res = weather.forecast(37.77, -122.42, { days: 3 });
for (var i = 0; i < res.daily.time.length; i++) {
    printf("%s: %s/%s %s\n",
        res.daily.time[i],
        res.daily.temperature_2m_max[i],
        res.daily.temperature_2m_min[i],
        res.daily.weather_code_description[i]);
}

almanac.weather.history()

Get historical weather data (from 1940 to present).

Usage:

var res = weather.history(latitude, longitude, startDate, endDate [, options]);

/* async version */
weather.historyAsync(latitude, longitude, startDate, endDate [, options], callback);

Where:

  • latitude is a Number.
  • longitude is a Number.
  • startDate is a String (parsed by autoScanDate), a JavaScript Date, or a Number (epoch seconds).
  • endDate is a String (parsed by autoScanDate), a JavaScript Date, or a Number (epoch seconds).
  • options is an optional Object with hourly, daily, timezone and unit overrides.

Return Value: Same shape as almanac.weather.forecast().

Results are cached for 7 days.

Example:

var res = weather.history(37.77, -122.42, '2024-01-01', '2024-01-07');
printf("High on Jan 1: %s\n", res.daily.temperature_2m_max[0]);

almanac.weather.airQuality()

Get air quality data.

Usage:

var res = weather.airQuality(latitude, longitude [, options]);

/* async version */
weather.airQualityAsync(latitude, longitude [, options], callback);

Where:

  • latitude is a Number.

  • longitude is a Number.

  • options is an optional Object with:

    • hourly - Array of Strings. Default:

      ['pm10', 'pm2_5', 'us_aqi', 'european_aqi', 'uv_index']
      

      See the Open-Meteo air quality API documentation for all available variables.

    • forecast_days - Number.

    • past_days - Number.

Return Value: An Object containing hourly data with time, pollutant concentrations and air quality indices.

Results are cached for 1 hour.

Example:

var res = weather.airQuality(37.77, -122.42);
printf("PM2.5: %s\n", res.hourly.pm2_5[0]);
printf("US AQI: %s\n", res.hourly.us_aqi[0]);

almanac.weather.marine()

Get marine/ocean weather data.

Usage:

var res = weather.marine(latitude, longitude [, options]);

/* async version */
weather.marineAsync(latitude, longitude [, options], callback);

Where:

  • latitude is a Number.

  • longitude is a Number.

  • options is an optional Object with:

    • hourly - Array of Strings. Default:

      ['wave_height', 'wave_direction', 'wave_period',
       'sea_surface_temperature']
      
    • daily - Array of Strings. Default:

      ['wave_height_max', 'wave_direction_dominant', 'wave_period_max']
      
    • forecast_days - Number.

    • past_days - Number.

    See the Open-Meteo marine API documentation for all available variables.

Return Value: An Object containing hourly and daily data with the requested variables.

Results are cached for 1 hour.

Example:

var res = weather.marine(36.95, -122.02);
printf("Wave height: %s m\n", res.hourly.wave_height[0]);

almanac.weather.weatherAt()

Convenience function: geocode a place name and fetch the full forecast in a single call.

Usage:

var res = weather.weatherAt(placeName [, options]);

/* async version */
weather.weatherAtAsync(placeName [, options], callback);

Where:

  • placeName is a String. Supports "City", "City, CC" (two-letter country code) and common aliases like "UK" for "GB" and "USA" for "US".
  • options is an optional Object with the same options as almanac.weather.forecast().

Return Value: An Object containing location (with name, country, latitude, longitude, timezone), current, hourly, daily and their units.

Example:

var res = weather.weatherAt('London, UK');
printf("%s, %s: %s°C %s\n",
    res.location.name, res.location.country,
    res.current.temperature_2m,
    res.current.weather_code_description);

almanac.weather.clearCache()

Delete the LMDB cache database. The cache will be recreated automatically on the next API call.

Usage:

weather.clearCache();

almanac.weather.weatherCodes

An Object mapping WMO weather codes to human-readable descriptions.

printf("%s\n", weather.weatherCodes[0]);   /* "Clear sky" */
printf("%s\n", weather.weatherCodes[61]);  /* "Rain: slight" */
printf("%s\n", weather.weatherCodes[95]);  /* "Thunderstorm: slight or moderate" */

Async Callbacks

All async methods (searchAsync, currentAsync, forecastAsync, historyAsync, airQualityAsync, marineAsync, weatherAtAsync) take a callback as the last argument with the signature function(error, data).

weather.weatherAtAsync('Tokyo', function(err, data) {
    if (err) {
        printf("Error: %s\n", err);
        return;
    }
    printf("Temperature in %s: %s°C\n",
        data.location.name, data.current.temperature_2m);
});