diff --git a/hebcal.d.ts b/hebcal.d.ts index c1c5615..a52030a 100644 --- a/hebcal.d.ts +++ b/hebcal.d.ts @@ -989,7 +989,7 @@ declare module '@hebcal/core' { * @param desc - Description (not translated) */ export class TimedEvent extends Event { - constructor(date: HDate, desc: string, mask: number, eventTime: Date, location: Location, linkedEvent?: Event); + constructor(date: HDate, desc: string, mask: number, eventTime: Date, location: Location, linkedEvent?: Event, options?: CalOptions); render(locale?: string): string; renderBrief(locale?: string): string; getCategories(): string[]; @@ -999,11 +999,11 @@ declare module '@hebcal/core' { readonly linkedEvent?: Event; } export class CandleLightingEvent extends TimedEvent { - constructor(date: HDate, mask: number, eventTime: Date, location: Location, linkedEvent?: Event); + constructor(date: HDate, mask: number, eventTime: Date, location: Location, linkedEvent?: Event, options?: CalOptions); getEmoji(): string; } export class HavdalahEvent extends TimedEvent { - constructor(date: HDate, mask: number, eventTime: Date, location: Location, havdalahMins?: number, linkedEvent?: Event); + constructor(date: HDate, mask: number, eventTime: Date, location: Location, havdalahMins?: number, linkedEvent?: Event, options?: CalOptions); render(locale?: string): string; renderBrief(locale?: string): string; getEmoji(): string; diff --git a/package.json b/package.json index 64ae992..db37eba 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@hebcal/core", - "version": "5.0.6", + "version": "5.0.7", "author": "Michael J. Radwin (https://github.com/mjradwin)", "contributors": [ "Eyal Schachter (https://github.com/Scimonster)", diff --git a/src/candles.js b/src/candles.js index a25f4ff..2e48264 100644 --- a/src/candles.js +++ b/src/candles.js @@ -2,6 +2,7 @@ import {months} from '@hebcal/hdate'; import {Event, flags} from './event.js'; import {Locale} from './locale.js'; +import {reformatTimeStr} from './reformatTimeStr.js'; import {Zmanim} from './zmanim.js'; const FRI = 5; @@ -12,11 +13,10 @@ const SAT = 6; * @param {Event} e * @param {HDate} hd * @param {number} dow - * @param {Location} location * @param {CalOptions} options * @return {Event} */ -export function makeCandleEvent(e, hd, dow, location, options) { +export function makeCandleEvent(e, hd, dow, options) { let havdalahTitle = false; let useHavdalahOffset = dow === SAT; let mask = e ? e.getFlags() : flags.LIGHT_CANDLES; @@ -36,15 +36,16 @@ export function makeCandleEvent(e, hd, dow, location, options) { } // if offset is 0 or undefined, we'll use tzeit time const offset = useHavdalahOffset ? options.havdalahMins : options.candleLightingMins; + const location = options.location; const zmanim = new Zmanim(location, hd, options.useElevation); const time = offset ? zmanim.sunsetOffset(offset, true) : zmanim.tzeit(options.havdalahDeg); if (isNaN(time.getTime())) { return null; // no sunset } if (havdalahTitle) { - return new HavdalahEvent(hd, mask, time, location, options.havdalahMins, e); + return new HavdalahEvent(hd, mask, time, location, options.havdalahMins, e, options); } else { - return new CandleLightingEvent(hd, mask, time, location, e); + return new CandleLightingEvent(hd, mask, time, location, e, options); } } @@ -57,13 +58,16 @@ export class TimedEvent extends Event { * @param {Date} eventTime * @param {Location} location * @param {Event} linkedEvent + * @param {CalOptions} options */ - constructor(date, desc, mask, eventTime, location, linkedEvent) { + constructor(date, desc, mask, eventTime, location, linkedEvent, options) { super(date, desc, mask); this.eventTime = Zmanim.roundTime(eventTime); this.location = location; const timeFormat = location.getTimeFormatter(); this.eventTimeStr = Zmanim.formatTime(this.eventTime, timeFormat); + const opts = Object.assign({location}, options); + this.fmtTime = reformatTimeStr(this.eventTimeStr, 'pm', opts); if (typeof linkedEvent !== 'undefined') { this.linkedEvent = linkedEvent; } @@ -73,7 +77,7 @@ export class TimedEvent extends Event { * @return {string} */ render(locale) { - return Locale.gettext(this.getDesc(), locale) + ': ' + this.eventTimeStr; + return Locale.gettext(this.getDesc(), locale) + ': ' + this.fmtTime; } /** * Returns translation of "Candle lighting" without the time. @@ -110,9 +114,10 @@ export class HavdalahEvent extends TimedEvent { * @param {Location} location * @param {number} havdalahMins * @param {Event} linkedEvent + * @param {CalOptions} options */ - constructor(date, mask, eventTime, location, havdalahMins, linkedEvent) { - super(date, 'Havdalah', mask, eventTime, location, linkedEvent); + constructor(date, mask, eventTime, location, havdalahMins, linkedEvent, options) { + super(date, 'Havdalah', mask, eventTime, location, linkedEvent, options); if (havdalahMins) { this.havdalahMins = havdalahMins; } @@ -122,7 +127,7 @@ export class HavdalahEvent extends TimedEvent { * @return {string} */ render(locale) { - return this.renderBrief(locale) + ': ' + this.eventTimeStr; + return this.renderBrief(locale) + ': ' + this.fmtTime; } /** * Returns translation of "Havdalah" without the time. @@ -151,9 +156,10 @@ export class CandleLightingEvent extends TimedEvent { * @param {Date} eventTime * @param {Location} location * @param {Event} linkedEvent + * @param {CalOptions} options */ - constructor(date, mask, eventTime, location, linkedEvent) { - super(date, 'Candle lighting', mask, eventTime, location, linkedEvent); + constructor(date, mask, eventTime, location, linkedEvent, options) { + super(date, 'Candle lighting', mask, eventTime, location, linkedEvent, options); } /** @return {string} */ getEmoji() { @@ -181,14 +187,14 @@ export function makeFastStartEnd(ev, options) { const zmanim = new Zmanim(location, dt, options.useElevation); if (desc === 'Erev Tish\'a B\'Av') { const sunset = zmanim.sunset(); - ev.startEvent = makeTimedEvent(hd, sunset, 'Fast begins', ev, location); + ev.startEvent = makeTimedEvent(hd, sunset, 'Fast begins', ev, options); } else if (desc.startsWith('Tish\'a B\'Av')) { - ev.endEvent = makeTimedEvent(hd, zmanim.tzeit(fastEndDeg), 'Fast ends', ev, location); + ev.endEvent = makeTimedEvent(hd, zmanim.tzeit(fastEndDeg), 'Fast ends', ev, options); } else { const dawn = zmanim.alotHaShachar(); - ev.startEvent = makeTimedEvent(hd, dawn, 'Fast begins', ev, location); + ev.startEvent = makeTimedEvent(hd, dawn, 'Fast begins', ev, options); if (dt.getDay() !== 5 && !(hd.getDate() === 14 && hd.getMonth() === months.NISAN)) { - ev.endEvent = makeTimedEvent(hd, zmanim.tzeit(fastEndDeg), 'Fast ends', ev, location); + ev.endEvent = makeTimedEvent(hd, zmanim.tzeit(fastEndDeg), 'Fast ends', ev, options); } } return ev; @@ -200,14 +206,15 @@ export function makeFastStartEnd(ev, options) { * @param {Date} time * @param {string} desc * @param {Event} ev - * @param {Location} location + * @param {CalOptions} options * @return {TimedEvent} */ -function makeTimedEvent(hd, time, desc, ev, location) { +function makeTimedEvent(hd, time, desc, ev, options) { if (isNaN(time.getTime())) { return null; } - return new TimedEvent(hd, desc, ev.getFlags(), time, location, ev); + const location = options.location; + return new TimedEvent(hd, desc, ev.getFlags(), time, location, ev, options); } @@ -224,5 +231,5 @@ export function makeWeekdayChanukahCandleLighting(ev, hd, options) { const zmanim = new Zmanim(location, hd.greg(), options.useElevation); const candleLightingTime = zmanim.dusk(); // const candleLightingTime = zmanim.tzeit(4.6667); - return makeTimedEvent(hd, candleLightingTime, ev.getDesc(), ev, location); + return makeTimedEvent(hd, candleLightingTime, ev.getDesc(), ev, options); } diff --git a/src/candles.spec.js b/src/candles.spec.js index ddf6bb0..d6f3987 100644 --- a/src/candles.spec.js +++ b/src/candles.spec.js @@ -23,6 +23,11 @@ function eventDateDesc(ev) { test('makeCandleEvent-nosunset', (t) => { const location = Location.lookup('Helsinki'); + const options = { + location, + candleLightingMins: -18, + useElevation: true, + }; const dates = [ [2020, 4, 15], @@ -37,10 +42,7 @@ test('makeCandleEvent-nosunset', (t) => { const events = []; for (const dt of dates) { const hd = new HDate(new Date(dt[0], dt[1], dt[2])); - const ev = makeCandleEvent(undefined, hd, hd.getDay(), location, { - candleLightingMins: -18, - useElevation: true, - }); + const ev = makeCandleEvent(undefined, hd, hd.getDay(), options); events.push(ev); } const result = events.map(eventDateDesc); @@ -56,14 +58,11 @@ test('makeCandleEvent-nosunset', (t) => { ]; t.deepEqual(result, expected); + options.havdalahMins = 72; const events2 = []; for (const dt of dates) { const hd = new HDate(new Date(dt[0], dt[1], dt[2])); - const ev = makeCandleEvent(undefined, hd, hd.getDay(), location, { - candleLightingMins: -18, - havdalahMins: 72, - useElevation: true, - }); + const ev = makeCandleEvent(undefined, hd, hd.getDay(), options); events2.push(ev); } const result2 = events2.map(eventDateDesc); @@ -88,6 +87,7 @@ test('candles-only-diaspora', (t) => { location: Location.lookup('Chicago'), candlelighting: true, useElevation: true, + hour12: 0, }; const events = HebrewCalendar.calendar(options); t.is(events.length, 132); @@ -148,7 +148,7 @@ test('havdalah-mins', (t) => { .filter((ev) => ev.getDesc().startsWith('Havdalah')); const ev = events[0]; t.is(ev.getFlags(), flags.LIGHT_CANDLES_TZEIS); - t.is(ev.render('en'), 'Havdalah (47 min): 20:02'); + t.is(ev.render('en'), 'Havdalah (47 min): 8:02pm'); t.is(ev.getDesc(), 'Havdalah'); t.is(ev.eventTimeStr, '20:02'); const actual = events.slice(1, 5).map(eventTitleDateTime); @@ -478,7 +478,7 @@ test('no-chanukah-candles-when-noHolidays', (t) => { test('renderBrief', (t) => { const dt = new Date('2020-12-28T20:12:14.987Z'); const hd = new HDate(dt); - const location = new Location(0, 0, false, 'UTC'); + const location = new Location(0, 0, false, 'UTC', undefined, 'GB'); const timed = new TimedEvent(hd, 'Foo Bar', 0, dt, location); const candleLighting = new CandleLightingEvent(hd, flags.LIGHT_CANDLES, dt, location); const havdalah = new HavdalahEvent(hd, flags.LIGHT_CANDLES_TZEIS, dt, location, 42); @@ -508,16 +508,16 @@ test('renderBrief', (t) => { test('havdalahDeg', (t) => { const hd = new HDate(new Date(2020, 4, 16)); const dow = hd.getDay(); - const location = new Location(0, 0, false, 'UTC'); + const location = new Location(0, 0, false, 'UTC', undefined, 'GB'); const events = [ - makeCandleEvent(undefined, hd, dow, location, {}), - makeCandleEvent(undefined, hd, dow, location, {havdalahDeg: 6.5}), - makeCandleEvent(undefined, hd, dow, location, {havdalahDeg: 7.0833}), - makeCandleEvent(undefined, hd, dow, location, {havdalahDeg: 7.5}), - makeCandleEvent(undefined, hd, dow, location, {havdalahDeg: 8.5}), - makeCandleEvent(undefined, hd, dow, location, {havdalahMins: 42}), - makeCandleEvent(undefined, hd, dow, location, {havdalahMins: 50}), - makeCandleEvent(undefined, hd, dow, location, {havdalahMins: 72}), + makeCandleEvent(undefined, hd, dow, {location}), + makeCandleEvent(undefined, hd, dow, {location, havdalahDeg: 6.5}), + makeCandleEvent(undefined, hd, dow, {location, havdalahDeg: 7.0833}), + makeCandleEvent(undefined, hd, dow, {location, havdalahDeg: 7.5}), + makeCandleEvent(undefined, hd, dow, {location, havdalahDeg: 8.5}), + makeCandleEvent(undefined, hd, dow, {location, havdalahMins: 42}), + makeCandleEvent(undefined, hd, dow, {location, havdalahMins: 50}), + makeCandleEvent(undefined, hd, dow, {location, havdalahMins: 72}), ]; const results = events.map(eventDateDesc); const expected = [ @@ -642,3 +642,39 @@ test('makeFastStartEnd', (t) => { t.is(startEvent.eventTime.toISOString(), '2023-07-27T00:10:00.000Z'); t.is(startEvent.eventTimeStr, '20:10'); }); + +test('candles-hour12-true', (t) => { + const events = HebrewCalendar.calendar({ + start: new Date(2022, 9, 4), + end: new Date(2022, 9, 5), + noHolidays: true, + location: Location.lookup('Boston'), + candlelighting: true, + useElevation: true, + hour12: true, + }); + const actual = events.map(eventDateDesc); + const expected = [ + {date: '2022-10-04', desc: 'Candle lighting: 6:03pm'}, + {date: '2022-10-05', desc: 'Havdalah: 7:00pm'}, + ]; + t.deepEqual(actual, expected); +}); + +test('candles-hour12-false', (t) => { + const events = HebrewCalendar.calendar({ + start: new Date(2022, 9, 4), + end: new Date(2022, 9, 5), + noHolidays: true, + location: Location.lookup('Boston'), + candlelighting: true, + useElevation: true, + hour12: false, + }); + const actual = events.map(eventDateDesc); + const expected = [ + {date: '2022-10-04', desc: 'Candle lighting: 18:03'}, + {date: '2022-10-05', desc: 'Havdalah: 19:00'}, + ]; + t.deepEqual(actual, expected); +}); diff --git a/src/hebcal.js b/src/hebcal.js index 244c767..1ec6bd2 100644 --- a/src/hebcal.js +++ b/src/hebcal.js @@ -40,6 +40,7 @@ import {Locale} from './locale.js'; import {Location} from './location.js'; import {MoladEvent} from './molad.js'; import {OmerEvent} from './omer.js'; +import {reformatTimeStr} from './reformatTimeStr.js'; import {tachanun_} from './tachanun.js'; import {Zmanim} from './zmanim.js'; @@ -429,10 +430,6 @@ const MASK_LIGHT_CANDLES = const defaultLocation = new Location(0, 0, false, 'UTC'); -const hour12cc = { - US: 1, CA: 1, BR: 1, AU: 1, NZ: 1, DO: 1, PR: 1, GR: 1, IN: 1, KR: 1, NP: 1, ZA: 1, -}; - /** * @private * @param {CalOptions} options @@ -689,7 +686,7 @@ export class HebrewCalendar { evts.push(new MoladEvent(hd, hyear, monNext)); } if (!candlesEv && options.candlelighting && (dow == FRI || dow == SAT)) { - candlesEv = makeCandleEvent(undefined, hd, dow, location, options); + candlesEv = makeCandleEvent(undefined, hd, dow, options); if (dow === FRI && candlesEv && sedra) { candlesEv.memo = sedra.getString(abs); } @@ -857,27 +854,7 @@ export class HebrewCalendar { * @return {string} */ static reformatTimeStr(timeStr, suffix, options) { - if (typeof timeStr !== 'string') throw new TypeError(`Bad timeStr: ${timeStr}`); - const cc = options.location?.cc || (options.il ? 'IL' : 'US'); - if (typeof options.hour12 !== 'undefined' && !options.hour12) { - return timeStr; - } - if (!options.hour12 && typeof hour12cc[cc] === 'undefined') { - return timeStr; - } - const hm = timeStr.split(':'); - let hour = parseInt(hm[0], 10); - if (hour < 12 && suffix) { - suffix = suffix.replace('p', 'a').replace('P', 'A'); - if (hour === 0) { - hour = 12; - } - } else if (hour > 12) { - hour = hour % 12; - } else if (hour === 0) { - hour = '00'; - } - return `${hour}:${hm[1]}${suffix}`; + return reformatTimeStr(timeStr, suffix, options); } /** @return {string} */ @@ -977,7 +954,7 @@ function appendHolidayAndRelated(events, ev, options, candlesEv, dow) { if ((eFlags & options.mask) || (!eFlags && !options.userMask)) { if (options.candlelighting && eFlags & MASK_LIGHT_CANDLES) { const hd = ev.getDate(); - candlesEv = makeCandleEvent(ev, hd, dow, location, options); + candlesEv = makeCandleEvent(ev, hd, dow, options); if (eFlags & CHANUKAH_CANDLES && candlesEv && !options.noHolidays) { const chanukahEv = (dow === FRI || dow === SAT) ? candlesEv : makeWeekdayChanukahCandleLighting(ev, hd, options); diff --git a/src/hebcal.spec.js b/src/hebcal.spec.js index 90d3440..2b81582 100644 --- a/src/hebcal.spec.js +++ b/src/hebcal.spec.js @@ -335,62 +335,25 @@ test('renderBrief', (t) => { test('reformatTimeStr', (t) => { t.is(HebrewCalendar.reformatTimeStr('20:30', 'pm', {}), '8:30pm'); t.is(HebrewCalendar.reformatTimeStr('20:30', ' P.M.', {}), '8:30 P.M.'); - t.is(HebrewCalendar.reformatTimeStr('20:30', '', {}), '8:30'); - t.is(HebrewCalendar.reformatTimeStr('20:30', '', {locale: 'fr'}), '8:30'); - t.is(HebrewCalendar.reformatTimeStr('20:30', '', {locale: 'en'}), '8:30'); - t.is(HebrewCalendar.reformatTimeStr('20:30', '', {locale: 'ashkenazi'}), '8:30'); - t.is(HebrewCalendar.reformatTimeStr('20:30', ' PM', {location: {cc: 'FR'}}), '20:30'); - t.is(HebrewCalendar.reformatTimeStr('20:30', ' PM', {location: {cc: 'IL'}}), '20:30'); - t.is(HebrewCalendar.reformatTimeStr('20:30', ' PM', {location: {cc: 'US'}}), '8:30 PM'); - t.is(HebrewCalendar.reformatTimeStr('20:30', ' PM', {location: {cc: 'CA'}}), '8:30 PM'); t.is(HebrewCalendar.reformatTimeStr('20:30', ' PM', {location: {cc: 'BR'}}), '8:30 PM'); t.is(HebrewCalendar.reformatTimeStr('20:30', ' PM', {location: {cc: 'MX'}}), '20:30'); t.is(HebrewCalendar.reformatTimeStr('11:45', 'pm', {}), '11:45am'); - t.is(HebrewCalendar.reformatTimeStr('11:45', ' P.M.', {}), '11:45 A.M.'); - t.is(HebrewCalendar.reformatTimeStr('11:45', '', {}), '11:45'); - t.is(HebrewCalendar.reformatTimeStr('11:45', '', {locale: 'fr'}), '11:45'); - t.is(HebrewCalendar.reformatTimeStr('11:45', '', {locale: 'en'}), '11:45'); - t.is(HebrewCalendar.reformatTimeStr('11:45', '', {locale: 'ashkenazi'}), '11:45'); - t.is(HebrewCalendar.reformatTimeStr('11:45', ' PM', {location: {cc: 'FR'}}), '11:45'); - t.is(HebrewCalendar.reformatTimeStr('11:45', ' PM', {location: {cc: 'IL'}}), '11:45'); - t.is(HebrewCalendar.reformatTimeStr('11:45', ' PM', {location: {cc: 'US'}}), '11:45 AM'); - t.is(HebrewCalendar.reformatTimeStr('11:45', ' PM', {location: {cc: 'CA'}}), '11:45 AM'); t.is(HebrewCalendar.reformatTimeStr('11:45', ' PM', {location: {cc: 'BR'}}), '11:45 AM'); t.is(HebrewCalendar.reformatTimeStr('11:45', ' PM', {location: {cc: 'MX'}}), '11:45'); t.is(HebrewCalendar.reformatTimeStr('00:07', 'pm', {}), '12:07am'); t.is(HebrewCalendar.reformatTimeStr('00:07', ' P.M.', {}), '12:07 A.M.'); - t.is(HebrewCalendar.reformatTimeStr('00:07', '', {}), '00:07'); - t.is(HebrewCalendar.reformatTimeStr('00:07', '', {locale: 'fr'}), '00:07'); - t.is(HebrewCalendar.reformatTimeStr('00:07', '', {locale: 'en'}), '00:07'); - t.is(HebrewCalendar.reformatTimeStr('00:07', '', {locale: 'ashkenazi'}), '00:07'); - t.is(HebrewCalendar.reformatTimeStr('00:07', ' PM', {location: {cc: 'FR'}}), '00:07'); - t.is(HebrewCalendar.reformatTimeStr('00:07', ' PM', {location: {cc: 'IL'}}), '00:07'); - t.is(HebrewCalendar.reformatTimeStr('00:07', ' PM', {location: {cc: 'US'}}), '12:07 AM'); - t.is(HebrewCalendar.reformatTimeStr('00:07', ' PM', {location: {cc: 'CA'}}), '12:07 AM'); t.is(HebrewCalendar.reformatTimeStr('00:07', ' PM', {location: {cc: 'BR'}}), '12:07 AM'); t.is(HebrewCalendar.reformatTimeStr('00:07', ' PM', {location: {cc: 'MX'}}), '00:07'); }); test('reformatTimeStr-hour12', (t) => { t.is(HebrewCalendar.reformatTimeStr('23:56', '', {locale: 'fr', hour12: true}), '11:56'); - t.is(HebrewCalendar.reformatTimeStr('23:56', '', {locale: 'en', hour12: true}), '11:56'); - t.is(HebrewCalendar.reformatTimeStr('23:56', '', {locale: 'ashkenazi', hour12: true}), '11:56'); - t.is(HebrewCalendar.reformatTimeStr('23:56', ' PM', {location: {cc: 'FR'}, hour12: true}), '11:56 PM'); - t.is(HebrewCalendar.reformatTimeStr('23:56', ' PM', {location: {cc: 'IL'}, hour12: true}), '11:56 PM'); - t.is(HebrewCalendar.reformatTimeStr('23:56', ' PM', {location: {cc: 'US'}, hour12: true}), '11:56 PM'); - t.is(HebrewCalendar.reformatTimeStr('23:56', ' PM', {location: {cc: 'CA'}, hour12: true}), '11:56 PM'); t.is(HebrewCalendar.reformatTimeStr('23:56', ' PM', {location: {cc: 'BR'}, hour12: true}), '11:56 PM'); t.is(HebrewCalendar.reformatTimeStr('23:56', ' PM', {location: {cc: 'MX'}, hour12: true}), '11:56 PM'); t.is(HebrewCalendar.reformatTimeStr('23:56', '', {locale: 'fr', hour12: false}), '23:56'); - t.is(HebrewCalendar.reformatTimeStr('23:56', '', {locale: 'en', hour12: false}), '23:56'); - t.is(HebrewCalendar.reformatTimeStr('23:56', '', {locale: 'ashkenazi', hour12: false}), '23:56'); - t.is(HebrewCalendar.reformatTimeStr('23:56', ' PM', {location: {cc: 'FR'}, hour12: false}), '23:56'); - t.is(HebrewCalendar.reformatTimeStr('23:56', ' PM', {location: {cc: 'IL'}, hour12: false}), '23:56'); - t.is(HebrewCalendar.reformatTimeStr('23:56', ' PM', {location: {cc: 'US'}, hour12: false}), '23:56'); - t.is(HebrewCalendar.reformatTimeStr('23:56', ' PM', {location: {cc: 'CA'}, hour12: false}), '23:56'); t.is(HebrewCalendar.reformatTimeStr('23:56', ' PM', {location: {cc: 'BR'}, hour12: false}), '23:56'); t.is(HebrewCalendar.reformatTimeStr('23:56', ' PM', {location: {cc: 'MX'}, hour12: false}), '23:56'); }); @@ -705,3 +668,27 @@ test('year1-sedrot', (t) => { ]; t.deepEqual(actual, expected); }); + +test('mevarchim-only', (t) => { + const events = HebrewCalendar.calendar({ + year: 5784, + isHebrewYear: true, + mask: flags.SHABBAT_MEVARCHIM, + }); + const actual = events.map(eventDateDesc); + const expected = [ + {date: '10/14/2023', desc: 'Shabbat Mevarchim Chodesh Cheshvan'}, + {date: '11/11/2023', desc: 'Shabbat Mevarchim Chodesh Kislev'}, + {date: '12/9/2023', desc: 'Shabbat Mevarchim Chodesh Tevet'}, + {date: '1/6/2024', desc: 'Shabbat Mevarchim Chodesh Sh\'vat'}, + {date: '2/3/2024', desc: 'Shabbat Mevarchim Chodesh Adar I'}, + {date: '3/9/2024', desc: 'Shabbat Mevarchim Chodesh Adar II'}, + {date: '4/6/2024', desc: 'Shabbat Mevarchim Chodesh Nisan'}, + {date: '5/4/2024', desc: 'Shabbat Mevarchim Chodesh Iyyar'}, + {date: '6/1/2024', desc: 'Shabbat Mevarchim Chodesh Sivan'}, + {date: '6/29/2024', desc: 'Shabbat Mevarchim Chodesh Tamuz'}, + {date: '8/3/2024', desc: 'Shabbat Mevarchim Chodesh Av'}, + {date: '8/31/2024', desc: 'Shabbat Mevarchim Chodesh Elul'}, + ]; + t.deepEqual(actual, expected); +}); diff --git a/src/reformatTimeStr.js b/src/reformatTimeStr.js new file mode 100644 index 0000000..20d2677 --- /dev/null +++ b/src/reformatTimeStr.js @@ -0,0 +1,35 @@ +const hour12cc = { + US: 1, CA: 1, BR: 1, AU: 1, NZ: 1, DO: 1, PR: 1, GR: 1, IN: 1, KR: 1, NP: 1, ZA: 1, +}; + +/** + * @private + * @param {string} timeStr - original time like "20:30" + * @param {string} suffix - "p" or "pm" or " P.M.". Add leading space if you want it + * @param {CalOptions} options + * @return {string} + */ +export function reformatTimeStr(timeStr, suffix, options) { + if (typeof timeStr !== 'string') throw new TypeError(`Bad timeStr: ${timeStr}`); + const cc = options?.location?.cc || (options?.il ? 'IL' : 'US'); + const hour12 = options?.hour12; + if (typeof hour12 !== 'undefined' && !hour12) { + return timeStr; + } + if (!hour12 && typeof hour12cc[cc] === 'undefined') { + return timeStr; + } + const hm = timeStr.split(':'); + let hour = parseInt(hm[0], 10); + if (hour < 12 && suffix) { + suffix = suffix.replace('p', 'a').replace('P', 'A'); + if (hour === 0) { + hour = 12; + } + } else if (hour > 12) { + hour = hour % 12; + } else if (hour === 0) { + hour = '00'; + } + return `${hour}:${hm[1]}${suffix}`; +} diff --git a/src/reformatTimeStr.spec.js b/src/reformatTimeStr.spec.js new file mode 100644 index 0000000..1b97581 --- /dev/null +++ b/src/reformatTimeStr.spec.js @@ -0,0 +1,77 @@ +import test from 'ava'; +import {reformatTimeStr} from './reformatTimeStr.js'; + +test('reformatTimeStr', (t) => { + t.is(reformatTimeStr('20:30', 'pm', {}), '8:30pm'); + t.is(reformatTimeStr('20:30', ' P.M.', {}), '8:30 P.M.'); + t.is(reformatTimeStr('20:30', '', {}), '8:30'); + t.is(reformatTimeStr('20:30', '', {locale: 'fr'}), '8:30'); + t.is(reformatTimeStr('20:30', '', {locale: 'en'}), '8:30'); + t.is(reformatTimeStr('20:30', '', {locale: 'ashkenazi'}), '8:30'); + t.is(reformatTimeStr('20:30', ' PM', {location: {cc: 'FR'}}), '20:30'); + t.is(reformatTimeStr('20:30', ' PM', {location: {cc: 'IL'}}), '20:30'); + t.is(reformatTimeStr('20:30', ' PM', {location: {cc: 'US'}}), '8:30 PM'); + t.is(reformatTimeStr('20:30', ' PM', {location: {cc: 'CA'}}), '8:30 PM'); + t.is(reformatTimeStr('20:30', ' PM', {location: {cc: 'BR'}}), '8:30 PM'); + t.is(reformatTimeStr('20:30', ' PM', {location: {cc: 'MX'}}), '20:30'); + + t.is(reformatTimeStr('11:45', 'pm', {}), '11:45am'); + t.is(reformatTimeStr('11:45', ' P.M.', {}), '11:45 A.M.'); + t.is(reformatTimeStr('11:45', '', {}), '11:45'); + t.is(reformatTimeStr('11:45', '', {locale: 'fr'}), '11:45'); + t.is(reformatTimeStr('11:45', '', {locale: 'en'}), '11:45'); + t.is(reformatTimeStr('11:45', '', {locale: 'ashkenazi'}), '11:45'); + t.is(reformatTimeStr('11:45', ' PM', {location: {cc: 'FR'}}), '11:45'); + t.is(reformatTimeStr('11:45', ' PM', {location: {cc: 'IL'}}), '11:45'); + t.is(reformatTimeStr('11:45', ' PM', {location: {cc: 'US'}}), '11:45 AM'); + t.is(reformatTimeStr('11:45', ' PM', {location: {cc: 'CA'}}), '11:45 AM'); + t.is(reformatTimeStr('11:45', ' PM', {location: {cc: 'BR'}}), '11:45 AM'); + t.is(reformatTimeStr('11:45', ' PM', {location: {cc: 'MX'}}), '11:45'); + + t.is(reformatTimeStr('00:07', 'pm', {}), '12:07am'); + t.is(reformatTimeStr('00:07', ' P.M.', {}), '12:07 A.M.'); + t.is(reformatTimeStr('00:07', '', {}), '00:07'); + t.is(reformatTimeStr('00:07', '', {locale: 'fr'}), '00:07'); + t.is(reformatTimeStr('00:07', '', {locale: 'en'}), '00:07'); + t.is(reformatTimeStr('00:07', '', {locale: 'ashkenazi'}), '00:07'); + t.is(reformatTimeStr('00:07', ' PM', {location: {cc: 'FR'}}), '00:07'); + t.is(reformatTimeStr('00:07', ' PM', {location: {cc: 'IL'}}), '00:07'); + t.is(reformatTimeStr('00:07', ' PM', {location: {cc: 'US'}}), '12:07 AM'); + t.is(reformatTimeStr('00:07', ' PM', {location: {cc: 'CA'}}), '12:07 AM'); + t.is(reformatTimeStr('00:07', ' PM', {location: {cc: 'BR'}}), '12:07 AM'); + t.is(reformatTimeStr('00:07', ' PM', {location: {cc: 'MX'}}), '00:07'); +}); + +test('reformatTimeStr-hour12', (t) => { + t.is(reformatTimeStr('23:56', '', {locale: 'fr', hour12: true}), '11:56'); + t.is(reformatTimeStr('23:56', '', {locale: 'en', hour12: true}), '11:56'); + t.is(reformatTimeStr('23:56', '', {locale: 'ashkenazi', hour12: true}), '11:56'); + t.is(reformatTimeStr('23:56', ' PM', {location: {cc: 'FR'}, hour12: true}), '11:56 PM'); + t.is(reformatTimeStr('23:56', ' PM', {location: {cc: 'IL'}, hour12: true}), '11:56 PM'); + t.is(reformatTimeStr('23:56', ' PM', {location: {cc: 'US'}, hour12: true}), '11:56 PM'); + t.is(reformatTimeStr('23:56', ' PM', {location: {cc: 'CA'}, hour12: true}), '11:56 PM'); + t.is(reformatTimeStr('23:56', ' PM', {location: {cc: 'BR'}, hour12: true}), '11:56 PM'); + t.is(reformatTimeStr('23:56', ' PM', {location: {cc: 'MX'}, hour12: true}), '11:56 PM'); + + t.is(reformatTimeStr('23:56', '', {locale: 'fr', hour12: false}), '23:56'); + t.is(reformatTimeStr('23:56', '', {locale: 'en', hour12: false}), '23:56'); + t.is(reformatTimeStr('23:56', '', {locale: 'ashkenazi', hour12: false}), '23:56'); + t.is(reformatTimeStr('23:56', ' PM', {location: {cc: 'FR'}, hour12: false}), '23:56'); + t.is(reformatTimeStr('23:56', ' PM', {location: {cc: 'IL'}, hour12: false}), '23:56'); + t.is(reformatTimeStr('23:56', ' PM', {location: {cc: 'US'}, hour12: false}), '23:56'); + t.is(reformatTimeStr('23:56', ' PM', {location: {cc: 'CA'}, hour12: false}), '23:56'); + t.is(reformatTimeStr('23:56', ' PM', {location: {cc: 'BR'}, hour12: false}), '23:56'); + t.is(reformatTimeStr('23:56', ' PM', {location: {cc: 'MX'}, hour12: false}), '23:56'); +}); + +test('reformatTimeStr-hour12-am', (t) => { + t.is(reformatTimeStr('01:23', '', {locale: 'fr', hour12: true}), '1:23'); + t.is(reformatTimeStr('01:23', '', {locale: 'en', hour12: true}), '1:23'); + t.is(reformatTimeStr('01:23', '', {locale: 'ashkenazi', hour12: true}), '1:23'); + t.is(reformatTimeStr('01:23', ' P.M.', {location: {cc: 'FR'}, hour12: true}), '1:23 A.M.'); + t.is(reformatTimeStr('01:23', ' P.M.', {location: {cc: 'IL'}, hour12: true}), '1:23 A.M.'); + t.is(reformatTimeStr('01:23', ' P.M.', {location: {cc: 'US'}, hour12: true}), '1:23 A.M.'); + t.is(reformatTimeStr('01:23', ' P.M.', {location: {cc: 'CA'}, hour12: true}), '1:23 A.M.'); + t.is(reformatTimeStr('01:23', ' P.M.', {location: {cc: 'BR'}, hour12: true}), '1:23 A.M.'); + t.is(reformatTimeStr('01:23', ' P.M.', {location: {cc: 'MX'}, hour12: true}), '1:23 A.M.'); +});