<commit_before>import {
  amod,
  estimatePriorSolarLongitude,
  newMoonAtOrAfter,
  newMoonBefore,
  next,
  solarLongitude,
  standardToUniversal,
  universalToStandard
} from '../Astro';
import { INVALID_DAY, INVALID_LEAP_MONTH, INVALID_MONTH, INVALID_YEAR, J0000, MEAN_SYNODIC_MONTH, MEAN_TROPICAL_YEAR, Season, chinese } from '../Const';
import { Location } from '../Location';

import { ChineseDate } from './ChineseDate';
import { CalendarDateValidationException } from './core';

export class ChineseCalendar {
  // Calculate Chinese calendar date from rata die
  public static fromRd(rataDie: number): ChineseDate {
    const s1: number = this.chineseWinterSolsticeOnOrBefore(rataDie);
    const s2: number = this.chineseWinterSolsticeOnOrBefore(s1 + 370);
    const nextM11: number = this.chineseNewMoonBefore(1 + s2);
    const m12: number = this.chineseNewMoonOnOrAfter(1 + s1);
    const yearLeap: boolean = Math.round((nextM11 - m12) / MEAN_SYNODIC_MONTH) === 12;

    const m: number = this.chineseNewMoonBefore(1 + rataDie);
    const month: number = amod(Math.round((m - m12) / MEAN_SYNODIC_MONTH) - (yearLeap && this.isChinesePriorLeapMonth(m12, m) ? 1 : 0), 12);
    const monthLeap: boolean = yearLeap && this.isChineseNoMajorSolarTerm(m) && !this.isChinesePriorLeapMonth(m12, this.chineseNewMoonBefore(m));
    const years: number = Math.floor(1.5 - month / 12 + (rataDie - chinese.EPOCH_RD) / MEAN_TROPICAL_YEAR);
    const cycle: number = 1 + Math.floor((years - 1) / 60);
    const year: number = amod(years, 60);
    const day: number = 1 + rataDie - m;

    return new ChineseDate(rataDie + J0000, cycle, year, month, monthLeap, day);
  }

  public static fromJdn(jdn: number): ChineseDate {
    return this.fromRd (jdn - J0000);
  }

  // Determine Julian day number (JDN) from Chinese calendar date
  public static toJdn(cycle: number, year: number, month: number, monthLeap: boolean, day: number): number {
    const jdn = this.calculateJdn(cycle, year, month, monthLeap, day);
    this.validate(cycle, year, month, monthLeap, day, jdn);

    return jdn;
  }

  // Determine Julian day number (JDN) from Chinese calendar date
  private static calculateJdn(cycle: number, year: number, month: number, monthLeap: boolean, day: number): number {
    const midYear = Math.floor(chinese.EPOCH + ((cycle - 1) * 60 + year - 0.5) * MEAN_TROPICAL_YEAR) - J0000;
    const newYear = this.chineseNewYearOnOrBefore(midYear);
    const p = this.chineseNewMoonOnOrAfter(newYear + (month - 1) * 29);
    const d = this.fromRd(p);
    const priorNewMoon = month === d.getMonth() && monthLeap === d.isMonthLeap() ? p : this.chineseNewMoonOnOrAfter(1 + p);

    return priorNewMoon + J0000 + day - 1;
  }

  private static validate(cycle: number, year: number, month: number, monthLeap: boolean, day: number, jdn: number) {
    if (year < 1 || year > 60) {
      throw new CalendarDateValidationException(INVALID_YEAR);
    }

    if (month < 1 || month > 12) {
      throw new CalendarDateValidationException(INVALID_MONTH);
    }

    const date: ChineseDate = this.fromJdn(jdn);
    if (monthLeap && !date.isMonthLeap()) {
      throw new CalendarDateValidationException(INVALID_LEAP_MONTH);
    }

    if (date.getDay() !== day || day < 1 || day > 30) {
      throw new CalendarDateValidationException(INVALID_DAY);
    }
  }

  // Return True if there is a Chinese leap month on or after lunar month starting on
  // Julian day number (JDN) m_prime and at or before lunar month starting at m.
  private static isChinesePriorLeapMonth(mPrime: number, m: number): boolean {
    return m >= mPrime && (this.isChineseNoMajorSolarTerm(m) || this.isChinesePriorLeapMonth(mPrime, this.chineseNewMoonBefore(m)));
  }

  // Return Julian day number (JDN) of Chinese New Year in given Gregorian year.
  /*
  private static chineseNewYear(gYear: number): number {
    return this.chineseNewYearOnOrBefore(GregorianDate.toJdn(gYear, Month.JULY, 1));
  }
  */

  // Return Julian day number (JDN) of Chinese New Year on or before given date.
  private static chineseNewYearOnOrBefore(fixed: number): number {
    const newYear: number = this.chineseNewYearInSui(fixed);

    return fixed >= newYear ? newYear : this.chineseNewYearInSui(fixed - 180);
  }

