From 14c3db82b1a069408798ea83321b307aa3864843 Mon Sep 17 00:00:00 2001 From: DT3264 Date: Mon, 7 Aug 2023 20:17:41 -0600 Subject: [PATCH] Adds directives validator To ease the testing, [here's](https://onlinegdb.com/wkorOlpCk) a link to an online go compiler, it is only needed to click "Fork this" to be able to modify the cpci.hdf file and try different directives combinantions --- base-system/tools/hsync-validator/cpci.hdf | 40 ++ .../tools/hsync-validator/hsync-validator.go | 532 ++++++++++++++++++ base-system/tools/hsync-validator/layouts.txt | 100 ++++ .../tools/hsync-validator/software.txt | 38 ++ .../tools/hsync-validator/timezones.txt | 349 ++++++++++++ 5 files changed, 1059 insertions(+) create mode 100644 base-system/tools/hsync-validator/cpci.hdf create mode 100644 base-system/tools/hsync-validator/hsync-validator.go create mode 100644 base-system/tools/hsync-validator/layouts.txt create mode 100644 base-system/tools/hsync-validator/software.txt create mode 100644 base-system/tools/hsync-validator/timezones.txt diff --git a/base-system/tools/hsync-validator/cpci.hdf b/base-system/tools/hsync-validator/cpci.hdf new file mode 100644 index 00000000..2d3e10c3 --- /dev/null +++ b/base-system/tools/hsync-validator/cpci.hdf @@ -0,0 +1,40 @@ +#Comment line +[Global] +TimeZone=America/Mexico_City +ConfigExpirationTime=2023-07-22T12:15:00 +AvailableKeyboardLayouts=latam|us|es|dvorak| +DefaultKeyboardLayout=latam +EventConfig=false +ContestConfig=true +#ShutdownTime=none + +[Always] +AllowedWebsites=all +AllowUsbStorage=true +AvailableSoftware=internet/chromium|internet/crow|internet/firefox|langs/g++|langs/gcc|langs/javac|langs/kotlinc|langs/pypy3|langs/python3|tools/konsole|programming/atom|programming/codeblocks|programming/eclipse|programming/geany|programming/gedit|programming/gvim|programming/intellij|programming/kate|programming/kdevelop|programming/pycharm|programming/sublime|programming/vim|programming/vscode| +Bookmarks={VJUDGE^https://vjudge.net}|{Codeforces^https://cpciitsur.contest.codeforces.com}| +Wallpaper=http://23.239.16.64/files/wallpaper/gpmx2023_round1_contest_mode.png +WallpaperSha256=824d8560d35ec070589a5b72f2da85aff38203a7f6b1aee8bae7900c608d4369 +#Comment line +[Event] +AllowedWebsites=all +AllowUsbStorage=true +AvailableSoftware=internet/chromium|internet/crow|internet/firefox|langs/g++|langs/gcc|langs/javac|langs/kotlinc|langs/pypy3|langs/python3|tools/konsole|programming/atom|programming/codeblocks|programming/eclipse|programming/geany|programming/gedit|programming/gvim|programming/intellij|programming/kate|programming/kdevelop|programming/pycharm|programming/sublime|programming/vim| +Bookmarks={huronOS^https://huronos.org}|{Contest^https://boca.icpcmexico.org}| +Wallpaper=default + +[Contest] +AllowedWebsites=vj.csgrandeur.cn|gravatar.loli.net|lib.baomitu.com|vjudge.net|deepl.com|static.deepl.com|w.deepl.com|s.deepl.com|login-wall.deepl.com|www2.deepl.com|dict.deepl.com +AllowUsbStorage=false +AvailableSoftware=internet/chromium|internet/crow|internet/firefox|langs/g++|langs/gcc|langs/javac|langs/kotlinc|langs/pypy3|langs/python3|tools/konsole|programming/atom|programming/codeblocks|programming/eclipse|programming/emacs|programming/geany|programming/gedit|programming/gvim|programming/intellij|programming/kate|programming/kdevelop|programming/pycharm|programming/sublime|programming/vim|programming/vscode| +Bookmarks={VJudge^https://vjudge.net}|{Traductor^https://www.deepl.com/en/translator#en/es/}| +Wallpaper=https://directives.huronos.org/external/itsur/cpci_itsur_wallpaper.png +WallpaperSha256=c2340e3f28e23fb4c5649beed7f20d1ef38bdc9a4560b125490f33797eddd44d + +[Event-Times] + +[Contest-Times] +2023-07-22T11:50:00 2023-07-22T12:15:00 +2023-07-22T11:50:00 2023-07-22T12:15:00 +2023-07-22T11:50:00 2023-07-22T12:15:00 +2023-07-22T11:50:00 2023-07-22T12:15:00 diff --git a/base-system/tools/hsync-validator/hsync-validator.go b/base-system/tools/hsync-validator/hsync-validator.go new file mode 100644 index 00000000..12dddd9b --- /dev/null +++ b/base-system/tools/hsync-validator/hsync-validator.go @@ -0,0 +1,532 @@ +package main + +import ( + "bufio" + "fmt" + "os" + "regexp" + "strings" + "time" +) + +// Create a map to store sections with struct values +var sectionMap = make(map[string]Section) +var availableLayouts []string +var availableTimezones []string +var availableSoftware []string + +// Define the struct for sections +type Section struct { + SectionDirectives map[string]string + Times []TimeSpan + Warnings []string +} + +// Define the struct for time span +type TimeSpan struct { + Begin string + End string +} + +// Warnings section +func addEmptyDirectiveWarning(section, directiveName string) { + addWarning(section, fmt.Sprintf("Empty directive: %s\nDirective either not defined or empty\n", directiveName)) +} + +func addInvalidDirectiveValueWarning(section, directiveName, directiveValue string) { + addWarning(section, fmt.Sprintf("Invalid directive value\nDirective: *%s*\nValue: *%s*\n", directiveName, directiveValue)) +} + +func addInvalidDirectiveNameWarning(section, directiveName string) { + addWarning(section, fmt.Sprintf("Invalid directive detected\nDirective: *%s*\n", directiveName)) +} + +func addWarning(section, warning string) { + currentSection := sectionMap[section] + currentSection.Warnings = append(currentSection.Warnings, warning) + sectionMap[section] = currentSection +} + +// File things +func readLinesFromFile(filePath string) []string { + var lines []string + file, err := os.Open(filePath) + if err != nil { + fmt.Printf("Error reading file %s. %s", filePath, err.Error()) + } + defer file.Close() + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + lines = append(lines, line) + } + + if err := scanner.Err(); err != nil { + fmt.Printf("Error reading file %s. %s", filePath, err.Error()) + } + return lines +} + +// Parser things +func parseLine(line string) (directive, content string) { + splitResult := strings.SplitN(line, "=", 2) + + // Check if the split result contains exactly two parts + if len(splitResult) != 2 { + directive = line + content = "" + return + } + directive = splitResult[0] + content = splitResult[1] + return +} + +func parseTimeSpan(line string) (begin, end string, err bool) { + timeParts := strings.Split(line, " ") + if len(timeParts) != 2 { + err = true + begin = line + end = "" + return + } + + begin = timeParts[0] + end = timeParts[1] + return +} + +func parseBookmark(line string) (name, url string, err error) { + bookmarkData := strings.Split(line, "^") + if len(bookmarkData) != 2 { + err = fmt.Errorf("\tFound\n\t*{%s}*\n\tIt has to be *{Name^URL}\n", line) + name = line + url = "" + return + } + + name = bookmarkData[0] + url = bookmarkData[1] + return +} + +func parseTime(timeStr string) time.Time { + parsedTime, _ := time.Parse("2006-01-02T15:04:05", timeStr) + return parsedTime +} + +// Line things +func getLineSplittedByPipe(str string) []string { + return strings.Split(strings.Trim(str, "|"), "|") +} + +func doesLineExists(query string, lines []string) bool { + for _, line := range lines { + if line == query { + return true + } + } + return false +} + +// Functions to validate things used on multiple places +func isTimeValid(timeStr string) bool { + _, err := time.Parse("2006-01-02T15:04:05", timeStr) + if err != nil { + return false + } + return true +} + +func areTimesValid(section string, times []TimeSpan) bool { + areValid := true + for _, timeSpan := range times { + isBeginTimeValid := isTimeValid(timeSpan.Begin) + isEndTimeValid := isTimeValid(timeSpan.End) + if !isBeginTimeValid || !isEndTimeValid { + warning := fmt.Sprintf("Bad time range in %s section\nBegin: %s\nEnd: %s\n", section, timeSpan.Begin, timeSpan.End) + if !isBeginTimeValid { + warning += fmt.Sprintf("Bad begin time\n") + } + if !isEndTimeValid { + warning += fmt.Sprintf("Bad end time\n") + } + addWarning(section, warning) + areValid = false + } + beginTime := parseTime(timeSpan.Begin) + endTime := parseTime(timeSpan.End) + if endTime.Equal(beginTime) || endTime.Before(beginTime) { + warning := fmt.Sprintf("Bad time range in %s section\nEnd is equal or before Begin\nBegin: %s\nEnd: %s\n", section, timeSpan.Begin, timeSpan.End) + addWarning(section, warning) + areValid = false + } + } + return areValid +} + +func isUrlValid(url string) bool { + matchString, err := regexp.MatchString("(^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?)", url) + if err != nil { + return false + } + return matchString +} + +// Global Config validations +func isTimezoneValid(timezone string) bool { + if timezone == "" { + addEmptyDirectiveWarning("Global", "TimeZone") + return false + } + isValid := doesLineExists(timezone, availableTimezones) + if !isValid { + addInvalidDirectiveValueWarning("Global", "TimeZone", timezone) + } + return isValid +} +func isConfigExpirationTimeValid(configExpirationTime string) bool { + if configExpirationTime == "" { + addEmptyDirectiveWarning("Global", "ConfigExpirationTime") + return false + } + isValid := configExpirationTime == "never" || isTimeValid(configExpirationTime) + if !isValid { + addInvalidDirectiveValueWarning("Global", "ConfigExpirationTime", configExpirationTime) + } + return isValid +} + +func areAvailableKeyboardLayoutsValid(availableKeyboardLayouts string) bool { + areValid := true + layouts := getLineSplittedByPipe(availableKeyboardLayouts) + if availableKeyboardLayouts == "" || len(layouts) == 0 || (len(layouts) == 1 && layouts[0] == "") { + addEmptyDirectiveWarning("Global", "AvailableKeyboardLayouts") + return false + } + for _, layout := range layouts { + if !doesLineExists(layout, availableLayouts) { + addWarning("Global", "Invalid keyboard layout ") + areValid = false + } + } + return areValid +} + +func isDefaultKeyboardLayoutValid(defaultKeyboardLayout string) bool { + if defaultKeyboardLayout == "" { + addEmptyDirectiveWarning("Global", "DefaultKeyboardLayout") + return false + } + isValid := doesLineExists(defaultKeyboardLayout, availableLayouts) + if !isValid { + addInvalidDirectiveValueWarning("Global", "DefaultKeyboardLayout", defaultKeyboardLayout) + } + return isValid +} + +func isEventConfigValid(eventConfig string) bool { + if eventConfig == "" { + addEmptyDirectiveWarning("Global", "EventConfig") + return false + } + if eventConfig == "true" && len(sectionMap["Event"].Warnings) != 0 { + addWarning("Global", "EventConfig is set to true, but either the Event or Event-Times sections are invalid\n") + return false + } + isValid := eventConfig == "false" || (eventConfig == "true" && len(sectionMap["Event"].Warnings) == 0) + if !isValid { + addInvalidDirectiveValueWarning("Global", "EventConfig", eventConfig) + } + return isValid +} +func isContestConfigValid(contestConfig string) bool { + if contestConfig == "" { + addEmptyDirectiveWarning("Global", "ContestConfig") + return false + } + if contestConfig == "true" && len(sectionMap["Contest"].Warnings) != 0 { + addWarning("Global", "ContestConfig is set to true, but either the Contest or Contest-Times sections are invalid\n") + return false + } + isValid := contestConfig == "false" || (contestConfig == "true" && len(sectionMap["Contest"].Warnings) == 0) + if !isValid { + addInvalidDirectiveValueWarning("Global", "ContestConfig", contestConfig) + } + return isValid +} + +//Specific modes validations +func areAllowedWebsitesValid(section, allowedWebsitesStr string) bool { + if allowedWebsitesStr == "" { + addEmptyDirectiveWarning(section, "AllowedWebsites") + return false + } + if allowedWebsitesStr=="all" { + return true + } + areValid := true + allowedWebsites := getLineSplittedByPipe(allowedWebsitesStr) + for _, url := range allowedWebsites { + if !isUrlValid(url) { + addInvalidDirectiveValueWarning(section, "AllowedWebsites", url) + areValid = false + } + } + return areValid +} +func isAllowUsbStorageValid(section, allowUsbStorage string) bool { + if allowUsbStorage == "" { + addEmptyDirectiveWarning(section, "AllowUsbStorage") + return false + } + isValid := allowUsbStorage == "true" || allowUsbStorage == "false" + if !isValid { + addInvalidDirectiveValueWarning(section, "AllowUsbStorage", allowUsbStorage) + } + return isValid +} + +func isAvailableSoftwareValid(section, softwareStr string) bool { + if softwareStr == "" { + addEmptyDirectiveWarning(section, "AvailableSoftware") + return false + } + isValid := true + softwareList := getLineSplittedByPipe(softwareStr) + for _, software := range softwareList { + if !doesLineExists(software, availableSoftware) { + addInvalidDirectiveValueWarning(section, "AvailableSoftware", software) + isValid = false + } + } + return isValid +} +func areBookmarksValid(section, bookmarksStr string) bool { + if bookmarksStr == "" { + addEmptyDirectiveWarning(section, "Bookmarks") + return false + } + areValid := true + bookmarks := getLineSplittedByPipe(bookmarksStr) + for _, bookmarkStr := range bookmarks { + // Is there a need to check if the name of a bookmark is valid? + _, bookmarkUrl, err := parseBookmark(strings.TrimRight(strings.TrimLeft(bookmarkStr, "{"), "}")) + if err != nil { + addWarning(section, fmt.Sprintf("Error parsing bookmark:\n%s", err.Error())) + areValid = false + continue + } + if !isUrlValid(bookmarkUrl) { + addInvalidDirectiveValueWarning(section, "Bookmarks", bookmarkUrl) + } + } + return areValid +} +func isWallpaperValid(section, wallpaperUrl string) bool { + if wallpaperUrl == "" { + addEmptyDirectiveWarning(section, "Wallpaper") + return false + } + return true +} +func isWallpaperSha256Valid(section, wallpaperSha256 string) bool { + // If the default walpaper is set, no need for a sha string + if sectionMap[section].SectionDirectives["Wallpaper"] == "default" { + return true + } + if wallpaperSha256 == "" { + addEmptyDirectiveWarning(section, "WallpaperSha256") + return false + } + return true +} + +// Function to validate the global directives section +func validateGlobalDirectives(directives map[string]string) bool { + // availableGlobalDirectives as map to be able to quickly check if a value is in the map or not + availableGlobalDirectives := map[string]struct{}{ + "AvailableKeyboardLayouts": {}, + "ConfigExpirationTime": {}, + "ContestConfig": {}, + "DefaultKeyboardLayout": {}, + "EventConfig": {}, + "TimeZone": {}, + } + for directiveName, _ := range directives { + _, isDirectivePresent := availableGlobalDirectives[directiveName] + if !isDirectivePresent { + addInvalidDirectiveNameWarning("Global", directiveName) + } + } + areAllDirectivesValid := true + if !isTimezoneValid(directives["TimeZone"]) { + areAllDirectivesValid = false + } + if !isConfigExpirationTimeValid(directives["ConfigExpirationTime"]) { + areAllDirectivesValid = false + } + if !areAvailableKeyboardLayoutsValid(directives["AvailableKeyboardLayouts"]) { + areAllDirectivesValid = false + } + if !isDefaultKeyboardLayoutValid(directives["DefaultKeyboardLayout"]) { + areAllDirectivesValid = false + } + if !isEventConfigValid(directives["EventConfig"]) { + areAllDirectivesValid = false + } + if !isContestConfigValid(directives["ContestConfig"]) { + areAllDirectivesValid = false + } + return areAllDirectivesValid +} + +// Function to validate the Always/Event/Contest directives sections +func validateModeDirectives(section string, directives map[string]string) bool { + // availableGlobalDirectives as map to be able to quickly check if a value is in the map or not + availableModeDirectives := map[string]struct{}{ + "AllowedWebsites": {}, + "AllowUsbStorage": {}, + "AvailableSoftware": {}, + "Bookmarks": {}, + "Wallpaper": {}, + "WallpaperSha256": {}, + } + for directiveName, _ := range directives { + _, isDirectivePresent := availableModeDirectives[directiveName] + if !isDirectivePresent { + addWarning(section, fmt.Sprintf("Invalid directive detected, it will be ignored by the system\nDirective: *%s*\n", directiveName)) + } + } + areAllDirectivesValid := true + if !areAllowedWebsitesValid(section, directives["AllowedWebsites"]) { + areAllDirectivesValid = false + } + if !isAllowUsbStorageValid(section, directives["AllowUsbStorage"]) { + areAllDirectivesValid = false + } + if !isAvailableSoftwareValid(section, directives["AvailableSoftware"]) { + areAllDirectivesValid = false + } + if !areBookmarksValid(section, directives["Bookmarks"]) { + areAllDirectivesValid = false + } + if !isWallpaperValid(section, directives["Wallpaper"]) { + areAllDirectivesValid = false + } + if !isWallpaperSha256Valid(section, directives["WallpaperSha256"]) { + areAllDirectivesValid = false + } + return areAllDirectivesValid +} + +func main() { + availableLayouts = readLinesFromFile("layouts.txt") + availableTimezones = readLinesFromFile("timezones.txt") + availableSoftware = readLinesFromFile("software.txt") + + // Read the file + file, err := os.Open("cpci.hdf") + if err != nil { + fmt.Println("Error opening file:", err) + return + } + defer file.Close() + + // Read the file line by line + scanner := bufio.NewScanner(file) + var currentSection string + for scanner.Scan() { + line := scanner.Text() + + // Skip empty lines and comments + if line == "" || strings.HasPrefix(line, "#") { + continue + } + + // Check if the line is a section header + if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") { + currentSection = strings.TrimPrefix(strings.TrimSuffix(line, "]"), "[") + continue + } + + // Check if the section ends with -Times + if strings.HasSuffix(currentSection, "-Times") { + // Add the line to the corresponding section's Times + sectionName := strings.TrimSuffix(currentSection, "-Times") + // Retrieve the current section value from the map + currentSectionValue := sectionMap[sectionName] + // Modify the Times field of the struct + begin, end, err := parseTimeSpan(line) + if err == true { + addWarning(sectionName, fmt.Sprintf("Error parsing time span:%s", line)) + return + } + currentSectionValue.Times = append(currentSectionValue.Times, TimeSpan{Begin: begin, End: end}) + // Update the map with the modified section value + sectionMap[sectionName] = currentSectionValue + } else if currentSection != "" { + // Retrieve the current section value from the map + currentSectionValue := sectionMap[currentSection] + // Modify the Content field of the struct + directive, content := parseLine(line) + if currentSectionValue.SectionDirectives == nil { + currentSectionValue.SectionDirectives = make(map[string]string) + } + currentSectionValue.SectionDirectives[directive] = content + // Update the map with the modified section value + sectionMap[currentSection] = currentSectionValue + } + } + + if err := scanner.Err(); err != nil { + fmt.Println("Error reading file:", err) + return + } + + // Validate sections and print validation results + // Validate Event and Contest sections before Global + // in case EventConfig or ContestConfig are set + // to throw error if not configured correctly + + // Validate Event section + currentSection = "Event" + if validateModeDirectives(currentSection, sectionMap[currentSection].SectionDirectives) && areTimesValid(currentSection, sectionMap[currentSection].Times) { + fmt.Printf("%s validation: Passed\n", currentSection) + } else { + fmt.Printf("%s validation: Failed\n", currentSection) + printWarnings(sectionMap[currentSection].Warnings) + } + // Validate Contest section + currentSection = "Contest" + if validateModeDirectives(currentSection, sectionMap[currentSection].SectionDirectives) && areTimesValid(currentSection, sectionMap[currentSection].Times) { + fmt.Printf("%s validation: Passed\n", currentSection) + } else { + fmt.Printf("%s validation: Failed\n", currentSection) + printWarnings(sectionMap[currentSection].Warnings) + } + // Validate Always section + currentSection = "Always" + if validateModeDirectives(currentSection, sectionMap[currentSection].SectionDirectives) { + fmt.Printf("%s validation: Passed\n", currentSection) + } else { + fmt.Printf("%s validation: Failed\n", currentSection) + printWarnings(sectionMap[currentSection].Warnings) + } + // Validate Global section + currentSection = "Global" + if validateGlobalDirectives(sectionMap[currentSection].SectionDirectives) { + fmt.Printf("%s validation: Passed\n", currentSection) + } else { + fmt.Printf("%s validation: Failed\n", currentSection) + printWarnings(sectionMap[currentSection].Warnings) + } +} + +func printWarnings(warnings []string) { + for _, warning := range warnings { + fmt.Printf("*****WARNING*****\n") + fmt.Printf("%s", warning) + } +} diff --git a/base-system/tools/hsync-validator/layouts.txt b/base-system/tools/hsync-validator/layouts.txt new file mode 100644 index 00000000..02be4d24 --- /dev/null +++ b/base-system/tools/hsync-validator/layouts.txt @@ -0,0 +1,100 @@ +us +dvorak +af +ara +al +am +at +au +az +by +be +bd +in +ba +br +bg +dz +ma +cm +mm +ca +cd +cn +hr +cz +dk +nl +bt +ee +ir +iq +fo +fi +fr +gh +gn +ge +de +gr +hu +is +il +it +jp +kg +kh +kz +la +latam +lt +lv +mao +me +mk +mt +mn +no +pl +pt +ro +ru +rs +si +sk +es +se +ch +sy +tj +lk +th +tr +tw +ua +gb +uz +vn +kr +nec_vndr/jp +ie +pk +mv +za +epo +np +ng +et +sn +brai +tm +ml +tz +tg +ke +bw +ph +md +id +jv +my \ No newline at end of file diff --git a/base-system/tools/hsync-validator/software.txt b/base-system/tools/hsync-validator/software.txt new file mode 100644 index 00000000..4ec209ac --- /dev/null +++ b/base-system/tools/hsync-validator/software.txt @@ -0,0 +1,38 @@ +debuggers/ddd +debuggers/gdb +debuggers/valgrind +debuggers/visualvm +internet/chrome +internet/chromium +internet/crow +internet/firefox +langs/dotnet +langs/g++ +langs/gcc +langs/javac +langs/kotlinc +langs/mono +langs/pypy3 +langs/python3 +langs/ruby +tools/byobu +tools/konsole +tools/midnight-commander +programming/atom +programming/clion +programming/codeblocks +programming/eclipse +programming/emacs +programming/geany +programming/gedit +programming/gvim +programming/intellij +programming/joe +programming/kate +programming/kdevelop +programming/neovim +programming/pycharm +programming/rider +programming/sublime +programming/vim +programming/vscode \ No newline at end of file diff --git a/base-system/tools/hsync-validator/timezones.txt b/base-system/tools/hsync-validator/timezones.txt new file mode 100644 index 00000000..60d5e133 --- /dev/null +++ b/base-system/tools/hsync-validator/timezones.txt @@ -0,0 +1,349 @@ +Africa/Abidjan +Africa/Accra +Africa/Algiers +Africa/Bissau +Africa/Cairo +Africa/Casablanca +Africa/Ceuta +Africa/El_Aaiun +Africa/Johannesburg +Africa/Juba +Africa/Khartoum +Africa/Lagos +Africa/Maputo +Africa/Monrovia +Africa/Nairobi +Africa/Ndjamena +Africa/Sao_Tome +Africa/Tripoli +Africa/Tunis +Africa/Windhoek +America/Adak +America/Anchorage +America/Araguaina +America/Argentina/Buenos_Aires +America/Argentina/Catamarca +America/Argentina/Cordoba +America/Argentina/Jujuy +America/Argentina/La_Rioja +America/Argentina/Mendoza +America/Argentina/Rio_Gallegos +America/Argentina/Salta +America/Argentina/San_Juan +America/Argentina/San_Luis +America/Argentina/Tucuman +America/Argentina/Ushuaia +America/Asuncion +America/Atikokan +America/Bahia +America/Bahia_Banderas +America/Barbados +America/Belem +America/Belize +America/Blanc-Sablon +America/Boa_Vista +America/Bogota +America/Boise +America/Cambridge_Bay +America/Campo_Grande +America/Cancun +America/Caracas +America/Cayenne +America/Chicago +America/Chihuahua +America/Ciudad_Juarez +America/Costa_Rica +America/Creston +America/Cuiaba +America/Curacao +America/Danmarkshavn +America/Dawson +America/Dawson_Creek +America/Denver +America/Detroit +America/Edmonton +America/Eirunepe +America/El_Salvador +America/Fort_Nelson +America/Fortaleza +America/Glace_Bay +America/Goose_Bay +America/Grand_Turk +America/Guatemala +America/Guayaquil +America/Guyana +America/Halifax +America/Havana +America/Hermosillo +America/Indiana/Indianapolis +America/Indiana/Knox +America/Indiana/Marengo +America/Indiana/Petersburg +America/Indiana/Tell_City +America/Indiana/Vevay +America/Indiana/Vincennes +America/Indiana/Winamac +America/Inuvik +America/Iqaluit +America/Jamaica +America/Juneau +America/Kentucky/Louisville +America/Kentucky/Monticello +America/La_Paz +America/Lima +America/Los_Angeles +America/Maceio +America/Managua +America/Manaus +America/Martinique +America/Matamoros +America/Mazatlan +America/Menominee +America/Merida +America/Metlakatla +America/Mexico_City +America/Miquelon +America/Moncton +America/Monterrey +America/Montevideo +America/Nassau +America/New_York +America/Nipigon +America/Nome +America/Noronha +America/North_Dakota/Beulah +America/North_Dakota/Center +America/North_Dakota/New_Salem +America/Nuuk +America/Ojinaga +America/Panama +America/Pangnirtung +America/Paramaribo +America/Phoenix +America/Port-au-Prince +America/Port_of_Spain +America/Porto_Velho +America/Puerto_Rico +America/Punta_Arenas +America/Rainy_River +America/Rankin_Inlet +America/Recife +America/Regina +America/Resolute +America/Rio_Branco +America/Santarem +America/Santiago +America/Santo_Domingo +America/Sao_Paulo +America/Scoresbysund +America/Sitka +America/St_Johns +America/Swift_Current +America/Tegucigalpa +America/Thule +America/Thunder_Bay +America/Tijuana +America/Toronto +America/Vancouver +America/Whitehorse +America/Winnipeg +America/Yakutat +America/Yellowknife +Antarctica/Casey +Antarctica/Davis +Antarctica/DumontDUrville +Antarctica/Macquarie +Antarctica/Mawson +Antarctica/Palmer +Antarctica/Rothera +Antarctica/Syowa +Antarctica/Troll +Antarctica/Vostok +Asia/Almaty +Asia/Amman +Asia/Anadyr +Asia/Aqtau +Asia/Aqtobe +Asia/Ashgabat +Asia/Atyrau +Asia/Baghdad +Asia/Baku +Asia/Bangkok +Asia/Barnaul +Asia/Beirut +Asia/Bishkek +Asia/Brunei +Asia/Chita +Asia/Choibalsan +Asia/Colombo +Asia/Damascus +Asia/Dhaka +Asia/Dili +Asia/Dubai +Asia/Dushanbe +Asia/Famagusta +Asia/Gaza +Asia/Hebron +Asia/Ho_Chi_Minh +Asia/Hong_Kong +Asia/Hovd +Asia/Irkutsk +Asia/Jakarta +Asia/Jayapura +Asia/Jerusalem +Asia/Kabul +Asia/Kamchatka +Asia/Karachi +Asia/Kathmandu +Asia/Khandyga +Asia/Kolkata +Asia/Krasnoyarsk +Asia/Kuala_Lumpur +Asia/Kuching +Asia/Macau +Asia/Magadan +Asia/Makassar +Asia/Manila +Asia/Nicosia +Asia/Novokuznetsk +Asia/Novosibirsk +Asia/Omsk +Asia/Oral +Asia/Pontianak +Asia/Pyongyang +Asia/Qatar +Asia/Qostanay +Asia/Qyzylorda +Asia/Riyadh +Asia/Sakhalin +Asia/Samarkand +Asia/Seoul +Asia/Shanghai +Asia/Singapore +Asia/Srednekolymsk +Asia/Taipei +Asia/Tashkent +Asia/Tbilisi +Asia/Tehran +Asia/Thimphu +Asia/Tokyo +Asia/Tomsk +Asia/Ulaanbaatar +Asia/Urumqi +Asia/Ust-Nera +Asia/Vladivostok +Asia/Yakutsk +Asia/Yangon +Asia/Yekaterinburg +Asia/Yerevan +Atlantic/Azores +Atlantic/Bermuda +Atlantic/Canary +Atlantic/Cape_Verde +Atlantic/Faroe +Atlantic/Madeira +Atlantic/Reykjavik +Atlantic/South_Georgia +Atlantic/Stanley +Australia/Adelaide +Australia/Brisbane +Australia/Broken_Hill +Australia/Darwin +Australia/Eucla +Australia/Hobart +Australia/Lindeman +Australia/Lord_Howe +Australia/Melbourne +Australia/Perth +Australia/Sydney +Europe/Amsterdam +Europe/Andorra +Europe/Astrakhan +Europe/Athens +Europe/Belgrade +Europe/Berlin +Europe/Brussels +Europe/Bucharest +Europe/Budapest +Europe/Chisinau +Europe/Copenhagen +Europe/Dublin +Europe/Gibraltar +Europe/Helsinki +Europe/Istanbul +Europe/Kaliningrad +Europe/Kiev +Europe/Kirov +Europe/Lisbon +Europe/London +Europe/Luxembourg +Europe/Madrid +Europe/Malta +Europe/Minsk +Europe/Monaco +Europe/Moscow +Europe/Oslo +Europe/Paris +Europe/Prague +Europe/Riga +Europe/Rome +Europe/Samara +Europe/Saratov +Europe/Simferopol +Europe/Sofia +Europe/Stockholm +Europe/Tallinn +Europe/Tirane +Europe/Ulyanovsk +Europe/Uzhgorod +Europe/Vienna +Europe/Vilnius +Europe/Volgograd +Europe/Warsaw +Europe/Zaporozhye +Europe/Zurich +Indian/Chagos +Indian/Christmas +Indian/Cocos +Indian/Kerguelen +Indian/Mahe +Indian/Maldives +Indian/Mauritius +Indian/Reunion +Pacific/Apia +Pacific/Auckland +Pacific/Bougainville +Pacific/Chatham +Pacific/Chuuk +Pacific/Easter +Pacific/Efate +Pacific/Enderbury +Pacific/Fakaofo +Pacific/Fiji +Pacific/Funafuti +Pacific/Galapagos +Pacific/Gambier +Pacific/Guadalcanal +Pacific/Guam +Pacific/Honolulu +Pacific/Kiritimati +Pacific/Kosrae +Pacific/Kwajalein +Pacific/Majuro +Pacific/Marquesas +Pacific/Nauru +Pacific/Niue +Pacific/Norfolk +Pacific/Noumea +Pacific/Pago_Pago +Pacific/Palau +Pacific/Pitcairn +Pacific/Pohnpei +Pacific/Port_Moresby +Pacific/Rarotonga +Pacific/Tahiti +Pacific/Tarawa +Pacific/Tongatapu +Pacific/Wake +Pacific/Wallis +UTC \ No newline at end of file