diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 6b1ffb198..000f65b29 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -199,5 +199,9 @@
./tests/USA
+
+
+ ./tests/USA/NYSE
+
diff --git a/src/Yasumi/Provider/Brazil.php b/src/Yasumi/Provider/Brazil.php
index 92b835a65..a4e38ec03 100644
--- a/src/Yasumi/Provider/Brazil.php
+++ b/src/Yasumi/Provider/Brazil.php
@@ -89,6 +89,7 @@ protected function calculateProclamationOfRepublicDay(): void
'proclamationOfRepublicDay',
['pt' => 'Dia da Proclamação da República'],
new \DateTime("{$this->year}-11-15", DateTimeZoneFactory::getDateTimeZone($this->timezone)),
+ $this->locale
));
}
}
diff --git a/src/Yasumi/Provider/USA/NYSE.php b/src/Yasumi/Provider/USA/NYSE.php
new file mode 100644
index 000000000..e4808d4bb
--- /dev/null
+++ b/src/Yasumi/Provider/USA/NYSE.php
@@ -0,0 +1,107 @@
+
+ * @author Art Kurbakov
+ */
+
+// This file in addition to regular holidays observed by
+// the New York Stock Exchange (NYSE),
+// includes full day special closure events due to:
+// weather, national emergencies and presidential proclamations.
+// All closure events are included from year 2000.
+
+namespace Yasumi\Provider\USA;
+
+use Yasumi\Holiday;
+use Yasumi\Provider\USA;
+
+class NYSE extends USA
+{
+ /**
+ * Initialize holidays for the NYSE.
+ *
+ * @throws \Exception
+ */
+ public function initialize(): void
+ {
+ $this->timezone = 'America/New_York';
+
+ // Add exchange-specific holidays
+ $this->addHoliday($this->newYearsDay($this->year, $this->timezone, $this->locale));
+ $this->calculateMartinLutherKingday();
+ $this->calculateWashingtonsBirthday();
+ $this->addHoliday($this->goodFriday($this->year, $this->timezone, $this->locale));
+ $this->calculateMemorialDay();
+ if (2021 < $this->year) {
+ $this->calculateJuneteenth();
+ }
+ $this->calculateIndependenceDay();
+ $this->calculateLabourDay();
+ $this->calculateThanksgivingDay();
+ $this->addHoliday($this->christmasDay($this->year, $this->timezone, $this->locale));
+
+ $this->calculateSubstituteHolidays();
+
+ // Add other full-day closure events
+ $this->addWeatherEvents();
+ $this->addEmergencies();
+ $this->addProclamations();
+ }
+
+ public function getSources(): array
+ {
+ return [
+ 'https://www.nyse.com/trader-update/history#11507',
+ 'https://s3.amazonaws.com/armstrongeconomics-wp/2013/07/NYSE-Closings.pdf',
+ 'https://ir.theice.com/press/news-details/2018/New-York-Stock-Exchange-to-Honor-President-George-H-W-Bush/default.aspx',
+ 'https://ir.theice.com/press/news-details/2024/The-New-York-Stock-Exchange-Will-Close-Markets-on-January-9-to-Honor-the-Passing-of-Former-President-Jimmy-Carter-on-National-Day-of-Mourning/default.aspx',
+ 'https://www.thecorporatecounsel.net/blog/2021/10/nyse-makes-juneteenth-a-new-market-holiday.html',
+ ];
+ }
+
+ private function addWeatherEvents()
+ {
+ if (2012 == $this->year) {
+ $this->addHoliday(new Holiday('hurricaneSandy1', [], new \DateTime('2012-10-29', new \DateTimeZone($this->timezone))));
+ $this->addHoliday(new Holiday('hurricaneSandy2', [], new \DateTime('2012-10-30', new \DateTimeZone($this->timezone))));
+ }
+ }
+
+ private function addEmergencies()
+ {
+ if (2001 == $this->year) {
+ $this->addHoliday(new Holiday('groundZero1', [], new \DateTime('2001-09-11', new \DateTimeZone($this->timezone))));
+ $this->addHoliday(new Holiday('groundZero2', [], new \DateTime('2001-09-12', new \DateTimeZone($this->timezone))));
+ $this->addHoliday(new Holiday('groundZero3', [], new \DateTime('2001-09-13', new \DateTimeZone($this->timezone))));
+ $this->addHoliday(new Holiday('groundZero4', [], new \DateTime('2001-09-14', new \DateTimeZone($this->timezone))));
+ }
+ }
+
+ private function addProclamations()
+ {
+ if (2004 == $this->year) {
+ $this->addHoliday(new Holiday('ReaganMourning', [], new \DateTime('2004-06-11', new \DateTimeZone($this->timezone))));
+ }
+ if (2007 == $this->year) {
+ $this->addHoliday(new Holiday('GRFordMourning', [], new \DateTime('2007-01-02', new \DateTimeZone($this->timezone))));
+ }
+ if (2018 == $this->year) {
+ $this->addHoliday(new Holiday('HWBushMourning', [], new \DateTime('2018-12-05', new \DateTimeZone($this->timezone))));
+ }
+ if (2025 == $this->year) {
+ $this->addHoliday(new Holiday('CarterMourning', [], new \DateTime('2025-01-09', new \DateTimeZone($this->timezone))));
+ }
+ }
+}
diff --git a/tests/USA/NYSE/CarterMourningTest.php b/tests/USA/NYSE/CarterMourningTest.php
new file mode 100644
index 000000000..e59cb0c4c
--- /dev/null
+++ b/tests/USA/NYSE/CarterMourningTest.php
@@ -0,0 +1,80 @@
+
+ * @author Art Kurbakov
+ */
+
+namespace Yasumi\tests\USA\NYSE;
+
+use Yasumi\tests\USA\USABaseTestCase;
+
+/**
+ * Class to test day of closure of NYSE due to Jimmy Carter mourning proclamation
+ */
+class CarterMourningTest extends USABaseTestCase
+{
+ /**
+ * Name of provider to be tested.
+ */
+ public const REGION = 'USA/NYSE';
+
+ /**
+ * The year when the closure was observed.
+ */
+ public const OBSERVED_YEAR = 2025;
+
+ /**
+ * Tests day of closure on January 9th 2025
+ *
+ * @throws \Exception
+ */
+ public function testCarterMourning(): void
+ {
+ $this->assertHoliday(
+ self::REGION,
+ 'CarterMourning',
+ self::OBSERVED_YEAR,
+ new \DateTime('2025-01-09', new \DateTimeZone(self::TIMEZONE))
+ );
+ }
+
+ /**
+ * Tests years before 2025
+ *
+ * @throws \Exception
+ */
+ public function testCarterMourningBefore2025(): void
+ {
+ $this->assertNotHoliday(
+ self::REGION,
+ 'CarterMourning',
+ self::OBSERVED_YEAR - 1
+ );
+ }
+
+ /**
+ * Tests years after 2025
+ *
+ * @throws \Exception
+ */
+ public function testCarterMourningAfter2025(): void
+ {
+ $this->assertNotHoliday(
+ self::REGION,
+ 'CarterMourning',
+ self::OBSERVED_YEAR + 1
+ );
+ }
+}
diff --git a/tests/USA/NYSE/GRFordMourningTest.php b/tests/USA/NYSE/GRFordMourningTest.php
new file mode 100644
index 000000000..b1a59afc0
--- /dev/null
+++ b/tests/USA/NYSE/GRFordMourningTest.php
@@ -0,0 +1,80 @@
+
+ * @author Art Kurbakov
+ */
+
+namespace Yasumi\tests\USA\NYSE;
+
+use Yasumi\tests\USA\USABaseTestCase;
+
+/**
+ * Class to test day of closure of NYSE due to G.R. Ford Mourning proclamation
+ */
+class GRFordMourningTest extends USABaseTestCase
+{
+ /**
+ * Name of provider to be tested.
+ */
+ public const REGION = 'USA/NYSE';
+
+ /**
+ * The year when the closure was observed.
+ */
+ public const OBSERVED_YEAR = 2007;
+
+ /**
+ * Tests day of closure on January 2nd 2007
+ *
+ * @throws \Exception
+ */
+ public function testGRFordMourning(): void
+ {
+ $this->assertHoliday(
+ self::REGION,
+ 'GRFordMourning',
+ self::OBSERVED_YEAR,
+ new \DateTime('2007-01-02', new \DateTimeZone(self::TIMEZONE))
+ );
+ }
+
+ /**
+ * Tests years before 2007
+ *
+ * @throws \Exception
+ */
+ public function testGRFordMourningBefore2007(): void
+ {
+ $this->assertNotHoliday(
+ self::REGION,
+ 'GRFordMourning',
+ self::OBSERVED_YEAR - 1
+ );
+ }
+
+ /**
+ * Tests years after 2007
+ *
+ * @throws \Exception
+ */
+ public function testGRFordMourningAfter2007(): void
+ {
+ $this->assertNotHoliday(
+ self::REGION,
+ 'GRFordMourning',
+ self::OBSERVED_YEAR + 1
+ );
+ }
+}
diff --git a/tests/USA/NYSE/GroundZeroTest.php b/tests/USA/NYSE/GroundZeroTest.php
new file mode 100644
index 000000000..3cc328850
--- /dev/null
+++ b/tests/USA/NYSE/GroundZeroTest.php
@@ -0,0 +1,137 @@
+
+ * @author Art Kurbakov
+ */
+
+namespace Yasumi\tests\USA\NYSE;
+
+use Yasumi\tests\USA\USABaseTestCase;
+
+/**
+ * Class to test 4 days of closure of NYSE due to September 11th attacks
+ */
+class GroundZeroTest extends USABaseTestCase
+{
+ /**
+ * Name of provider to be tested.
+ */
+ public const REGION = 'USA/NYSE';
+
+ /**
+ * The year when the closure was observed.
+ */
+ public const OBSERVED_YEAR = 2001;
+
+ /**
+ * Tests 4 days of closure on September 11th 2001
+ *
+ * @throws \Exception
+ */
+ public function testGroundZero(): void
+ {
+ $this->assertHoliday(
+ self::REGION,
+ 'groundZero1',
+ self::OBSERVED_YEAR,
+ new \DateTime('2001-09-11', new \DateTimeZone(self::TIMEZONE))
+ );
+
+ $this->assertHoliday(
+ self::REGION,
+ 'groundZero2',
+ self::OBSERVED_YEAR,
+ new \DateTime('2001-09-12', new \DateTimeZone(self::TIMEZONE))
+ );
+
+ $this->assertHoliday(
+ self::REGION,
+ 'groundZero3',
+ self::OBSERVED_YEAR,
+ new \DateTime('2001-09-13', new \DateTimeZone(self::TIMEZONE))
+ );
+
+ $this->assertHoliday(
+ self::REGION,
+ 'groundZero4',
+ self::OBSERVED_YEAR,
+ new \DateTime('2001-09-14', new \DateTimeZone(self::TIMEZONE))
+ );
+ }
+
+ /**
+ * Tests years before 2001
+ *
+ * @throws \Exception
+ */
+ public function testGroundZeroBefore2001(): void
+ {
+ $this->assertNotHoliday(
+ self::REGION,
+ 'groundZero1',
+ self::OBSERVED_YEAR - 1
+ );
+
+ $this->assertNotHoliday(
+ self::REGION,
+ 'groundZero2',
+ self::OBSERVED_YEAR - 1
+ );
+
+ $this->assertNotHoliday(
+ self::REGION,
+ 'groundZero3',
+ self::OBSERVED_YEAR - 1
+ );
+
+ $this->assertNotHoliday(
+ self::REGION,
+ 'groundZero4',
+ self::OBSERVED_YEAR - 1
+ );
+ }
+
+ /**
+ * Tests years after 2001
+ *
+ * @throws \Exception
+ */
+ public function testGroundZeroAfter2001(): void
+ {
+ $this->assertNotHoliday(
+ self::REGION,
+ 'groundZero1',
+ self::OBSERVED_YEAR + 1
+ );
+
+ $this->assertNotHoliday(
+ self::REGION,
+ 'groundZero2',
+ self::OBSERVED_YEAR + 1
+ );
+
+ $this->assertNotHoliday(
+ self::REGION,
+ 'groundZero3',
+ self::OBSERVED_YEAR + 1
+ );
+
+ $this->assertNotHoliday(
+ self::REGION,
+ 'groundZero4',
+ self::OBSERVED_YEAR + 1
+ );
+ }
+}
diff --git a/tests/USA/NYSE/HWBushMourningTest.php b/tests/USA/NYSE/HWBushMourningTest.php
new file mode 100644
index 000000000..dba469c05
--- /dev/null
+++ b/tests/USA/NYSE/HWBushMourningTest.php
@@ -0,0 +1,80 @@
+
+ * @author Art Kurbakov
+ */
+
+namespace Yasumi\tests\USA\NYSE;
+
+use Yasumi\tests\USA\USABaseTestCase;
+
+/**
+ * Class to test day of closure of NYSE due to H.W. Bush Mourning proclamation
+ */
+class HWBushMourningTest extends USABaseTestCase
+{
+ /**
+ * Name of provider to be tested.
+ */
+ public const REGION = 'USA/NYSE';
+
+ /**
+ * The year when the closure was observed.
+ */
+ public const OBSERVED_YEAR = 2018;
+
+ /**
+ * Tests day of closure on December 5th 2018
+ *
+ * @throws \Exception
+ */
+ public function testHWBushMourning(): void
+ {
+ $this->assertHoliday(
+ self::REGION,
+ 'HWBushMourning',
+ self::OBSERVED_YEAR,
+ new \DateTime('2018-12-05', new \DateTimeZone(self::TIMEZONE))
+ );
+ }
+
+ /**
+ * Tests years before 2018
+ *
+ * @throws \Exception
+ */
+ public function testHWBushMourningBefore2018(): void
+ {
+ $this->assertNotHoliday(
+ self::REGION,
+ 'HWBushMourning',
+ self::OBSERVED_YEAR - 1
+ );
+ }
+
+ /**
+ * Tests years after 2018
+ *
+ * @throws \Exception
+ */
+ public function testHWBushMourningAfter2018(): void
+ {
+ $this->assertNotHoliday(
+ self::REGION,
+ 'HWBushMourning',
+ self::OBSERVED_YEAR + 1
+ );
+ }
+}
diff --git a/tests/USA/NYSE/HurricaneSandyTest.php b/tests/USA/NYSE/HurricaneSandyTest.php
new file mode 100644
index 000000000..3365b7eb7
--- /dev/null
+++ b/tests/USA/NYSE/HurricaneSandyTest.php
@@ -0,0 +1,99 @@
+
+ * @author Art Kurbakov
+ */
+
+namespace Yasumi\tests\USA\NYSE;
+
+use Yasumi\tests\USA\USABaseTestCase;
+
+/**
+ * Class to test 2 days of closure of NYSE due to Hurricane Sandy
+ */
+class HurricaneSandyTest extends USABaseTestCase
+{
+ /**
+ * Name of provider to be tested.
+ */
+ public const REGION = 'USA/NYSE';
+
+ /**
+ * The year when the closure was observed.
+ */
+ public const OBSERVED_YEAR = 2012;
+
+ /**
+ * Tests both days of closure of October 29-30th, 2012
+ *
+ * @throws \Exception
+ */
+ public function testHurricaneSandyDays(): void
+ {
+ $this->assertHoliday(
+ self::REGION,
+ 'hurricaneSandy1',
+ self::OBSERVED_YEAR,
+ new \DateTime('2012-10-29', new \DateTimeZone(self::TIMEZONE))
+ );
+
+ $this->assertHoliday(
+ self::REGION,
+ 'hurricaneSandy2',
+ self::OBSERVED_YEAR,
+ new \DateTime('2012-10-30', new \DateTimeZone(self::TIMEZONE))
+ );
+ }
+
+ /**
+ * Tests years before 2012
+ *
+ * @throws \Exception
+ */
+ public function testHurricaneSandyBefore2012(): void
+ {
+ $this->assertNotHoliday(
+ self::REGION,
+ 'hurricaneSandy1',
+ self::OBSERVED_YEAR - 1
+ );
+
+ $this->assertNotHoliday(
+ self::REGION,
+ 'hurricaneSandy2',
+ self::OBSERVED_YEAR - 1
+ );
+ }
+
+ /**
+ * Tests years after 2012
+ *
+ * @throws \Exception
+ */
+ public function testHurricaneSandyAfter2012(): void
+ {
+ $this->assertNotHoliday(
+ self::REGION,
+ 'hurricaneSandy1',
+ self::OBSERVED_YEAR + 1
+ );
+
+ $this->assertNotHoliday(
+ self::REGION,
+ 'hurricaneSandy2',
+ self::OBSERVED_YEAR + 1
+ );
+ }
+}
diff --git a/tests/USA/NYSE/JuneteenthTest.php b/tests/USA/NYSE/JuneteenthTest.php
new file mode 100644
index 000000000..54d14bf30
--- /dev/null
+++ b/tests/USA/NYSE/JuneteenthTest.php
@@ -0,0 +1,105 @@
+
+ * @author Art Kurbakov
+ */
+
+namespace Yasumi\tests\USA\NYSE;
+
+use Yasumi\Holiday;
+use Yasumi\tests\USA\USABaseTestCase;
+
+/**
+ * Class to test Juneteenth.
+ */
+class JuneteenthTest extends USABaseTestCase
+{
+ /**
+ * Country (name) to be tested.
+ */
+ public const REGION = 'USA/NYSE';
+
+ /**
+ * The name of the holiday.
+ */
+ public const HOLIDAY = 'juneteenth';
+
+ /**
+ * The year in which the holiday was first established for NYSE.
+ */
+ public const ESTABLISHMENT_YEAR = 2022;
+
+ /**
+ * Tests Juneteenth on or after 2022. For NYSE Juneteenth is celebrated since 2022 on June 19th.
+ *
+ * @throws \Exception
+ */
+ public function testJuneteenthOnAfter2022(): void
+ {
+ $year = 2023;
+ $this->assertHoliday(
+ self::REGION,
+ self::HOLIDAY,
+ $year,
+ new \DateTime("{$year}-6-19", new \DateTimeZone(self::TIMEZONE))
+ );
+ }
+
+ /**
+ * Tests Juneteenth on or after 2022 when substituted on Monday (when Juneteenth falls on Sunday).
+ *
+ * @throws \Exception
+ */
+ public function testJuneteenthOnAfter2022SubstitutedMonday(): void
+ {
+ $year = 2022;
+ $this->assertSubstituteHoliday(
+ self::REGION,
+ self::HOLIDAY,
+ $year,
+ new \DateTime("{$year}-6-20", new \DateTimeZone(self::TIMEZONE))
+ );
+ }
+
+ /**
+ * Tests Juneteenth on or after 2022 when substituted on Friday (when Juneteenth falls on Saturday).
+ *
+ * @throws \Exception
+ */
+ public function testJuneteenthOnAfter2022SubstitutedFriday(): void
+ {
+ $year = 2027;
+ $this->assertSubstituteHoliday(
+ self::REGION,
+ self::HOLIDAY,
+ $year,
+ new \DateTime("{$year}-6-18", new \DateTimeZone(self::TIMEZONE))
+ );
+ }
+
+ /**
+ * Tests Juneteenth before 2022. For NYSE Juneteenth is celebrated since 2022 on June 19th.
+ *
+ * @throws \Exception
+ */
+ public function testJuneteenthBefore2022(): void
+ {
+ $this->assertNotHoliday(
+ self::REGION,
+ self::HOLIDAY,
+ self::ESTABLISHMENT_YEAR - 1
+ );
+ }
+}
diff --git a/tests/USA/NYSE/NYSETest.php b/tests/USA/NYSE/NYSETest.php
new file mode 100644
index 000000000..062520dbf
--- /dev/null
+++ b/tests/USA/NYSE/NYSETest.php
@@ -0,0 +1,111 @@
+
+ * @author Art Kurbakov
+ */
+
+namespace Yasumi\tests\USA\NYSE;
+
+use Yasumi\Yasumi;
+use Yasumi\Holiday;
+use Yasumi\tests\ProviderTestCase;
+use Yasumi\tests\USA\USABaseTestCase;
+
+/**
+ * Class for testing closure days for NYSE
+ */
+class NYSETest extends USABaseTestCase implements ProviderTestCase
+{
+ /**
+ * Country (name) to be tested.
+ */
+ public const REGION = 'USA/NYSE';
+
+ /**
+ * @var int year random year number used for all tests in this Test Case
+ */
+ protected int $year;
+
+ /**
+ * Initial setup of this Test Case.
+ *
+ * @throws \Exception
+ */
+ protected function setUp(): void
+ {
+ $this->year = $this->generateRandomYear(2000, 2100);
+ }
+
+ /**
+ * Tests if all official holidays in the USA are defined by the provider class.
+ */
+ public function testOfficialHolidays(): void
+ {
+ $holidays = [
+ 'newYearsDay',
+ 'martinLutherKingDay',
+ 'washingtonsBirthday',
+ 'goodFriday',
+ 'memorialDay',
+ 'independenceDay',
+ 'labourDay',
+ 'thanksgivingDay',
+ 'christmasDay',
+ ];
+
+ if (2001 == $this->year) {
+ $holidays[] = 'groundZero1';
+ $holidays[] = 'groundZero2';
+ $holidays[] = 'groundZero3';
+ $holidays[] = 'groundZero4';
+ }
+
+ if (2004 == $this->year) {
+ $holidays[] = 'ReaganMourning';
+ }
+
+ if (2007 == $this->year) {
+ $holidays[] = 'GRFordMourning';
+ }
+
+ if (2012 == $this->year) {
+ $holidays[] = 'hurricaneSandy1';
+ $holidays[] = 'hurricaneSandy2';
+ }
+
+ if (2018 == $this->year) {
+ $holidays[] = 'HWBushMourning';
+ }
+
+ if (2021 > $this->year) {
+ $holidays[] = 'juneteenth';
+ }
+
+ if (2025 > $this->year) {
+ $holidays[] = 'CarterMourning';
+ }
+
+ $this->assertDefinedHolidays($holidays, self::REGION, $this->year, Holiday::TYPE_OFFICIAL);
+ }
+
+ /**
+ * @throws \ReflectionException
+ * @throws \Exception
+ */
+ public function testSources(): void
+ {
+ $this->assertSources(self::REGION, 5);
+ }
+}
diff --git a/tests/USA/NYSE/ReaganMourningTest.php b/tests/USA/NYSE/ReaganMourningTest.php
new file mode 100644
index 000000000..0abde2a74
--- /dev/null
+++ b/tests/USA/NYSE/ReaganMourningTest.php
@@ -0,0 +1,80 @@
+
+ * @author Art Kurbakov
+ */
+
+namespace Yasumi\tests\USA\NYSE;
+
+use Yasumi\tests\USA\USABaseTestCase;
+
+/**
+ * Class to test day of closure of NYSE due to Reagan Mourning proclamation
+ */
+class ReaganMourningTest extends USABaseTestCase
+{
+ /**
+ * Name of provider to be tested.
+ */
+ public const REGION = 'USA/NYSE';
+
+ /**
+ * The year when the closure was observed.
+ */
+ public const OBSERVED_YEAR = 2004;
+
+ /**
+ * Tests day of closure on June 11th 2004
+ *
+ * @throws \Exception
+ */
+ public function testReaganMourning(): void
+ {
+ $this->assertHoliday(
+ self::REGION,
+ 'ReaganMourning',
+ self::OBSERVED_YEAR,
+ new \DateTime('2004-06-11', new \DateTimeZone(self::TIMEZONE))
+ );
+ }
+
+ /**
+ * Tests years before 2004
+ *
+ * @throws \Exception
+ */
+ public function testReaganMourningBefore2004(): void
+ {
+ $this->assertNotHoliday(
+ self::REGION,
+ 'ReaganMourning',
+ self::OBSERVED_YEAR - 1
+ );
+ }
+
+ /**
+ * Tests years after 2004
+ *
+ * @throws \Exception
+ */
+ public function testReaganMourningAfter2004(): void
+ {
+ $this->assertNotHoliday(
+ self::REGION,
+ 'ReaganMourning',
+ self::OBSERVED_YEAR + 1
+ );
+ }
+}