  // Return Julian day number (JDN) of Chinese New Year in sui (period from solstice
  // to solstice) containing given date.
  private static chineseNewYearInSui(fixed: number): number {
    const s1: number = this.chineseWinterSolsticeOnOrBefore(fixed);
    const s2: number = this.chineseWinterSolsticeOnOrBefore(s1 + 370);
    const nextM11: number = this.chineseNewMoonBefore(1 + s2);
    const m12: number = this.chineseNewMoonOnOrAfter(1 + s1);
    const m13: number = this.chineseNewMoonOnOrAfter(1 + m12);
    const yearLeap: boolean = Math.round((nextM11 - m12) / MEAN_SYNODIC_MONTH) === 12;

    if (yearLeap && (this.isChineseNoMajorSolarTerm(m12) || this.isChineseNoMajorSolarTerm(m13))) {
      return this.chineseNewMoonOnOrAfter(1 + m13);
    }

    return m13;
  }

  // Return Julian day number (JDN) (Beijing) of first new moon before given date.
  private static chineseNewMoonBefore(fixed: number): number {
    const tee: number = newMoonBefore(this.midnightInChina(fixed));

    return Math.floor(universalToStandard(tee, this.chineseLocation(tee)));
  }

  // Return Julian day number (JDN) (Beijing) of first new moon on or aftergiven date.
  private static chineseNewMoonOnOrAfter(fixed: number): number {
    const tee: number = newMoonAtOrAfter(this.midnightInChina(fixed));

    return Math.floor(universalToStandard(tee, this.chineseLocation(tee)));
  }

  // Return Julian day number (JDN) in the Chinese zone, of winter solstice on or before given date.
  private static chineseWinterSolsticeOnOrBefore(fixed: number): number {
    const approx: number = estimatePriorSolarLongitude(Season.WINTER, this.midnightInChina(fixed + 1));

    return next(Math.floor(approx) - 1, (day: number) => Season.WINTER < solarLongitude(ChineseCalendar.midnightInChina(1 + day)));
  }

  // Return Universal time of (clock) midnight at start of given date, in China.
  private static midnightInChina(fixed: number): number {
    return standardToUniversal(fixed, this.chineseLocation(fixed));
  }

