nickginsberg / noaa-gfs-js

A lightweight library for pulling GFS (Global Forecasting System) weather data from NOAA, without any major 3rd party depencies.
MIT License
7 stars 0 forks source link

A year offset by minus one with the output #7

Open sasha-cher opened 3 days ago

sasha-cher commented 3 days ago

In Get_gfs_data function, passing argument forecast_date with date 20240915 (2024-09-15) returns incorrect year 2023

javascript:

const noaa_gfs = require("noaa-gfs-js");

noaa_gfs.get_gfs_data(
    '0p25', // Options are 0p25, 0p50, or 1p00
    "20240915", // YYYMMDD format date
    '00', // Every 6 hours. 00, 06, 12, or 18
    [40.5, 40.5], // Lat range
    [-74,-74], // Lon range
    5,  // Number of 8 hour  fwd increments to include in addition to the forecast time
    'rh2m', // The requested data item
    true // Whether or not to convert the times into dates or keep as NOAA formatted times (see below for more details)
).then((res) => console.log(res));

Result:

https://nomads.ncep.noaa.gov/dods/gfs_0p25/gfs20240915/gfs_0p25_00z.ascii?rh2m[0:5][522:522][1144:1144]
{
  array_format: [
    {
      time: '2023-09-16, 00:00:31',
      lat: 40.5,
      lon: -74,
      value: 79.200005
    },
    { time: '2023-09-16, 03:00:31', lat: 40.5, lon: -74, value: 80.1 },
    { time: '2023-09-16, 06:00:31', lat: 40.5, lon: -74, value: 80.1 },
    { time: '2023-09-16, 09:00:31', lat: 40.5, lon: -74, value: 79.4 },
    { time: '2023-09-16, 12:00:31', lat: 40.5, lon: -74, value: 84.4 },
    { time: '2023-09-16, 15:00:31', lat: 40.5, lon: -74, value: 79 }
  ],
  obj_format: {
    '2023-09-16, 00:00:31': { '40.5': [Object] },
    '2023-09-16, 03:00:31': { '40.5': [Object] },
    '2023-09-16, 06:00:31': { '40.5': [Object] },
    '2023-09-16, 09:00:31': { '40.5': [Object] },
    '2023-09-16, 12:00:31': { '40.5': [Object] },
    '2023-09-16, 15:00:31': { '40.5': [Object] }
  },
  times: [
    '2023-09-16, 00:00:31',
    '2023-09-16, 03:00:31',
    '2023-09-16, 06:00:31',
    '2023-09-16, 09:00:31',
    '2023-09-16, 12:00:31',
    '2023-09-16, 15:00:31'
  ],
  lats: [ 40.5 ],
  lons: [ -74 ],
  levs: [],
  url: 'https://nomads.ncep.noaa.gov/dods/gfs_0p25/gfs20240915/gfs_0p25_00z.ascii?rh2m[0:5][522:522][1144:1144]'
}

In the line of the noaa-gfs-js/index.js:17 (function noaa_time_to_utc_datetime(noaa_time)) -> result.setFullYear(0); As a result, it starts from 1 year BC. Possible correction: result.setUTCFullYear(1);

nickginsberg commented 3 days ago

Thanks @sasha-cher ! I'm trying to replicate, but I'm unable to--it seems to be working fine in my environment, and there are test cases which validate that the time/date conversions work as well. Is it possible there's something different about your environment? Screenshot of the ourput when I run the same code you posted is below for reference.

image

sasha-cher commented 1 day ago

The problem is that depending on the time zone set in the system, the results will be different. If UTC is greater than 0, the year-month-day will lag behind, and if UTC is less than 0, they will be ahead.

const mins_per_day = 60 * 24;

function noaa_time_to_utc_datetime(noaa_time) {
    /**
    * Handy function to convert times from the NOAA format
    * NOAA times are days since year 0 (not 1900, legit year 0)
    * So 738931.25 is Feb 14th, 2024, at 6:00 AM (738,931 full days and .25 days = 6 hours)
    * @param  {float} noaa_time The days since year 0 including fractions
    * @return {string} Returns a string representing the time
    */
    const result = new Date(Date.UTC(0, 0, 1, 0, 0, 0));
    result.setFullYear(0);
    // Note: Subtract 1 from noaa time in days because otherwise we double count Jan 1, 0000
    result.setDate(result.getDate() + Math.floor(noaa_time - 1));
    // Convert the fraction of a day into hours/minutes
    const day_frac = noaa_time - Math.floor(noaa_time);
    const minutes_elapsed_in_day = day_frac * mins_per_day;
    const hours_elapsed_in_day = Math.floor(minutes_elapsed_in_day / 60);
    const minutes_remainder = minutes_elapsed_in_day - hours_elapsed_in_day * 60;
    result.setHours(hours_elapsed_in_day);
    result.setMinutes(minutes_remainder);
    return result.toLocaleString();
}

And here's how we ran it and got different results by changing the system time zone settings. Example:

noaa_time_to_utc_datetime(739145.25)

Output: UTC-4:

'9/15/2024, 6:00:00 AM'

UTC-8:

"9/15/2024, 6:00:22 AM"

UTC+9:

"9/16/2023, 6:00:00 AM"

UTC+0:

"9/15/2024, 6:00:52 AM"

As a result, the program outputs different values depending on the time zone set in the systems.

The Python example outputs the correct results:

from datetime import datetime, timedelta

def calc_date(days_to_add):
    return datetime(1, 1, 1) + timedelta(days=days_to_add)

calc_date(739145.25).strftime("%Y-%m-%d %H:%M:%S")

Output:

'2024-09-17 06:00:00'

Possible fixes:

const mins_per_day = 60 * 24;

function noaa_time_to_utc_datetime(noaa_time) {
    /**
    * Handy function to convert times from the NOAA format
    * NOAA times are days since year 0 (not 1900, legit year 0)
    * So 738931.25 is Feb 14th, 2024, at 6:00 AM (738,931 full days and .25 days = 6 hours)
    * @param  {float} noaa_time The days since year 0 including fractions
    * @return {string} Returns a string representing the time
    */
    const result = new Date(Date.UTC(0, 0, 1, 0, 0, 0));
    result.setUTCFullYear(1);
    // Note: Subtract 1 from noaa time in days because otherwise we double count Jan 1, 0000
    result.setUTCDate(result.getUTCDate() + Math.floor(noaa_time));
    // Convert the fraction of a day into hours/minutes
    const day_frac = noaa_time - Math.floor(noaa_time);
    const minutes_elapsed_in_day = day_frac * mins_per_day;
    const hours_elapsed_in_day = Math.floor(minutes_elapsed_in_day / 60);
    const minutes_remainder = minutes_elapsed_in_day - hours_elapsed_in_day * 60;
    result.setUTCHours(hours_elapsed_in_day);
    result.setUTCMinutes(minutes_remainder);
    return result.toUTCString();
}