1# time - clocks and pauses.2#3# Durations are milliseconds, whole numbers. Wall clock time is what a clock4# on the wall says and can jump backwards, the monotonic one only moves5# forward and is the one to measure with.67syscall = import("syscall")89Millisecond = 110Second = 100011Minute = 6000012Hour = 36000001314# Now returns the wall clock time in milliseconds since the Unix epoch.15Now = fn() { syscall.TimeMillis() }1617# Unix returns the wall clock time in whole seconds since the Unix epoch.18Unix = fn() { syscall.TimeUnix() }1920# Mono returns a monotonic timestamp in milliseconds. Only differences21# between two of these have a meaning.22Mono = fn() { syscall.TimeMono() }2324# Since returns the milliseconds elapsed since the monotonic timestamp t.25Since = fn(t) { Mono() - t }2627# Sleep pauses the current tau routine for ms milliseconds.28Sleep = fn(ms) { syscall.SleepMillis(ms) }2930# Measure returns the milliseconds taken by f().31Measure = fn(f) {32 start = Mono()33 f()34 return Since(start)35}3637# ========== Dates ==========38#39# A date is UTC unless an offset says otherwise. Local takes the offset from40# the timezone database of the system, daylight saving included, so an hour41# in Rome is the hour in Rome.4243Days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]44Months = ["January", "February", "March", "April", "May", "June", "July",45 "August", "September", "October", "November", "December"]4647# div is the integer division that rounds towards minus infinity, the one the48# calendar arithmetic below is written in terms of. Dividing two integers49# rounds towards zero, which is a different answer on the negative side, so50# the quotient is pushed down by one where the two disagree.51div = fn(a, b) {52 q = a / b53 if a % b != 0 && (a < 0) != (b < 0) {54 --q55 }56 return q57}5859mod = fn(a, b) { a - div(a, b) * b }6061# Date returns what the wall clock time sec, in seconds since the Unix epoch,62# stands for: Year, Month (1 to 12), Day, Hour, Minute, Second, the Weekday63# with Sunday as 0, and the YearDay starting at 1.64#65# The calendar arithmetic is the one Howard Hinnant wrote for chrono: days66# are counted from the first of March so that the leap day falls at the end67# of the year, where it does no harm.68Date = fn(sec) { DateIn(sec, 0) }6970# DateIn is Date with a zone: offset is the seconds between that zone and71# UTC, 3600 for Rome in winter and 7200 in summer. The fields read as the72# clock on that wall reads, while Unix stays the instant it is.73DateIn = fn(sec, offset) {74 if offset == null {75 offset = 076 }7778 local = sec + offset79 days = div(local, 86400)80 rem = local - days * 864008182 z = days + 71946883 era = div(z, 146097)84 doe = z - era * 14609785 yoe = div(doe - div(doe, 1460) + div(doe, 36524) - div(doe, 146096), 365)86 y = yoe + era * 40087 doy = doe - (365 * yoe + div(yoe, 4) - div(yoe, 100))88 mp = div(5 * doy + 2, 153)89 d = doy - div(153 * mp + 2, 5) + 190 m = if mp < 10 { mp + 3 } else { mp - 9 }91 if m <= 2 {92 ++y93 }9495 t = new()96 t.Unix = sec97 t.Offset = offset98 t.Year = y99 t.Month = m100 t.Day = d101 t.Hour = div(rem, 3600)102 t.Minute = mod(div(rem, 60), 60)103 t.Second = mod(rem, 60)104 t.Weekday = mod(days + 4, 7)105 t.YearDay = days - daysFromCivil(y, 1, 1) + 1106 return t107}108109# LocalOffset is the offset of the local zone at the instant sec, or right110# now when sec is null.111LocalOffset = fn(sec) {112 if sec == null {113 sec = Unix()114 }115 return syscall.TzOffset(sec)116}117118# LocalZone is the abbreviation of the local zone at that instant: CET, CEST.119LocalZone = fn(sec) {120 if sec == null {121 sec = Unix()122 }123 return syscall.TzName(sec)124}125126# Local returns the date sec stands for where this machine is.127Local = fn(sec) { DateIn(sec, LocalOffset(sec)) }128129# Now returns the local date of this instant.130NowLocal = fn() { Local(Unix()) }131132# daysFromCivil is the number of days between the Unix epoch and the given133# date, the way back from Date.134daysFromCivil = fn(y, m, d) {135 if m <= 2 {136 --y137 }138 era = div(y, 400)139 yoe = y - era * 400140 mp = if m > 2 { m - 3 } else { m + 9 }141 doy = div(153 * mp + 2, 5) + d - 1142 doe = yoe * 365 + div(yoe, 4) - div(yoe, 100) + doy143 return era * 146097 + doe - 719468144}145146# FromDate returns the seconds since the epoch of the given UTC date, the way147# back from Date.148FromDate = fn(year, month, day, hour, minute, second) {149 return daysFromCivil(year, month, day) * 86400 + hour * 3600 + minute * 60 + second150}151152# FromDateIn is FromDate in a zone: the date is read as the clock of that153# zone reads it, and the instant comes back in UTC.154FromDateIn = fn(year, month, day, hour, minute, second, offset) {155 if offset == null {156 offset = 0157 }158 return FromDate(year, month, day, hour, minute, second) - offset159}160161# ========== Weeks ==========162#163# Weekday counts from Sunday, the way Go does. Where the week starts is a164# question of where you are: in Italy it is Monday, which is what ISOWeekday165# and the ISO week number below are about.166167Sunday = 0168Monday = 1169Tuesday = 2170Wednesday = 3171Thursday = 4172Friday = 5173Saturday = 6174175# ISOWeekday numbers the days from Monday as 1 to Sunday as 7.176ISOWeekday = fn(t) { if t.Weekday == 0 { 7 } else { t.Weekday } }177178# IsWeekend reports whether the date falls on a Saturday or a Sunday.179IsWeekend = fn(t) { t.Weekday == Sunday || t.Weekday == Saturday }180181# StartOfDay returns the instant the day of t begins, in its own zone.182StartOfDay = fn(t) {183 return FromDateIn(t.Year, t.Month, t.Day, 0, 0, 0, t.Offset)184}185186# StartOfWeek returns the instant the week holding t begins, with first187# saying which day that is: Monday in most of Europe, Sunday elsewhere. With188# no first, Monday.189StartOfWeek = fn(t, first) {190 if first == null {191 first = Monday192 }193194 back = mod(t.Weekday - first, 7)195 return StartOfDay(t) - back * 86400196}197198# Week returns the number of the week holding t, counting from the first week199# that starts on a first day, with the days before it in week 0. It is what a200# calendar on a wall shows.201Week = fn(t, first) {202 if first == null {203 first = Monday204 }205206 jan1 = DateIn(FromDateIn(t.Year, 1, 1, 0, 0, 0, t.Offset), t.Offset)207 lead = mod(first - jan1.Weekday, 7)208 if t.YearDay <= lead {209 return 0210 }211 return div(t.YearDay - lead - 1, 7) + 1212}213214# ISOWeek returns the ISO 8601 year and week of t: weeks start on Monday and215# week 1 is the one holding the 4th of January, so the first days of January216# can belong to the last week of the year before.217ISOWeek = fn(t) {218 # The Thursday of this week decides which year the week belongs to.219 thursday = StartOfDay(t) + (4 - ISOWeekday(t)) * 86400220 th = DateIn(thursday, t.Offset)221222 jan4 = DateIn(FromDateIn(th.Year, 1, 4, 0, 0, 0, t.Offset), t.Offset)223 firstWeek = StartOfWeek(jan4, Monday)224225 return [th.Year, div(thursday - firstWeek, 604800) + 1]226}227228# IsLeap reports whether the year has a 29th of February.229IsLeap = fn(y) { y % 4 == 0 && (y % 100 != 0 || y % 400 == 0) }230231# pad writes n with at least width digits.232pad = fn(n, width) {233 s = string(n)234 for len(s) < width {235 s = "0" + s236 }237 return s238}239240# Format writes t, a date from Date, in the given layout. The verbs are the241# ones of strftime, the handful that are worth having:242#243# %Y year %m month %d day %j day of the year244# %H hour %M minute %S second245# %b month name, short %B month name246# %a day name, short %A day name247# %z offset from UTC %% a per cent sign248#249# With no layout the date is written as RFC 3339: 2006-01-02T15:04:05Z.250Format = fn(t, layout) {251 if layout == null {252 # RFC 3339: a Z for UTC, the offset for anywhere else.253 layout = if t.Offset == null || t.Offset == 0 { "%Y-%m-%dT%H:%M:%SZ" } else { "%Y-%m-%dT%H:%M:%S%z" }254 }255256 out = ""257 for i = 0; i < len(layout); ++i {258 if layout[i] != "%" || i == len(layout) - 1 {259 out = out + layout[i]260 continue261 }262263 ++i264 out = out + verb(t, layout[i])265 }266 return out267}268269# verb writes the one field of t a format verb stands for. An unknown verb is270# left as it was written, so that a mistake shows up in the output instead of271# disappearing.272verb = fn(t, c) {273 if c == "Y" { return pad(t.Year, 4) }274 if c == "m" { return pad(t.Month, 2) }275 if c == "d" { return pad(t.Day, 2) }276 if c == "H" { return pad(t.Hour, 2) }277 if c == "M" { return pad(t.Minute, 2) }278 if c == "S" { return pad(t.Second, 2) }279 if c == "j" { return pad(t.YearDay, 3) }280 if c == "B" { return Months[t.Month - 1] }281 if c == "b" { return slice(Months[t.Month - 1], 0, 3) }282 if c == "A" { return Days[t.Weekday] }283 if c == "a" { return slice(Days[t.Weekday], 0, 3) }284 if c == "z" { return offsetString(t.Offset) }285 if c == "%" { return "%" }286 return "%" + c287}288289# offsetString writes a zone offset as RFC 3339 wants it: +01:00.290offsetString = fn(off) {291 if off == null {292 off = 0293 }294295 sign = "+"296 if off < 0 {297 sign = "-"298 off = -off299 }300 return sign + pad(div(off, 3600), 2) + ":" + pad(mod(div(off, 60), 60), 2)301}302303# Parse reads a date written as RFC 3339, "2006-01-02T15:04:05Z", and returns304# the seconds since the epoch. The time part may be left out.305Parse = fn(s) {306 if len(s) < 10 {307 return error("time: {s} is not a date")308 }309310 num = fn(from, to) {311 part = slice(s, from, to)312 for i = 0; i < len(part); ++i {313 if part[i] < "0" || part[i] > "9" {314 return error("time: {s} is not a date")315 }316 }317 return int(part)318 }319320 if failed(year = num(0, 4)) {321 return year322 }323 if failed(month = num(5, 7)) {324 return month325 }326 if failed(day = num(8, 10)) {327 return day328 }329330 if len(s) < 19 {331 return FromDate(year, month, day, 0, 0, 0)332 }333334 if failed(hour = num(11, 13)) {335 return hour336 }337 if failed(minute = num(14, 16)) {338 return minute339 }340 if failed(second = num(17, 19)) {341 return second342 }343 return FromDate(year, month, day, hour, minute, second)344}345346# String writes the date of the wall clock time sec as RFC 3339.347String = fn(sec) { Format(Date(sec), null) }