  // Return location of Beijing; time zone varies with time.
  private static chineseLocation(fixed: number): Location {
    return fixed < chinese.EPOCH_1929_RD ? chinese.LOCATION_BEFORE_1929 : chinese.LOCATION_SINCE_1929;<commit_msg>Corrected comments related to rata die / JDN.<commit_after>import {
  amod,
  estimatePriorSolarLongitude,
  newMoonAtOrAfter,
  newMoonBefore,
  next,
  solarLongitude,
  standardToUniversal,
  universalToStandard
} from '../Astro';
import { INVALID_DAY, INVALID_LEAP_MONTH, INVALID_MONTH, INVALID_YEAR, J0000, MEAN_SYNODIC_MONTH, MEAN_TROPICAL_YEAR, Season, chinese } from '../Const';
import { Location } from '../Location';

import { ChineseDate } from './ChineseDate';
import { CalendarDateValidationException } from './core';

export class ChineseCalendar {
  // Calculate Chinese calendar date from rata die
  public static fromRd(rataDie: number): ChineseDate {
    const s1: number = this.chineseWinterSolsticeOnOrBefore(rataDie);
    const s2: number = this.chineseWinterSolsticeOnOrBefore(s1 + 370);
    const nextM11: number = this.chineseNewMoonBefore(1 + s2);
    const m12: number = this.chineseNewMoonOnOrAfter(1 + s1);
    const yearLeap: boolean = Math.round((nextM11 - m12) / MEAN_SYNODIC_MONTH) === 12;

    const m: number = this.chineseNewMoonBefore(1 + rataDie);
    const month: number = amod(Math.round((m - m12) / MEAN_SYNODIC_MONTH) - (yearLeap && this.isChinesePriorLeapMonth(m12, m) ? 1 : 0), 12);
    const monthLeap: boolean = yearLeap && this.isChineseNoMajorSolarTerm(m) && !this.isChinesePriorLeapMonth(m12, this.chineseNewMoonBefore(m));
    const years: number = Math.floor(1.5 - month / 12 + (rataDie - chinese.EPOCH_RD) / MEAN_TROPICAL_YEAR);
    const cycle: number = 1 + Math.floor((years - 1) / 60);
    const year: number = amod(years, 60);
    const day: number = 1 + rataDie - m;

    return new ChineseDate(rataDie + J0000, cycle, year, month, monthLeap, day);
  }

  public static fromJdn(jdn: number): ChineseDate {
    return this.fromRd (jdn - J0000);
  }

  // Determine Julian day number (JDN) from Chinese calendar date
  public static toJdn(cycle: number, year: number, month: number, monthLeap: boolean, day: number): number {
    const jdn = this.calculateJdn(cycle, year, month, monthLeap, day);
    this.validate(cycle, year, month, monthLeap, day, jdn);

    return jdn;
  }

  // Determine Julian day number (JDN) from Chinese calendar date
  private static calculateJdn(cycle: number, year: number, month: number, monthLeap: boolean, day: number): number {
    const midYear = Math.floor(chinese.EPOCH + ((cycle - 1) * 60 + year - 0.5) * MEAN_TROPICAL_YEAR) - J0000;
    const newYear = this.chineseNewYearOnOrBefore(midYear);
    const p = this.chineseNewMoonOnOrAfter(newYear + (month - 1) * 29);
    const d = this.fromRd(p);
    const priorNewMoon = month === d.getMonth() && monthLeap === d.isMonthLeap() ? p : this.chineseNewMoonOnOrAfter(1 + p);

    return priorNewMoon + J0000 + day - 1;
  }

  private static validate(cycle: number, year: number, month: number, monthLeap: boolean, day: number, jdn: number) {
    if (year < 1 || year > 60) {
      throw new CalendarDateValidationException(INVALID_YEAR);
    }

    if (month < 1 || month > 12) {
      throw new CalendarDateValidationException(INVALID_MONTH);
    }

    const date: ChineseDate = this.fromJdn(jdn);
    if (monthLeap && !date.isMonthLeap()) {
      throw new CalendarDateValidationException(INVALID_LEAP_MONTH);
    }

    if (date.getDay() !== day || day < 1 || day > 30) {
      throw new CalendarDateValidationException(INVALID_DAY);
    }
  }

  // Return True if there is a Chinese leap month on or after lunar month starting on
  // rata die mPrime and at or before lunar month starting at m.
  private static isChinesePriorLeapMonth(mPrime: number, m: number): boolean {
    return m >= mPrime && (this.isChineseNoMajorSolarTerm(m) || this.isChinesePriorLeapMonth(mPrime, this.chineseNewMoonBefore(m)));
  }

  // Return rata die of Chinese New Year in given Gregorian year.
  /*
  private static chineseNewYear(gYear: number): number {
    return this.chineseNewYearOnOrBefore(GregorianDate.toJdn(gYear, Month.JULY, 1));
  }
  */

  // Return rata die of Chinese New Year on or before given date.
  private static chineseNewYearOnOrBefore(fixed: number): number {
    const newYear: number = this.chineseNewYearInSui(fixed);

    return fixed >= newYear ? newYear : this.chineseNewYearInSui(fixed - 180);
  }

  // Return rata die of Chinese New Year in sui (period from solstice
  // to solstice) containing given date.
  private static chineseNewYearInSui(fixed: number): number {
    const s1: number = this.chineseWinterSolsticeOnOrBefore(fixed);
    const s2: number = this.chineseWinterSolsticeOnOrBefore(s1 + 370);
    const nextM11: number = this.chineseNewMoonBefore(1 + s2);
    const m12: number = this.chineseNewMoonOnOrAfter(1 + s1);
    const m13: number = this.chineseNewMoonOnOrAfter(1 + m12);
    const yearLeap: boolean = Math.round((nextM11 - m12) / MEAN_SYNODIC_MONTH) === 12;

    if (yearLeap && (this.isChineseNoMajorSolarTerm(m12) || this.isChineseNoMajorSolarTerm(m13))) {
      return this.chineseNewMoonOnOrAfter(1 + m13);
    }

    return m13;
  }

  // Return rata die (Beijing) of first new moon before given date.
  private static chineseNewMoonBefore(fixed: number): number {
    const tee: number = newMoonBefore(this.midnightInChina(fixed));

    return Math.floor(universalToStandard(tee, this.chineseLocation(tee)));
  }

  // Return rata die (Beijing) of first new moon on or after given date.
  private static chineseNewMoonOnOrAfter(fixed: number): number {
    const tee: number = newMoonAtOrAfter(this.midnightInChina(fixed));

    return Math.floor(universalToStandard(tee, this.chineseLocation(tee)));
  }

  // Return rata die in the Chinese zone, of winter solstice on or before given date.
  private static chineseWinterSolsticeOnOrBefore(fixed: number): number {
    const approx: number = estimatePriorSolarLongitude(Season.WINTER, this.midnightInChina(fixed + 1));

    return next(Math.floor(approx) - 1, (day: number) => Season.WINTER < solarLongitude(ChineseCalendar.midnightInChina(1 + day)));
  }

  // Return Universal time of (clock) midnight at start of given date, in China.
  private static midnightInChina(fixed: number): number {
    return standardToUniversal(fixed, this.chineseLocation(fixed));
  }

  // Return location of Beijing; time zone varies with time.
  private static chineseLocation(fixed: number): Location {
    return fixed < chinese.EPOCH_1929_RD ? chinese.LOCATION_BEFORE_1929 : chinese.LOCATION_SINCE_1929;
{'fraction_non_alphanumeric': 0.10957422146665637, 'fraction_numerical': 0.020709373309636042, 'mean_word_length': 4.150019896538002, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 15, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '498617', 'n_tokens_mistral': 4513, 'n_tokens_neox': 4172, 'n_words': 1142}