കൊല്ലവർഷം - JavaScript API (v1.3.7)
Show:

File: lib/calendar.js

/*
 * kollavarsham
 * http://kollavarsham.org
 *
 * Copyright (c) 2014-2018 The Kollavarsham Team
 * Licensed under the MIT license.
 */

/**
 * @module calendar
 */
import JulianDate from './dates/julianDate.js';
import MathHelper from './mathHelper.js';

// TODO: Refactor this out
let samkranti = { // eslint-disable-line no-underscore-dangle
  ahargana : true,
  Year     : true,
  Month    : true,
  Day      : true,
  Hour     : true,
  Min      : true
};

/**
 *
 *  **INTERNAL/PRIVATE**
 *
 * @class Calendar
 */
class Calendar {

  constructor(celestial) {
    this.celestial = celestial;
  }

  static get weekdays() {
    return {
      0 : {en : 'Monday', ml : 'തിങ്കൾ'},
      1 : {en : 'Tuesday', ml : 'ചൊവ്വ'},
      2 : {en : 'Wednesday', ml : 'ബുധൻ'},
      3 : {en : 'Thursday', ml : 'വ്യാഴം'},
      4 : {en : 'Friday', ml : 'വെള്ളി'},
      5 : {en : 'Saturday', ml : 'ശനി'},
      6 : {en : 'Sunday', ml : 'ഞായർ'}
    };
  }

  static get months() {
    return {
      January   : 0,
      February  : 1,
      March     : 2,
      April     : 3,
      May       : 4,
      June      : 5,
      July      : 6,
      August    : 7,
      September : 8,
      October   : 9,
      November  : 10,
      December  : 11
    };
  }

  static get naksatras() {
    return {
      0  : {saka : 'Asvini', enMalayalam : 'Ashwathi', mlMalayalam : 'അശ്വതി'},
      1  : {saka : 'Bharani', enMalayalam : 'Bharani', mlMalayalam : 'ഭരണി'},
      2  : {saka : 'Krttika', enMalayalam : 'Karthika', mlMalayalam : 'കാർത്തിക'},
      3  : {saka : 'Rohini', enMalayalam : 'Rohini', mlMalayalam : 'രോഹിണി'},
      4  : {saka : 'Mrgasira', enMalayalam : 'Makiryam', mlMalayalam : 'മകയിരം'},
      5  : {saka : 'Ardra', enMalayalam : 'Thiruvathira', mlMalayalam : 'തിരുവാതിര'},
      6  : {saka : 'Punarvasu', enMalayalam : 'Punartham', mlMalayalam : 'പുണർതം'},
      7  : {saka : 'Pusya', enMalayalam : 'Pooyam', mlMalayalam : 'പൂയം'},
      8  : {saka : 'Aslesa', enMalayalam : 'Aayilyam', mlMalayalam : 'ആയില്യം'},
      9  : {saka : 'Magha', enMalayalam : 'Makam', mlMalayalam : 'മകം'},
      10 : {saka : 'P-phalguni', enMalayalam : 'Pooram', mlMalayalam : 'പൂരം'},
      11 : {saka : 'U-phalguni', enMalayalam : 'Uthram', mlMalayalam : 'ഉത്രം'},
      12 : {saka : 'Hasta', enMalayalam : 'Atham', mlMalayalam : 'അത്തം'},
      13 : {saka : 'Citra', enMalayalam : 'Chithra', mlMalayalam : 'ചിത്ര'},
      14 : {saka : 'Svati', enMalayalam : 'Chothi', mlMalayalam : 'ചോതി'},
      15 : {saka : 'Visakha', enMalayalam : 'Vishakham', mlMalayalam : 'വിശാഖം'},
      16 : {saka : 'Anuradha', enMalayalam : 'Anizham', mlMalayalam : 'അനിഴം'},
      17 : {saka : 'Jyestha', enMalayalam : 'Thrikketta', mlMalayalam : 'തൃക്കേട്ട'},
      18 : {saka : 'Mula', enMalayalam : 'Moolam', mlMalayalam : 'മൂലം'},
      19 : {saka : 'P-asadha', enMalayalam : 'Pooradam', mlMalayalam : 'പൂരാടം'},
      20 : {saka : 'U-asadha', enMalayalam : 'Uthradam', mlMalayalam : 'ഉത്രാടം'},
      21 : {saka : 'Sravana', enMalayalam : 'Thiruvonam', mlMalayalam : 'തിരുവോണം'},
      22 : {saka : 'Dhanistha', enMalayalam : 'Avittam', mlMalayalam : 'അവിട്ടം'},
      23 : {saka : 'Satabhisaj', enMalayalam : 'Chathayam', mlMalayalam : 'ചതയം'},
      24 : {saka : 'P-bhadrapada', enMalayalam : 'Poororuttathi', mlMalayalam : 'പൂരുരുട്ടാതി'},
      25 : {saka : 'U-bhadrapada', enMalayalam : 'Uthrattathi', mlMalayalam : 'ഉത്രട്ടാതി'},
      26 : {saka : 'Revati', enMalayalam : 'Revathi', mlMalayalam : 'രേവതി'},
      27 : {saka : 'Asvini', enMalayalam : 'Ashwathi', mlMalayalam : 'അശ്വതി'}
    };
  }

