arrangement.ts 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. import dayjs from "../utils/dayjs";
  2. export enum Holiday {
  3. NY = "New Year's Day,元旦",
  4. S = "Spring Festival,春节",
  5. T = "Tomb-sweeping Day,清明",
  6. L = "Labour Day,劳动节",
  7. D = "Dragon Boat Festival,端午",
  8. N = "National Day,国庆节",
  9. M = "Mid-autumn Festival,中秋",
  10. /** special holidays */
  11. A = "Anti-Fascist 70th Day,中国人民抗日战争暨世界反法西斯战争胜利70周年纪念日",
  12. }
  13. interface DayDetails {
  14. year?: number;
  15. month?: number;
  16. day?: number;
  17. holiday?: Holiday;
  18. dayType?: DayType;
  19. }
  20. enum DayType {
  21. Workday = 1,
  22. Holiday = 2,
  23. InLieu = 3,
  24. }
  25. /** 国务院规定的天数,1999-2025的变化 */
  26. const holidayDays: Record<number, Partial<Record<Holiday, number>>> = {
  27. // 1999 元旦 1 天、春节、劳动节、国庆节放假 3天
  28. 1999: {
  29. [Holiday.NY]: 1,
  30. [Holiday.S]: 3,
  31. [Holiday.L]: 3,
  32. [Holiday.N]: 3,
  33. },
  34. // 2008 劳动节改为 1 天,增加清明、端午、中秋各 1 天
  35. 2008: {
  36. [Holiday.T]: 1,
  37. [Holiday.L]: 1,
  38. [Holiday.D]: 1,
  39. [Holiday.M]: 1,
  40. },
  41. // 2014 春节剔除除夕,改为初一、二、三,依旧 3 天
  42. // 2015 增加 中国人民抗日战争暨世界反法西斯战争胜利70周年纪念日 1 天
  43. 2015: {
  44. [Holiday.A]: 1,
  45. },
  46. // 2025 春节和劳动节 各增加 1 天
  47. 2025: {
  48. [Holiday.S]: 4,
  49. [Holiday.L]: 2,
  50. },
  51. };
  52. class Arrangement {
  53. private dayDetails: DayDetails = {};
  54. public holidays: Record<string, string> = {};
  55. public workdays: Record<string, string> = {};
  56. public inLieuDays: Record<string, string> = {};
  57. y(year: number) {
  58. this.dayDetails.year = year;
  59. return this;
  60. }
  61. /** 查询某年 节假日天数 */
  62. getHolidayDays(year: number, holiday: Holiday): number {
  63. let lastDefinedDays = 0;
  64. // 遍历规则,查找适用于该年份的节日天数
  65. for (const [ruleYear, holidays] of Object.entries(holidayDays)) {
  66. const ruleYearNum = parseInt(ruleYear);
  67. if (ruleYearNum > year) break;
  68. if (holidays[holiday] !== undefined) {
  69. lastDefinedDays = holidays[holiday];
  70. }
  71. }
  72. return lastDefinedDays;
  73. }
  74. mark(holiday: Holiday) {
  75. this.dayDetails.holiday = holiday;
  76. // No functional change, just removing comment blocks per instruction
  77. return this;
  78. }
  79. save(month: number, day: number, dayType: DayType) {
  80. if (!this.dayDetails.year) {
  81. throw new Error("should set year before saving holiday");
  82. }
  83. if (!this.dayDetails.holiday) {
  84. throw new Error("should set holiday before saving holiday");
  85. }
  86. this.dayDetails.month = month;
  87. this.dayDetails.day = day;
  88. this.dayDetails.dayType = dayType;
  89. const date = dayjs(`${this.dayDetails.year}-${month}-${day}`).format("YYYY-MM-DD");
  90. const holidayDays = this.getHolidayDays(this.dayDetails.year, this.dayDetails.holiday);
  91. const holidayDescription = `${this.dayDetails.holiday},${holidayDays}`
  92. if (dayType === DayType.Holiday) {
  93. this.holidays[date] = holidayDescription;
  94. } else if (dayType === DayType.Workday) {
  95. this.workdays[date] = holidayDescription;
  96. } else if (dayType === DayType.InLieu) {
  97. this.inLieuDays[date] = holidayDescription;
  98. }
  99. return this;
  100. }
  101. to(month: number, day: number) {
  102. if (
  103. !this.dayDetails.holiday ||
  104. !this.dayDetails.year ||
  105. !this.dayDetails.month ||
  106. !this.dayDetails.day
  107. ) {
  108. throw new Error("should set year/month/day before saving holiday range");
  109. }
  110. const startDate = dayjs(
  111. `${this.dayDetails.year}-${this.dayDetails.month}-${this.dayDetails.day}`
  112. );
  113. const endDate = dayjs(`${this.dayDetails.year}-${month}-${day}`);
  114. if (endDate.isBefore(startDate) || endDate.isSame(startDate)) {
  115. throw new Error("end date should be after start date");
  116. }
  117. const holidayDays = this.getHolidayDays(this.dayDetails.year, this.dayDetails.holiday);
  118. const holidayDescription = `${this.dayDetails.holiday},${holidayDays}`
  119. const diffDays = endDate.diff(startDate, "day");
  120. for (let i = 1; i <= diffDays; i++) {
  121. const theDate = startDate.add(i, "day").format("YYYY-MM-DD");
  122. if (this.dayDetails.dayType === DayType.Holiday) {
  123. this.holidays[theDate] = holidayDescription;
  124. } else if (this.dayDetails.dayType === DayType.Workday) {
  125. this.workdays[theDate] = holidayDescription;
  126. } else if (this.dayDetails.dayType === DayType.InLieu) {
  127. this.inLieuDays[theDate] = holidayDescription;
  128. }
  129. }
  130. return this;
  131. }
  132. w(month: number, day: number) {
  133. return this.save(month, day, DayType.Workday);
  134. }
  135. r(month: number, day: number) {
  136. return this.save(month, day, DayType.Holiday);
  137. }
  138. i(month: number, day: number) {
  139. return this.save(month, day, DayType.InLieu);
  140. }
  141. /** New Year's Day 元旦 */
  142. ny() {
  143. return this.mark(Holiday.NY);
  144. }
  145. /** Spring Festival 春节 */
  146. s() {
  147. return this.mark(Holiday.S);
  148. }
  149. /** Tomb-sweeping Day 清明 */
  150. t() {
  151. return this.mark(Holiday.T);
  152. }
  153. /** Labour Day 五一 */
  154. l() {
  155. return this.mark(Holiday.L);
  156. }
  157. /** Dragon Boat Festival 端午 */
  158. d() {
  159. return this.mark(Holiday.D);
  160. }
  161. /** National Day 国庆节 */
  162. n() {
  163. return this.mark(Holiday.N);
  164. }
  165. /** Mid-autumn Festival 中秋 */
  166. m() {
  167. return this.mark(Holiday.M);
  168. }
  169. /** Anti-Fascist 70th Day 中国人民抗日战争暨世界反法西斯战争胜利70周年纪念日 */
  170. a() {
  171. return this.mark(Holiday.A);
  172. }
  173. }
  174. export default Arrangement;