Skip to main content
FomiVIP
Tech Sharing

Syncing WeCom Check-ins: Everything Came Out 8 Hours Off

WeCom check-in data worked perfectly on local machines — then the server exported a timesheet 8 hours off. The timestamp was formatted twice, re-parsed as UTC in between. Along the way: access_token caching, duplicate pagination cursors, and row-by-row inserts for 2,000 records.

March 8, 2019API IntegrationTime ZonesPitfalls

We built an attendance summary for a client: every night, pull the previous day's check-in records from WeCom, write them to MySQL, and export an Excel for HR. Three days of local testing were flawless. On launch morning, HR sent one line: everyone's clock-in times are 8 hours late.

1. The Timestamp Got Converted Twice

The API returns checkin_time as a Unix timestamp — no timezone attached. Our script formatted it with PHP's date(), the server timezone set to Asia/Shanghai; that step was fine. The problem was an earlier "unified time handling" layer a colleague had written: it treated the timestamp as a string and parsed it again as UTC, adding one extra lap and shifting everything overall.

The fix was unglamorous: treat the value as a raw timestamp across the entire pipeline; format only at the final export step, with no conversions in between. We also added a raw_ts column to the database to keep the original value — when something breaks, you can always look back.

2. access_token Fetched on Every Request

Version one took the lazy path: every request called the token endpoint. Syncing 200-plus people meant dozens of requests, and one day we tripped the rate limit and the whole batch failed. We switched to Redis with a 7,000-second expiry — the official value is 7,200; leave margin — refreshing just before it lapses.

We'd integrated DingTalk's attendance API six months earlier. Different pits, much the same rate limits.

3. The Pagination Cursor Used Wrong

The check-in API paginates by cursor. Version one passed the last record's timestamp from the previous page as the cursor, so the boundary record got pulled twice and a few duplicates appeared in the timesheet. We switched to honestly using the next_cursor from the response, and added a unique index on (user_id, checkin_time) at write time, ignoring duplicates outright.

4. Inserting 2,000 Rows One at a Time

With 2,000-plus rows, the first pass looped single INSERTs and took over 20 seconds. Batching them into grouped INSERTs of 500 brought it down to just over 1 second. No cleverness involved — just don't forget.

5. We Ignored Makeup Punches

Besides normal check-ins, the API returns makeup-punch records — same fields, different source. Version one didn't distinguish them and counted a makeup punch as a second check-in that day. We later added a source field, and the export lists it in its own column per HR's rules.

Wrapping Up

That morning we corrected the 8-hour shift, re-ran the full sync, and HR re-exported. Before sending the sheet out, we hand-computed three people's times from their raw timestamps and compared — no more drift.

The first line of that script's comments now reads: time is converted once, at the end.

Consulting · Free initial chat

Need advice for your scenario?

Tell us your business context and current systems, and we'll give an actionable approach.

Contact us

Mon–Fri 9:00–18:00 · We respond on business days within one business day.

WeChat (Business Only)
WeChat (Business Only)二维码

Scan and add our WeChat for consulting

Or fill out the requirements form