From ddbcca4de32640935c2ec4319a32ed29d97df9a7 Mon Sep 17 00:00:00 2001 From: SkyratBot <59378654+SkyratBot@users.noreply.github.com> Date: Mon, 7 Dec 2020 11:32:35 +0100 Subject: [PATCH] [MIRROR] Fix nth week holidays (#2061) * Fix nth week holidays (#55364) fix: Holidays appearing on the nth day of the month like Thanksgiving will work now. tweak: Columbus day is now called Indigenous Peoples' Day. * Fix nth week holidays Co-authored-by: Jack LeCroy <3073035+jacklecroy@users.noreply.github.com> --- code/__HELPERS/time.dm | 33 +++------- code/modules/holiday/foreign_calendar.dm | 32 ++++++++-- code/modules/holiday/holidays.dm | 45 +------------ code/modules/holiday/nth_week.dm | 80 ++++++++++++++++++++++++ code/modules/unit_tests/_unit_tests.dm | 1 + code/modules/unit_tests/holidays.dm | 33 ++++++++++ tgstation.dme | 1 + 7 files changed, 155 insertions(+), 70 deletions(-) create mode 100644 code/modules/holiday/nth_week.dm create mode 100644 code/modules/unit_tests/holidays.dm diff --git a/code/__HELPERS/time.dm b/code/__HELPERS/time.dm index a3d55881c00..a2402005d51 100644 --- a/code/__HELPERS/time.dm +++ b/code/__HELPERS/time.dm @@ -65,29 +65,16 @@ GLOBAL_VAR_INIT(rollovercheck_last_timeofday, 0) return CEILING(date / 7, 1) + 1 -///Returns the first day of the current month in number format, from 1 (monday) - 7 (sunday). -/proc/first_day_of_month() - var/year = text2num(time2text(world.timeofday, "YY")) - var/month = text2num(time2text(world.timeofday, "MM")) - - var/day = (1 + (((13 * month) + (13 * 1)) / 5) + (year) + (year/4) + (20/4) + (5 * 20)) % 7 //Zeller's congruence - - switch(day) //convert to 1-7 monday first format - if(0) - day = 6 - if(1) - day = 7 - if(2) - day = 1 - if(3) - day = 2 - if(4) - day = 3 - if(5) - day = 4 - if(6) - day = 5 - return day +///Returns the first day of the given year and month in number format, from 1 (monday) - 7 (sunday). +/proc/first_day_of_month(year, month) + // https://en.wikipedia.org/wiki/Zeller%27s_congruence + var/m = month < 3 ? month + 12 : month // month (march = 3, april = 4...february = 14) + var/K = year % 100 // year of century + var/J = round(year / 100) // zero-based century + // day 0-6 saturday to sunday: + var/h = (1 + round(13 * (m + 1) / 5) + K + round(K / 4) + round(J / 4) - 2 * J) % 7 + //convert to ISO 1-7 monday first format + return ((h + 5) % 7) + 1 //Takes a value of time in deciseconds. diff --git a/code/modules/holiday/foreign_calendar.dm b/code/modules/holiday/foreign_calendar.dm index 07a51b5776b..1c217082252 100644 --- a/code/modules/holiday/foreign_calendar.dm +++ b/code/modules/holiday/foreign_calendar.dm @@ -1,6 +1,7 @@ #define BYOND_EPOCH 2451544.5 #define HEBREW_EPOCH 347995.5 #define ISLAMIC_EPOCH 1948439.5 +#define GREGORIAN_EPOCH 1721424.5 /* Source for the method of calcuation @@ -8,21 +9,41 @@ https://www.fourmilab.ch/documents/calendar/ by John Walker 2015, released under public domain */ /datum/foreign_calendar - var/static/jd + var/jd var/yy var/mm var/dd -/datum/foreign_calendar/New() +/datum/foreign_calendar/New(yy, mm, dd) if (!jd) - jd = realtime_to_jd() + jd = gregorian_to_jd(yy, mm, dd) set_date(jd) /datum/foreign_calendar/proc/set_date() return -/datum/foreign_calendar/proc/realtime_to_jd() - return round(world.realtime / 864000) + BYOND_EPOCH +///Converts Gregorian date to Julian Day +/datum/foreign_calendar/proc/gregorian_to_jd(year, month, day) + . = day // days this month + . += (GREGORIAN_EPOCH - 1) // start at gregorian epoch + . += (365 * (year - 1)) // add number of days + . += round(((367 * month) - 362) / 12) // days from previous months this year + . += round((year - 1) / 4) // figuring out leap years + . -= round((year - 1) / 100) + . += round((year - 1) / 400) + if (month > 2) + if (leap_gregorian(year)) + . -= 1 + else + . -= 2 + +///Returns whether a year is a leap year in the Gregorian calendar +/datum/foreign_calendar/proc/leap_gregorian(year) + return (year % 4 == 0) && ((year % 400 == 0) || (year % 100 != 0)) + +///Converts BYOND realtime to Julian Day +/datum/foreign_calendar/proc/realtime_to_jd(realtime) + return round(realtime / 864000) + BYOND_EPOCH ////////////////////////////// // Islamic Calendar // @@ -134,3 +155,4 @@ by John Walker 2015, released under public domain #undef ISLAMIC_EPOCH #undef BYOND_EPOCH #undef HEBREW_EPOCH +#undef GREGORIAN_EPOCH diff --git a/code/modules/holiday/holidays.dm b/code/modules/holiday/holidays.dm index e99cc60dd34..97a6a4ba8e3 100644 --- a/code/modules/holiday/holidays.dm +++ b/code/modules/holiday/holidays.dm @@ -5,8 +5,6 @@ var/begin_month = 0 var/end_day = 0 // Default of 0 means the holiday lasts a single day var/end_month = 0 - var/begin_week = FALSE //If set to a number, then this holiday will begin on certain week - var/begin_weekday = FALSE //If set to a weekday, then this will trigger the holiday on the above week var/always_celebrate = FALSE // for christmas neverending, or testing. var/current_year = 0 var/year_offset = 0 @@ -35,9 +33,6 @@ end_day = begin_day if(!end_month) end_month = begin_month - if(begin_week && begin_weekday) - if(begin_week == ww && begin_weekday == ddd && begin_month == mm) - return TRUE if(end_month > begin_month) //holiday spans multiple months in one year if(mm == end_month) //in final month if(dd <= end_day) @@ -419,47 +414,13 @@ begin_month = DECEMBER drone_hat = /obj/item/clothing/mask/gas/monkeymask -/datum/holiday/thanksgiving - name = "Thanksgiving in the United States" - begin_week = 4 - begin_month = NOVEMBER - begin_weekday = THURSDAY - drone_hat = /obj/item/clothing/head/that //This is the closest we can get to a pilgrim's hat - -/datum/holiday/thanksgiving/canada - name = "Thanksgiving in Canada" - begin_week = 2 - begin_month = OCTOBER - begin_weekday = MONDAY - -/datum/holiday/columbus - name = "Columbus Day" - begin_week = 2 - begin_month = OCTOBER - begin_weekday = MONDAY - -/datum/holiday/mother - name = "Mother's Day" - begin_week = 2 - begin_month = MAY - begin_weekday = SUNDAY - -/datum/holiday/mother/greet() - return "Happy Mother's Day in most of the Americas, Asia, and Oceania!" - -/datum/holiday/father - name = "Father's Day" - begin_week = 3 - begin_month = JUNE - begin_weekday = SUNDAY - /datum/holiday/moth name = "Moth Week" /datum/holiday/moth/shouldCelebrate(dd, mm, yy, ww, ddd) //National Moth Week falls on the last full week of July, including the saturday and sunday before. See http://nationalmothweek.org/ for precise tracking. if(mm == JULY) var/week - if(first_day_of_month() >= 5) //Friday or later start of the month means week 5 is a full week. + if(first_day_of_month(yy, mm) >= 5) //Friday or later start of the month means week 5 is a full week. week = 5 else week = 4 @@ -474,7 +435,7 @@ name = "Islamic calendar code broken" /datum/holiday/islamic/shouldCelebrate(dd, mm, yy, ww, ddd) - var/datum/foreign_calendar/islamic/cal = new + var/datum/foreign_calendar/islamic/cal = new(yy, mm, dd) return ..(cal.dd, cal.mm, cal.yy, ww, ddd) /datum/holiday/islamic/ramadan @@ -629,7 +590,7 @@ name = "If you see this the Hebrew holiday calendar code is broken" /datum/holiday/hebrew/shouldCelebrate(dd, mm, yy, ww, ddd) - var/datum/foreign_calendar/hebrew/cal = new + var/datum/foreign_calendar/hebrew/cal = new(yy, mm, dd) return ..(cal.dd, cal.mm, cal.yy, ww, ddd) /datum/holiday/hebrew/hanukkah diff --git a/code/modules/holiday/nth_week.dm b/code/modules/holiday/nth_week.dm new file mode 100644 index 00000000000..4195ce11097 --- /dev/null +++ b/code/modules/holiday/nth_week.dm @@ -0,0 +1,80 @@ +///A holiday lasting one day only that falls on the nth weekday in a month i.e. 3rd Wednesday of February. +/datum/holiday/nth_week + ///Nth weekday of type begin_weekday in begin_month to start on (1 to 5). + var/begin_week = 1 + ///Weekday of begin_week to start on. + var/begin_weekday = MONDAY + +/datum/holiday/nth_week/shouldCelebrate(dd, mm, yy, ww, ddd) + // Does not support end_day or end_month. Find me a holiday that needs that and I'll add it. + // Same with adding an end_weekday or end_week. + if (mm != begin_month) + return FALSE + if (begin_weekday != ddd) + return FALSE + var/day_number = 0 + // format of first_day_of_month proc (Monday 1 Sunday 7) + switch (begin_weekday) + if (MONDAY) + day_number = 1 + if (TUESDAY) + day_number = 2 + if (WEDNESDAY) + day_number = 3 + if (THURSDAY) + day_number = 4 + if (FRIDAY) + day_number = 5 + if (SATURDAY) + day_number = 6 + if (SUNDAY) + day_number = 7 + var/fd = first_day_of_month(yy, mm) + var/weekday_diff = day_number - fd + if (weekday_diff < 0) + weekday_diff += 7 + var/correct_day = (begin_week - 1) * 7 + weekday_diff + 1 + if (dd == correct_day) + return TRUE + return FALSE + +/datum/holiday/nth_week/thanksgiving + name = "Thanksgiving in the United States" + begin_week = 4 + begin_month = NOVEMBER + begin_weekday = THURSDAY + drone_hat = /obj/item/clothing/head/that //This is the closest we can get to a pilgrim's hat + +/datum/holiday/nth_week/thanksgiving/canada + name = "Thanksgiving in Canada" + begin_week = 2 + begin_month = OCTOBER + begin_weekday = MONDAY + +/datum/holiday/nth_week/indigenous + // not Columbus day anymore get rekt, Columbus + name = "Indigenous Peoples' Day" + begin_week = 2 + begin_month = OCTOBER + begin_weekday = MONDAY + +/datum/holiday/nth_week/mother + name = "Mother's Day" + begin_week = 2 + begin_month = MAY + begin_weekday = SUNDAY + +/datum/holiday/nth_week/mother/greet() + return "Happy Mother's Day in most of the Americas, Asia, and Oceania!" + +/datum/holiday/nth_week/father + name = "Father's Day" + begin_week = 3 + begin_month = JUNE + begin_weekday = SUNDAY + +/datum/holiday/nth_week/todaytest + name = "First Saturday of December" + begin_week = 1 + begin_month = DECEMBER + begin_weekday = SATURDAY diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm index 091d2064d27..167f9df247b 100644 --- a/code/modules/unit_tests/_unit_tests.dm +++ b/code/modules/unit_tests/_unit_tests.dm @@ -42,6 +42,7 @@ #include "confusion.dm" #include "emoting.dm" #include "heretic_knowledge.dm" +#include "holidays.dm" #include "initialize_sanity.dm" #include "keybinding_init.dm" #include "machine_disassembly.dm" diff --git a/code/modules/unit_tests/holidays.dm b/code/modules/unit_tests/holidays.dm new file mode 100644 index 00000000000..4df5443e2ee --- /dev/null +++ b/code/modules/unit_tests/holidays.dm @@ -0,0 +1,33 @@ +// test Jewish holiday +/datum/unit_test/hanukkah_2123/Run() + var/datum/holiday/hebrew/hanukkah/hanukkah = new + TEST_ASSERT(hanukkah.shouldCelebrate(14, DECEMBER, 2123, 2, TUESDAY), "December 14, 2123 was not Hanukkah.") + +// test Islamic holiday +/datum/unit_test/ramadan_2165/Run() + var/datum/holiday/islamic/ramadan/ramadan = new + TEST_ASSERT(ramadan.shouldCelebrate(6, NOVEMBER, 2165, 1, WEDNESDAY), "November 6, 2165 was not Ramadan.") + +// nth day of week +/datum/unit_test/thanksgiving_2020/Run() + var/datum/holiday/nth_week/thanksgiving/thanksgiving = new + TEST_ASSERT(thanksgiving.shouldCelebrate(26, NOVEMBER, 2020, 4, THURSDAY), "November 26, 2020 was not Thanksgiving.") + +// another nth day of week +/datum/unit_test/indigenous_3683/Run() + var/datum/holiday/nth_week/indigenous/indigenous = new + TEST_ASSERT(indigenous.shouldCelebrate(11, OCTOBER, 3683, 2, MONDAY), "October 11, 3683 was not Indigenous Peoples' Day.") + +// plain old simple holiday +/datum/unit_test/hello_2020/Run() + var/datum/holiday/hello/hello = new + TEST_ASSERT(hello.shouldCelebrate(21, NOVEMBER, 2020, 3, SATURDAY), "November 21, 2020 was not Hello day.") + +// holiday which goes across months +/datum/unit_test/new_year_1983/Run() + var/datum/holiday/new_year/new_year = new + TEST_ASSERT(new_year.shouldCelebrate(2, JANUARY, 1983, 1, SUNDAY), "January 2, 1983 was not New Year.") + +/datum/unit_test/moth_week_2020/Run() + var/datum/holiday/moth/moth = new + TEST_ASSERT(moth.shouldCelebrate(19, JULY, 2020, 3, SATURDAY), "July 19, 2020 was not Moth Week.") diff --git a/tgstation.dme b/tgstation.dme index 129b3c0674c..effecf33d8e 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -2003,6 +2003,7 @@ #include "code\modules\holiday\easter.dm" #include "code\modules\holiday\foreign_calendar.dm" #include "code\modules\holiday\holidays.dm" +#include "code\modules\holiday\nth_week.dm" #include "code\modules\holodeck\area_copy.dm" #include "code\modules\holodeck\computer.dm" #include "code\modules\holodeck\holo_effect.dm"