  static timeIntoFractionalDay(date) {
    // TODO: Incorporate this into calculating the multiple-naksatra-per-day (time precision)
    // The year, month and day from the passed in date is discarded and only the time is used.
    // And even from the time information only the hour and minute is used and seconds, milliseconds etc. is discarded
    if (!(date instanceof Date)) {
      throw new Error('Invalid parameter. \'date\' should be an instance of \'Date\'');
    }
    const hour = date.getHours();
    const minute = date.getMinutes();
    return (hour * 60 + minute) / (24 * 60);
  }

  static gregorianDateToJulianDay(date) {
    //  TODO:
    // Annotate all the magic numbers below !
    // There is some explanation here - http://quasar.as.utexas.edu/BillInfo/JulianDatesG.html

    let year = date.getFullYear();
    let month = date.getMonth() + 1;
    let day = date.getDate();

    if (month < 3) {
      year -= 1;
      month += 12;
    }

    let julianDay = MathHelper.truncate(365.25 * year) + MathHelper.truncate(30.59 * (month - 2)) + day + 1721086.5;

    if (year < 0) {
      julianDay -= 1;
      if (year % 4 === 0 && month > 3) {
        julianDay += 1;
      }
    }

    if (julianDay >= 2299160) {
      julianDay += MathHelper.truncate(year / 400) - MathHelper.truncate(year / 100) + 2;
    }

    return julianDay;
  }

  static julianDayToJulianDate(julianDay) {
    const j = MathHelper.truncate(julianDay) + 1402;
    const k = MathHelper.truncate((j - 1) / 1461);
    const l = j - 1461 * k;
    const n = MathHelper.truncate((l - 1) / 365) - MathHelper.truncate(l / 1461);
    const i = l - 365 * n + 30;
    const J = MathHelper.truncate(80 * i / 2447);
    const I = MathHelper.truncate(J / 11);

    const day = i - MathHelper.truncate(2447 * J / 80);
    const month = J + 2 - 12 * I;
    const year = 4 * k + n + I - 4716;

    return new JulianDate(year, month, day);
  }

  static julianDayToGregorianDate(julianDay) {
    const a = julianDay + 68569;
    const b = MathHelper.truncate(a / 36524.25);
    const c = a - MathHelper.truncate(36524.25 * b + 0.75);
    const e = MathHelper.truncate((c + 1) / 365.2425);
    const f = c - MathHelper.truncate(365.25 * e) + 31;
    const g = MathHelper.truncate(f / 30.59);
    const h = MathHelper.truncate(g / 11);

    const day = MathHelper.truncate(f - MathHelper.truncate(30.59 * g) + (julianDay - MathHelper.truncate(julianDay)));
    const month = MathHelper.truncate(g - 12 * h + 2);
    const year = MathHelper.truncate(100 * (b - 49) + e + h);

    const result = new Date(year, month - 1, day);
    if (year > 0 && year <= 99) {
      result.setFullYear(year);
    }
    return result;
  }

  static julianDayToModernDate(julianDay) {
    // Will return JulianDate object for any date before 1st January 1583 AD and Date objects for later dates
    return julianDay < 2299239 ? Calendar.julianDayToJulianDate(julianDay) : Calendar.julianDayToGregorianDate(julianDay);
  }

  static julianDayToAhargana(julianDay) {
    return julianDay - 588465.50;
  }

  static aharganaToJulianDay(ahargana) {
    return 588465.50 + ahargana;
  }

  static kaliToSaka(yearKali) {
    return yearKali - 3179;
  }

  static sakaToKali(yearSaka) {
    return yearSaka + 3179;
  }

  static julianDayToWeekday(julianDay) {
    return Calendar.weekdays[MathHelper.truncate(julianDay + 0.5) % 7];
  }

  static getAdhimasa(lastConjunctionLongitude, nextConjunctionLongitude) {
    let n1 = MathHelper.truncate(lastConjunctionLongitude / 30);
    let n2 = MathHelper.truncate(nextConjunctionLongitude / 30);
    return Math.abs(n1 - n2) < MathHelper.epsilon ? 'Adhika-' : '';
  }

  static getMasaNum(trueSolarLongitude, lastConjunctionLongitude) {
    let masaNum = MathHelper.truncate(trueSolarLongitude / 30) % 12;
    if (masaNum === MathHelper.truncate(lastConjunctionLongitude / 30) % 12) {
      masaNum += 1;
    }
    masaNum = (masaNum + 12) % 12;
    return masaNum;
  }

  static getNaksatra(trueLunarLongitude) {
    return Calendar.naksatras[MathHelper.truncate(trueLunarLongitude * 27 / 360)];
  }

  nextDate(date) {
    // TODO: This looks like a concern of the calling library - But could be exposed as a static utility method (0 usages other than tests)
    date.setUTCDate(date.getUTCDate() + 1);
    return date;
  }

