Skip to content

Instantly share code, notes, and snippets.

@byyam
Last active May 29, 2025 10:48
Show Gist options
  • Save byyam/94d0c55063119cf53fa8d402deda09d1 to your computer and use it in GitHub Desktop.
Save byyam/94d0c55063119cf53fa8d402deda09d1 to your computer and use it in GitHub Desktop.
get workdays
package workdays
import (
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"time"
)
const (
workdayCacheDir = ".workday_cache"
workdayQueryURLTemplate = "https://api.apihubs.cn/holiday/get?year=%d&month=%s&workday=1&size=31"
)
// WorkdayInfo represents a single day's information from the API
type WorkdayInfo struct {
Year int `json:"year"`
Month int `json:"month"`
Date int `json:"date"`
YearWeek int `json:"yearweek"`
YearDay int `json:"yearday"`
LunarYear int `json:"lunar_year"`
LunarMonth int `json:"lunar_month"`
LunarDate int `json:"lunar_date"`
LunarYearDay int `json:"lunar_yearday"`
Week int `json:"week"`
Weekend int `json:"weekend"`
Workday int `json:"workday"`
Holiday int `json:"holiday"`
HolidayOr int `json:"holiday_or"`
HolidayOvertime int `json:"holiday_overtime"`
HolidayToday int `json:"holiday_today"`
HolidayLegal int `json:"holiday_legal"`
HolidayRecess int `json:"holiday_recess"`
}
// WorkdayResponse represents the complete API response
type WorkdayResponse struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data struct {
List []WorkdayInfo `json:"list"`
Page int `json:"page"`
Size int `json:"size"`
Total int `json:"total"`
} `json:"data"`
}
// WorkdayParser defines the interface for parsing workday data
type WorkdayParser interface {
GetTotalWorkdays() int
GetWorkdayList() []WorkdayInfo
IsValid() bool
}
// Implement WorkdayParser for WorkdayResponse
func (w *WorkdayResponse) GetTotalWorkdays() int {
return w.Data.Total
}
func (w *WorkdayResponse) GetWorkdayList() []WorkdayInfo {
return w.Data.List
}
func (w *WorkdayResponse) IsValid() bool {
return w.Code == 0
}
func verifyDate(dateStr string) error {
_, err := time.Parse("200601", dateStr)
if err != nil {
return fmt.Errorf("not a valid date: '%s'", dateStr)
}
return nil
}
func queryWorkdays(year, month int, verbose bool) (WorkdayParser, error) {
dateStr := fmt.Sprintf("%d%02d", year, month)
queryURL := fmt.Sprintf(workdayQueryURLTemplate, year, dateStr)
if verbose {
fmt.Println("Query workdays url:", queryURL)
}
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Get(queryURL)
if err != nil {
return nil, fmt.Errorf("http request error: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("http request failed with status: %s", resp.Status)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response body: %v", err)
}
var response WorkdayResponse
err = json.Unmarshal(body, &response)
if err != nil {
return nil, fmt.Errorf("failed to parse JSON response: %v", err)
}
if !response.IsValid() {
return nil, fmt.Errorf("api error: %v", response.Msg)
}
return &response, nil
}
func GetWorkdays(year, month int, verbose bool) int {
dateStr := fmt.Sprintf("%d%02d", year, month)
if err := verifyDate(dateStr); err != nil {
fmt.Println("error:", err)
return 0
}
currentFileDir, _ := os.Getwd()
workdayCachePath := filepath.Join(currentFileDir, workdayCacheDir)
workdayCacheFile := filepath.Join(workdayCachePath, dateStr)
var parser WorkdayParser
if err := os.MkdirAll(workdayCachePath, os.ModePerm); err != nil {
fmt.Println("error creating cache directory:", err)
return 0
}
if _, err := os.Stat(workdayCacheFile); os.IsNotExist(err) {
// Cache file does not exist, query the workdays
var err error
parser, err = queryWorkdays(year, month, verbose)
if err != nil {
fmt.Println("error:", err)
return 0
}
// Cache the complete response
responseData, err := json.Marshal(parser.(*WorkdayResponse))
if err != nil {
fmt.Println("error marshaling response:", err)
return 0
}
if err := os.WriteFile(workdayCacheFile, responseData, 0644); err != nil {
fmt.Println("error writing cache file:", err)
return 0
}
if verbose {
fmt.Println("write cache file to store workdays data")
}
} else {
// Cache file exists, read from it
content, err := os.ReadFile(workdayCacheFile)
if err != nil {
fmt.Println("error reading cache file:", err)
return 0
}
var response WorkdayResponse
if err := json.Unmarshal(content, &response); err != nil {
fmt.Println("error parsing cache file:", err)
return 0
}
parser = &response
if verbose {
fmt.Println("read cache file to get workdays data")
}
}
return parser.GetTotalWorkdays()
}
// IsWorkday checks if the given date is a workday
// If year, month, and day are all 0, it will use the current date
func IsWorkday(year, month, day int, verbose bool) bool {
if year == 0 && month == 0 && day == 0 {
now := time.Now()
year = now.Year()
month = int(now.Month())
day = now.Day()
}
dateStr := fmt.Sprintf("%d%02d", year, month)
if err := verifyDate(dateStr); err != nil {
fmt.Println("error:", err)
return false
}
currentFileDir, _ := os.Getwd()
workdayCachePath := filepath.Join(currentFileDir, workdayCacheDir)
workdayCacheFile := filepath.Join(workdayCachePath, dateStr)
var parser WorkdayParser
if err := os.MkdirAll(workdayCachePath, os.ModePerm); err != nil {
fmt.Println("error creating cache directory:", err)
return false
}
if _, err := os.Stat(workdayCacheFile); os.IsNotExist(err) {
// Cache file does not exist, query the workdays
var err error
parser, err = queryWorkdays(year, month, verbose)
if err != nil {
fmt.Println("error:", err)
return false
}
// Cache the complete response
responseData, err := json.Marshal(parser.(*WorkdayResponse))
if err != nil {
fmt.Println("error marshaling response:", err)
return false
}
if err := os.WriteFile(workdayCacheFile, responseData, 0644); err != nil {
fmt.Println("error writing cache file:", err)
return false
}
if verbose {
fmt.Println("write cache file to store workdays data")
}
} else {
// Cache file exists, read from it
content, err := os.ReadFile(workdayCacheFile)
if err != nil {
fmt.Println("error reading cache file:", err)
return false
}
var response WorkdayResponse
if err := json.Unmarshal(content, &response); err != nil {
fmt.Println("error parsing cache file:", err)
return false
}
parser = &response
if verbose {
fmt.Println("read cache file to get workdays data")
}
}
// Check if the specific day is a workday
for _, workday := range parser.GetWorkdayList() {
if workday.Date == year*10000+month*100+day {
return workday.Workday == 1
}
}
return false
}
@byyam
Copy link
Author

byyam commented Sep 5, 2024

package main

import (
	"fmt"

	workdays "git.garena.com/yam.zhang/jira_story_points/pkg/workdays"
)

func main() {
	// Test get workdays
	year := 2024
	month := 9

	workday := workdays.GetWorkdays(year, month, true)
	fmt.Printf("Workdays in %d-%02d: %d\n", year, month, workday)

	// Test is workday
	day := 1
	isWorkday := workdays.IsWorkday(year, month, day, true)
	fmt.Printf("Is workday in %d-%02d-%02d: %t\n", year, month, day, isWorkday)
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment