feat: add local holidays
diff --git a/erpnext/setup/doctype/holiday_list/holiday_list.js b/erpnext/setup/doctype/holiday_list/holiday_list.js
index ea033c7..dc4cd9f 100644
--- a/erpnext/setup/doctype/holiday_list/holiday_list.js
+++ b/erpnext/setup/doctype/holiday_list/holiday_list.js
@@ -6,13 +6,39 @@
 		if (frm.doc.holidays) {
 			frm.set_value("total_holidays", frm.doc.holidays.length);
 		}
+
+		frm.call("get_supported_countries").then(r => {
+			frm.subdivisions_by_country = r.message;
+			frm.set_df_property("country", "options", Object.keys(r.message));
+
+			if (frm.doc.country) {
+				frm.trigger("set_subdivisions");
+			}
+		});
 	},
 	from_date: function(frm) {
 		if (frm.doc.from_date && !frm.doc.to_date) {
 			var a_year_from_start = frappe.datetime.add_months(frm.doc.from_date, 12);
 			frm.set_value("to_date", frappe.datetime.add_days(a_year_from_start, -1));
 		}
-	}
+	},
+	country: function(frm) {
+		frm.set_value("subdivision", "");
+
+		if (frm.doc.country) {
+			frm.trigger("set_subdivisions");
+		}
+	},
+	set_subdivisions: function(frm) {
+		const subdivisions = frm.subdivisions_by_country[frm.doc.country];
+		if (subdivisions.length > 0) {
+			frm.set_df_property("subdivision", "options", frm.subdivisions_by_country[frm.doc.country]);
+			frm.set_df_property("subdivision", "hidden", 0);
+		} else {
+			frm.set_df_property("subdivision", "options", "");
+			frm.set_df_property("subdivision", "hidden", 1);
+		}
+	},
 });
 
 frappe.tour["Holiday List"] = [
diff --git a/erpnext/setup/doctype/holiday_list/holiday_list.json b/erpnext/setup/doctype/holiday_list/holiday_list.json
index 4bbe6a6..2d24db2 100644
--- a/erpnext/setup/doctype/holiday_list/holiday_list.json
+++ b/erpnext/setup/doctype/holiday_list/holiday_list.json
@@ -1,480 +1,166 @@
 {
- "allow_copy": 0,
- "allow_guest_to_view": 0,
+ "actions": [],
  "allow_import": 1,
  "allow_rename": 1,
  "autoname": "field:holiday_list_name",
- "beta": 0,
  "creation": "2013-01-10 16:34:14",
- "custom": 0,
- "docstatus": 0,
  "doctype": "DocType",
  "document_type": "Setup",
- "editable_grid": 0,
  "engine": "InnoDB",
+ "field_order": [
+  "holiday_list_name",
+  "from_date",
+  "to_date",
+  "column_break_4",
+  "total_holidays",
+  "add_weekly_holidays",
+  "weekly_off",
+  "get_weekly_off_dates",
+  "add_local_holidays",
+  "country",
+  "subdivision",
+  "get_local_holidays",
+  "holidays_section",
+  "holidays",
+  "clear_table",
+  "section_break_9",
+  "color"
+ ],
  "fields": [
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "fieldname": "holiday_list_name",
    "fieldtype": "Data",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
-   "in_list_view": 0,
-   "in_standard_filter": 0,
    "label": "Holiday List Name",
-   "length": 0,
-   "no_copy": 0,
    "oldfieldname": "holiday_list_name",
    "oldfieldtype": "Data",
-   "permlevel": 0,
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
    "reqd": 1,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
    "unique": 1
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "fieldname": "from_date",
    "fieldtype": "Date",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
    "in_list_view": 1,
-   "in_standard_filter": 0,
    "label": "From Date",
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 1,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "reqd": 1
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "fieldname": "to_date",
    "fieldtype": "Date",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
    "in_list_view": 1,
-   "in_standard_filter": 0,
    "label": "To Date",
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 1,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "reqd": 1
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "fieldname": "column_break_4",
-   "fieldtype": "Column Break",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
-   "in_list_view": 0,
-   "in_standard_filter": 0,
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "fieldtype": "Column Break"
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "fieldname": "total_holidays",
    "fieldtype": "Int",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
    "in_list_view": 1,
-   "in_standard_filter": 0,
    "label": "Total Holidays",
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 1,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "read_only": 1
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
    "collapsible": 1,
-   "columns": 0,
+   "depends_on": "eval: doc.from_date && doc.to_date",
    "fieldname": "add_weekly_holidays",
    "fieldtype": "Section Break",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
-   "in_list_view": 0,
-   "in_standard_filter": 0,
-   "label": "Add Weekly Holidays",
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "label": "Add Weekly Holidays"
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "fieldname": "weekly_off",
    "fieldtype": "Select",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
-   "in_list_view": 0,
    "in_standard_filter": 1,
    "label": "Weekly Off",
-   "length": 0,
    "no_copy": 1,
    "options": "\nSunday\nMonday\nTuesday\nWednesday\nThursday\nFriday\nSaturday",
-   "permlevel": 0,
    "print_hide": 1,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 1,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "report_hide": 1
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "fieldname": "get_weekly_off_dates",
    "fieldtype": "Button",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
-   "in_list_view": 0,
-   "in_standard_filter": 0,
    "label": "Add to Holidays",
-   "length": 0,
-   "no_copy": 0,
-   "options": "get_weekly_off_dates",
-   "permlevel": 0,
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "options": "get_weekly_off_dates"
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "fieldname": "holidays_section",
    "fieldtype": "Section Break",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
-   "in_list_view": 0,
-   "in_standard_filter": 0,
-   "label": "Holidays",
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "label": "Holidays"
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "fieldname": "holidays",
    "fieldtype": "Table",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
-   "in_list_view": 0,
-   "in_standard_filter": 0,
    "label": "Holidays",
-   "length": 0,
-   "no_copy": 0,
    "oldfieldname": "holiday_list_details",
    "oldfieldtype": "Table",
-   "options": "Holiday",
-   "permlevel": 0,
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "options": "Holiday"
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "fieldname": "clear_table",
    "fieldtype": "Button",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
-   "in_list_view": 0,
-   "in_standard_filter": 0,
    "label": "Clear Table",
-   "length": 0,
-   "no_copy": 0,
-   "options": "clear_table",
-   "permlevel": 0,
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "options": "clear_table"
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "fieldname": "section_break_9",
-   "fieldtype": "Section Break",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
-   "in_list_view": 0,
-   "in_standard_filter": 0,
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "fieldtype": "Section Break"
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "fieldname": "color",
    "fieldtype": "Color",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
-   "in_list_view": 0,
-   "in_standard_filter": 0,
    "label": "Color",
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 1,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "print_hide": 1
+  },
+  {
+   "fieldname": "country",
+   "fieldtype": "Select",
+   "label": "Country"
+  },
+  {
+   "depends_on": "country",
+   "fieldname": "subdivision",
+   "fieldtype": "Select",
+   "label": "Subdivision"
+  },
+  {
+   "collapsible": 1,
+   "depends_on": "eval: doc.from_date && doc.to_date",
+   "fieldname": "add_local_holidays",
+   "fieldtype": "Section Break",
+   "label": "Add Local Holidays"
+  },
+  {
+   "fieldname": "get_local_holidays",
+   "fieldtype": "Button",
+   "label": "Add to Holidays",
+   "options": "get_local_holidays"
   }
  ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
  "icon": "fa fa-calendar",
  "idx": 1,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2018-07-03 07:22:46.474096",
+ "links": [],
+ "modified": "2023-07-13 13:12:32.082690",
  "modified_by": "Administrator",
  "module": "Setup",
  "name": "Holiday List",
+ "naming_rule": "By fieldname",
  "owner": "Administrator",
  "permissions": [
   {
-   "amend": 0,
-   "cancel": 0,
    "create": 1,
    "delete": 1,
    "email": 1,
-   "export": 0,
-   "if_owner": 0,
-   "import": 0,
-   "permlevel": 0,
    "print": 1,
    "read": 1,
    "report": 1,
    "role": "HR Manager",
-   "set_user_permissions": 0,
    "share": 1,
-   "submit": 0,
    "write": 1
   }
  ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
  "sort_field": "modified",
  "sort_order": "DESC",
- "track_changes": 0,
- "track_seen": 0,
- "track_views": 0
+ "states": []
 }
\ No newline at end of file
diff --git a/erpnext/setup/doctype/holiday_list/holiday_list.py b/erpnext/setup/doctype/holiday_list/holiday_list.py
index 84d0d35..1aec032 100644
--- a/erpnext/setup/doctype/holiday_list/holiday_list.py
+++ b/erpnext/setup/doctype/holiday_list/holiday_list.py
@@ -3,11 +3,14 @@
 
 
 import json
+from datetime import date
 
 import frappe
 from frappe import _, throw
 from frappe.model.document import Document
-from frappe.utils import cint, formatdate, getdate, today
+from frappe.utils import formatdate, getdate, today
+from holidays import country_holidays
+from holidays.utils import list_supported_countries
 
 
 class OverlapError(frappe.ValidationError):
@@ -21,25 +24,59 @@
 
 	@frappe.whitelist()
 	def get_weekly_off_dates(self):
-		self.validate_values()
-		date_list = self.get_weekly_off_date_list(self.from_date, self.to_date)
-		last_idx = max(
-			[cint(d.idx) for d in self.get("holidays")]
-			or [
-				0,
-			]
-		)
-		for i, d in enumerate(date_list):
-			ch = self.append("holidays", {})
-			ch.description = _(self.weekly_off)
-			ch.holiday_date = d
-			ch.weekly_off = 1
-			ch.idx = last_idx + i + 1
-
-	def validate_values(self):
 		if not self.weekly_off:
 			throw(_("Please select weekly off day"))
 
+		existing_holidays = self.get_holidays()
+
+		for d in self.get_weekly_off_date_list(self.from_date, self.to_date):
+			if d in existing_holidays:
+				continue
+
+			self.append("holidays", {"description": _(self.weekly_off), "holiday_date": d, "weekly_off": 1})
+
+		self.sort_holidays()
+
+	@frappe.whitelist()
+	def get_supported_countries(self):
+		return list_supported_countries()
+
+	@frappe.whitelist()
+	def get_local_holidays(self):
+		if not self.country:
+			throw(_("Please select Country"))
+
+		existing_holidays = self.get_holidays()
+		system_language = frappe.db.get_single_value("System Settings", "language")
+		from_date = getdate(self.from_date)
+		to_date = getdate(self.to_date)
+
+		for holiday_date, holiday_name in country_holidays(
+			self.country,
+			subdiv=self.subdivision,
+			years=[from_date.year, to_date.year],
+			language=system_language,
+		).items():
+			if holiday_date in existing_holidays:
+				continue
+
+			if holiday_date < from_date or holiday_date > to_date:
+				continue
+
+			self.append(
+				"holidays", {"description": holiday_name, "holiday_date": holiday_date, "weekly_off": 0}
+			)
+
+		self.sort_holidays()
+
+	def sort_holidays(self):
+		self.holidays.sort(key=lambda x: getdate(x.holiday_date))
+		for i in range(len(self.holidays)):
+			self.holidays[i].idx = i + 1
+
+	def get_holidays(self) -> list[date]:
+		return [getdate(holiday.holiday_date) for holiday in self.holidays]
+
 	def validate_days(self):
 		if getdate(self.from_date) > getdate(self.to_date):
 			throw(_("To Date cannot be before From Date"))
diff --git a/pyproject.toml b/pyproject.toml
index 012ffb1..3e0dfb2 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -13,6 +13,7 @@
     "Unidecode~=1.3.6",
     "barcodenumber~=0.5.0",
     "rapidfuzz~=2.15.0",
+    "holidays~=0.28",
 
     # integration dependencies
     "gocardless-pro~=1.22.0",