  julianInEngland(julianDay) {
    // TODO: This might be exposed as a static utility method (0 usages other than tests)
    // Gregorian calendar was first introduced in most of Europe in 1582,
    // but it wasn't adopted in England (and so in US and Canada) until 1752
    //
    // - http://www.timeanddate.com/calendar/julian-gregorian-switch.html
    //
    // This returns true between
    //      October 14th, 1582 and September 14th, 1752, both dates exclusive
    return julianDay >= 2299160 && julianDay <= 2361221;
  }

  aharganaToKali(ahargana) {
    return MathHelper.truncate(ahargana * this.celestial.planets.sun.YugaRotation / this.celestial.yuga.CivilDays);
  }

  kaliToAhargana(yearKali, masaNum, tithiDay) {
    const sauraMasas = yearKali * 12 + masaNum; // expired saura masas

    const adhiMasas = MathHelper.truncate(sauraMasas * this.celestial.yuga.Adhimasa / ( 12 * this.celestial.planets.sun.YugaRotation )); // expired adhimasas

    const candraMasas = sauraMasas + adhiMasas; // expired candra masas

    const tithis = 30 * candraMasas + tithiDay - 1; // expired tithis

    const avamas = MathHelper.truncate(tithis * this.celestial.yuga.Ksayadina / this.celestial.yuga.Tithi); // expired avamas

    return tithis - avamas;
  }

  findSamkranti(leftAhargana, rightAhargana) {
    let width = (rightAhargana - leftAhargana) / 2;
    let centreAhargana = (rightAhargana + leftAhargana) / 2;

    if (width < MathHelper.epsilon) {
      return centreAhargana;
    } else {
      let centreTslong = this.celestial.getTrueSolarLongitude(centreAhargana);
      centreTslong -= MathHelper.truncate(centreTslong / 30) * 30;
      if (centreTslong < 5) {
        return this.findSamkranti(leftAhargana, centreAhargana);
      } else {
        return this.findSamkranti(centreAhargana, rightAhargana);
      }
    }
  }

  calculateSamkranti(ahargana, desantara) {
    samkranti.ahargana = this.findSamkranti(ahargana, ahargana + 1) + desantara;
    // below line is the fix that Yano-san worked in for Kerala dates - #20140223 cf. try_calculations
    let roundedSamkranti = MathHelper.truncate(samkranti.ahargana) + 0.5;
    let samkrantiModernDate = Calendar.julianDayToModernDate(Calendar.aharganaToJulianDay(roundedSamkranti));
    if (JulianDate.prototype.isPrototypeOf(samkrantiModernDate)) {
      samkranti.Year = samkrantiModernDate.year;
      samkranti.Month = samkrantiModernDate.month;
      samkranti.Day = samkrantiModernDate.date;
    } else {
      samkranti.Year = samkrantiModernDate.getFullYear();
      samkranti.Month = samkrantiModernDate.getMonth() + 1;
      samkranti.Day = samkrantiModernDate.getDate();
    }
    let fractionalDay = MathHelper.fractional(samkranti.ahargana) * 24;
    samkranti.Hour = MathHelper.truncate(fractionalDay);
    samkranti.Min = MathHelper.truncate(60 * MathHelper.fractional(fractionalDay));
  }

  isTodaySauraMasaFirst(ahargana, desantara) {
    /*
     //    Definition of the first day
     //    samkranti is between today's 0:00 and 24:00
     //    ==
     //    at 0:00 before 30x, at 24:00 after 30x
     */
    let trueSolarLongitudeToday = this.celestial.getTrueSolarLongitude(ahargana - desantara);
    let trueSolarLongitudeTomorrow = this.celestial.getTrueSolarLongitude(ahargana - desantara + 1);

    trueSolarLongitudeToday -= MathHelper.truncate(trueSolarLongitudeToday / 30) * 30;
    trueSolarLongitudeTomorrow -= MathHelper.truncate(trueSolarLongitudeTomorrow / 30) * 30;

    if (25 < trueSolarLongitudeToday && trueSolarLongitudeTomorrow < 5) { // eslint-disable-line yoda
      this.calculateSamkranti(ahargana, desantara);
      return true;
    }

    return false;
  }

  getSauraMasaAndSauraDivasa(ahargana, desantara) {
    // If today is the first day then 1
    // Otherwise yesterday's + 1
    let month;
    let day;
    ahargana = MathHelper.truncate(ahargana);
    if (this.isTodaySauraMasaFirst(ahargana, desantara)) {
      day = 1;
      let tsLongTomorrow = this.celestial.getTrueSolarLongitude(ahargana + 1);
      month = MathHelper.truncate(tsLongTomorrow / 30) % 12;
      month = (month + 12) % 12;
    } else {
      const {sauraMasa, sauraDivasa} = this.getSauraMasaAndSauraDivasa(ahargana - 1, desantara);
      month = sauraMasa;
      day = sauraDivasa + 1;
    }
    return {sauraMasa : month, sauraDivasa : day};
  }

}

export default Calendar;