mirror of
https://github.com/muhac/chinese-holidays-calendar.git
synced 2024-11-27 01:50:00 +08:00
Merge pull request #19 from theRank/dev
This commit is contained in:
commit
5231d258ee
@ -1,15 +1,28 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
|
||||
# Matches multiple files with brace expansion notation
|
||||
[*.{py,java,cpp,go,js,html}]
|
||||
charset = utf-8
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
|
||||
# Matches multiple files with brace expansion notation
|
||||
[{*.go,go.mod,go.sum}]
|
||||
charset = utf-8
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.py]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.{js,html,css,json,yml}]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.{md,txt}]
|
||||
trim_trailing_whitespace = false
|
||||
|
@ -1,4 +1,4 @@
|
||||
name: Update Calendar Data
|
||||
name: Continuous Delivery
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
@ -6,7 +6,7 @@ on:
|
||||
- cron: "0 0 1 * *"
|
||||
push:
|
||||
branches:
|
||||
- "**"
|
||||
- "main"
|
||||
|
||||
jobs:
|
||||
main:
|
||||
@ -18,7 +18,7 @@ jobs:
|
||||
ref: ${{ github.head_ref }}
|
||||
fetch-depth: 2
|
||||
|
||||
- name: Set up Python environment
|
||||
- name: Setup Python environment
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.9
|
||||
@ -28,11 +28,11 @@ jobs:
|
||||
python -m pip install --upgrade pip
|
||||
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
|
||||
|
||||
- name: Run crawler
|
||||
- name: Run crawler script
|
||||
shell: bash
|
||||
run: python crawler.py
|
||||
|
||||
- name: Setup Go
|
||||
- name: Setup Go environment
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.19
|
||||
@ -40,7 +40,7 @@ jobs:
|
||||
- name: Go Build
|
||||
run: go build -o . main
|
||||
|
||||
- name: Generate ICS
|
||||
- name: Generate ICS files
|
||||
run: ./main
|
||||
|
||||
- name: Commit changes
|
22
.github/workflows/ci.yml
vendored
22
.github/workflows/ci.yml
vendored
@ -49,3 +49,25 @@ jobs:
|
||||
with:
|
||||
version: latest
|
||||
working-directory: .
|
||||
|
||||
pylint:
|
||||
name: Pylint
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [ "3.9", "3.10" ]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install pylint
|
||||
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
|
||||
- name: Analysing the code with pylint
|
||||
run: |
|
||||
pylint $(git ls-files '*.py')
|
||||
|
@ -1,8 +1,8 @@
|
||||
# 中国大陆节假日安排 · 日历订阅<br><br>Chinese Holidays Calendar
|
||||
|
||||
subscription link of public holidays in mainland China
|
||||
Subscription link of public holidays in mainland China
|
||||
|
||||
> Calendar data updated at 1:28 on September 16, 2022
|
||||
> Calendar data updated at 9:59 on December 1, 2022
|
||||
|
||||
## Demo
|
||||
|
||||
|
79
crawler.py
79
crawler.py
@ -1,3 +1,5 @@
|
||||
"""从国务院官网抓取放假信息"""
|
||||
|
||||
import os
|
||||
import re
|
||||
from datetime import datetime, timezone, timedelta
|
||||
@ -7,6 +9,7 @@ import requests
|
||||
|
||||
|
||||
def main():
|
||||
"""更新节假日信息"""
|
||||
comments: list[str] = [
|
||||
"// automatically generated by crawler.py",
|
||||
"// manually checked by DATA NOT VERIFIED",
|
||||
@ -17,92 +20,90 @@ def main():
|
||||
file = f'./data/{year}.txt'
|
||||
|
||||
if os.path.isfile(file):
|
||||
with open(file) as f:
|
||||
existing = f.read()
|
||||
with open(file, encoding='utf-8') as f_obj:
|
||||
existing = f_obj.read()
|
||||
if comments[0] in existing and comments[1] not in existing:
|
||||
continue
|
||||
continue # 数据已人工确认
|
||||
|
||||
with open(file, 'w') as f:
|
||||
f.write(
|
||||
with open(file, 'w', encoding='utf-8') as f_obj:
|
||||
f_obj.write(
|
||||
f"{comments[0]} ({beijing_time().strftime('%-m/%-d/%Y')})\n"
|
||||
f"{comments[1]}\n// source: {link}\n\n{holidays}"
|
||||
)
|
||||
|
||||
with open('./README.md', 'r', encoding='utf-8') as f_obj:
|
||||
content = f_obj.read().split('\n')
|
||||
|
||||
update_info = "> Calendar data updated "
|
||||
with open('./README.md', 'r') as f:
|
||||
content = f.read().split('\n')
|
||||
for i in range(len(content)):
|
||||
if content[i].startswith(update_info):
|
||||
for i, line in enumerate(content):
|
||||
if line.startswith(update_info):
|
||||
content[i] = update_info + beijing_time().strftime("at %-H:%M on %B %-d, %Y")
|
||||
with open('./README.md', 'w') as f:
|
||||
f.write('\n'.join(content))
|
||||
|
||||
with open('./README.md', 'w', encoding='utf-8') as f_obj:
|
||||
f_obj.write('\n'.join(content))
|
||||
|
||||
|
||||
def data() -> Iterator[Tuple[str, str, str]]:
|
||||
"""爬取国务院网站数据"""
|
||||
for year, link in source():
|
||||
print(f"\n\n{year}: {link}")
|
||||
results: list[str] = []
|
||||
|
||||
r = requests.get(link)
|
||||
r.encoding = r.apparent_encoding
|
||||
response = requests.get(link, timeout=(5, 10))
|
||||
response.encoding = response.apparent_encoding
|
||||
|
||||
line_regex = r"(?P<id>.)、(?P<name>.*):(</.*?>)?(?P<detail>.*放假.*。)"
|
||||
for line in r.text.replace('<br/>', '\n').split('\n'):
|
||||
match = re.search(line_regex, line)
|
||||
if match is None:
|
||||
continue
|
||||
|
||||
work, rest, *_ = match.group('detail').split('。')
|
||||
dates = ';'.join((match.group('name'), parse(work), parse(rest)))
|
||||
print(dates) # todo: 需要人工干预如下情况: 1.与周末连休, 2.补休
|
||||
results.append(f"{dates:30} // {match.group('detail')}")
|
||||
for line in response.text.replace('<br/>', '\n').split('\n'):
|
||||
if match := re.search(line_regex, line):
|
||||
work, rest, *_ = match.group('detail').split('。')
|
||||
dates = ';'.join((match.group('name'), parse(work), parse(rest)))
|
||||
print(dates) # 已知需要人工干预如下情况: 1.与周末连休, 2.补休
|
||||
results.append(f"{dates:30} // {match.group('detail')}")
|
||||
|
||||
yield year, link, '\n'.join(results)
|
||||
|
||||
|
||||
def parse(text: str) -> str:
|
||||
"""解析节假日安排数据"""
|
||||
results: list[str] = []
|
||||
range_type_a = r"(?P<m1>\d?\d)月(?P<d1>\d?\d)日至(?P<m2>\d?\d)月(?P<d2>\d?\d)日"
|
||||
range_type_b = r"(?P<m1>\d?\d)月(?P<d1>\d?\d)日至(?P<d2>\d?\d)日"
|
||||
single_date = r"(?P<m1>\d?\d)月(?P<d1>\d?\d)日"
|
||||
|
||||
for item in text.split('、'):
|
||||
match = re.search(range_type_a, item)
|
||||
if match is not None:
|
||||
results.append(f"{match.group('m1')}.{match.group('d1')}-{match.group('m2')}.{match.group('d2')}")
|
||||
if match := re.search(range_type_a, item):
|
||||
results.append(f"{match.group('m1')}.{match.group('d1')}-"
|
||||
f"{match.group('m2')}.{match.group('d2')}")
|
||||
print(f"\tA: {results[-1]:15} {item}")
|
||||
continue
|
||||
|
||||
match = re.search(range_type_b, item)
|
||||
if match is not None:
|
||||
results.append(f"{match.group('m1')}.{match.group('d1')}-{match.group('m1')}.{match.group('d2')}")
|
||||
elif match := re.search(range_type_b, item):
|
||||
results.append(f"{match.group('m1')}.{match.group('d1')}-"
|
||||
f"{match.group('m1')}.{match.group('d2')}")
|
||||
print(f"\tB: {results[-1]:15} {item}")
|
||||
continue
|
||||
|
||||
match = re.search(single_date, item)
|
||||
if match is not None:
|
||||
elif match := re.search(single_date, item):
|
||||
results.append(f"{match.group('m1')}.{match.group('d1')}")
|
||||
print(f"\tS: {results[-1]:15} {item}")
|
||||
continue
|
||||
|
||||
print(f"\tX: {'':15} {item}")
|
||||
else:
|
||||
print(f"\tX: {'':15} {item}")
|
||||
|
||||
return ','.join(results)
|
||||
|
||||
|
||||
def source() -> Iterator[Tuple[str, str]]:
|
||||
"""获取官网发布通知列表"""
|
||||
search_url = "http://sousuo.gov.cn/s.htm?t=paper&advance=false&n=&codeYear=&codeCode=" \
|
||||
"&searchfield=title&sort=&q=%E8%8A%82%E5%81%87%E6%97%A5%E5%AE%89%E6%8E%92"
|
||||
link_regex = r"href=['\"](?P<link>.*?)['\"].*国务院办公厅关于(?P<year>20\d\d)年.*通知"
|
||||
|
||||
for line in requests.get(search_url).text.split('\n'):
|
||||
match = re.search(link_regex, line)
|
||||
if match is None:
|
||||
continue
|
||||
yield match.group('year'), match.group('link')
|
||||
for line in requests.get(search_url, timeout=(5, 10)).text.split('\n'):
|
||||
if match := re.search(link_regex, line):
|
||||
yield match.group('year'), match.group('link')
|
||||
|
||||
|
||||
def beijing_time() -> datetime:
|
||||
"""获取当前北京时间"""
|
||||
utc_time = datetime.utcnow().replace(tzinfo=timezone.utc)
|
||||
return utc_time.astimezone(timezone(timedelta(hours=8)))
|
||||
|
||||
|
@ -3,8 +3,10 @@
|
||||
|
||||
数据格式
|
||||
节日名;放假日期;补班日期
|
||||
没有补班留空,“;”不可省略
|
||||
没有补班留空,“;” 不可省略
|
||||
|
||||
日期格式
|
||||
M.D,M.D-M.D
|
||||
支持多选“,”和区间“-”
|
||||
支持多选 “,” 和区间 “-”
|
||||
前一年的日期格式为 “0.X”
|
||||
X 为距离 1 月 1 日的天数
|
||||
|
7
go.mod
7
go.mod
@ -2,4 +2,9 @@ module main
|
||||
|
||||
go 1.19
|
||||
|
||||
require github.com/google/uuid v1.3.0
|
||||
require (
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/samber/lo v1.28.2
|
||||
)
|
||||
|
||||
require golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect
|
||||
|
9
go.sum
9
go.sum
@ -1,2 +1,11 @@
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/samber/lo v1.28.2 h1:f1gctelJ5YQk336wCN+Elr90FyhZ6ArhelD5kjhNTz4=
|
||||
github.com/samber/lo v1.28.2/go.mod h1:it33p9UtPMS7z72fP4gw/EIfQB2eI8ke7GR2wc6+Rhg=
|
||||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||
github.com/thoas/go-funk v0.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M=
|
||||
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 h1:3MTrJm4PyNL9NBqvYDSj3DHl46qQakyfqfWo4jgfaEM=
|
||||
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
|
14
main.go
14
main.go
@ -1,18 +1,16 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"main/parse/base"
|
||||
"main/parse/app"
|
||||
"main/parse/core"
|
||||
)
|
||||
|
||||
func main() {
|
||||
holidays := core.Data().Read(`^20\d\d`).From("data").Parse().Sort().Get()
|
||||
holidays := app.Data().Read(`^20\d\d`).From("data").Parse().Sort().Get().Print("==== HOLIDAYS ====")
|
||||
|
||||
holidays.Print("==== HOLIDAYS ====")
|
||||
app.Data(holidays).Write("index.html").To("docs").Title("节假日").Set()
|
||||
app.Data(holidays).Write("holiday.ics").To("docs").Title("节假日").Set()
|
||||
|
||||
core.Data(holidays).Write("index.html").To("docs").Title("节假日").Set()
|
||||
core.Data(holidays).Write("holiday.ics").To("docs").Title("节假日").Set()
|
||||
|
||||
core.Data(holidays.Select(base.Rest)).Write("rest.ics").To("docs").Title("节假日(假期)").Set()
|
||||
core.Data(holidays.Select(base.Work)).Write("work.ics").To("docs").Title("节假日(补班)").Set()
|
||||
app.Data(holidays.Select(core.Rest)).Write("rest.ics").To("docs").Title("节假日(假期)").Set()
|
||||
app.Data(holidays.Select(core.Work)).Write("work.ics").To("docs").Title("节假日(补班)").Set()
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
package core
|
||||
package app
|
||||
|
||||
import "main/parse/base"
|
||||
import "main/parse/core"
|
||||
|
||||
func Data(optional ...base.Holidays) Handler {
|
||||
func Data(optional ...core.Holidays) Handler {
|
||||
return newHandler(optional...)
|
||||
}
|
||||
|
||||
@ -21,7 +21,7 @@ type readData interface {
|
||||
|
||||
type getData interface {
|
||||
Sort() getData
|
||||
Get() base.Holidays
|
||||
Get() core.Holidays
|
||||
}
|
||||
|
||||
type setDirOut interface {
|
@ -1,9 +1,9 @@
|
||||
package core
|
||||
package app
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"main/parse/base"
|
||||
"main/parse/core"
|
||||
"main/parse/data"
|
||||
"main/parse/data/input"
|
||||
"main/parse/data/output"
|
||||
@ -11,15 +11,15 @@ import (
|
||||
"main/parse/data/write"
|
||||
)
|
||||
|
||||
func newHandler(optional ...base.Holidays) Handler {
|
||||
if len(optional) == 0 {
|
||||
func newHandler(optionalData ...core.Holidays) Handler {
|
||||
if len(optionalData) == 0 {
|
||||
return handler{}
|
||||
}
|
||||
return handler{data: optional[0]}
|
||||
return handler{data: optionalData[0]}
|
||||
}
|
||||
|
||||
type handler struct {
|
||||
data base.Holidays
|
||||
data core.Holidays
|
||||
|
||||
reader data.Reader
|
||||
writer data.Writer
|
||||
@ -65,7 +65,7 @@ func (h handler) Title(name string) writeData {
|
||||
return h
|
||||
}
|
||||
|
||||
func (h handler) Get() base.Holidays {
|
||||
func (h handler) Get() core.Holidays {
|
||||
return h.data
|
||||
}
|
||||
|
@ -1,6 +0,0 @@
|
||||
package base
|
||||
|
||||
const (
|
||||
Rest = iota // 假日
|
||||
Work // 补班
|
||||
)
|
@ -1,29 +0,0 @@
|
||||
package base
|
||||
|
||||
import "time"
|
||||
|
||||
// Holidays data
|
||||
type Holidays []Holiday
|
||||
|
||||
// Holiday data per day
|
||||
type Holiday struct {
|
||||
Group string
|
||||
Date time.Time
|
||||
Name string
|
||||
Type int
|
||||
Nth int
|
||||
Total int
|
||||
}
|
||||
|
||||
func (h Holidays) Where(filter func(Holiday) bool) (result Holidays) {
|
||||
for _, item := range h {
|
||||
if filter(item) {
|
||||
result = append(result, item)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (h Holidays) Len() int { return len(h) }
|
||||
func (h Holidays) Less(i, j int) bool { return h[i].Date.Before(h[j].Date) }
|
||||
func (h Holidays) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
|
@ -1,26 +0,0 @@
|
||||
package base
|
||||
|
||||
import "fmt"
|
||||
|
||||
func selectType(t int) func(Holiday) bool {
|
||||
return func(holidays Holiday) bool {
|
||||
return holidays.Type == t
|
||||
}
|
||||
}
|
||||
|
||||
func (h Holidays) Select(t int) Holidays {
|
||||
return h.Where(selectType(t))
|
||||
}
|
||||
|
||||
func (h Holidays) Print(titles ...string) (result string) {
|
||||
for _, title := range titles {
|
||||
fmt.Println(title)
|
||||
}
|
||||
|
||||
for _, day := range h {
|
||||
result += fmt.Sprintf("%+v\n", day)
|
||||
}
|
||||
|
||||
fmt.Println(result)
|
||||
return
|
||||
}
|
42
parse/core/base.go
Normal file
42
parse/core/base.go
Normal file
@ -0,0 +1,42 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type Status string
|
||||
|
||||
const (
|
||||
Rest Status = "rest" // 假日
|
||||
Work Status = "work" // 补班
|
||||
)
|
||||
|
||||
// Holidays data
|
||||
type Holidays []Holiday
|
||||
|
||||
// Holiday data per day
|
||||
type Holiday struct {
|
||||
Group string
|
||||
Date time.Time
|
||||
Name string
|
||||
Type Status
|
||||
Nth int
|
||||
Total int
|
||||
}
|
||||
|
||||
func (h Holidays) Select(t Status) Holidays {
|
||||
return lo.Filter(h, func(d Holiday, _ int) bool { return d.Type == t })
|
||||
}
|
||||
|
||||
func (h Holidays) Print(titles ...string) Holidays {
|
||||
lo.ForEach(titles, func(title string, _ int) { log.Println(title) })
|
||||
lo.ForEach(h, func(day Holiday, _ int) { log.Printf("%+v\n", day) })
|
||||
return h
|
||||
}
|
||||
|
||||
func (h Holidays) Len() int { return len(h) }
|
||||
func (h Holidays) Less(i, j int) bool { return h[i].Date.Before(h[j].Date) }
|
||||
func (h Holidays) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
|
@ -4,7 +4,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"main/parse/base"
|
||||
"main/parse/core"
|
||||
"main/parse/data"
|
||||
)
|
||||
|
||||
@ -14,7 +14,7 @@ func NewParser() data.Parser {
|
||||
|
||||
type parser struct{}
|
||||
|
||||
func (p parser) Parse(raw data.Input) (result base.Holidays) {
|
||||
func (p parser) Parse(raw data.Input) (result core.Holidays) {
|
||||
for _, year := range raw {
|
||||
days, _ := parse(year)
|
||||
result = append(result, days...)
|
||||
@ -22,33 +22,33 @@ func (p parser) Parse(raw data.Input) (result base.Holidays) {
|
||||
return
|
||||
}
|
||||
|
||||
func parse(raw data.InputRaw) (result base.Holidays, err error) {
|
||||
dayCount := make(map[string]map[int]int)
|
||||
func parse(raw data.InputRaw) (result core.Holidays, err error) {
|
||||
dayCount := make(map[string]map[core.Status]int)
|
||||
|
||||
for group, holiday := range raw.Data {
|
||||
groupName := fmt.Sprintf("%04d%02d", raw.Year, group+1)
|
||||
dayCount[groupName] = make(map[int]int)
|
||||
dayCount[groupName] = make(map[core.Status]int)
|
||||
info := strings.Split(holiday, ";")
|
||||
|
||||
for i, day := range holidays(raw.Year, info[1]) {
|
||||
restDay := base.Holiday{
|
||||
restDay := core.Holiday{
|
||||
Group: groupName,
|
||||
Name: info[0],
|
||||
Nth: i + 1,
|
||||
Date: day,
|
||||
Type: base.Rest,
|
||||
Type: core.Rest,
|
||||
}
|
||||
result = append(result, restDay)
|
||||
dayCount[restDay.Group][restDay.Type]++
|
||||
}
|
||||
|
||||
for i, day := range holidays(raw.Year, info[2]) {
|
||||
workDay := base.Holiday{
|
||||
workDay := core.Holiday{
|
||||
Group: groupName,
|
||||
Name: info[0],
|
||||
Nth: i + 1,
|
||||
Date: day,
|
||||
Type: base.Work,
|
||||
Type: core.Work,
|
||||
}
|
||||
result = append(result, workDay)
|
||||
dayCount[workDay.Group][workDay.Type]++
|
||||
|
@ -18,17 +18,18 @@ func date(year int, date string) (result time.Time) {
|
||||
return
|
||||
}
|
||||
|
||||
func holidays(year int, daysRaw string) (result []time.Time) {
|
||||
if daysRaw == "" {
|
||||
func holidays(year int, days string) (result []time.Time) {
|
||||
if days == "" {
|
||||
return
|
||||
}
|
||||
|
||||
days := strings.Split(daysRaw, ",")
|
||||
for _, day := range days {
|
||||
for _, day := range strings.Split(days, ",") {
|
||||
if strings.Contains(day, "-") {
|
||||
period := strings.Split(day, "-")
|
||||
for d := date(year, period[0]); !d.After(date(year, period[1])); d = d.AddDate(0, 0, 1) {
|
||||
d := date(year, period[0])
|
||||
for !d.After(date(year, period[1])) {
|
||||
result = append(result, d)
|
||||
d = d.AddDate(0, 0, 1)
|
||||
}
|
||||
} else {
|
||||
result = append(result, date(year, day))
|
||||
|
@ -1,17 +1,17 @@
|
||||
package data
|
||||
|
||||
import "main/parse/base"
|
||||
import "main/parse/core"
|
||||
|
||||
type Reader interface {
|
||||
Read() Input
|
||||
}
|
||||
|
||||
type Parser interface {
|
||||
Parse(Input) base.Holidays
|
||||
Parse(Input) core.Holidays
|
||||
}
|
||||
|
||||
type Formatter interface {
|
||||
Format(base.Holidays) Output
|
||||
Format(core.Holidays) Output
|
||||
}
|
||||
|
||||
type Writer interface {
|
||||
|
@ -6,8 +6,9 @@ import (
|
||||
"math/rand"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/samber/lo"
|
||||
|
||||
"main/parse/base"
|
||||
"main/parse/core"
|
||||
"main/parse/data"
|
||||
)
|
||||
|
||||
@ -19,21 +20,21 @@ type formatter struct {
|
||||
name string
|
||||
}
|
||||
|
||||
func (f formatter) Format(info base.Holidays) (result data.Output) {
|
||||
result.Prefix = fmt.Sprintf(IcsHead, f.name)
|
||||
result.Suffix = IcsTail
|
||||
func (f formatter) Format(info core.Holidays) (result data.Output) {
|
||||
result.Prefix = fmt.Sprintf(icsHead, f.name)
|
||||
result.Suffix = icsTail
|
||||
|
||||
uuid.SetRand(rand.New(rand.NewSource(int64(crc32.ChecksumIEEE([]byte(f.name))))))
|
||||
|
||||
for _, day := range info {
|
||||
outputDay := event{
|
||||
Id: uuid.NewString(),
|
||||
Group: day.Group,
|
||||
Title: getTitle(day),
|
||||
Date: day.Date,
|
||||
Desc: getDesc(day),
|
||||
}
|
||||
result.Body = append(result.Body, outputDay.Ics())
|
||||
}
|
||||
result.Body = lo.Map(info, func(day core.Holiday, i int) string {
|
||||
return event{
|
||||
id: uuid.NewString(),
|
||||
group: day.Group,
|
||||
title: getTitle(day),
|
||||
date: day.Date,
|
||||
desc: getDesc(day),
|
||||
}.Ics()
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -4,46 +4,46 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"main/parse/base"
|
||||
"main/parse/core"
|
||||
)
|
||||
|
||||
const (
|
||||
IcsHead = "BEGIN:VCALENDAR\nVERSION:2.0\nPRODID:-//Rank Technology//Chinese Holidays//EN\nX-WR-CALNAME:%s"
|
||||
IcsEvent = "BEGIN:VEVENT\nUID:%s\nDTSTART;VALUE=DATE:%s\nSUMMARY:%s\nDESCRIPTION:%s\nEND:VEVENT"
|
||||
IcsTail = "END:VCALENDAR"
|
||||
icsHead = "BEGIN:VCALENDAR\nVERSION:2.0\nPRODID:-//Rank Technology//Chinese Holidays//EN\nX-WR-CALNAME:%s"
|
||||
icsEvent = "BEGIN:VEVENT\nUID:%s\nDTSTART;VALUE=DATE:%s\nSUMMARY:%s\nDESCRIPTION:%s\nEND:VEVENT"
|
||||
icsTail = "END:VCALENDAR"
|
||||
)
|
||||
|
||||
// event data
|
||||
type event struct {
|
||||
Id string
|
||||
Group string
|
||||
Title string
|
||||
Date time.Time
|
||||
Desc string
|
||||
id string
|
||||
group string
|
||||
title string
|
||||
date time.Time
|
||||
desc string
|
||||
}
|
||||
|
||||
func (d event) Ics() string {
|
||||
return fmt.Sprintf(
|
||||
IcsEvent,
|
||||
d.Id,
|
||||
d.Date.Format("20060102"),
|
||||
d.Title,
|
||||
d.Desc,
|
||||
icsEvent,
|
||||
d.id,
|
||||
d.date.Format("20060102"),
|
||||
d.title,
|
||||
d.desc,
|
||||
)
|
||||
}
|
||||
|
||||
func getStatusName(status int) string {
|
||||
name := map[int]string{
|
||||
base.Rest: "假期",
|
||||
base.Work: "补班",
|
||||
func getStatusName(status core.Status) string {
|
||||
name := map[core.Status]string{
|
||||
core.Rest: "假期",
|
||||
core.Work: "补班",
|
||||
}
|
||||
return name[status]
|
||||
}
|
||||
|
||||
func getTitle(item base.Holiday) string {
|
||||
func getTitle(item core.Holiday) string {
|
||||
return fmt.Sprintf("%s%s", item.Name, getStatusName(item.Type))
|
||||
}
|
||||
|
||||
func getDesc(item base.Holiday) string {
|
||||
func getDesc(item core.Holiday) string {
|
||||
return fmt.Sprintf("%s 第%d天/共%d天", getStatusName(item.Type), item.Nth, item.Total)
|
||||
}
|
||||
|
@ -9,6 +9,8 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/samber/lo"
|
||||
|
||||
"main/parse/data"
|
||||
)
|
||||
|
||||
@ -37,20 +39,20 @@ func (dw dataReader) Read() (result data.Input) {
|
||||
defer wg.Done()
|
||||
raw, err := dw.load(file.Name)
|
||||
if err != nil {
|
||||
fmt.Printf("Error loading %s: %s", file.Name, err)
|
||||
log.Printf("Error loading %s: %s\n", file.Name, err)
|
||||
return
|
||||
}
|
||||
|
||||
result := data.InputRaw{
|
||||
res := data.InputRaw{
|
||||
Year: file.Year,
|
||||
Data: lines(raw),
|
||||
}
|
||||
if len(result.Data) == 0 {
|
||||
fmt.Printf("No data in %s", file.Name)
|
||||
if len(res.Data) == 0 {
|
||||
log.Printf("No data in %s\n", file.Name)
|
||||
return
|
||||
}
|
||||
|
||||
resultChan <- result
|
||||
resultChan <- res
|
||||
}(f)
|
||||
}
|
||||
|
||||
@ -71,12 +73,11 @@ func (dw dataReader) fileList() (result []fileInfo) {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
if yr, err := year(file.Name(), dw.File); err == nil && !file.IsDir() {
|
||||
result = append(result, fileInfo{Name: file.Name(), Year: yr})
|
||||
}
|
||||
}
|
||||
return result
|
||||
return lo.FilterMap(files, func(file os.DirEntry, _ int) (fileInfo, bool) {
|
||||
yr, e := year(file.Name(), dw.File)
|
||||
isFile := e == nil && !file.IsDir()
|
||||
return fileInfo{Name: file.Name(), Year: yr}, isFile
|
||||
})
|
||||
}
|
||||
|
||||
func (dw dataReader) load(filename string) (result string, err error) {
|
||||
@ -105,12 +106,11 @@ func lines(data string) (result []string) {
|
||||
dateRegex = regexp.MustCompile(fmt.Sprintf(`^[^;]+;%s;%s$`, dateAccept, dateAccept))
|
||||
)
|
||||
|
||||
for _, line := range strings.Split(data, "\n") {
|
||||
line = strings.Split(line, "//")[0]
|
||||
line = strings.TrimSpace(line)
|
||||
if dateRegex.MatchString(line) {
|
||||
result = append(result, line)
|
||||
}
|
||||
}
|
||||
return result
|
||||
return lo.FilterMap(strings.Split(data, "\n"),
|
||||
func(line string, _ int) (string, bool) {
|
||||
line = strings.Split(line, "//")[0]
|
||||
line = strings.TrimSpace(line)
|
||||
return line, dateRegex.MatchString(line)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
@ -45,18 +45,18 @@ func Test_lines(t *testing.T) {
|
||||
args args
|
||||
wantResult []string
|
||||
}{
|
||||
{"1", args{"// none"}, nil},
|
||||
{"2", args{";1.1;2.2"}, nil},
|
||||
{"1", args{"// none"}, []string{}},
|
||||
{"2", args{";1.1;2.2"}, []string{}},
|
||||
{"3", args{"3;1.1;2.2"}, []string{"3;1.1;2.2"}},
|
||||
{"4", args{"4;1.1;"}, []string{"4;1.1;"}},
|
||||
{"5", args{"5;1.1;2.2,3.3"}, []string{"5;1.1;2.2,3.3"}},
|
||||
{"6", args{"6;1.1,2.2;3.3,4.4"}, []string{"6;1.1,2.2;3.3,4.4"}},
|
||||
{"7", args{"7;1.1,2.2;3.3,4.4-5.5"}, []string{"7;1.1,2.2;3.3,4.4-5.5"}},
|
||||
{"8", args{"8;1.1;2.2;"}, nil},
|
||||
{"9", args{"9;,1.1;2.2"}, nil},
|
||||
{"10", args{"10;1.1"}, nil},
|
||||
{"11", args{"11;1.1;2.2,"}, nil},
|
||||
{"12", args{"// 13;1.1;2.2 "}, nil},
|
||||
{"8", args{"8;1.1;2.2;"}, []string{}},
|
||||
{"9", args{"9;,1.1;2.2"}, []string{}},
|
||||
{"10", args{"10;1.1"}, []string{}},
|
||||
{"11", args{"11;1.1;2.2,"}, []string{}},
|
||||
{"12", args{"// 13;1.1;2.2 "}, []string{}},
|
||||
{"13", args{"13;1.1;2.2 // none"}, []string{"13;1.1;2.2"}},
|
||||
{"14", args{"14;1.1;2.2 "}, []string{"14;1.1;2.2"}},
|
||||
{"15", args{" 15;1.1;2.2"}, []string{"15;1.1;2.2"}},
|
||||
|
@ -1,7 +1,6 @@
|
||||
package write
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
@ -38,5 +37,5 @@ func (dw dataWriter) Write(data data.Output) {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Println("write", n, "bytes to", dw.File)
|
||||
log.Println("write", n, "bytes to", dw.File)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user