2017年9月12日 星期二

happy-go-lucky 計算下班時間

以後不知道要寫什麼文章
就來寫些小程式誕生的故事

今天第一個故事就是計算下班時間的小工具

雖然說身為 RD 的我們不太可能準時下班
但偶爾也會有早來想要早點下班運動的時候

很不巧的我們的刷卡記錄要明天才能查詢
雖然我們可以偶爾不做滿 8個小時
但奉公守法的我還是覺得很不習慣

於是就想要知道大概的上班時間
這樣才能確定要幾點才能下班

後來靈機一動
想要利用電腦開機的時間來當做刷卡時間
理論上刷卡時間一定會在電腦開機之前
故以這個時間當基準一定不會有錯

註:不管是什麼機器,時間都需要校時
以我以前 MIS 的經驗,刷卡機會跟資料庫主機對時
而資料庫主機又會跟 AD Server 對時
AD Server 又會跟外部服務對時
這樣才能確保大家的時間都一致

以 Windows來說
我們可以去 事件檢視器 -> Windows 記錄 -> 系統
尋找一個事件檢視碼為 12 的事件
裡面就會有我們開機的時間

這時候利用 Google 我們查到了 "wevtutil" 這個指令
它可以用來查詢相關的事件並指定 Filter 條件(時間)
由於這個指令會把所有事件都查詢出來
故我們再加上 Find 這個指令
只挑選出 EventID 12 的行數
wevtutil qe system  "/q:*[System[TimeCreated[@SystemTime>='2017-09-10T00:00:00']]]" | 
find /N "12</EventID>"

另外一種解法
EventID 12 有一個特殊欄位
只有 EventID 12 才有
wevtutil qe system /q:Event[EventData[Data[@Name='StartTime']>'2017-09-10T00:00:00']]

出來的結果如下
<Event xmlns='http://schemas.microsoft.com/win/2004/08/events/event'>
<System>
<Provider Name='Microsoft-Windows-Kernel-General' Guid='{A68CA8B7-004F-D7B6-A698-07E2DE0F1F5D}'/>
<EventID>12</EventID>
<Version>0</Version>
<Level>4</Level>
<Task>0</Task>
<Opcode>0</Opcode>
<Keywords>0x8000000000000000</Keywords>
<TimeCreated SystemTime='2017-09-12T01:30:41.749600400Z'/>
<EventRecordID>302816</EventRecordID>
<Correlation/>
<Execution ProcessID='4' ThreadID='8'/>
<Channel>System</Channel>
<Computer> xxxxx </Computer>
<Security UserID='S-1-5-18'/>
</System>
<EventData>
<Data Name='MajorVersion'>6</Data>
<Data Name='MinorVersion'>1</Data>
<Data Name='BuildVersion'>7601</Data>
<Data Name='QfeVersion'>23807</Data>
<Data Name='ServiceVersion'>1</Data>
<Data Name='BootMode'>0</Data>
<Data Name='StartTime'>2017-09-12T01:30:41.125599300Z</Data>
</EventData>
</Event>

好了,食材都有了,就讓我們開始下廚吧

我們利用 Golang os/exec, time, strings 等 package
就可以簡單炒出一盤好菜了

package main

import (
    "fmt"
    "log"
    "os/exec"
    "strings"
    "time"
)

const workTime = 8 * 60 + 75

func getCMDResult(today string) (string, error) {
    subcmd := fmt.Sprintf("wevtutil qe system /q:Event[EventData[Data[@Name='StartTime']^>'%s']]", today)
    cmd := exec.Command("cmd", "/C", subcmd)

    out, err := cmd.Output()
    if err != nil {
        return "", err
    }

    return string(out), nil
}

func getStartTime(log string) string {
    pattern := "'StartTime'>"

    i := strings.Index(log, pattern)
    if i >= 0 {
        start := i + len(pattern)
        out := log[start:start+30]
        return out
    }

    return ""
}

func getHumanTime(t time.Time) string {
    return fmt.Sprintf(
        "%04d-%02d-%02d %02d:%02d",
        t.Year(),
        t.Month(),
        t.Day(),
        t.Hour(),
        t.Minute(),
    )
}

func printWorkTime(startTime string) {
    t1, err := time.Parse(time.RFC3339, startTime)
    if err != nil {
        fmt.Println(err)
        return
    }

    fmt.Println("")
    fmt.Printf("clock in  : %s \n", getHumanTime(t1.Local()))
    fmt.Printf("clock out : %s \n", getHumanTime(t1.Add(time.Duration(workTime)*time.Minute).Local()))
}

func main() {
    t1 := time.Now()
    t2 := time.Date(t1.Year(), t1.Month(), t1.Day(), 0, 0, 0, 0, time.FixedZone("UTC", 0))

    out, err := getCMDResult(t2.Format(time.RFC3339))
    if err != nil {
        log.Fatal(err)
    }

    startTime := getStartTime(out)

    if startTime != "" {
        printWorkTime(startTime)
        return
    }

    fmt.Println("Can't get start time in Windows event log")
}



https://github.com/tylpk1216/go-clockout

沒有留言:

張貼留言