diff --git a/programs/GNUmakefile b/programs/GNUmakefile index e16a294..955b3d9 100644 --- a/programs/GNUmakefile +++ b/programs/GNUmakefile @@ -59,7 +59,7 @@ BINDIR := bin PROGRAMS := $(notdir $(wildcard src/*)) # Programs with custom Makefiles (built separately). -CUSTOM_BUILDS := doom fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer volume music video bluetooth terminal klog procmgr calculator desktop login shell rpgdemo paint tcc lua screenshot texteditor mandelbrot printers printd printctl dialogs test_dialogs test_dl libloader libhello crashpad +CUSTOM_BUILDS := doom fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer volume music video bluetooth terminal klog procmgr calculator desktop login shell rpgdemo paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs test_dialogs test_dl libloader libhello crashpad SYSTEM_PROGRAMS := $(filter-out $(CUSTOM_BUILDS),$(PROGRAMS)) # Build targets: system programs go to bin/os/, apps go to bin/apps//. @@ -86,9 +86,9 @@ CONFIGDST := $(patsubst $(CONFIGDIR)/%,$(BINDIR)/config/%,$(CONFIGSRC)) # Common shared assets (wallpapers, etc.) COMMONKEEP := $(BINDIR)/common/.keep -.PHONY: all clean doom fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer volume music video bluetooth terminal klog procmgr calculator login desktop shell rpgdemo paint tcc lua screenshot texteditor mandelbrot printers printd printctl dialogs icons fonts configs bearssl libc tls libjpeg libjpegwrite install-apps libloader libs libhello test_dialogs test_dl crashpad +.PHONY: all clean doom fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer volume music video bluetooth terminal klog procmgr calculator login desktop shell rpgdemo paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs icons fonts configs bearssl libc tls libjpeg libjpegwrite install-apps libloader libs libhello test_dialogs test_dl crashpad -all: bearssl libc libjpeg libjpegwrite tls libloader libs libhello $(TARGETS) fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer volume music video bluetooth terminal klog procmgr calculator doom rpgdemo paint tcc lua screenshot texteditor mandelbrot printers printd printctl dialogs login desktop shell icons fonts install-apps test_dialogs test_dl crashpad $(MANDST) $(WWWDST) $(CA_CERTS) $(CONFIGDST) $(COMMONKEEP) +all: bearssl libc libjpeg libjpegwrite tls libloader libs libhello $(TARGETS) fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer volume music video bluetooth terminal klog procmgr calculator doom rpgdemo paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs login desktop shell icons fonts install-apps test_dialogs test_dl crashpad $(MANDST) $(WWWDST) $(CA_CERTS) $(CONFIGDST) $(COMMONKEEP) # Build BearSSL static library (cross-compiled for freestanding x86_64). BEARSSL_INCLUDES := -isystem $(shell cd .. && pwd)/kernel/freestnd-c-hdrs/x86_64/include -isystem $(abspath include/libc) @@ -117,6 +117,7 @@ libloader: libc libs: libloader $(MAKE) -C libs/libmath $(MAKE) -C libs/libprintersapplet + $(MAKE) -C libs/libtimezonesapplet # Build libhello shared GUI library. libhello: libloader @@ -214,6 +215,10 @@ calculator: libc printers: bearssl libc tls $(MAKE) -C src/printers +# Build time zone standalone GUI tool (depends on libc). +timezone: libc + $(MAKE) -C src/timezone + # Build login screen via its own Makefile (depends on bearssl and libc). login: bearssl libc $(MAKE) -C src/login @@ -299,7 +304,7 @@ test_dialogs: libc libloader dialogs $(MAKE) -C src/test_dialogs # Install app bundles (manifests, icons, data files) into bin/apps//. -install-apps: doom rpgdemo paint spreadsheet wordprocessor weather wikipedia imageviewer fontpreview pdfviewer disks devexplorer installer volume music video bluetooth terminal klog procmgr calculator screenshot texteditor mandelbrot printers crashpad +install-apps: doom rpgdemo paint spreadsheet wordprocessor weather wikipedia imageviewer fontpreview pdfviewer disks devexplorer installer volume music video bluetooth terminal klog procmgr calculator screenshot texteditor mandelbrot printers timezone crashpad ../scripts/install_apps.sh # Copy man pages into bin/man/ so mkramdisk.sh picks them up. @@ -364,6 +369,7 @@ clean: $(MAKE) -C src/procmgr clean $(MAKE) -C src/calculator clean $(MAKE) -C src/printers clean + $(MAKE) -C src/timezone clean $(MAKE) -C src/paint clean $(MAKE) -C src/rpgdemo clean $(MAKE) -C src/screenshot clean @@ -376,5 +382,6 @@ clean: $(MAKE) -C src/libloader clean $(MAKE) -C libs/libmath clean $(MAKE) -C libs/libhello clean + $(MAKE) -C libs/libtimezonesapplet clean $(MAKE) -C src/test_dialogs clean $(MAKE) -C src/test_dl clean diff --git a/programs/data/config/desktop-applets.toml b/programs/data/config/desktop-applets.toml index 74cc874..ce64cb6 100644 --- a/programs/data/config/desktop-applets.toml +++ b/programs/data/config/desktop-applets.toml @@ -3,3 +3,9 @@ label = "Printers" library = "0:/os/libprintersapplet.lib" icon = "0:/icons/printer.svg" enabled = true + +[applets.timezone] +label = "Time Zone" +library = "0:/os/libtimezonesapplet.lib" +icon = "0:/icons/preferences-system-time.svg" +enabled = true diff --git a/programs/data/config/timezonedata.toml b/programs/data/config/timezonedata.toml index 9dbed43..439d287 100644 --- a/programs/data/config/timezonedata.toml +++ b/programs/data/config/timezonedata.toml @@ -1,23 +1,20 @@ # MontaukOS Time Zone Data # Contains all countries, their timezones, and DST rules. -# Generated: 2026-04-24 # ============================================================================= # STRUCTURE # ============================================================================= # [countries.] - Country entry # name: Full country name -# timezones: Array of city/timezone names within that country +# timezones: Object mapping city names to their UTC offsets # dst_enabled: Whether DST is used anywhere in this country -# default_tz: Primary/default timezone (used when only one needed) +# default_tz: Primary/default timezone (city name) # dst_rules: DST configuration (only if dst_enabled = true) # rule: DST rule name (references [dst_rules.]) -# note: Optional note about DST exceptions # ============================================================================= # DST RULES # ============================================================================= -# DST rules define when DST starts and ends and what offset change occurs. [dst_rules.eu] # European Union (EU) DST rules @@ -84,7 +81,6 @@ offset_change = 3600 [dst_rules.canada] # Canada DST rules (follows US with some exceptions) -# Saskatchewan, parts of Ontario/British Columbia: No DST or non-standard start = "2nd-sun-mar 02:00" end = "1st-sun-nov 02:00" offset_change = 3600 @@ -99,98 +95,66 @@ offset_change = 3600 [dst_rules.falklands] # Falkland Islands DST -# Starts: First Sunday in September at 02:00 local -# Ends: First Sunday in April at 02:00 local start = "1st-sun-sep 02:00" end = "1st-sun-apr 02:00" offset_change = 3600 [dst_rules.iran] # Iran DST rules -# Starts: Farvardin 1 (approx March 21) at 00:00 local -# Ends: Mehr 1 (approx September 21) at 00:00 local -# Offset: +1 hour start = "mar-21 00:00" end = "sep-21 00:00" offset_change = 3600 [dst_rules.israel] # Israel DST rules -# Starts: Friday before last Sunday in March -# Ends: Last Sunday in October -# Offset: +1 hour start = "fri-before-last-sun-mar" end = "last-sun-oct" offset_change = 3600 [dst_rules.jordan] # Jordan DST rules -# Starts: Last Thursday in March at 00:00 local -# Ends: Last Friday in October at 00:00 local -# Offset: +1 hour (was +2, changed 2023) start = "last-thu-mar 00:00" end = "last-fri-oct 00:00" offset_change = 3600 [dst_rules.lebanon] # Lebanon DST rules -# Starts: Last Sunday in March at 00:00 local -# Ends: Last Sunday in October at 00:00 local -# Offset: +1 hour start = "last-sun-mar 00:00" end = "last-sun-oct 00:00" offset_change = 3600 [dst_rules.syria] # Syria DST rules -# Starts: Last Friday in March at 00:00 local -# Ends: Last Friday in October at 00:00 local -# Offset: +1 hour start = "last-fri-mar 00:00" end = "last-fri-oct 00:00" offset_change = 3600 [dst_rules.palestine] # Palestine (Gaza/West Bank) DST rules -# Starts: Last Saturday in March at 00:00 local -# Ends: Last Saturday in October at 00:00 local -# Offset: +1 hour start = "last-sat-mar 00:00" end = "last-sat-oct 00:00" offset_change = 3600 [dst_rules.morocco] # Morocco DST rules (complex, with Eid al-Fitr exception) -# Starts: Last Sunday in March at 02:00 local -# Ends: Last Sunday in October at 03:00 local -# Offset: +1 hour (but ends at 03:00 not 02:00) start = "last-sun-mar 02:00" end = "last-sun-oct 03:00" offset_change = 3600 [dst_rules.tunisia] # Tunisia DST rules -# Starts: Last Sunday in March at 02:00 local -# Ends: Last Sunday in October at 03:00 local -# Offset: +1 hour start = "last-sun-mar 02:00" end = "last-sun-oct 03:00" offset_change = 3600 [dst_rules.egypt] # Egypt DST rules -# Starts: Last Friday in April at 00:00 local -# Ends: Last Thursday in October at 23:59 local -# Offset: +1 hour start = "last-fri-apr 00:00" end = "thu-before-last-sat-oct 23:59" offset_change = 3600 [dst_rules.namibia] # Namibia DST rules -# Starts: First Sunday in September at 02:00 local -# Ends: First Sunday in April at 02:00 local -# Offset: +1 hour start = "1st-sun-sep 02:00" end = "1st-sun-apr 02:00" offset_change = 3600 @@ -201,329 +165,329 @@ offset_change = 3600 [countries.DZ] name = "Algeria" -timezones = ["Algiers"] +timezones = { Algiers = "+01:00" } dst_enabled = false default_tz = "Algiers" [countries.AO] name = "Angola" -timezones = ["Luanda"] +timezones = { Luanda = "+01:00" } dst_enabled = false default_tz = "Luanda" [countries.BJ] name = "Benin" -timezones = ["Porto-Novo"] +timezones = { Porto-Novo = "+01:00" } dst_enabled = false default_tz = "Porto-Novo" [countries.BW] name = "Botswana" -timezones = ["Gaborone"] +timezones = { Gaborone = "+02:00" } dst_enabled = false default_tz = "Gaborone" [countries.BF] name = "Burkina Faso" -timezones = ["Ouagadougou"] +timezones = { Ouagadougou = "+00:00" } dst_enabled = false default_tz = "Ouagadougou" [countries.BI] name = "Burundi" -timezones = ["Bujumbura"] +timezones = { Bujumbura = "+02:00" } dst_enabled = false default_tz = "Bujumbura" [countries.CV] name = "Cabo Verde" -timezones = ["Praia"] +timezones = { Praia = "-01:00" } dst_enabled = false default_tz = "Praia" [countries.CM] name = "Cameroon" -timezones = ["Douala"] +timezones = { Douala = "+01:00" } dst_enabled = false default_tz = "Douala" [countries.CF] name = "Central African Republic" -timezones = ["Bangui"] +timezones = { Bangui = "+01:00" } dst_enabled = false default_tz = "Bangui" [countries.TD] name = "Chad" -timezones = ["Ndjamena"] +timezones = { Ndjamena = "+01:00" } dst_enabled = false default_tz = "Ndjamena" [countries.KM] name = "Comoros" -timezones = ["Moroni"] +timezones = { Moroni = "+03:00" } dst_enabled = false default_tz = "Moroni" [countries.CG] name = "Republic of the Congo" -timezones = ["Brazzaville"] +timezones = { Brazzaville = "+01:00" } dst_enabled = false default_tz = "Brazzaville" [countries.CD] name = "Democratic Republic of the Congo" -timezones = ["Kinshasa", "Lubumbashi"] +timezones = { Kinshasa = "+01:00", Lubumbashi = "+02:00" } dst_enabled = false default_tz = "Kinshasa" [countries.CI] name = "Cote d'Ivoire" -timezones = ["Abidjan"] +timezones = { Abidjan = "+00:00" } dst_enabled = false default_tz = "Abidjan" [countries.DJ] name = "Djibouti" -timezones = ["Djibouti"] +timezones = { Djibouti = "+03:00" } dst_enabled = false default_tz = "Djibouti" [countries.EG] name = "Egypt" -timezones = ["Cairo"] +timezones = { Cairo = "+02:00" } dst_enabled = true default_tz = "Cairo" dst_rules.rule = "egypt" [countries.GQ] name = "Equatorial Guinea" -timezones = ["Malabo"] +timezones = { Malabo = "+01:00" } dst_enabled = false default_tz = "Malabo" [countries.ER] name = "Eritrea" -timezones = ["Asmara"] +timezones = { Asmara = "+03:00" } dst_enabled = false default_tz = "Asmara" [countries.SZ] name = "Eswatini" -timezones = ["Mbabane"] +timezones = { Mbabane = "+02:00" } dst_enabled = false default_tz = "Mbabane" [countries.ET] name = "Ethiopia" -timezones = ["Addis Ababa"] +timezones = { "Addis Ababa" = "+03:00" } dst_enabled = false default_tz = "Addis Ababa" [countries.GA] name = "Gabon" -timezones = ["Libreville"] +timezones = { Libreville = "+01:00" } dst_enabled = false default_tz = "Libreville" [countries.GM] name = "Gambia" -timezones = ["Banjul"] +timezones = { Banjul = "+00:00" } dst_enabled = false default_tz = "Banjul" [countries.GH] name = "Ghana" -timezones = ["Accra"] +timezones = { Accra = "+00:00" } dst_enabled = false default_tz = "Accra" [countries.GN] name = "Guinea" -timezones = ["Conakry"] +timezones = { Conakry = "+00:00" } dst_enabled = false default_tz = "Conakry" [countries.GW] name = "Guinea-Bissau" -timezones = ["Bissau"] +timezones = { Bissau = "+00:00" } dst_enabled = false default_tz = "Bissau" [countries.KE] name = "Kenya" -timezones = ["Nairobi"] +timezones = { Nairobi = "+03:00" } dst_enabled = false default_tz = "Nairobi" [countries.LS] name = "Lesotho" -timezones = ["Maseru"] +timezones = { Maseru = "+02:00" } dst_enabled = false default_tz = "Maseru" [countries.LR] name = "Liberia" -timezones = ["Monrovia"] +timezones = { Monrovia = "+00:00" } dst_enabled = false default_tz = "Monrovia" [countries.LY] name = "Libya" -timezones = ["Tripoli"] +timezones = { Tripoli = "+02:00" } dst_enabled = false default_tz = "Tripoli" [countries.MG] name = "Madagascar" -timezones = ["Antananarivo"] +timezones = { Antananarivo = "+03:00" } dst_enabled = false default_tz = "Antananarivo" [countries.MW] name = "Malawi" -timezones = ["Blantyre"] +timezones = { Blantyre = "+02:00" } dst_enabled = false default_tz = "Blantyre" [countries.ML] name = "Mali" -timezones = ["Bamako"] +timezones = { Bamako = "+00:00" } dst_enabled = false default_tz = "Bamako" [countries.MR] name = "Mauritania" -timezones = ["Nouakchott"] +timezones = { Nouakchott = "+00:00" } dst_enabled = false default_tz = "Nouakchott" [countries.MU] name = "Mauritius" -timezones = ["Port Louis"] +timezones = { "Port Louis" = "+04:00" } dst_enabled = false default_tz = "Port Louis" [countries.MA] name = "Morocco" -timezones = ["Casablanca", "El Aaiun"] +timezones = { Casablanca = "+01:00", "El Aaiun" = "+01:00" } dst_enabled = true default_tz = "Casablanca" dst_rules.rule = "morocco" [countries.MZ] name = "Mozambique" -timezones = ["Maputo"] +timezones = { Maputo = "+02:00" } dst_enabled = false default_tz = "Maputo" [countries.NA] name = "Namibia" -timezones = ["Windhoek"] +timezones = { Windhoek = "+02:00" } dst_enabled = true default_tz = "Windhoek" dst_rules.rule = "namibia" [countries.NE] name = "Niger" -timezones = ["Niamey"] +timezones = { Niamey = "+01:00" } dst_enabled = false default_tz = "Niamey" [countries.NG] name = "Nigeria" -timezones = ["Lagos"] +timezones = { Lagos = "+01:00" } dst_enabled = false default_tz = "Lagos" [countries.RW] name = "Rwanda" -timezones = ["Kigali"] +timezones = { Kigali = "+02:00" } dst_enabled = false default_tz = "Kigali" [countries.ST] name = "Sao Tome and Principe" -timezones = ["Sao Tome"] +timezones = { "Sao Tome" = "+00:00" } dst_enabled = false default_tz = "Sao Tome" [countries.SN] name = "Senegal" -timezones = ["Dakar"] +timezones = { Dakar = "+00:00" } dst_enabled = false default_tz = "Dakar" [countries.SC] name = "Seychelles" -timezones = ["Victoria"] +timezones = { Victoria = "+04:00" } dst_enabled = false default_tz = "Victoria" [countries.SL] name = "Sierra Leone" -timezones = ["Freetown"] +timezones = { Freetown = "+00:00" } dst_enabled = false default_tz = "Freetown" [countries.SO] name = "Somalia" -timezones = ["Mogadishu"] +timezones = { Mogadishu = "+03:00" } dst_enabled = false default_tz = "Mogadishu" [countries.ZA] name = "South Africa" -timezones = ["Johannesburg"] +timezones = { Johannesburg = "+02:00" } dst_enabled = false default_tz = "Johannesburg" [countries.SS] name = "South Sudan" -timezones = ["Juba"] +timezones = { Juba = "+02:00" } dst_enabled = false default_tz = "Juba" [countries.SD] name = "Sudan" -timezones = ["Khartoum"] +timezones = { Khartoum = "+02:00" } dst_enabled = false default_tz = "Khartoum" [countries.TZ] name = "Tanzania" -timezones = ["Dar es Salaam"] +timezones = { "Dar es Salaam" = "+03:00" } dst_enabled = false default_tz = "Dar es Salaam" [countries.TG] name = "Togo" -timezones = ["Lome"] +timezones = { Lome = "+00:00" } dst_enabled = false default_tz = "Lome" [countries.TN] name = "Tunisia" -timezones = ["Tunis"] +timezones = { Tunis = "+01:00" } dst_enabled = true default_tz = "Tunis" dst_rules.rule = "tunisia" [countries.UG] name = "Uganda" -timezones = ["Kampala"] +timezones = { Kampala = "+03:00" } dst_enabled = false default_tz = "Kampala" [countries.ZM] name = "Zambia" -timezones = ["Lusaka"] +timezones = { Lusaka = "+02:00" } dst_enabled = false default_tz = "Lusaka" [countries.ZW] name = "Zimbabwe" -timezones = ["Harare"] +timezones = { Harare = "+02:00" } dst_enabled = false default_tz = "Harare" @@ -533,17 +497,17 @@ default_tz = "Harare" [countries.AQ] name = "Antarctica" -timezones = [ - "Casey", - "Davis", - "Dumont d'Urville", - "Mawson", - "Palmer", - "Rothera", - "Syowa", - "Troll", - "Vostok" -] +timezones = { + Casey = "+11:00", + Davis = "+07:00", + "Dumont d'Urville" = "+10:00", + Mawson = "+05:00", + Palmer = "-03:00", + Rothera = "-03:00", + Syowa = "+03:00", + Troll = "+02:00", + Vostok = "+06:00" +} dst_enabled = false default_tz = "Casey" @@ -553,319 +517,315 @@ default_tz = "Casey" [countries.AF] name = "Afghanistan" -timezones = ["Kabul"] +timezones = { Kabul = "+04:30" } dst_enabled = false default_tz = "Kabul" [countries.AM] name = "Armenia" -timezones = ["Yerevan"] +timezones = { Yerevan = "+04:00" } dst_enabled = false default_tz = "Yerevan" [countries.AZ] name = "Azerbaijan" -timezones = ["Baku"] +timezones = { Baku = "+04:00" } dst_enabled = false default_tz = "Baku" [countries.BH] name = "Bahrain" -timezones = ["Manama"] +timezones = { Manama = "+03:00" } dst_enabled = false default_tz = "Manama" [countries.BD] name = "Bangladesh" -timezones = ["Dhaka"] +timezones = { Dhaka = "+06:00" } dst_enabled = false default_tz = "Dhaka" [countries.BT] name = "Bhutan" -timezones = ["Thimphu"] +timezones = { Thimphu = "+06:00" } dst_enabled = false default_tz = "Thimphu" [countries.BN] name = "Brunei" -timezones = ["Bandar Seri Begawan"] +timezones = { "Bandar Seri Begawan" = "+08:00" } dst_enabled = false default_tz = "Bandar Seri Begawan" [countries.KH] name = "Cambodia" -timezones = ["Phnom Penh"] +timezones = { "Phnom Penh" = "+07:00" } dst_enabled = false default_tz = "Phnom Penh" [countries.CN] name = "China" -timezones = ["Shanghai", "Urumqi"] +timezones = { Shanghai = "+08:00", Urumqi = "+06:00" } dst_enabled = false default_tz = "Shanghai" -note = "China uses single national timezone but Xinjiang uses Urumqi" [countries.CY] name = "Cyprus" -timezones = ["Nicosia", "Famagusta"] +timezones = { Nicosia = "+02:00", Famagusta = "+02:00" } dst_enabled = true default_tz = "Nicosia" -note = "Northern Cyprus uses different DST rules" +dst_rules.rule = "eu" [countries.GE] name = "Georgia" -timezones = ["Tbilisi"] +timezones = { Tbilisi = "+04:00" } dst_enabled = false default_tz = "Tbilisi" [countries.HK] name = "Hong Kong" -timezones = ["Hong Kong"] +timezones = { "Hong Kong" = "+08:00" } dst_enabled = false default_tz = "Hong Kong" -note = "DST ended 2019" [countries.IN] name = "India" -timezones = ["Kolkata"] +timezones = { Kolkata = "+05:30" } dst_enabled = false default_tz = "Kolkata" [countries.ID] name = "Indonesia" -timezones = ["Jakarta", "Pontianak", "Makassar", "Jayapura"] +timezones = { Jakarta = "+07:00", Pontianak = "+07:00", Makassar = "+08:00", Jayapura = "+09:00" } dst_enabled = false default_tz = "Jakarta" [countries.IR] name = "Iran" -timezones = ["Tehran"] +timezones = { Tehran = "+03:30" } dst_enabled = true default_tz = "Tehran" dst_rules.rule = "iran" [countries.IQ] name = "Iraq" -timezones = ["Baghdad"] +timezones = { Baghdad = "+03:00" } dst_enabled = false default_tz = "Baghdad" [countries.IL] name = "Israel" -timezones = ["Jerusalem"] +timezones = { Jerusalem = "+02:00" } dst_enabled = true default_tz = "Jerusalem" dst_rules.rule = "israel" [countries.JP] name = "Japan" -timezones = ["Tokyo"] +timezones = { Tokyo = "+09:00" } dst_enabled = false default_tz = "Tokyo" [countries.JO] name = "Jordan" -timezones = ["Amman"] +timezones = { Amman = "+03:00" } dst_enabled = true default_tz = "Amman" dst_rules.rule = "jordan" [countries.KZ] name = "Kazakhstan" -timezones = ["Almaty", "Qyzylorda", "Aqtobe", "Atyrau", "Oral"] +timezones = { Almaty = "+06:00", Qyzylorda = "+06:00", Aqtobe = "+05:00", Atyrau = "+05:00", Oral = "+05:00" } dst_enabled = false default_tz = "Almaty" [countries.KW] name = "Kuwait" -timezones = ["Kuwait"] +timezones = { Kuwait = "+03:00" } dst_enabled = false default_tz = "Kuwait" [countries.KG] name = "Kyrgyzstan" -timezones = ["Bishkek"] +timezones = { Bishkek = "+06:00" } dst_enabled = false default_tz = "Bishkek" [countries.LA] name = "Laos" -timezones = ["Vientiane"] +timezones = { Vientiane = "+07:00" } dst_enabled = false default_tz = "Vientiane" [countries.LB] name = "Lebanon" -timezones = ["Beirut"] +timezones = { Beirut = "+02:00" } dst_enabled = true default_tz = "Beirut" dst_rules.rule = "lebanon" [countries.MO] name = "Macau" -timezones = ["Macau"] +timezones = { Macau = "+08:00" } dst_enabled = false default_tz = "Macau" -note = "DST ended" [countries.MY] name = "Malaysia" -timezones = ["Kuala Lumpur", "Kuching"] +timezones = { "Kuala Lumpur" = "+08:00", Kuching = "+08:00" } dst_enabled = false default_tz = "Kuala Lumpur" [countries.MV] name = "Maldives" -timezones = ["Male"] +timezones = { Male = "+05:00" } dst_enabled = false default_tz = "Male" [countries.MN] name = "Mongolia" -timezones = ["Ulaanbaatar", "Hovd", "Choibalsan"] +timezones = { Ulaanbaatar = "+08:00", Hovd = "+07:00", Choibalsan = "+08:00" } dst_enabled = false default_tz = "Ulaanbaatar" [countries.MM] name = "Myanmar" -timezones = ["Yangon"] +timezones = { Yangon = "+06:30" } dst_enabled = false default_tz = "Yangon" [countries.NP] name = "Nepal" -timezones = ["Kathmandu"] +timezones = { Kathmandu = "+05:45" } dst_enabled = false default_tz = "Kathmandu" [countries.KP] name = "North Korea" -timezones = ["Pyongyang"] +timezones = { Pyongyang = "+09:00" } dst_enabled = false default_tz = "Pyongyang" [countries.MK] name = "North Macedonia" -timezones = ["Skopje"] +timezones = { Skopje = "+01:00" } dst_enabled = true default_tz = "Skopje" dst_rules.rule = "eu" [countries.OM] name = "Oman" -timezones = ["Muscat"] +timezones = { Muscat = "+04:00" } dst_enabled = false default_tz = "Muscat" [countries.PK] name = "Pakistan" -timezones = ["Karachi"] +timezones = { Karachi = "+05:00" } dst_enabled = false default_tz = "Karachi" [countries.PS] name = "Palestine" -timezones = ["Gaza", "Hebron"] +timezones = { Gaza = "+02:00", Hebron = "+02:00" } dst_enabled = true default_tz = "Gaza" dst_rules.rule = "palestine" [countries.PH] name = "Philippines" -timezones = ["Manila"] +timezones = { Manila = "+08:00" } dst_enabled = false default_tz = "Manila" [countries.QA] name = "Qatar" -timezones = ["Doha"] +timezones = { Doha = "+03:00" } dst_enabled = false default_tz = "Doha" [countries.SA] name = "Saudi Arabia" -timezones = ["Riyadh"] +timezones = { Riyadh = "+03:00" } dst_enabled = false default_tz = "Riyadh" [countries.SG] name = "Singapore" -timezones = ["Singapore"] +timezones = { Singapore = "+08:00" } dst_enabled = false default_tz = "Singapore" [countries.KR] name = "South Korea" -timezones = ["Seoul"] +timezones = { Seoul = "+09:00" } dst_enabled = false default_tz = "Seoul" [countries.LK] name = "Sri Lanka" -timezones = ["Colombo"] +timezones = { Colombo = "+05:30" } dst_enabled = false default_tz = "Colombo" [countries.SY] name = "Syria" -timezones = ["Damascus"] +timezones = { Damascus = "+03:00" } dst_enabled = true default_tz = "Damascus" dst_rules.rule = "syria" [countries.TW] name = "Taiwan" -timezones = ["Taipei"] +timezones = { Taipei = "+08:00" } dst_enabled = false default_tz = "Taipei" -note = "DST ended 1979" [countries.TJ] name = "Tajikistan" -timezones = ["Dushanbe"] +timezones = { Dushanbe = "+05:00" } dst_enabled = false default_tz = "Dushanbe" [countries.TH] name = "Thailand" -timezones = ["Bangkok"] +timezones = { Bangkok = "+07:00" } dst_enabled = false default_tz = "Bangkok" [countries.TL] name = "Timor-Leste" -timezones = ["Dili"] +timezones = { Dili = "+09:00" } dst_enabled = false default_tz = "Dili" [countries.TM] name = "Turkmenistan" -timezones = ["Ashgabat"] +timezones = { Ashgabat = "+05:00" } dst_enabled = false default_tz = "Ashgabat" [countries.AE] name = "United Arab Emirates" -timezones = ["Dubai"] +timezones = { Dubai = "+04:00" } dst_enabled = false default_tz = "Dubai" [countries.UZ] name = "Uzbekistan" -timezones = ["Tashkent", "Samarkand"] +timezones = { Tashkent = "+05:00", Samarkand = "+05:00" } dst_enabled = false default_tz = "Tashkent" [countries.VN] name = "Vietnam" -timezones = ["Ho Chi Minh City"] +timezones = { "Ho Chi Minh City" = "+07:00" } dst_enabled = false default_tz = "Ho Chi Minh City" [countries.YE] name = "Yemen" -timezones = ["Sanaa"] +timezones = { Sanaa = "+03:00" } dst_enabled = false default_tz = "Sanaa" @@ -875,332 +835,328 @@ default_tz = "Sanaa" [countries.AL] name = "Albania" -timezones = ["Tirane"] +timezones = { Tirane = "+01:00" } dst_enabled = true default_tz = "Tirane" dst_rules.rule = "eu" [countries.AD] name = "Andorra" -timezones = ["Andorra"] +timezones = { Andorra = "+01:00" } dst_enabled = true default_tz = "Andorra" dst_rules.rule = "eu" [countries.AT] name = "Austria" -timezones = ["Vienna"] +timezones = { Vienna = "+01:00" } dst_enabled = true default_tz = "Vienna" dst_rules.rule = "eu" [countries.BY] name = "Belarus" -timezones = ["Minsk"] +timezones = { Minsk = "+03:00" } dst_enabled = false default_tz = "Minsk" [countries.BE] name = "Belgium" -timezones = ["Brussels"] +timezones = { Brussels = "+01:00" } dst_enabled = true default_tz = "Brussels" dst_rules.rule = "eu" [countries.BA] name = "Bosnia and Herzegovina" -timezones = ["Sarajevo"] +timezones = { Sarajevo = "+01:00" } dst_enabled = true default_tz = "Sarajevo" dst_rules.rule = "eu" [countries.BG] name = "Bulgaria" -timezones = ["Sofia"] +timezones = { Sofia = "+02:00" } dst_enabled = true default_tz = "Sofia" dst_rules.rule = "eu" [countries.HR] name = "Croatia" -timezones = ["Zagreb"] +timezones = { Zagreb = "+01:00" } dst_enabled = true default_tz = "Zagreb" dst_rules.rule = "eu" [countries.CZ] name = "Czech Republic" -timezones = ["Prague"] +timezones = { Prague = "+01:00" } dst_enabled = true default_tz = "Prague" dst_rules.rule = "eu" [countries.DK] name = "Denmark" -timezones = ["Copenhagen"] +timezones = { Copenhagen = "+01:00" } dst_enabled = true default_tz = "Copenhagen" dst_rules.rule = "eu" [countries.EE] name = "Estonia" -timezones = ["Tallinn"] +timezones = { Tallinn = "+02:00" } dst_enabled = true default_tz = "Tallinn" dst_rules.rule = "eu" [countries.FI] name = "Finland" -timezones = ["Helsinki"] +timezones = { Helsinki = "+02:00" } dst_enabled = true default_tz = "Helsinki" dst_rules.rule = "eu" [countries.FR] name = "France" -timezones = ["Paris"] +timezones = { Paris = "+01:00" } dst_enabled = true default_tz = "Paris" dst_rules.rule = "eu" [countries.DE] name = "Germany" -timezones = ["Berlin"] +timezones = { Berlin = "+01:00" } dst_enabled = true default_tz = "Berlin" dst_rules.rule = "eu" [countries.GR] name = "Greece" -timezones = ["Athens"] +timezones = { Athens = "+02:00" } dst_enabled = true default_tz = "Athens" dst_rules.rule = "eu" [countries.HU] name = "Hungary" -timezones = ["Budapest"] +timezones = { Budapest = "+01:00" } dst_enabled = true default_tz = "Budapest" dst_rules.rule = "eu" [countries.IS] name = "Iceland" -timezones = ["Reykjavik"] +timezones = { Reykjavik = "+00:00" } dst_enabled = false default_tz = "Reykjavik" [countries.IE] name = "Ireland" -timezones = ["Dublin"] +timezones = { Dublin = "+00:00" } dst_enabled = true default_tz = "Dublin" dst_rules.rule = "eu" [countries.IT] name = "Italy" -timezones = ["Rome"] +timezones = { Rome = "+01:00" } dst_enabled = true default_tz = "Rome" dst_rules.rule = "eu" [countries.XK] name = "Kosovo" -timezones = ["Pristina"] +timezones = { Pristina = "+01:00" } dst_enabled = true default_tz = "Pristina" dst_rules.rule = "eu" [countries.LV] name = "Latvia" -timezones = ["Riga"] +timezones = { Riga = "+02:00" } dst_enabled = true default_tz = "Riga" dst_rules.rule = "eu" [countries.LI] name = "Liechtenstein" -timezones = ["Vaduz"] +timezones = { Vaduz = "+01:00" } dst_enabled = true default_tz = "Vaduz" dst_rules.rule = "eu" [countries.LT] name = "Lithuania" -timezones = ["Vilnius"] +timezones = { Vilnius = "+02:00" } dst_enabled = true default_tz = "Vilnius" dst_rules.rule = "eu" [countries.LU] name = "Luxembourg" -timezones = ["Luxembourg"] +timezones = { Luxembourg = "+01:00" } dst_enabled = true default_tz = "Luxembourg" dst_rules.rule = "eu" [countries.MT] name = "Malta" -timezones = ["Valletta"] +timezones = { Valletta = "+01:00" } dst_enabled = true default_tz = "Valletta" dst_rules.rule = "eu" [countries.MD] name = "Moldova" -timezones = ["Chisinau"] +timezones = { Chisinau = "+02:00" } dst_enabled = true default_tz = "Chisinau" dst_rules.rule = "eu" [countries.MC] name = "Monaco" -timezones = ["Monaco"] +timezones = { Monaco = "+01:00" } dst_enabled = true default_tz = "Monaco" dst_rules.rule = "eu" [countries.ME] name = "Montenegro" -timezones = ["Podgorica"] +timezones = { Podgorica = "+01:00" } dst_enabled = true default_tz = "Podgorica" dst_rules.rule = "eu" [countries.NL] name = "Netherlands" -timezones = ["Amsterdam"] +timezones = { Amsterdam = "+01:00" } dst_enabled = true default_tz = "Amsterdam" dst_rules.rule = "eu" [countries.NO] name = "Norway" -timezones = ["Oslo"] +timezones = { Oslo = "+01:00" } dst_enabled = true default_tz = "Oslo" dst_rules.rule = "eu" [countries.PL] name = "Poland" -timezones = ["Warsaw"] +timezones = { Warsaw = "+01:00" } dst_enabled = true default_tz = "Warsaw" dst_rules.rule = "eu" [countries.PT] name = "Portugal" -timezones = ["Lisbon", "Funchal", "Ponta Delgada"] +timezones = { Lisbon = "+00:00", Funchal = "+00:00", "Ponta Delgada" = "-01:00" } dst_enabled = true default_tz = "Lisbon" dst_rules.rule = "eu" -note = "Madeira follows EU rules; Azores has separate rules" [countries.RO] name = "Romania" -timezones = ["Bucharest"] +timezones = { Bucharest = "+02:00" } dst_enabled = true default_tz = "Bucharest" dst_rules.rule = "eu" [countries.RU] name = "Russia" -timezones = [ - "Kaliningrad", - "Moscow", - "Volgograd", - "Samara", - "Yekaterinburg", - "Omsk", - "Novosibirsk", - "Krasnoyarsk", - "Irkutsk", - "Yakutsk", - "Vladivostok", - "Magadan", - "Sakhalin", - "Srednekolymsk", - "Petropavlovsk-Kamchatsky", - "Anadyr" -] +timezones = { + Kaliningrad = "+02:00", + Moscow = "+03:00", + Volgograd = "+03:00", + Samara = "+04:00", + Yekaterinburg = "+05:00", + Omsk = "+06:00", + Novosibirsk = "+07:00", + Krasnoyarsk = "+07:00", + Irkutsk = "+08:00", + Yakutsk = "+09:00", + Vladivostok = "+10:00", + Magadan = "+11:00", + Sakhalin = "+11:00", + "Srednekolymsk" = "+12:00", + "Petropavlovsk-Kamchatsky" = "+12:00", + Anadyr = "+12:00" +} dst_enabled = false default_tz = "Moscow" -note = "Russia abolished DST in 2014; all zones use permanent offsets" [countries.SM] name = "San Marino" -timezones = ["San Marino"] +timezones = { "San Marino" = "+01:00" } dst_enabled = true default_tz = "San Marino" dst_rules.rule = "eu" [countries.RS] name = "Serbia" -timezones = ["Belgrade"] +timezones = { Belgrade = "+01:00" } dst_enabled = true default_tz = "Belgrade" dst_rules.rule = "eu" [countries.SK] name = "Slovakia" -timezones = ["Bratislava"] +timezones = { Bratislava = "+01:00" } dst_enabled = true default_tz = "Bratislava" dst_rules.rule = "eu" [countries.SI] name = "Slovenia" -timezones = ["Ljubljana"] +timezones = { Ljubljana = "+01:00" } dst_enabled = true default_tz = "Ljubljana" dst_rules.rule = "eu" [countries.ES] name = "Spain" -timezones = ["Madrid", "Ceuta", "Santa Cruz de Tenerife"] +timezones = { Madrid = "+01:00", Ceuta = "+01:00", "Santa Cruz de Tenerife" = "+00:00" } dst_enabled = true default_tz = "Madrid" dst_rules.rule = "eu" -note = "Canary Islands follow EU rules" [countries.SE] name = "Sweden" -timezones = ["Stockholm"] +timezones = { Stockholm = "+01:00" } dst_enabled = true default_tz = "Stockholm" dst_rules.rule = "eu" [countries.CH] name = "Switzerland" -timezones = ["Zurich"] +timezones = { Zurich = "+01:00" } dst_enabled = true default_tz = "Zurich" dst_rules.rule = "eu" [countries.TR] name = "Turkey" -timezones = ["Istanbul"] +timezones = { Istanbul = "+03:00" } dst_enabled = false default_tz = "Istanbul" -note = "Turkey abolished DST in 2016; uses permanent +3 offset" [countries.UA] name = "Ukraine" -timezones = ["Kiev", "Uzhgorod", "Zaporozhye"] +timezones = { Kiev = "+02:00", Uzhgorod = "+02:00", Zaporozhye = "+02:00" } dst_enabled = true default_tz = "Kiev" dst_rules.rule = "eu" [countries.GB] name = "United Kingdom" -timezones = ["London"] +timezones = { London = "+00:00" } dst_enabled = true default_tz = "London" dst_rules.rule = "eu" [countries.VA] name = "Vatican City" -timezones = ["Vatican"] +timezones = { Vatican = "+01:00" } dst_enabled = true default_tz = "Vatican" dst_rules.rule = "eu" @@ -1211,199 +1167,196 @@ dst_rules.rule = "eu" [countries.AG] name = "Antigua and Barbuda" -timezones = ["St. John's"] +timezones = { "St. John's" = "-04:00" } dst_enabled = false default_tz = "St. John's" [countries.BS] name = "Bahamas" -timezones = ["Nassau"] +timezones = { Nassau = "-05:00" } dst_enabled = true default_tz = "Nassau" dst_rules.rule = "us" [countries.BB] name = "Barbados" -timezones = ["Bridgetown"] +timezones = { Bridgetown = "-04:00" } dst_enabled = false default_tz = "Bridgetown" -note = "DST ended 1980" [countries.BZ] name = "Belize" -timezones = ["Belmopan"] +timezones = { Belmopan = "-06:00" } dst_enabled = false default_tz = "Belmopan" [countries.CA] name = "Canada" -timezones = [ - "St. John's", - "Halifax", - "Charlottetown", - "Toronto", - "Iqaluit", - "Winnipeg", - "Regina", - "Edmonton", - "Vancouver", - "Dawson Creek", - "Whitehorse", - "Dawson" -] +timezones = { + "St. John's" = "-03:30", + Halifax = "-04:00", + Charlottetown = "-04:00", + Toronto = "-05:00", + Iqaluit = "-05:00", + Winnipeg = "-06:00", + Regina = "-06:00", + Edmonton = "-07:00", + Vancouver = "-08:00", + "Dawson Creek" = "-07:00", + Whitehorse = "-07:00", + Dawson = "-07:00" +} dst_enabled = true default_tz = "Toronto" dst_rules.rule = "canada" -note = "Several regions don't observe DST: Saskatchewan (most), parts of Ontario, British Columbia" [countries.CR] name = "Costa Rica" -timezones = ["San Jose"] +timezones = { "San Jose" = "-06:00" } dst_enabled = false default_tz = "San Jose" [countries.CU] name = "Cuba" -timezones = ["Havana"] +timezones = { Havana = "-05:00" } dst_enabled = true default_tz = "Havana" [countries.DM] name = "Dominica" -timezones = ["Roseau"] +timezones = { Roseau = "-04:00" } dst_enabled = false default_tz = "Roseau" [countries.DO] name = "Dominican Republic" -timezones = ["Santo Domingo"] +timezones = { "Santo Domingo" = "-04:00" } dst_enabled = false default_tz = "Santo Domingo" [countries.SV] name = "El Salvador" -timezones = ["San Salvador"] +timezones = { "San Salvador" = "-06:00" } dst_enabled = false default_tz = "San Salvador" [countries.GD] name = "Grenada" -timezones = ["St. George's"] +timezones = { "St. George's" = "-04:00" } dst_enabled = false default_tz = "St. George's" [countries.GT] name = "Guatemala" -timezones = ["Guatemala City"] +timezones = { "Guatemala City" = "-06:00" } dst_enabled = false default_tz = "Guatemala City" [countries.HT] name = "Haiti" -timezones = ["Port-au-Prince"] +timezones = { "Port-au-Prince" = "-05:00" } dst_enabled = true default_tz = "Port-au-Prince" [countries.HN] name = "Honduras" -timezones = ["Tegucigalpa"] +timezones = { Tegucigalpa = "-06:00" } dst_enabled = false default_tz = "Tegucigalpa" [countries.JM] name = "Jamaica" -timezones = ["Kingston"] +timezones = { Kingston = "-05:00" } dst_enabled = false default_tz = "Kingston" [countries.MX] name = "Mexico" -timezones = [ - "Mexico City", - "Cancun", - "Merida", - "Monterrey", - "Tijuana", - "Chihuahua", - "Hermosillo", - "Mazatlan" -] +timezones = { + "Mexico City" = "-06:00", + Cancun = "-05:00", + Merida = "-06:00", + Monterrey = "-06:00", + Tijuana = "-08:00", + Chihuahua = "-07:00", + Hermosillo = "-07:00", + Mazatlan = "-07:00" +} dst_enabled = true default_tz = "Mexico City" -note = "Border regions follow US DST; most of Mexico follows Mexico-specific rules" +dst_rules.rule = "mexico" [countries.NI] name = "Nicaragua" -timezones = ["Managua"] +timezones = { Managua = "-06:00" } dst_enabled = false default_tz = "Managua" [countries.PA] name = "Panama" -timezones = ["Panama City"] +timezones = { "Panama City" = "-05:00" } dst_enabled = false default_tz = "Panama City" [countries.KN] name = "Saint Kitts and Nevis" -timezones = ["Basseterre"] +timezones = { Basseterre = "-04:00" } dst_enabled = false default_tz = "Basseterre" [countries.LC] name = "Saint Lucia" -timezones = ["Castries"] +timezones = { Castries = "-04:00" } dst_enabled = false default_tz = "Castries" [countries.VC] name = "Saint Vincent and the Grenadines" -timezones = ["Kingstown"] +timezones = { Kingstown = "-04:00" } dst_enabled = false default_tz = "Kingstown" [countries.TT] name = "Trinidad and Tobago" -timezones = ["Port of Spain"] +timezones = { "Port of Spain" = "-04:00" } dst_enabled = false default_tz = "Port of Spain" [countries.US] name = "United States" -timezones = [ - "New York", - "Detroit", - "Louisville", - "Indianapolis", - "Vincennes", - "Winamac", - "Marengo", - "Petersburg", - "Knox", - "Tell City", - "Posen", - "Chicago", - "Menominee", - "Center", - "New Salem", - "Beulah", - "Denver", - "Boise", - "Phoenix", - "Los Angeles", - "Anchorage", - "Juneau", - "Sitka", - "Metlakatla", - "Yakutat", - "Nome", - "Adak", - "Honolulu" -] +timezones = { + "New York" = "-05:00", + Detroit = "-05:00", + Louisville = "-05:00", + Indianapolis = "-05:00", + Vincennes = "-05:00", + Winamac = "-05:00", + Marengo = "-05:00", + Petersburg = "-05:00", + Knox = "-05:00", + "Tell City" = "-05:00", + Posen = "-05:00", + Chicago = "-06:00", + Menominee = "-06:00", + Center = "-06:00", + "New Salem" = "-06:00", + Beulah = "-06:00", + Denver = "-07:00", + Boise = "-07:00", + Phoenix = "-07:00", + "Los Angeles" = "-08:00", + Anchorage = "-09:00", + Juneau = "-09:00", + Sitka = "-09:00", + Metlakatla = "-09:00", + Yakutat = "-09:00", + Nome = "-09:00", + Adak = "-10:00", + Honolulu = "-10:00" +} dst_enabled = true default_tz = "New York" dst_rules.rule = "us" -note = "Arizona and Hawaii don't observe DST; some Indiana counties non-standard" # ============================================================================= # OCEANIA @@ -1411,65 +1364,65 @@ note = "Arizona and Hawaii don't observe DST; some Indiana counties non-standard [countries.AU] name = "Australia" -timezones = [ - "Lord Howe Island", - "Hobart", - "Currie", - "Melbourne", - "Sydney", - "Broken Hill", - "Darwin", - "Perth", - "Eucla", - "Adelaide", - "Brisbane", - "Lindeman" -] +timezones = { + "Lord Howe Island" = "+10:30", + Hobart = "+10:00", + Currie = "+10:00", + Melbourne = "+10:00", + Sydney = "+10:00", + "Broken Hill" = "+09:30", + Darwin = "+09:30", + Perth = "+08:00", + Eucla = "+08:45", + Adelaide = "+09:30", + Brisbane = "+10:00", + Lindeman = "+10:00" +} dst_enabled = true default_tz = "Sydney" -note = "Queensland, Northern Territory, Western Australia don't observe DST" +dst_rules.rule = "au" [countries.FJ] name = "Fiji" -timezones = ["Suva"] +timezones = { Suva = "+12:00" } dst_enabled = true default_tz = "Suva" dst_rules.rule = "nz" [countries.NZ] name = "New Zealand" -timezones = ["Auckland", "Chatham Islands"] +timezones = { Auckland = "+12:00", "Chatham Islands" = "+12:45" } dst_enabled = true default_tz = "Auckland" dst_rules.rule = "nz" [countries.PG] name = "Papua New Guinea" -timezones = ["Port Moresby"] +timezones = { "Port Moresby" = "+10:00" } dst_enabled = false default_tz = "Port Moresby" [countries.WS] name = "Samoa" -timezones = ["Apia"] +timezones = { Apia = "+13:00" } dst_enabled = false default_tz = "Apia" [countries.SB] name = "Solomon Islands" -timezones = ["Honiara"] +timezones = { Honiara = "+11:00" } dst_enabled = false default_tz = "Honiara" [countries.TO] name = "Tonga" -timezones = ["Nukualofa"] +timezones = { Nukualofa = "+13:00" } dst_enabled = false default_tz = "Nukualofa" [countries.VU] name = "Vanuatu" -timezones = ["Port Vila"] +timezones = { "Port Vila" = "+11:00" } dst_enabled = false default_tz = "Port Vila" @@ -1479,107 +1432,105 @@ default_tz = "Port Vila" [countries.AR] name = "Argentina" -timezones = [ - "Buenos Aires", - "Cordoba", - "Salta", - "Jujuy", - "La Rioja", - "San Juan", - "Mendoza", - "San Luis", - "Tucuman", - "Catamarca", - "ComodRivadavia", - "Ushuaia" -] +timezones = { + "Buenos Aires" = "-03:00", + Cordoba = "-03:00", + Salta = "-03:00", + Jujuy = "-03:00", + "La Rioja" = "-03:00", + "San Juan" = "-03:00", + Mendoza = "-03:00", + "San Luis" = "-03:00", + Tucuman = "-03:00", + Catamarca = "-03:00", + ComodRivadavia = "-03:00", + Ushuaia = "-03:00" +} dst_enabled = false default_tz = "Buenos Aires" [countries.BO] name = "Bolivia" -timezones = ["La Paz"] +timezones = { "La Paz" = "-04:00" } dst_enabled = false default_tz = "La Paz" [countries.BR] name = "Brazil" -timezones = [ - "Araguaina", - "Bahia", - "Belem", - "Boa Vista", - "Campo Grande", - "Cuiaba", - "Fortaleza", - "Maceio", - "Manaus", - "Noronha", - "Recife", - "Rio Branco", - "Santarem", - "Sao Paulo" -] +timezones = { + Araguaina = "-03:00", + Bahia = "-03:00", + Belem = "-03:00", + "Boa Vista" = "-04:00", + "Campo Grande" = "-04:00", + Cuiaba = "-04:00", + Fortaleza = "-03:00", + Maceio = "-03:00", + Manaus = "-04:00", + Noronha = "-02:00", + Recife = "-03:00", + "Rio Branco" = "-05:00", + Santarem = "-03:00", + "Sao Paulo" = "-03:00" +} dst_enabled = true default_tz = "Sao Paulo" dst_rules.rule = "br" -note = "Some states don't observe DST: Amazonas, Para, Roraima, Amapa, Acre" [countries.CL] name = "Chile" -timezones = ["Santiago", "Easter Island"] +timezones = { Santiago = "-04:00", "Easter Island" = "-06:00" } dst_enabled = true default_tz = "Santiago" dst_rules.rule = "cl" -note = "Easter Island has separate rules" [countries.CO] name = "Colombia" -timezones = ["Bogota"] +timezones = { Bogota = "-05:00" } dst_enabled = false default_tz = "Bogota" [countries.EC] name = "Ecuador" -timezones = ["Guayaquil", "Galapagos"] +timezones = { Guayaquil = "-05:00", Galapagos = "-06:00" } dst_enabled = false default_tz = "Guayaquil" [countries.GY] name = "Guyana" -timezones = ["Georgetown"] +timezones = { Georgetown = "-04:00" } dst_enabled = false default_tz = "Georgetown" [countries.PY] name = "Paraguay" -timezones = ["Asuncion"] +timezones = { Asuncion = "-04:00" } dst_enabled = true default_tz = "Asuncion" dst_rules.rule = "paraguay" [countries.PE] name = "Peru" -timezones = ["Lima"] +timezones = { Lima = "-05:00" } dst_enabled = false default_tz = "Lima" [countries.SR] name = "Suriname" -timezones = ["Paramaribo"] +timezones = { Paramaribo = "-03:00" } dst_enabled = false default_tz = "Paramaribo" [countries.UY] name = "Uruguay" -timezones = ["Montevideo"] +timezones = { Montevideo = "-03:00" } dst_enabled = true default_tz = "Montevideo" dst_rules.rule = "eu" [countries.VE] name = "Venezuela" -timezones = ["Caracas"] +timezones = { Caracas = "-04:00" } dst_enabled = false default_tz = "Caracas" @@ -1589,116 +1540,115 @@ default_tz = "Caracas" [countries.AI] name = "Anguilla" -timezones = ["The Valley"] +timezones = { "The Valley" = "-04:00" } dst_enabled = false default_tz = "The Valley" [countries.AW] name = "Aruba" -timezones = ["Oranjestad"] +timezones = { Oranjestad = "-04:00" } dst_enabled = false default_tz = "Oranjestad" [countries.BM] name = "Bermuda" -timezones = ["Hamilton"] +timezones = { Hamilton = "-04:00" } dst_enabled = true default_tz = "Hamilton" dst_rules.rule = "us" [countries.VG] name = "British Virgin Islands" -timezones = ["Road Town"] +timezones = { "Road Town" = "-04:00" } dst_enabled = false default_tz = "Road Town" [countries.KY] name = "Cayman Islands" -timezones = ["George Town"] +timezones = { "George Town" = "-05:00" } dst_enabled = false default_tz = "George Town" [countries.CW] name = "Curacao" -timezones = ["Willemstad"] +timezones = { Willemstad = "-04:00" } dst_enabled = false default_tz = "Willemstad" [countries.FK] name = "Falkland Islands" -timezones = ["Stanley"] +timezones = { Stanley = "-03:00" } dst_enabled = true default_tz = "Stanley" dst_rules.rule = "falklands" [countries.GL] name = "Greenland" -timezones = [ - "Danmarkshavn", - "Ittoqqortoormiit", - "Thule", - "Nuuk" -] +timezones = { + Danmarkshavn = "+00:00", + Ittoqqortoormiit = "-01:00", + Thule = "-04:00", + Nuuk = "-02:00" +} dst_enabled = true default_tz = "Nuuk" -note = "Most of Greenland doesn't observe DST; Ittoqqortoormiit follows EU rules" [countries.GP] name = "Guadeloupe" -timezones = ["Basse-Terre"] +timezones = { "Basse-Terre" = "-04:00" } dst_enabled = true default_tz = "Basse-Terre" dst_rules.rule = "eu" [countries.MQ] name = "Martinique" -timezones = ["Fort-de-France"] +timezones = { "Fort-de-France" = "-04:00" } dst_enabled = true default_tz = "Fort-de-France" dst_rules.rule = "eu" [countries.MS] name = "Montserrat" -timezones = ["Brades"] +timezones = { Brades = "-04:00" } dst_enabled = false default_tz = "Brades" [countries.PR] name = "Puerto Rico" -timezones = ["San Juan"] +timezones = { "San Juan" = "-04:00" } dst_enabled = false default_tz = "San Juan" [countries.BL] name = "Saint Barthelemy" -timezones = ["Gustavia"] +timezones = { Gustavia = "-04:00" } dst_enabled = true default_tz = "Gustavia" dst_rules.rule = "eu" [countries.MF] name = "Saint Martin" -timezones = ["Marigot"] +timezones = { Marigot = "-04:00" } dst_enabled = true default_tz = "Marigot" dst_rules.rule = "eu" [countries.SX] name = "Sint Maarten" -timezones = ["Philipsburg"] +timezones = { Philipsburg = "-04:00" } dst_enabled = false default_tz = "Philipsburg" [countries.TC] name = "Turks and Caicos Islands" -timezones = ["Cockburn Town"] +timezones = { "Cockburn Town" = "-05:00" } dst_enabled = true default_tz = "Cockburn Town" dst_rules.rule = "us" [countries.VI] name = "US Virgin Islands" -timezones = ["Charlotte Amalie"] +timezones = { "Charlotte Amalie" = "-04:00" } dst_enabled = false default_tz = "Charlotte Amalie" @@ -1708,131 +1658,131 @@ default_tz = "Charlotte Amalie" [countries.AS] name = "American Samoa" -timezones = ["Pago Pago"] +timezones = { "Pago Pago" = "-11:00" } dst_enabled = false default_tz = "Pago Pago" [countries.IO] name = "British Indian Ocean Territory" -timezones = ["Diego Garcia"] +timezones = { "Diego Garcia" = "+06:00" } dst_enabled = false default_tz = "Diego Garcia" [countries.CX] name = "Christmas Island" -timezones = ["Flying Fish Cove"] +timezones = { "Flying Fish Cove" = "+07:00" } dst_enabled = false default_tz = "Flying Fish Cove" [countries.CC] name = "Cocos (Keeling) Islands" -timezones = ["West Island"] +timezones = { "West Island" = "+06:30" } dst_enabled = false default_tz = "West Island" [countries.CK] name = "Cook Islands" -timezones = ["Avarua"] +timezones = { Avarua = "-10:00" } dst_enabled = false default_tz = "Avarua" [countries.GG] name = "Guernsey" -timezones = ["St. Peter Port"] +timezones = { "St. Peter Port" = "+00:00" } dst_enabled = true default_tz = "St. Peter Port" dst_rules.rule = "eu" [countries.IM] name = "Isle of Man" -timezones = ["Douglas"] +timezones = { Douglas = "+00:00" } dst_enabled = true default_tz = "Douglas" dst_rules.rule = "eu" [countries.JE] name = "Jersey" -timezones = ["St. Helier"] +timezones = { "St. Helier" = "+00:00" } dst_enabled = true default_tz = "St. Helier" dst_rules.rule = "eu" [countries.NC] name = "New Caledonia" -timezones = ["Noumea"] +timezones = { Noumea = "+11:00" } dst_enabled = false default_tz = "Noumea" [countries.NF] name = "Norfolk Island" -timezones = ["Kingston"] +timezones = { Kingston = "+11:00" } dst_enabled = false default_tz = "Kingston" [countries.NU] name = "Niue" -timezones = ["Alofi"] +timezones = { Alofi = "-11:00" } dst_enabled = false default_tz = "Alofi" [countries.PN] name = "Pitcairn Islands" -timezones = ["Adamstown"] +timezones = { Adamstown = "-08:00" } dst_enabled = false default_tz = "Adamstown" [countries.PF] name = "French Polynesia" -timezones = ["Papeete", "Taiohae", "Gambier"] +timezones = { Papeete = "-10:00", Taiohae = "-09:30", Gambier = "-09:00" } dst_enabled = false default_tz = "Papeete" -[countries.PG_guam] +[countries.GU] name = "Guam" -timezones = ["Hagatna"] +timezones = { Hagatna = "+10:00" } dst_enabled = false default_tz = "Hagatna" [countries.PW] name = "Palau" -timezones = ["Ngerulmud"] +timezones = { Ngerulmud = "+09:00" } dst_enabled = false default_tz = "Ngerulmud" [countries.PM] name = "Saint Pierre and Miquelon" -timezones = ["St. Pierre"] +timezones = { "St. Pierre" = "-03:00" } dst_enabled = true default_tz = "St. Pierre" dst_rules.rule = "us" [countries.SJ] name = "Svalbard and Jan Mayen" -timezones = ["Longyearbyen"] +timezones = { Longyearbyen = "+01:00" } dst_enabled = true default_tz = "Longyearbyen" dst_rules.rule = "eu" [countries.SH] name = "Saint Helena" -timezones = ["Jamestown"] +timezones = { Jamestown = "+00:00" } dst_enabled = false default_tz = "Jamestown" [countries.TK] name = "Tokelau" -timezones = ["Fakaofo"] +timezones = { Fakaofo = "+13:00" } dst_enabled = false default_tz = "Fakaofo" [countries.TV] name = "Tuvalu" -timezones = ["Funafuti"] +timezones = { Funafuti = "+12:00" } dst_enabled = false default_tz = "Funafuti" [countries.WF] name = "Wallis and Futuna" -timezones = ["Mata-Utu"] +timezones = { "Mata-Utu" = "+12:00" } dst_enabled = false default_tz = "Mata-Utu" diff --git a/programs/libs/libtimezonesapplet/Makefile b/programs/libs/libtimezonesapplet/Makefile new file mode 100644 index 0000000..84b800a --- /dev/null +++ b/programs/libs/libtimezonesapplet/Makefile @@ -0,0 +1,69 @@ +# Makefile for the Time Zone shared desktop applet +# Copyright (c) 2026 Daniel Hammer + +MAKEFLAGS += -rR +.SUFFIXES: + +TOOLCHAIN_PREFIX := $(shell cd ../../.. && pwd)/toolchain/local/bin/x86_64-elf- +ifneq ($(wildcard $(TOOLCHAIN_PREFIX)gcc),) + CXX := $(TOOLCHAIN_PREFIX)g++ + LD := $(TOOLCHAIN_PREFIX)ld +else + CXX := g++ + LD := ld +endif + +OUTDIR := ../../bin/os +OBJDIR := obj +TARGET := $(OUTDIR)/libtimezonesapplet.lib + +CXXFLAGS := \ + -std=gnu++20 \ + -g -O2 -pipe \ + -Wall \ + -Wextra \ + -nostdinc \ + -ffreestanding \ + -fno-stack-protector \ + -fno-stack-check \ + -fPIC \ + -fno-rtti \ + -fno-exceptions \ + -ffunction-sections \ + -fdata-sections \ + -m64 \ + -march=x86-64 \ + -mno-red-zone \ + -mcmodel=small \ + -MMD -MP \ + -I ../../include \ + -isystem ../../include/libc \ + -isystem ../../../kernel/freestnd-c-hdrs/x86_64/include \ + -isystem ../../../kernel/freestnd-cxx-hdrs/x86_64/include + +LDFLAGS := \ + -shared \ + --build-id=none \ + --hash-style=sysv \ + -m elf_x86_64 \ + -z max-page-size=0x1000 + +SRCS := src/libtimezonesapplet.cpp +OBJS := $(addprefix $(OBJDIR)/,$(SRCS:.cpp=.o)) + +.PHONY: all clean + +all: $(TARGET) + +$(TARGET): $(OBJS) Makefile + mkdir -p $(OUTDIR) + $(LD) $(LDFLAGS) $(OBJS) -o $@ + +$(OBJDIR)/%.o: %.cpp Makefile + mkdir -p $(dir $@) + $(CXX) $(CXXFLAGS) -c $< -o $@ + +-include $(OBJS:.o=.d) + +clean: + rm -rf $(OBJDIR) $(TARGET) diff --git a/programs/libs/libtimezonesapplet/obj/src/libtimezonesapplet.d b/programs/libs/libtimezonesapplet/obj/src/libtimezonesapplet.d new file mode 100644 index 0000000..779da65 --- /dev/null +++ b/programs/libs/libtimezonesapplet/obj/src/libtimezonesapplet.d @@ -0,0 +1,6 @@ +obj/src/libtimezonesapplet.o: src/libtimezonesapplet.cpp \ + ../../include/gui/desktop_applet.hpp ../../include/montauk/syscall.h \ + ../../include/Api/Syscall.hpp +../../include/gui/desktop_applet.hpp: +../../include/montauk/syscall.h: +../../include/Api/Syscall.hpp: diff --git a/programs/libs/libtimezonesapplet/obj/src/libtimezonesapplet.o b/programs/libs/libtimezonesapplet/obj/src/libtimezonesapplet.o new file mode 100644 index 0000000..150d1ec Binary files /dev/null and b/programs/libs/libtimezonesapplet/obj/src/libtimezonesapplet.o differ diff --git a/programs/libs/libtimezonesapplet/src/libtimezonesapplet.cpp b/programs/libs/libtimezonesapplet/src/libtimezonesapplet.cpp new file mode 100644 index 0000000..1fd80ea --- /dev/null +++ b/programs/libs/libtimezonesapplet/src/libtimezonesapplet.cpp @@ -0,0 +1,28 @@ +/* + * libtimezonesapplet.cpp + * Shared desktop applet that launches the Time Zone configuration tool + * Copyright (c) 2026 Daniel Hammer +*/ + +#include +#include + +using gui::desktop_applet::Descriptor; +using gui::desktop_applet::Host; + +static constexpr Descriptor kAppletDescriptor = { + gui::desktop_applet::ABI_VERSION, + gui::desktop_applet::CAP_SYSTEM_CONFIGURATION, + "timezone", + "Time Zone", + "0:/icons/preferences-system-time.svg", +}; + +extern "C" const Descriptor* desktop_applet_query_v1() { + return &kAppletDescriptor; +} + +extern "C" bool desktop_applet_open_v1(const Host* host) { + (void)host; + return montauk::spawn("0:/apps/timezone/timezone.elf") >= 0; +} diff --git a/programs/src/desktop/apps/settings/display.cpp b/programs/src/desktop/apps/settings/display.cpp index 575692c..8e7d8d8 100644 --- a/programs/src/desktop/apps/settings/display.cpp +++ b/programs/src/desktop/apps/settings/display.cpp @@ -53,32 +53,6 @@ void settings_draw_display(Canvas& c, SettingsState* st) { mtk::widget_state(s.ui_scale == 1), theme); mtk::draw_button(c, {bx + (sbw + 8) * 2, y, sbw, btn_h}, "Large", mtk::BUTTON_SECONDARY, mtk::widget_state(s.ui_scale == 2), theme); - y += btn_h + 20; - - mtk::draw_separator(c, x, y, c.w - 2 * x, theme); - y += 16; - - c.text(x, y + 6, "UTC Offset", theme.text); - Rect minus_btn = {bx, y, 36, btn_h}; - Rect plus_btn = {bx + 36 + 90, y, 36, btn_h}; - mtk::draw_button(c, minus_btn, "-", mtk::BUTTON_SECONDARY, mtk::widget_state(), theme); - mtk::draw_button(c, plus_btn, "+", mtk::BUTTON_SECONDARY, mtk::widget_state(), theme); - - int off = s.tz_offset_minutes; - int off_h = off / 60; - int off_m = off % 60; - if (off_m < 0) off_m = -off_m; - - char tz_label[16]; - if (off_m) { - snprintf(tz_label, sizeof(tz_label), "UTC%s%d:%02d", off_h >= 0 ? "+" : "", off_h, off_m); - } else { - snprintf(tz_label, sizeof(tz_label), "UTC%s%d", off_h >= 0 ? "+" : "", off_h); - } - - int tw = text_width(tz_label); - int label_w = 90; - c.text(bx + 36 + (label_w - tw) / 2, y + 6, tz_label, theme.text); } bool settings_handle_display_click(SettingsState* st, int mx, int cy) { @@ -142,26 +116,6 @@ bool settings_handle_display_click(SettingsState* st, int mx, int cy) { settings_persist(st); return true; } - y += btn_h + 20 + 16; - - Rect minus_btn = {bx, y, 36, btn_h}; - Rect plus_btn = {bx + 36 + 90, y, 36, btn_h}; - if (minus_btn.contains(mx, cy)) { - if (s.tz_offset_minutes > -720) { - s.tz_offset_minutes -= 60; - montauk::settz(s.tz_offset_minutes); - settings_persist_tz(st); - } - return true; - } - if (plus_btn.contains(mx, cy)) { - if (s.tz_offset_minutes < 840) { - s.tz_offset_minutes += 60; - montauk::settz(s.tz_offset_minutes); - settings_persist_tz(st); - } - return true; - } return false; } diff --git a/programs/src/desktop/apps/settings/persistence.cpp b/programs/src/desktop/apps/settings/persistence.cpp index b0210d6..cad9590 100644 --- a/programs/src/desktop/apps/settings/persistence.cpp +++ b/programs/src/desktop/apps/settings/persistence.cpp @@ -45,12 +45,4 @@ void settings_persist(SettingsState* st) { doc.destroy(); } -void settings_persist_tz(SettingsState* st) { - montauk::toml::Doc doc; - doc.init(); - montauk::config::set_int(&doc, "timezone.offset_minutes", st->desktop->settings.tz_offset_minutes); - montauk::config::save("timezone", &doc); - doc.destroy(); -} - } // namespace settings_app diff --git a/programs/src/desktop/apps/settings/settings_internal.hpp b/programs/src/desktop/apps/settings/settings_internal.hpp index ebee8e1..c607c4b 100644 --- a/programs/src/desktop/apps/settings/settings_internal.hpp +++ b/programs/src/desktop/apps/settings/settings_internal.hpp @@ -91,6 +91,5 @@ bool settings_handle_display_click(SettingsState* st, int mx, int cy); void settings_draw_about(Canvas& c, SettingsState* st); void settings_persist(SettingsState* st); -void settings_persist_tz(SettingsState* st); } // namespace settings_app diff --git a/programs/src/timezone/Makefile b/programs/src/timezone/Makefile new file mode 100644 index 0000000..0c62c9b --- /dev/null +++ b/programs/src/timezone/Makefile @@ -0,0 +1,78 @@ +# Makefile for timezone (standalone Time Zone configuration app) on MontaukOS +# Copyright (c) 2026 Daniel Hammer + +MAKEFLAGS += -rR +.SUFFIXES: + +TOOLCHAIN_PREFIX := $(shell cd ../../.. && pwd)/toolchain/local/bin/x86_64-elf- +ifneq ($(wildcard $(TOOLCHAIN_PREFIX)gcc),) + CXX := $(TOOLCHAIN_PREFIX)g++ +else + CXX := g++ +endif + +PROG_INC := ../../include +LINK_LD := ../../link.ld +BINDIR := ../../bin +OBJDIR := obj +LIBDIR := ../../lib + +CXXFLAGS := \ + -std=gnu++20 \ + -g -O2 -pipe \ + -Wall \ + -Wextra \ + -Wno-unused-parameter \ + -Wno-unused-function \ + -nostdinc \ + -ffreestanding \ + -fno-stack-protector \ + -fno-stack-check \ + -fno-PIC \ + -fno-rtti \ + -fno-exceptions \ + -ffunction-sections \ + -fdata-sections \ + -m64 \ + -march=x86-64 \ + -msse \ + -msse2 \ + -mno-red-zone \ + -mcmodel=small \ + -MMD -MP \ + -I $(PROG_INC) \ + -isystem $(PROG_INC)/libc \ + -isystem ../../../kernel/freestnd-c-hdrs/x86_64/include \ + -isystem ../../../kernel/freestnd-cxx-hdrs/x86_64/include + +LDFLAGS := \ + -nostdlib \ + -static \ + -Wl,--build-id=none \ + -Wl,--gc-sections \ + -Wl,-m,elf_x86_64 \ + -z max-page-size=0x1000 \ + -T $(LINK_LD) + +SRCS := main.cpp stb_truetype_impl.cpp font_data.cpp +OBJS := $(addprefix $(OBJDIR)/,$(SRCS:.cpp=.o)) + +TARGET := $(BINDIR)/apps/timezone/timezone.elf +LIBS := $(LIBDIR)/libc/liblibc.a + +.PHONY: all clean + +all: $(TARGET) + +$(TARGET): $(OBJS) $(LINK_LD) Makefile $(LIBS) + mkdir -p $(BINDIR)/apps/timezone + $(CXX) $(CXXFLAGS) $(LDFLAGS) $(OBJS) $(LIBS) -o $@ + +$(OBJDIR)/%.o: %.cpp Makefile + mkdir -p $(dir $@) + $(CXX) $(CXXFLAGS) -c $< -o $@ + +-include $(OBJS:.o=.d) + +clean: + rm -rf $(OBJDIR) $(TARGET) diff --git a/programs/src/timezone/font_data.cpp b/programs/src/timezone/font_data.cpp new file mode 100644 index 0000000..a591f69 --- /dev/null +++ b/programs/src/timezone/font_data.cpp @@ -0,0 +1 @@ +#include "../desktop/font_data.cpp" diff --git a/programs/src/timezone/main.cpp b/programs/src/timezone/main.cpp new file mode 100644 index 0000000..3661b8e --- /dev/null +++ b/programs/src/timezone/main.cpp @@ -0,0 +1,832 @@ +/* + * main.cpp + * MontaukOS Time Zone configuration applet + * Copyright (c) 2026 Daniel Hammer + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" { +#include +} + +using namespace gui; + +static constexpr int INIT_W = 640; +static constexpr int INIT_H = 460; +static constexpr int HEADER_H = 14; +static constexpr int FOOTER_H = 58; +static constexpr int PAD = 16; +static constexpr int GAP = 12; +static constexpr int LABEL_H = 24; +static constexpr int ROW_H = 30; +static constexpr int MAX_COUNTRIES = 320; + +struct CountryEntry { + char key[64]; // Full TOML table key, e.g. countries.NO + char code[24]; // Country code suffix, e.g. NO + const char* name; + const char* default_tz; + const char* note; + bool dst_enabled; + char dst_rule[32]; + montauk::toml::Value* zones; +}; + +static WsWindow g_win; +static montauk::toml::Doc g_data; +static bool g_data_loaded = false; +static CountryEntry g_countries[MAX_COUNTRIES]; +static int g_country_count = 0; + +static int g_selected_country = 0; +static int g_selected_zone = 0; +static int g_saved_country = 0; +static int g_saved_zone = 0; +static int g_country_scroll = 0; +static int g_zone_scroll = 0; +static int g_focus_pane = 0; // 0=country list, 1=zone list +static int g_mouse_x = -1; +static int g_mouse_y = -1; +static bool g_dirty = false; +static char g_status[160] = {}; +static uint64_t g_status_time = 0; +static Color g_accent = colors::ACCENT; + +static void safe_copy(char* dst, int cap, const char* src) { + montauk::strncpy(dst, src ? src : "", cap); +} + +static int str_compare_ci(const char* a, const char* b) { + while (*a && *b) { + char ca = (*a >= 'A' && *a <= 'Z') ? (*a + 32) : *a; + char cb = (*b >= 'A' && *b <= 'Z') ? (*b + 32) : *b; + if (ca != cb) return ca - cb; + a++; + b++; + } + return (unsigned char)*a - (unsigned char)*b; +} + +static void set_status(const char* msg) { + safe_copy(g_status, sizeof(g_status), msg); + g_status_time = montauk::get_milliseconds(); +} + +static bool status_visible() { + return g_status[0] && (montauk::get_milliseconds() - g_status_time < 5000); +} + +static mtk::Theme app_theme() { + mtk::Theme theme = mtk::make_theme(g_accent); + theme.disabled_bg = Color::from_rgb(0xE4, 0xE4, 0xE4); + theme.disabled_fg = Color::from_rgb(0x9A, 0x9A, 0x9A); + return theme; +} + +static void apply_scale(int scale) { + switch (scale) { + case 0: + fonts::UI_SIZE = 14; + fonts::TITLE_SIZE = 14; + fonts::TERM_SIZE = 14; + fonts::LARGE_SIZE = 22; + break; + case 2: + fonts::UI_SIZE = 22; + fonts::TITLE_SIZE = 22; + fonts::TERM_SIZE = 22; + fonts::LARGE_SIZE = 34; + break; + default: + fonts::UI_SIZE = 18; + fonts::TITLE_SIZE = 18; + fonts::TERM_SIZE = 18; + fonts::LARGE_SIZE = 28; + break; + } +} + +static void load_accent() { + g_accent = colors::ACCENT; + + char user[64] = {}; + if (montauk::getuser(user, sizeof(user)) > 0 && user[0]) { + auto doc = montauk::config::load_user(user, "desktop"); + int64_t accent = doc.get_int("appearance.accent_color", -1); + if (accent >= 0) { + g_accent = Color::from_rgb((uint8_t)((accent >> 16) & 0xFF), + (uint8_t)((accent >> 8) & 0xFF), + (uint8_t)(accent & 0xFF)); + doc.destroy(); + return; + } + doc.destroy(); + } + + auto doc = montauk::config::load("desktop"); + int64_t accent = doc.get_int("appearance.accent_color", -1); + if (accent >= 0) { + g_accent = Color::from_rgb((uint8_t)((accent >> 16) & 0xFF), + (uint8_t)((accent >> 8) & 0xFF), + (uint8_t)(accent & 0xFF)); + } + doc.destroy(); +} + +static void build_key(char* out, int cap, const char* base, const char* field) { + snprintf(out, cap, "%s.%s", base, field); +} + +static const char* zone_name(const CountryEntry& country, int index) { + if (!country.zones || index < 0 || index >= country.zones->array.count) return ""; + montauk::toml::Value* item = country.zones->array.items[index]; + if (!item || item->type != montauk::toml::Type::String) return ""; + + if (country.zones->type == montauk::toml::Type::Array) + return item->str ? item->str : ""; + + char prefix[96]; + snprintf(prefix, sizeof(prefix), "%s.timezones.", country.key); + return (item->key && montauk::starts_with(item->key, prefix)) + ? item->key + montauk::slen(prefix) + : ""; +} + +static int zone_count(const CountryEntry& country) { + return country.zones ? country.zones->array.count : 0; +} + +static int find_zone_index(const CountryEntry& country, const char* zone) { + if (!zone || !zone[0]) return -1; + int count = zone_count(country); + for (int i = 0; i < count; i++) { + if (montauk::streq(zone_name(country, i), zone)) + return i; + } + return -1; +} + +static int default_zone_index(const CountryEntry& country) { + int index = find_zone_index(country, country.default_tz); + return index >= 0 ? index : 0; +} + +static const char* zone_offset_string(const CountryEntry& country, int index) { + if (!country.zones || country.zones->type != montauk::toml::Type::Table) + return ""; + if (index < 0 || index >= country.zones->array.count) + return ""; + + montauk::toml::Value* item = country.zones->array.items[index]; + if (!item || item->type != montauk::toml::Type::String) + return ""; + return item->str ? item->str : ""; +} + +static bool parse_offset_minutes(const char* value, int* out_minutes) { + if (!value || !value[0]) return false; + + int sign = 1; + int pos = 0; + if (value[pos] == '+') { + pos++; + } else if (value[pos] == '-') { + sign = -1; + pos++; + } + + if (value[pos] < '0' || value[pos] > '9') return false; + int hours = 0; + while (value[pos] >= '0' && value[pos] <= '9') { + hours = hours * 10 + (value[pos] - '0'); + pos++; + } + + int minutes = 0; + if (value[pos] == ':') { + pos++; + if (value[pos] < '0' || value[pos] > '9') return false; + while (value[pos] >= '0' && value[pos] <= '9') { + minutes = minutes * 10 + (value[pos] - '0'); + pos++; + } + } + + if (value[pos] != '\0') return false; + if (hours > 24 || minutes > 59) return false; + + if (out_minutes) *out_minutes = sign * (hours * 60 + minutes); + return true; +} + +static int visible_rows(const Rect& list) { + int rows = list.h / ROW_H; + return rows > 0 ? rows : 1; +} + +static void clamp_scrolls() { + Rect country_list = {}; + Rect zone_list = {}; + int left_w = gui_min(250, (g_win.width - PAD * 2 - GAP) / 2); + int list_y = HEADER_H + LABEL_H; + int list_h = gui_max(g_win.height - HEADER_H - FOOTER_H - LABEL_H - PAD, ROW_H); + country_list = {PAD, list_y, left_w, list_h}; + zone_list = {PAD + left_w + GAP, list_y, + g_win.width - PAD * 2 - GAP - left_w, list_h}; + + int country_max = gui_max(0, g_country_count - visible_rows(country_list)); + if (g_country_scroll < 0) g_country_scroll = 0; + if (g_country_scroll > country_max) g_country_scroll = country_max; + + int zcount = (g_selected_country >= 0 && g_selected_country < g_country_count) + ? zone_count(g_countries[g_selected_country]) : 0; + int zone_max = gui_max(0, zcount - visible_rows(zone_list)); + if (g_zone_scroll < 0) g_zone_scroll = 0; + if (g_zone_scroll > zone_max) g_zone_scroll = zone_max; +} + +static void ensure_country_visible() { + Rect list = {PAD, HEADER_H + LABEL_H, 250, + gui_max(g_win.height - HEADER_H - FOOTER_H - LABEL_H - PAD, ROW_H)}; + int rows = visible_rows(list); + if (g_selected_country < g_country_scroll) + g_country_scroll = g_selected_country; + else if (g_selected_country >= g_country_scroll + rows) + g_country_scroll = g_selected_country - rows + 1; +} + +static void ensure_zone_visible() { + int left_w = gui_min(250, (g_win.width - PAD * 2 - GAP) / 2); + Rect list = {PAD + left_w + GAP, HEADER_H + LABEL_H, + g_win.width - PAD * 2 - GAP - left_w, + gui_max(g_win.height - HEADER_H - FOOTER_H - LABEL_H - PAD, ROW_H)}; + int rows = visible_rows(list); + if (g_selected_zone < g_zone_scroll) + g_zone_scroll = g_selected_zone; + else if (g_selected_zone >= g_zone_scroll + rows) + g_zone_scroll = g_selected_zone - rows + 1; +} + +static int find_country_by_code(const char* code) { + if (!code || !code[0]) return -1; + for (int i = 0; i < g_country_count; i++) { + if (montauk::streq(g_countries[i].code, code)) + return i; + } + return -1; +} + +static void sort_countries() { + for (int i = 1; i < g_country_count; i++) { + CountryEntry tmp = g_countries[i]; + int j = i - 1; + while (j >= 0 && str_compare_ci(tmp.name, g_countries[j].name) < 0) { + g_countries[j + 1] = g_countries[j]; + j--; + } + g_countries[j + 1] = tmp; + } +} + +static void load_timezone_data() { + g_data = montauk::config::load("timezonedata"); + g_data_loaded = true; + g_country_count = 0; + + static constexpr const char* prefix = "countries."; + int prefix_len = montauk::slen(prefix); + + for (int i = 0; i < g_data.entries.count && g_country_count < MAX_COUNTRIES; i++) { + montauk::toml::Value* item = g_data.entries.items[i]; + if (!item || item->type != montauk::toml::Type::Table || !item->key) continue; + if (!montauk::starts_with(item->key, prefix)) continue; + + CountryEntry entry = {}; + safe_copy(entry.key, sizeof(entry.key), item->key); + safe_copy(entry.code, sizeof(entry.code), item->key + prefix_len); + + char key[128]; + build_key(key, sizeof(key), item->key, "name"); + entry.name = g_data.get_string(key, entry.code); + + build_key(key, sizeof(key), item->key, "timezones"); + entry.zones = g_data.get(key); + if (!entry.zones || entry.zones->array.count <= 0) continue; + if (entry.zones->type != montauk::toml::Type::Array && + entry.zones->type != montauk::toml::Type::Table) { + continue; + } + + build_key(key, sizeof(key), item->key, "default_tz"); + entry.default_tz = g_data.get_string(key, zone_name(entry, 0)); + + build_key(key, sizeof(key), item->key, "dst_enabled"); + entry.dst_enabled = g_data.get_bool(key, false); + + build_key(key, sizeof(key), item->key, "dst_rules.rule"); + safe_copy(entry.dst_rule, sizeof(entry.dst_rule), g_data.get_string(key, "")); + + build_key(key, sizeof(key), item->key, "note"); + entry.note = g_data.get_string(key, ""); + + g_countries[g_country_count++] = entry; + } + + sort_countries(); +} + +static void load_saved_selection() { + char saved_code[48] = "NO"; + char saved_zone[96] = ""; + + auto cfg = montauk::config::load("timezone"); + safe_copy(saved_code, sizeof(saved_code), + cfg.get_string("timezone.country_code", saved_code)); + safe_copy(saved_zone, sizeof(saved_zone), + cfg.get_string("timezone.zone", "")); + cfg.destroy(); + + int country = find_country_by_code(saved_code); + if (country < 0) country = find_country_by_code("NO"); + if (country < 0 && g_country_count > 0) country = 0; + + g_selected_country = country >= 0 ? country : 0; + if (g_country_count > 0) { + CountryEntry& c = g_countries[g_selected_country]; + int zone = find_zone_index(c, saved_zone); + if (zone < 0) zone = default_zone_index(c); + g_selected_zone = zone; + } else { + g_selected_zone = 0; + } + + g_saved_country = g_selected_country; + g_saved_zone = g_selected_zone; + g_dirty = false; + ensure_country_visible(); + ensure_zone_visible(); + clamp_scrolls(); +} + +static void fit_text(char* out, int cap, const char* text, int max_w) { + safe_copy(out, cap, text); + if (text_width(out) <= max_w) return; + + static constexpr const char* ellipsis = "..."; + int len = montauk::slen(out); + while (len > 0) { + out[--len] = '\0'; + char candidate[160]; + snprintf(candidate, sizeof(candidate), "%s%s", out, ellipsis); + if (text_width(candidate) <= max_w) { + safe_copy(out, cap, candidate); + return; + } + } + safe_copy(out, cap, ellipsis); +} + +static void format_offset(int minutes, char* out, int cap) { + int h = minutes / 60; + int m = minutes % 60; + if (m < 0) m = -m; + if (m) snprintf(out, cap, "UTC%s%d:%02d", h >= 0 ? "+" : "", h, m); + else snprintf(out, cap, "UTC%s%d", h >= 0 ? "+" : "", h); +} + +static bool selected_offset_minutes(int* out_minutes) { + if (g_selected_country < 0 || g_selected_country >= g_country_count) + return false; + + CountryEntry& country = g_countries[g_selected_country]; + char key[128]; + + const char* offset = zone_offset_string(country, g_selected_zone); + if (parse_offset_minutes(offset, out_minutes)) + return true; + + build_key(key, sizeof(key), country.key, "offsets"); + montauk::toml::Value* offsets = g_data.get_array(key); + if (offsets && g_selected_zone >= 0 && g_selected_zone < offsets->array.count) { + montauk::toml::Value* item = offsets->array.items[g_selected_zone]; + if (item && item->type == montauk::toml::Type::Int) { + if (out_minutes) *out_minutes = (int)item->ival; + return true; + } + } + + build_key(key, sizeof(key), country.key, "offset_minutes"); + montauk::toml::Value* single = g_data.get(key); + if (single && single->type == montauk::toml::Type::Int) { + if (out_minutes) *out_minutes = (int)single->ival; + return true; + } + + return false; +} + +static int dst_offset_change_minutes(const CountryEntry& country) { + if (!country.dst_enabled || !country.dst_rule[0]) + return 0; + + char key[96]; + snprintf(key, sizeof(key), "dst_rules.%s.offset_change", country.dst_rule); + return (int)(g_data.get_int(key, 0) / 60); +} + +static void save_selection() { + if (g_country_count <= 0) return; + CountryEntry& country = g_countries[g_selected_country]; + const char* zone = zone_name(country, g_selected_zone); + + auto cfg = montauk::config::load("timezone"); + montauk::config::set_string(&cfg, "timezone.country_code", country.code); + montauk::config::set_string(&cfg, "timezone.country", country.name); + montauk::config::set_string(&cfg, "timezone.zone", zone); + montauk::config::set_bool(&cfg, "timezone.dst_enabled", country.dst_enabled); + if (country.dst_rule[0]) + montauk::config::set_string(&cfg, "timezone.dst_rule", country.dst_rule); + else + montauk::config::unset(&cfg, "timezone.dst_rule"); + + int dst_change = dst_offset_change_minutes(country); + if (country.dst_enabled && dst_change != 0) + montauk::config::set_int(&cfg, "timezone.dst_offset_minutes", dst_change); + else + montauk::config::unset(&cfg, "timezone.dst_offset_minutes"); + + int offset = 0; + bool has_offset = selected_offset_minutes(&offset); + if (has_offset) { + const char* offset_text = zone_offset_string(country, g_selected_zone); + if (offset_text[0]) + montauk::config::set_string(&cfg, "timezone.offset", offset_text); + montauk::config::set_int(&cfg, "timezone.offset_minutes", offset); + montauk::settz(offset); + } else { + montauk::config::unset(&cfg, "timezone.offset"); + } + + int ret = montauk::config::save("timezone", &cfg); + cfg.destroy(); + + if (ret < 0) { + set_status("Could not save time zone"); + return; + } + + g_saved_country = g_selected_country; + g_saved_zone = g_selected_zone; + g_dirty = false; + set_status(has_offset ? "Saved and applied" : "Saved selection; clock offset unchanged"); +} + +static void revert_selection() { + g_selected_country = g_saved_country; + g_selected_zone = g_saved_zone; + g_dirty = false; + ensure_country_visible(); + ensure_zone_visible(); + clamp_scrolls(); + set_status("Selection reverted"); +} + +static Rect country_list_rect() { + int left_w = gui_min(250, (g_win.width - PAD * 2 - GAP) / 2); + return {PAD, HEADER_H + LABEL_H, left_w, + gui_max(g_win.height - HEADER_H - FOOTER_H - LABEL_H - PAD, ROW_H)}; +} + +static Rect zone_list_rect() { + Rect country = country_list_rect(); + return {country.x + country.w + GAP, country.y, + g_win.width - PAD * 2 - GAP - country.w, country.h}; +} + +static Rect apply_button_rect() { + return {g_win.width - PAD - 104, g_win.height - FOOTER_H + 14, 104, 30}; +} + +static Rect revert_button_rect() { + return {g_win.width - PAD - 104 - GAP - 92, g_win.height - FOOTER_H + 14, 92, 30}; +} + +static bool mouse_in_rect(const Rect& rect) { + return rect.contains(g_mouse_x, g_mouse_y); +} + +static mtk::WidgetState button_state(const Rect& rect, bool enabled, bool active = false) { + return mtk::widget_state(active, mouse_in_rect(rect), enabled); +} + +static bool hover_changed(const Rect& rect, bool enabled, + int prev_x, int prev_y, int next_x, int next_y) { + if (!enabled) return false; + return rect.contains(prev_x, prev_y) != rect.contains(next_x, next_y); +} + +static void draw_list_frame(Canvas& canvas, const Rect& list, const mtk::Theme& theme) { + canvas.fill_rect(list.x, list.y, list.w, list.h, theme.surface_alt); + canvas.rect(list.x, list.y, list.w, list.h, theme.border); +} + +static void draw_country_list(Canvas& canvas, const Rect& list, const mtk::Theme& theme) { + draw_list_frame(canvas, list, theme); + int rows = visible_rows(list); + for (int row = 0; row < rows; row++) { + int index = g_country_scroll + row; + if (index >= g_country_count) break; + + Rect item = {list.x + 2, list.y + 2 + row * ROW_H, list.w - 4, ROW_H - 2}; + bool selected = index == g_selected_country; + mtk::draw_list_row(canvas, item, selected, (index & 1) != 0, theme); + + char label[128]; + fit_text(label, sizeof(label), g_countries[index].name, item.w - 16); + canvas.text(item.x + 8, item.y + (item.h - system_font_height()) / 2, + label, selected ? theme.accent_fg : theme.text); + } +} + +static void draw_zone_list(Canvas& canvas, const Rect& list, const mtk::Theme& theme) { + draw_list_frame(canvas, list, theme); + if (g_selected_country < 0 || g_selected_country >= g_country_count) return; + + CountryEntry& country = g_countries[g_selected_country]; + int count = zone_count(country); + int rows = visible_rows(list); + for (int row = 0; row < rows; row++) { + int index = g_zone_scroll + row; + if (index >= count) break; + + Rect item = {list.x + 2, list.y + 2 + row * ROW_H, list.w - 4, ROW_H - 2}; + bool selected = index == g_selected_zone; + mtk::draw_list_row(canvas, item, selected, (index & 1) != 0, theme); + + char label[128]; + fit_text(label, sizeof(label), zone_name(country, index), item.w - 16); + canvas.text(item.x + 8, item.y + (item.h - system_font_height()) / 2, + label, selected ? theme.accent_fg : theme.text); + } +} + +static void draw_details(Canvas& canvas, const mtk::Theme& theme) { + if (g_country_count <= 0) return; + + Rect country = country_list_rect(); + Rect zone = zone_list_rect(); + int x = zone.x; + int y = zone.y + zone.h + 8; + if (y > g_win.height - FOOTER_H - 24) return; + + CountryEntry& entry = g_countries[g_selected_country]; + char line[192]; + snprintf(line, sizeof(line), "%s / %s", entry.name, zone_name(entry, g_selected_zone)); + fit_text(line, sizeof(line), line, g_win.width - x - PAD); + canvas.text(x, y, line, theme.text); + + int offset = 0; + char right[96]; + if (selected_offset_minutes(&offset)) { + format_offset(offset, right, sizeof(right)); + } else { + format_offset(montauk::gettz(), right, sizeof(right)); + char tmp[96]; + snprintf(tmp, sizeof(tmp), "Clock: %s", right); + safe_copy(right, sizeof(right), tmp); + } + canvas.text(country.x, y, right, theme.text_subtle); + + y += system_font_height() + 6; + if (entry.dst_enabled) { + int dst_change = dst_offset_change_minutes(entry); + if (dst_change) + snprintf(line, sizeof(line), "DST: %s (+%d min)", + entry.dst_rule[0] ? entry.dst_rule : "enabled", dst_change); + else + snprintf(line, sizeof(line), "DST: %s", + entry.dst_rule[0] ? entry.dst_rule : "enabled"); + } else { + safe_copy(line, sizeof(line), "DST: not used"); + } + canvas.text(x, y, line, theme.text_subtle); + + if (entry.note && entry.note[0]) { + char note[192]; + fit_text(note, sizeof(note), entry.note, g_win.width - x - PAD); + canvas.text(x, y + system_font_height() + 4, note, theme.text_subtle); + } +} + +static void render() { + mtk::StandaloneHost host(&g_win); + Canvas canvas = host.canvas(); + mtk::Theme theme = app_theme(); + + canvas.fill(theme.window_bg); + + if (g_country_count <= 0) { + canvas.text(PAD, HEADER_H + 24, + "No time zone data was found in 0:/config/timezonedata.toml", + theme.danger); + host.present(); + return; + } + + Rect country = country_list_rect(); + Rect zone = zone_list_rect(); + + canvas.text(country.x, HEADER_H + 6, "Country", theme.text_subtle); + canvas.text(zone.x, HEADER_H + 6, "Time Zone", theme.text_subtle); + + draw_country_list(canvas, country, theme); + draw_zone_list(canvas, zone, theme); + draw_details(canvas, theme); + + Rect footer = {0, g_win.height - FOOTER_H, g_win.width, FOOTER_H}; + canvas.fill_rect(footer.x, footer.y, footer.w, footer.h, theme.surface); + mtk::draw_separator(canvas, 0, footer.y, g_win.width, theme); + + const char* footer_text = status_visible() + ? g_status + : (g_dirty ? "Unsaved time zone selection" : "Selection saved"); + int max_footer_text = revert_button_rect().x - PAD - GAP; + char footer_label[160]; + fit_text(footer_label, sizeof(footer_label), footer_text, max_footer_text); + canvas.text(PAD, footer.y + (FOOTER_H - system_font_height()) / 2, + footer_label, g_dirty ? theme.text : theme.text_subtle); + + Rect revert = revert_button_rect(); + Rect apply = apply_button_rect(); + mtk::draw_button(canvas, revert, "Revert", mtk::BUTTON_SECONDARY, + button_state(revert, g_dirty), theme); + mtk::draw_button(canvas, apply, "Apply", mtk::BUTTON_PRIMARY, + button_state(apply, g_dirty), theme); + + host.present(); +} + +static bool handle_mouse(const Montauk::WinEvent& ev) { + int prev_x = g_mouse_x; + int prev_y = g_mouse_y; + g_mouse_x = ev.mouse.x; + g_mouse_y = ev.mouse.y; + + Rect country = country_list_rect(); + Rect zone = zone_list_rect(); + Rect revert = revert_button_rect(); + Rect apply = apply_button_rect(); + bool redraw = false; + + redraw |= hover_changed(revert, g_dirty, prev_x, prev_y, g_mouse_x, g_mouse_y); + redraw |= hover_changed(apply, g_dirty, prev_x, prev_y, g_mouse_x, g_mouse_y); + + if (ev.mouse.scroll != 0) { + if (country.contains(g_mouse_x, g_mouse_y)) { + g_country_scroll -= ev.mouse.scroll * 3; + clamp_scrolls(); + redraw = true; + } else if (zone.contains(g_mouse_x, g_mouse_y)) { + g_zone_scroll -= ev.mouse.scroll * 3; + clamp_scrolls(); + redraw = true; + } + } + + bool clicked = (ev.mouse.buttons & 1) && !(ev.mouse.prev_buttons & 1); + if (!clicked) return redraw; + + if (country.contains(g_mouse_x, g_mouse_y)) { + int row = (g_mouse_y - country.y - 2) / ROW_H; + int index = g_country_scroll + row; + if (index >= 0 && index < g_country_count) { + g_selected_country = index; + g_selected_zone = default_zone_index(g_countries[g_selected_country]); + g_zone_scroll = 0; + g_focus_pane = 0; + g_dirty = (g_selected_country != g_saved_country || g_selected_zone != g_saved_zone); + clamp_scrolls(); + redraw = true; + } + return redraw; + } + + if (zone.contains(g_mouse_x, g_mouse_y)) { + int row = (g_mouse_y - zone.y - 2) / ROW_H; + int index = g_zone_scroll + row; + if (g_selected_country >= 0 && g_selected_country < g_country_count && + index >= 0 && index < zone_count(g_countries[g_selected_country])) { + g_selected_zone = index; + g_focus_pane = 1; + g_dirty = (g_selected_country != g_saved_country || g_selected_zone != g_saved_zone); + redraw = true; + } + return redraw; + } + + if (apply.contains(g_mouse_x, g_mouse_y)) { + if (g_dirty) save_selection(); + return true; + } + + if (revert.contains(g_mouse_x, g_mouse_y)) { + if (g_dirty) revert_selection(); + return true; + } + + return redraw; +} + +static bool handle_key(const Montauk::KeyEvent& key) { + if (!key.pressed || g_country_count <= 0) return false; + + if (key.ascii == '\n' || key.ascii == '\r') { + if (g_dirty) save_selection(); + return true; + } + if (key.ascii == '\033') { + if (g_dirty) revert_selection(); + return true; + } + + int* selected = g_focus_pane == 0 ? &g_selected_country : &g_selected_zone; + int count = g_focus_pane == 0 + ? g_country_count + : zone_count(g_countries[g_selected_country]); + + int old_country = g_selected_country; + + if (key.scancode == 0x4B) g_focus_pane = 0; // Left + else if (key.scancode == 0x4D) g_focus_pane = 1; // Right + else if (key.scancode == 0x48 && *selected > 0) (*selected)--; // Up + else if (key.scancode == 0x50 && *selected < count - 1) (*selected)++; // Down + else if (key.scancode == 0x49) *selected = gui_max(0, *selected - 8); // Page Up + else if (key.scancode == 0x51) *selected = gui_min(count - 1, *selected + 8); // Page Down + else return false; + + if (old_country != g_selected_country) { + g_selected_zone = default_zone_index(g_countries[g_selected_country]); + g_zone_scroll = 0; + } + + ensure_country_visible(); + ensure_zone_visible(); + clamp_scrolls(); + + g_dirty = (g_selected_country != g_saved_country || g_selected_zone != g_saved_zone); + return true; +} + +extern "C" void _start() { + if (!fonts::init()) + montauk::exit(1); + + apply_scale(montauk::win_getscale()); + load_accent(); + load_timezone_data(); + + if (!g_win.create("Time Zone", INIT_W, INIT_H)) + montauk::exit(1); + + load_saved_selection(); + render(); + + while (g_win.id >= 0 && !g_win.closed) { + Montauk::WinEvent ev; + int r = g_win.poll(&ev); + bool redraw = false; + + if (r < 0) break; + if (r == 0) { + montauk::sleep_ms(16); + continue; + } + + if (ev.type == 3) break; + if (ev.type == 2) { + redraw = true; + } else if (ev.type == 4) { + apply_scale(g_win.scale_factor); + redraw = true; + } else if (ev.type == 1) { + redraw = handle_mouse(ev); + } else if (ev.type == 0) { + redraw = handle_key(ev.key); + } + + if (redraw) render(); + } + + if (g_data_loaded) g_data.destroy(); + g_win.destroy(); + montauk::exit(0); +} diff --git a/programs/src/timezone/manifest.toml b/programs/src/timezone/manifest.toml new file mode 100644 index 0000000..c78420a --- /dev/null +++ b/programs/src/timezone/manifest.toml @@ -0,0 +1,8 @@ +[app] +name = "Time Zone" +binary = "timezone.elf" +icon = "preferences-system-time.svg" + +[menu] +category = "System" +visible = false diff --git a/programs/src/timezone/stb_truetype_impl.cpp b/programs/src/timezone/stb_truetype_impl.cpp new file mode 100644 index 0000000..c217e9c --- /dev/null +++ b/programs/src/timezone/stb_truetype_impl.cpp @@ -0,0 +1 @@ +#include "../desktop/stb_truetype_impl.cpp" diff --git a/scripts/copy_icons.sh b/scripts/copy_icons.sh index 6a81ff5..cec9d46 100755 --- a/scripts/copy_icons.sh +++ b/scripts/copy_icons.sh @@ -52,6 +52,7 @@ ICONS=( "apps/scalable/system-file-manager.svg" "apps/scalable/preferences-desktop-apps.svg" "categories/scalable/preferences-system.svg" + "categories/scalable/preferences-system-time.svg" "apps/scalable/accessories-calculator.svg" "apps/scalable/web-browser.svg" "places/scalable/folder.svg" diff --git a/scripts/install_apps.sh b/scripts/install_apps.sh index 26d1f5c..1804f1c 100755 --- a/scripts/install_apps.sh +++ b/scripts/install_apps.sh @@ -35,6 +35,7 @@ APPS=( "procmgr|apps/scalable/system-monitor.svg" "calculator|apps/scalable/accessories-calculator.svg" "printers|devices/scalable/preferences-devices-printer.svg" + "timezone|categories/scalable/preferences-system-time.svg" "rpgdemo|apps/scalable/utilities-terminal.svg" "paint|apps/scalable/kolourpaint.svg" "screenshot|apps/scalable/gnome-screenshot.svg"