Background
Data are often associated with a point in time, a particular
year;
month;
day;
hour, minute, second, …
Some issues with points in time:
R has data types to represent
R stores dates as days since January 1, 1970, and date-times as the the number of seconds since midnight on that day in Coordinated Universal Time (UTC) .
Date objects are less complicated than data-time objects, so if you only need dates you should stick with date objects.
Base R provides many facilities for dealing with dates and date-times.
The lubridate
package provides a useful interface.
library(lubridate)
The Dates and Times chapter of R for Data Science provides more details.
Creating Dates and Times
Today and Now
The lubridate
function today()
returns today’s date as a Date
object:
today()
## [1] "2023-05-05"
class(today())
## [1] "Date"
The lubridate
function now()
returns the current date-time as a POSIXct
object:
now()
## [1] "2023-05-05 19:31:08 CDT"
class(now())
## [1] "POSIXct" "POSIXt"
The printed representation follows the international standard for the representation of dates and times (ISO8601 ).
Date and date-time objects can be used with addition and subtraction:
now() + 3600 ## one hour from now
## [1] "2023-05-05 20:31:08 CDT"
today() - 7 ## one week ago
## [1] "2023-04-28"
Parsing Dates and Times From Strings
Some common date formats:
d1 <- "2023-04-16"
d2 <- "April 16, 2023"
d3 <- "16 April 2023"
d4 <- "16 April 23"
These can be decoded by the functions ymd()
, mdy()
, and dmy()
:
ymd(d1)
## [1] "2023-04-16"
mdy(d2)
## [1] "2023-04-16"
dmy(d3)
## [1] "2023-04-16"
dmy(d4)
## [1] "2023-04-16"
By default, these functions use the current locale settings for interpreting month names or abbreviations.
Sys.getlocale("LC_TIME")
## [1] "en_US.UTF-8"
If you need to parse a French date you might use
dmy("16 Avril, 2023", locale = "fr_FR.UTF-8")
## [1] "2023-04-16"
Date-times can be decoded with functions like mdy_hm
:
mdy_hm("April 16, 2023, 6:15 PM")
## [1] "2023-04-16 18:15:00 UTC"
or
mdy_hms("April 16, 2023, 6:15:08 PM")
## [1] "2023-04-16 18:15:08 UTC"
By default these assume the time is specified in the UTC time zone.
Creating Dates and Times from Components
Dates can be created from year, month, and day by make_date()
:
make_date(2023, 4, 16)
## [1] "2023-04-16"
Creating a date
variable from the year
, month
, and day
variables in the New York City flights
table:
library(nycflights13)
fl <- mutate(flights,
date = make_date(year, month, day))
ggplot
and other graphics systems know how to make useful axis labels for dates:
ggplot(count(fl, date)) +
geom_line(aes(x = date, y = n))
Weekday/weekend differenes are clearly visible.
Date-times can be created from year, month, day, hour, minute, and second using make_datetime()
:
make_datetime(2023, 4, 16, 18, 15)
## [1] "2023-04-16 18:15:00 UTC"
An attempt to recreate the time_hour
variable in the flights table:
fl <- mutate(fl,
th = make_datetime(year, month, day,
hour))
This does not quite re-create the time_hour
variable:
identical(fl$th, fl$time_hour)
## [1] FALSE
fl$th[1]
## [1] "2013-01-01 05:00:00 UTC"
fl$time_hour[1]
## [1] "2013-01-01 05:00:00 EST"
By default, make_datetime()
assumes the time points it is given are in UTC.
The time_hour
variable is using local (eastern US) time.
We will look at time zones more below .
Date and Time Components
Components of dates and date-times can be extracted with:
year()
, month()
, day()
, hour()
, minute()
, second()
yday()
– day of the year
mday()
– day of the month, same as day
wday()
– day of the week
By default, wday()
returns an integer:
wday(today())
## [1] 6
But it can also return a label:
wday(today(), label = TRUE)
## [1] Fri
## Levels: Sun < Mon < Tue < Wed < Thu < Fri < Sat
wday(today(), label = TRUE, abbr = FALSE)
## [1] Friday
## 7 Levels: Sunday < Monday < Tuesday < Wednesday < Thursday < ... < Saturday
Weekday names and abbreviations are obviously locale-specific, and you can specify an alternative to the default current locale:
wday(today(), label = TRUE, abbr = FALSE, locale = "de_DE.UTF-8")
## [1] Freitag
## 7 Levels: Sonntag < Montag < Dienstag < Mittwoch < Donnerstag < ... < Samstag
wday(today(), label = TRUE, locale = "de_DE.UTF-8")
## [1] Fr
## Levels: So < Mo < Di < Mi < Do < Fr < Sa
Even the integer value can be tricky:
In the US, Canada, Japan the first day of the week is Sunday.
In Germany, France, and the ISO8601 standard the first day of the week is Monday.
wday()
can be asked to use a different first day, and a global default can be set.
Using wday()
and the date
variable we can look at the distribution of the number of flights by day of the week:
ggplot(fl, aes(x = wday(date, label = TRUE))) +
geom_bar(fill = "deepskyblue3")
There were substantially fewer flights on Saturdays but only slightly fewer flights on Sundays.
Rounding
floor_date()
, round_date()
, and ceiling_date()
can be used to round to a particular unit; the most useful are week
and quarter
.
Flights by week:
The first and last weeks were incomplete:
as.character(wday(ymd("2013-01-01"),
label = TRUE, abbr = FALSE))
## [1] "Tuesday"
as.character(wday(ymd("2013-12-31"),
label = TRUE, abbr = FALSE))
## [1] "Tuesday"
Time Spans
Subtracting dates or date-times produces difftime
objects:
now() - as_datetime(today())
## Time difference of 1.021668 days
today() - ymd("2023-01-01")
## Time difference of 124 days
Working with different units can be awkward; lubridate
provides durations , which always work in seconds:
as.duration(now() - as_datetime(today()))
## [1] "88272.1419148445s (~1.02 days)"
as.duration(today() - ymd("2023-01-01"))
## [1] "10713600s (~17.71 weeks)"
Durations can be created with dyears()
, ddays()
, dweeks()
, etc.:
dyears(1)
## [1] "31557600s (~1 years)"
ddays(1)
## [1] "86400s (~1 days)"
Durations can be added to a date or date-time object and can be multiplied by a number:
today()
## [1] "2023-05-05"
today() + ddays(2)
## [1] "2023-05-07"
today() + 2 * ddays(1)
## [1] "2023-05-07"
(n1 <- now())
## [1] "2023-05-05 19:31:12 CDT"
n1 + dminutes(3)
## [1] "2023-05-05 19:34:12 CDT"
Duations represent an exact number of seconds, which can lead to surprises when DST is involved.
In 2023 the switch to DST happened in the US on March 12:
ymd_hm("2023-03-11 23:02", tz = "America/Chicago") + ddays(1)
## [1] "2023-03-13 00:02:00 CDT"
Periods are an alternative that may work more intuitively.
Periods are constructed with years()
, months()
, days()
, etc:
ymd_hm("2023-03-11 23:02", tz = "America/Chicago") + days(1)
## [1] "2023-03-12 23:02:00 CDT"
Time Zones
Date-time objects specify a point in time relative to second zero, minute zero, hour zero, on January 1, 1970 in Coordinated Universal Time (UTC) .
Date-time objects can have a time zone associated with them that affects how they are printed.
now()
returns a date-time object with the time zone set as the local time zone of the computer.
now()
## [1] "2023-05-05 19:31:12 CDT"
Time zones are complex, they can change on a regular basis (DST) or as a result of politics.
When a date-time object is created from components, by default it is given the UTC time zone.
To create a point in time based on local time information, such as 10 AM on April 16, 2023, in Iowa City, a time zone for interpreting the local time needs to be specified.
The short notations like CDT are not adequate for this: Both the US and Australia have EST, which are quite different.
R uses the Internet Assigned Numbers Authority (IANA) naming convention and data base.
The local time zone is:
Sys.timezone()
## [1] "America/Chicago"
The time point 10:00:00 AM on April 16, 2023 in Iowa City can be specified as
(tm <- make_datetime(2023, 4, 16, 10, tz = "America/Chicago"))
## [1] "2023-04-16 10:00:00 CDT"
Time zones of date-time objects can be changed in two ways:
The available time zone specifications are contained in OlsonNames
:
head(OlsonNames())
## [1] "Africa/Abidjan" "Africa/Accra" "Africa/Addis_Ababa"
## [4] "Africa/Algiers" "Africa/Asmara" "Africa/Asmera"
The instant tm
in some other time zones:
with_tz(tm, tz = "UTC")
## [1] "2023-04-16 15:00:00 UTC"
with_tz(tm, tz = "America/New_York")
## [1] "2023-04-16 11:00:00 EDT"
with_tz(tm, tz = "Asia/Shanghai")
## [1] "2023-04-16 23:00:00 CST"
with_tz(tm, tz = "Pacific/Auckland")
## [1] "2023-04-17 03:00:00 NZST"
with_tz(tm, tz = "Asia/Kolkata")
## [1] "2023-04-16 20:30:00 IST"
with_tz(tm, tz = "Canada/Newfoundland")
## [1] "2023-04-16 12:30:00 NDT"
with_tz(tm, tz = "Asia/Katmandu")
## [1] "2023-04-16 20:45:00 +0545"
Some more examples:
## All offsets that are not a full hour:
get_offset <- function(z)
abs(minute(with_tz(tm, tz = z)) - minute(tm))
offsets <- data.frame(zone = OlsonNames()) %>%
mutate(offset = sapply(zone, get_offset)) %>%
arrange(offset)
filter(offsets, offset != 0)
## Offsets for Australia:
filter(offsets, grepl("Australia", zone))
If we create the th
variable for the flights data as
fl <- mutate(flights, th = make_datetime(year, month, day, hour,
tz = "America/New_York"))
then the result matches the date_time
variable:
identical(fl$th, fl$time_hour)
## [1] TRUE
The time_hour
variable in the weather
table reflects actual points in time and, together with origin
, can serve as a primary key:
filter(count(weather, origin, time_hour), n > 1)
## # A tibble: 0 × 3
## # ℹ 3 variables: origin <chr>, time_hour <dttm>, n <int>
The month
, day
, hour
variables are confused by the time change.
In November there is a repeat:
count(weather, origin, month, day, hour) %>%
filter(n > 1)
## # A tibble: 3 × 5
## origin month day hour n
## <chr> <int> <int> <int> <int>
## 1 EWR 11 3 1 2
## 2 JFK 11 3 1 2
## 3 LGA 11 3 1 2
and there is a missing hour in March:
select(weather, origin, month, day, hour) %>%
filter(origin == "EWR", month == 3,
day == 10, hour <= 3)
## # A tibble: 3 × 4
## origin month day hour
## <chr> <int> <int> <int>
## 1 EWR 3 10 0
## 2 EWR 3 10 1
## 3 EWR 3 10 3
Things to Look Out For
For dates:
Language used for months and weekdays, and their abbreviations.
Ambiguous numerical conventions like 4/11/2023: is this April 11 or November 4?
Day 2 of the week: is this Monday or Tuesday?
For historical data, what calendar is being used? (The October Revolution happened on November 6/7, 1917 by the current Gregorian calendar; October 24/25 by the Julian calendar Russia was still using.)
For date-times
All of the above.
Daylight saving time.
Time zones.
Exercises
Using the NYC flights data, how many flights were there on Saturdays from Newark (EWR) to Cicago O’Hare (ORD) in 2013?
413
522
601
733
What day of the week will July 4, 2030, fall on?
Monday
Wednesday
Thursday
Saturday
LS0tCnRpdGxlOiAiRGF0ZXMgYW5kIFRpbWVzIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIHRvYzogeWVzCiAgICBjb2RlX2ZvbGRpbmc6IHNob3cKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKLS0tCgo8bGluayByZWw9InN0eWxlc2hlZXQiIGhyZWY9InN0YXQ0NTgwLmNzcyIgdHlwZT0idGV4dC9jc3MiIC8+CjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+IC5yZW1hcmstY29kZSB7IGZvbnQtc2l6ZTogODUlOyB9IDwvc3R5bGU+CmBgYHtyIHNldHVwLCBpbmNsdWRlID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0Kc291cmNlKGhlcmU6OmhlcmUoInNldHVwLlIiKSkKa25pdHI6Om9wdHNfY2h1bmskc2V0KGNvbGxhcHNlID0gVFJVRSwgbWVzc2FnZSA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgZmlnLmhlaWdodCA9IDUsIGZpZy53aWR0aCA9IDYsIGZpZy5hbGlnbiA9ICJjZW50ZXIiKQoKc2V0LnNlZWQoMTIzNDUpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShsYXR0aWNlKQpsaWJyYXJ5KGdyaWRFeHRyYSkKbGlicmFyeShwYXRjaHdvcmspCnNvdXJjZShoZXJlOjpoZXJlKCJkYXRhc2V0cy5SIikpCnRoZW1lX3NldCh0aGVtZV9taW5pbWFsKCkgKwogICAgICAgICAgdGhlbWUodGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTYpLAogICAgICAgICAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG9yID0gImdyZXkzMCIsIGZpbGwgPSBOQSkpKQpgYGAKCgojIyBCYWNrZ3JvdW5kCgpEYXRhIGFyZSBvZnRlbiBhc3NvY2lhdGVkIHdpdGggYSBwb2ludCBpbiB0aW1lLCBhIHBhcnRpY3VsYXIKCiogeWVhcjsKCiogbW9udGg7CgoqIGRheTsKCiogaG91ciwgbWludXRlLCBzZWNvbmQsIC4uLgoKU29tZSBpc3N1ZXMgd2l0aCBwb2ludHMgaW4gdGltZToKCiogTGVhcCB5ZWFycyBhbmQgbGVhcCBzZWNvbmRzLgoKKiBEYXlsaWdodCBzYXZpbmcgdGltZS4KCiogTG9jYWwgdGltZSBvcgogIFtfQ29vcmRpbmF0ZWQgVW5pdmVyc2FsIFRpbWUKICAgICAgKFVUQylfXShodHRwczovL3d3dy50aW1lYW5kZGF0ZS5jb20vdGltZS9hYm91dHV0Yy5odG1sKS4KCiogRm9yIGhpc3RvcmljYWwgZGF0YTogY2hhbmdlcyBpbiBjYWxlbmRhcnMuCgpSIGhhcyBkYXRhIHR5cGVzIHRvIHJlcHJlc2VudAoKKiBhIHBhcnRpY3VsYXIgZGF0ZSAoYERhdGVgKTsKCiogYSBwYXJ0aWN1bGFyIHNlY29uZCAoYFBPU0lYY3RgLCBkYXRlLXRpbWUpLgoKUiBzdG9yZXMgZGF0ZXMgYXMgZGF5cyBzaW5jZSBKYW51YXJ5IDEsIDE5NzAsIGFuZCBkYXRlLXRpbWVzIGFzIHRoZQp0aGUgbnVtYmVyIG9mIHNlY29uZHMgc2luY2UgbWlkbmlnaHQgb24gdGhhdCBkYXkgaW4KW19Db29yZGluYXRlZCBVbml2ZXJzYWwgVGltZV8KICAgIChVVEMpXShodHRwczovL3d3dy50aW1lYW5kZGF0ZS5jb20vdGltZS9hYm91dHV0Yy5odG1sKS4KCkRhdGUgb2JqZWN0cyBhcmUgbGVzcyBjb21wbGljYXRlZCB0aGFuIGRhdGEtdGltZSBvYmplY3RzLCBzbyBpZiB5b3UKb25seSBuZWVkIGRhdGVzIHlvdSBzaG91bGQgc3RpY2sgd2l0aCBkYXRlIG9iamVjdHMuCgpCYXNlIFIgcHJvdmlkZXMgbWFueSBmYWNpbGl0aWVzIGZvciBkZWFsaW5nIHdpdGggZGF0ZXMgYW5kIGRhdGUtdGltZXMuCgpUaGUgYGx1YnJpZGF0ZWAgcGFja2FnZSBwcm92aWRlcyBhIHVzZWZ1bCBpbnRlcmZhY2UuCgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFfQpsaWJyYXJ5KGx1YnJpZGF0ZSkKYGBgCgpUaGUKW19EYXRlcyBhbmQgVGltZXNfIGNoYXB0ZXJdKGh0dHBzOi8vcjRkcy5oYWQuY28ubnovZGF0ZXMtYW5kLXRpbWVzLmh0bWwpCm9mIFtfUiBmb3IgRGF0YSBTY2llbmNlX10oaHR0cHM6Ly9yNGRzLmhhZC5jby5uei8pIHByb3ZpZGVzIG1vcmUKZGV0YWlscy4KCgojIyBDcmVhdGluZyBEYXRlcyBhbmQgVGltZXMKCgojIyMgVG9kYXkgYW5kIE5vdwoKVGhlIGBsdWJyaWRhdGVgIGZ1bmN0aW9uIGB0b2RheSgpYCByZXR1cm5zIHRvZGF5J3MgZGF0ZSBhcyBhIGBEYXRlYCBvYmplY3Q6CgpgYGB7cn0KdG9kYXkoKQpgYGAKCmBgYHtyfQpjbGFzcyh0b2RheSgpKQpgYGAKClRoZSBgbHVicmlkYXRlYCBmdW5jdGlvbiBgbm93KClgIHJldHVybnMgdGhlIGN1cnJlbnQgZGF0ZS10aW1lIGFzIGEKYFBPU0lYY3RgIG9iamVjdDoKCmBgYHtyfQpub3coKQpgYGAKCmBgYHtyfQpjbGFzcyhub3coKSkKYGBgCgpUaGUgcHJpbnRlZCByZXByZXNlbnRhdGlvbiBmb2xsb3dzIHRoZSBpbnRlcm5hdGlvbmFsIHN0YW5kYXJkIGZvciB0aGUKcmVwcmVzZW50YXRpb24gb2YgZGF0ZXMgYW5kIHRpbWVzCihbSVNPODYwMV0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvSVNPXzg2MDEpKS4KCkRhdGUgYW5kIGRhdGUtdGltZSBvYmplY3RzIGNhbiBiZSB1c2VkIHdpdGggYWRkaXRpb24gYW5kIHN1YnRyYWN0aW9uOgoKYGBge3J9Cm5vdygpICsgMzYwMCAgIyMgb25lIGhvdXIgZnJvbSBub3cKYGBgCgpgYGB7cn0KdG9kYXkoKSAtIDcgICAjIyBvbmUgd2VlayBhZ28KYGBgCgoKIyMjIFBhcnNpbmcgRGF0ZXMgYW5kIFRpbWVzIEZyb20gU3RyaW5ncwoKU29tZSBjb21tb24gZGF0ZSBmb3JtYXRzOgoKYGBge3J9CmQxIDwtICIyMDIzLTA0LTE2IgpkMiA8LSAiQXByaWwgMTYsIDIwMjMiCmQzIDwtICIxNiBBcHJpbCAyMDIzIgpkNCA8LSAiMTYgQXByaWwgMjMiCmBgYAoKVGhlc2UgY2FuIGJlIGRlY29kZWQgYnkgdGhlIGZ1bmN0aW9ucyBgeW1kKClgLCBgbWR5KClgLCBhbmQgYGRteSgpYDoKCmBgYHtyfQp5bWQoZDEpCmBgYAoKYGBge3J9Cm1keShkMikKYGBgCgpgYGB7cn0KZG15KGQzKQpgYGAKCmBgYHtyfQpkbXkoZDQpCmBgYAoKQnkgZGVmYXVsdCwgdGhlc2UgZnVuY3Rpb25zIHVzZSB0aGUgY3VycmVudCBfbG9jYWxlXyBzZXR0aW5ncyBmb3IKaW50ZXJwcmV0aW5nIG1vbnRoIG5hbWVzIG9yIGFiYnJldmlhdGlvbnMuCgpgYGB7cn0KU3lzLmdldGxvY2FsZSgiTENfVElNRSIpCmBgYAoKSWYgeW91IG5lZWQgdG8gcGFyc2UgYSBGcmVuY2ggZGF0ZSB5b3UgbWlnaHQgdXNlCgpgYGB7cn0KZG15KCIxNiBBdnJpbCwgMjAyMyIsIGxvY2FsZSA9ICJmcl9GUi5VVEYtOCIpCmBgYAoKRGF0ZS10aW1lcyBjYW4gYmUgZGVjb2RlZCB3aXRoIGZ1bmN0aW9ucyBsaWtlIGBtZHlfaG1gOgoKYGBge3J9Cm1keV9obSgiQXByaWwgMTYsIDIwMjMsIDY6MTUgUE0iKQpgYGAKCm9yCgpgYGB7cn0KbWR5X2htcygiQXByaWwgMTYsIDIwMjMsIDY6MTU6MDggUE0iKQpgYGAKCkJ5IGRlZmF1bHQgdGhlc2UgYXNzdW1lIHRoZSB0aW1lIGlzIHNwZWNpZmllZCBpbiB0aGUgVVRDIHRpbWUgem9uZS4KCgojIyMgQ3JlYXRpbmcgRGF0ZXMgYW5kIFRpbWVzIGZyb20gQ29tcG9uZW50cwoKRGF0ZXMgY2FuIGJlIGNyZWF0ZWQgZnJvbSB5ZWFyLCBtb250aCwgYW5kIGRheSBieSBgbWFrZV9kYXRlKClgOgoKYGBge3J9Cm1ha2VfZGF0ZSgyMDIzLCA0LCAxNikKYGBgCgpDcmVhdGluZyBhIGBkYXRlYCB2YXJpYWJsZSBmcm9tIHRoZSBgeWVhcmAsIGBtb250aGAsIGFuZCBgZGF5YAp2YXJpYWJsZXMgaW4gdGhlIE5ldyBZb3JrIENpdHkgYGZsaWdodHNgIHRhYmxlOgoKYGBge3J9CmxpYnJhcnkobnljZmxpZ2h0czEzKQpmbCA8LSBtdXRhdGUoZmxpZ2h0cywKICAgICAgICAgICAgIGRhdGUgPSBtYWtlX2RhdGUoeWVhciwgbW9udGgsIGRheSkpCmBgYAoKYGdncGxvdGAgYW5kIG90aGVyIGdyYXBoaWNzIHN5c3RlbXMga25vdyBob3cgdG8gbWFrZSB1c2VmdWwgYXhpcwpsYWJlbHMgZm9yIGRhdGVzOgoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpnZ3Bsb3QoY291bnQoZmwsIGRhdGUpKSArCiAgICBnZW9tX2xpbmUoYWVzKHggPSBkYXRlLCB5ID0gbikpCmBgYAoKV2Vla2RheS93ZWVrZW5kIGRpZmZlcmVuZXMgYXJlIGNsZWFybHkgdmlzaWJsZS4KCkRhdGUtdGltZXMgY2FuIGJlIGNyZWF0ZWQgZnJvbSB5ZWFyLCBtb250aCwgZGF5LCBob3VyLCBtaW51dGUsIGFuZCBzZWNvbmQKdXNpbmcgYG1ha2VfZGF0ZXRpbWUoKWA6CgpgYGB7cn0KbWFrZV9kYXRldGltZSgyMDIzLCA0LCAxNiwgMTgsIDE1KQpgYGAKCkFuIGF0dGVtcHQgdG8gcmVjcmVhdGUgdGhlIGB0aW1lX2hvdXJgIHZhcmlhYmxlIGluIHRoZSBmbGlnaHRzIHRhYmxlOgoKYGBge3J9CmZsIDwtIG11dGF0ZShmbCwKICAgICAgICAgICAgIHRoID0gbWFrZV9kYXRldGltZSh5ZWFyLCBtb250aCwgZGF5LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhvdXIpKQpgYGAKClRoaXMgZG9lcyBub3QgcXVpdGUgcmUtY3JlYXRlIHRoZSBgdGltZV9ob3VyYCB2YXJpYWJsZToKYGBge3J9CmlkZW50aWNhbChmbCR0aCwgZmwkdGltZV9ob3VyKQpgYGAKCmBgYHtyfQpmbCR0aFsxXQpmbCR0aW1lX2hvdXJbMV0KYGBgCgpCeSBkZWZhdWx0LCBgbWFrZV9kYXRldGltZSgpYCBhc3N1bWVzIHRoZSB0aW1lIHBvaW50cyBpdCBpcyBnaXZlbiBhcmUgaW4gVVRDLgoKVGhlIGB0aW1lX2hvdXJgIHZhcmlhYmxlIGlzIHVzaW5nIGxvY2FsIChlYXN0ZXJuIFVTKSB0aW1lLgoKV2Ugd2lsbCBsb29rIGF0IHRpbWUgem9uZXMgbW9yZSBbYmVsb3ddKCN0aW1lLXpvbmVzKS4KCgojIyBEYXRlIGFuZCBUaW1lIENvbXBvbmVudHMKCkNvbXBvbmVudHMgb2YgZGF0ZXMgYW5kIGRhdGUtdGltZXMgY2FuIGJlIGV4dHJhY3RlZCB3aXRoOgoKKiBgeWVhcigpYCwgYG1vbnRoKClgLCBgZGF5KClgLCBgaG91cigpYCwgYG1pbnV0ZSgpYCwgYHNlY29uZCgpYAoqIGB5ZGF5KClgIC0tIGRheSBvZiB0aGUgeWVhcgoqIGBtZGF5KClgIC0tIGRheSBvZiB0aGUgbW9udGgsIHNhbWUgYXMgYGRheWAKKiBgd2RheSgpYCAtLSBkYXkgb2YgdGhlIHdlZWsKCkJ5IGRlZmF1bHQsIGB3ZGF5KClgIHJldHVybnMgYW4gaW50ZWdlcjoKCmBgYHtyfQp3ZGF5KHRvZGF5KCkpCmBgYAoKQnV0IGl0IGNhbiBhbHNvIHJldHVybiBhIGxhYmVsOgoKYGBge3J9CndkYXkodG9kYXkoKSwgbGFiZWwgPSBUUlVFKQpgYGAKCmBgYHtyfQp3ZGF5KHRvZGF5KCksIGxhYmVsID0gVFJVRSwgYWJiciA9IEZBTFNFKQpgYGAKCldlZWtkYXkgbmFtZXMgYW5kIGFiYnJldmlhdGlvbnMgYXJlIG9idmlvdXNseSBsb2NhbGUtc3BlY2lmaWMsIGFuZCB5b3UKY2FuIHNwZWNpZnkgYW4gYWx0ZXJuYXRpdmUgdG8gdGhlIGRlZmF1bHQgY3VycmVudCBsb2NhbGU6CgpgYGB7cn0Kd2RheSh0b2RheSgpLCBsYWJlbCA9IFRSVUUsIGFiYnIgPSBGQUxTRSwgbG9jYWxlID0gImRlX0RFLlVURi04IikKYGBgCgpgYGB7cn0Kd2RheSh0b2RheSgpLCBsYWJlbCA9IFRSVUUsIGxvY2FsZSA9ICJkZV9ERS5VVEYtOCIpCmBgYAoKRXZlbiB0aGUgaW50ZWdlciB2YWx1ZSBjYW4gYmUgdHJpY2t5OgoKKiBJbiB0aGUgVVMsIENhbmFkYSwgSmFwYW4gdGhlIGZpcnN0IGRheSBvZiB0aGUgd2VlayBpcyBTdW5kYXkuCgoqIEluIEdlcm1hbnksIEZyYW5jZSwgYW5kIHRoZSBJU084NjAxIHN0YW5kYXJkIHRoZSBmaXJzdCBkYXkgb2YgdGhlCiAgd2VlayBpcyBNb25kYXkuCgpgd2RheSgpYCBjYW4gYmUgYXNrZWQgdG8gdXNlIGEgZGlmZmVyZW50IGZpcnN0IGRheSwgYW5kIGEgZ2xvYmFsIGRlZmF1bHQKY2FuIGJlIHNldC4KClVzaW5nIGB3ZGF5KClgIGFuZCB0aGUgYGRhdGVgIHZhcmlhYmxlIHdlIGNhbiBsb29rIGF0IHRoZSBkaXN0cmlidXRpb24Kb2YgdGhlIG51bWJlciBvZiBmbGlnaHRzIGJ5IGRheSBvZiB0aGUgd2VlazoKCmBgYHtyIGZsaWdodHMtd2RheSwgZXZhbCA9IEZBTFNFfQpnZ3Bsb3QoZmwsIGFlcyh4ID0gd2RheShkYXRlLCBsYWJlbCA9IFRSVUUpKSkgKwogICAgZ2VvbV9iYXIoZmlsbCA9ICJkZWVwc2t5Ymx1ZTMiKQpgYGAKCmBgYHtyIGZsaWdodHMtd2RheSwgZWNobyA9IEZBTFNFfQpgYGAKClRoZXJlIHdlcmUgc3Vic3RhbnRpYWxseSBmZXdlciBmbGlnaHRzIG9uIFNhdHVyZGF5cyBidXQgb25seSBzbGlnaHRseQpmZXdlciBmbGlnaHRzIG9uIFN1bmRheXMuCgoKIyMgUm91bmRpbmcKCmBmbG9vcl9kYXRlKClgLCBgcm91bmRfZGF0ZSgpYCwgYW5kIGBjZWlsaW5nX2RhdGUoKWAgY2FuIGJlIHVzZWQgdG8Kcm91bmQgdG8gYSBwYXJ0aWN1bGFyIHVuaXQ7IHRoZSBtb3N0IHVzZWZ1bCBhcmUgYHdlZWtgIGFuZCBgcXVhcnRlcmAuCgpGbGlnaHRzIGJ5IHdlZWs6CgpgYGB7ciwgZWNobyA9IEZBTFNFLCBkcGkgPSAzMDAsIGZpZy5oZWlnaHQgPSA0fQpnZ3Bsb3QoZmwsIGFlcyh4ID0gcm91bmRfZGF0ZShkYXRlLCAid2VlayIpKSkgKwogICAgZ2VvbV9iYXIoZmlsbCA9ICJkZWVwc2t5Ymx1ZTMiKQpgYGAKClRoZSBmaXJzdCBhbmQgbGFzdCB3ZWVrcyB3ZXJlIGluY29tcGxldGU6CgpgYGB7cn0KYXMuY2hhcmFjdGVyKHdkYXkoeW1kKCIyMDEzLTAxLTAxIiksCiAgICAgICAgICAgICAgICAgIGxhYmVsID0gVFJVRSwgYWJiciA9IEZBTFNFKSkKYGBgCmBgYHtyfQphcy5jaGFyYWN0ZXIod2RheSh5bWQoIjIwMTMtMTItMzEiKSwKICAgICAgICAgICAgICAgICAgbGFiZWwgPSBUUlVFLCBhYmJyID0gRkFMU0UpKQpgYGAKCgojIyBUaW1lIFNwYW5zCgpTdWJ0cmFjdGluZyBkYXRlcyBvciBkYXRlLXRpbWVzIHByb2R1Y2VzIGBkaWZmdGltZWAgb2JqZWN0czoKCmBgYHtyfQpub3coKSAtIGFzX2RhdGV0aW1lKHRvZGF5KCkpCmBgYAoKYGBge3J9CnRvZGF5KCkgLSB5bWQoIjIwMjMtMDEtMDEiKQpgYGAKCldvcmtpbmcgd2l0aCBkaWZmZXJlbnQgdW5pdHMgY2FuIGJlIGF3a3dhcmQ7IGBsdWJyaWRhdGVgIHByb3ZpZGVzCl9kdXJhdGlvbnNfLCB3aGljaCBhbHdheXMgd29yayBpbiBzZWNvbmRzOgoKYGBge3J9CmFzLmR1cmF0aW9uKG5vdygpIC0gYXNfZGF0ZXRpbWUodG9kYXkoKSkpCmBgYAoKYGBge3J9CmFzLmR1cmF0aW9uKHRvZGF5KCkgLSB5bWQoIjIwMjMtMDEtMDEiKSkKYGBgCgpEdXJhdGlvbnMgY2FuIGJlIGNyZWF0ZWQgd2l0aCBgZHllYXJzKClgLCBgZGRheXMoKWAsIGBkd2Vla3MoKWAsIGV0Yy46CgpgYGB7cn0KZHllYXJzKDEpCmBgYAoKYGBge3J9CmRkYXlzKDEpCmBgYAoKRHVyYXRpb25zIGNhbiBiZSBhZGRlZCB0byBhIGRhdGUgb3IgZGF0ZS10aW1lIG9iamVjdCBhbmQgY2FuIGJlCm11bHRpcGxpZWQgYnkgYSBudW1iZXI6CgpgYGB7cn0KdG9kYXkoKQpgYGAKCmBgYHtyfQp0b2RheSgpICsgZGRheXMoMikKYGBgCgpgYGB7cn0KdG9kYXkoKSArIDIgKiBkZGF5cygxKQpgYGAKCmBgYHtyfQoobjEgPC0gbm93KCkpCmBgYAoKYGBge3J9Cm4xICsgZG1pbnV0ZXMoMykKYGBgCgpEdWF0aW9ucyByZXByZXNlbnQgYW4gZXhhY3QgbnVtYmVyIG9mIHNlY29uZHMsIHdoaWNoIGNhbiBsZWFkIHRvCnN1cnByaXNlcyB3aGVuIERTVCBpcyBpbnZvbHZlZC4KCkluIDIwMjMgdGhlIHN3aXRjaCB0byBEU1QgaGFwcGVuZWQgaW4gdGhlIFVTIG9uIE1hcmNoIDEyOgoKYGBge3J9CnltZF9obSgiMjAyMy0wMy0xMSAyMzowMiIsIHR6ID0gIkFtZXJpY2EvQ2hpY2FnbyIpICsgZGRheXMoMSkKYGBgCgpfUGVyaW9kc18gYXJlIGFuIGFsdGVybmF0aXZlIHRoYXQgbWF5IHdvcmsgbW9yZSBpbnR1aXRpdmVseS4KClBlcmlvZHMgYXJlIGNvbnN0cnVjdGVkIHdpdGggYHllYXJzKClgLCBgbW9udGhzKClgLCBgZGF5cygpYCwgZXRjOgoKYGBge3J9CnltZF9obSgiMjAyMy0wMy0xMSAyMzowMiIsIHR6ID0gIkFtZXJpY2EvQ2hpY2FnbyIpICsgZGF5cygxKQpgYGAKCgojIyBUaW1lIFpvbmVzCgpEYXRlLXRpbWUgb2JqZWN0cyBzcGVjaWZ5IGEgcG9pbnQgaW4gdGltZSByZWxhdGl2ZSB0byBzZWNvbmQgemVybywKbWludXRlIHplcm8sIGhvdXIgemVybywgb24gSmFudWFyeSAxLCAxOTcwIGluCltDb29yZGluYXRlZCBVbml2ZXJzYWwgVGltZSAoVVRDKV0oaHR0cHM6Ly93d3cudGltZWFuZGRhdGUuY29tL3RpbWUvYWJvdXR1dGMuaHRtbCkuCgpEYXRlLXRpbWUgb2JqZWN0cyBjYW4gaGF2ZSBhIHRpbWUgem9uZSBhc3NvY2lhdGVkIHdpdGggdGhlbSB0aGF0CmFmZmVjdHMgaG93IHRoZXkgYXJlIHByaW50ZWQuCgpgbm93KClgIHJldHVybnMgYSBkYXRlLXRpbWUgb2JqZWN0IHdpdGggdGhlIHRpbWUgem9uZSBzZXQgYXMgdGhlCmxvY2FsIHRpbWUgem9uZSBvZiB0aGUgY29tcHV0ZXIuCgpgYGB7cn0Kbm93KCkKYGBgCgpUaW1lIHpvbmVzIGFyZSBjb21wbGV4LCB0aGV5IGNhbiBjaGFuZ2Ugb24gYSByZWd1bGFyIGJhc2lzIChEU1QpIG9yIGFzCmEgcmVzdWx0IG9mIHBvbGl0aWNzLgoKV2hlbiBhIGRhdGUtdGltZSBvYmplY3QgaXMgY3JlYXRlZCBmcm9tIGNvbXBvbmVudHMsIGJ5IGRlZmF1bHQgaXQgaXMKZ2l2ZW4gdGhlIFVUQyB0aW1lIHpvbmUuCgpUbyBjcmVhdGUgYSBwb2ludCBpbiB0aW1lIGJhc2VkIG9uIGxvY2FsIHRpbWUgaW5mb3JtYXRpb24sIHN1Y2ggYXMgMTAKQU0gb24gQXByaWwgMTYsIDIwMjMsIGluIElvd2EgQ2l0eSwgYSB0aW1lIHpvbmUgZm9yIGludGVycHJldGluZyB0aGUKbG9jYWwgdGltZSBuZWVkcyB0byBiZSBzcGVjaWZpZWQuCgpUaGUgc2hvcnQgbm90YXRpb25zIGxpa2UgQ0RUIGFyZSBub3QgYWRlcXVhdGUgZm9yIHRoaXM6IEJvdGggdGhlIFVTCiBhbmQgQXVzdHJhbGlhIGhhdmUgRVNULCB3aGljaCBhcmUgcXVpdGUgZGlmZmVyZW50LgoKUiB1c2VzIHRoZQpbX0ludGVybmV0IEFzc2lnbmVkIE51bWJlcnMgQXV0aG9yaXR5XyAoSUFOQSldKGh0dHBzOi8vd3d3LmlhbmEub3JnL3RpbWUtem9uZXMpCm5hbWluZyBjb252ZW50aW9uIGFuZCBkYXRhIGJhc2UuCgpUaGUgbG9jYWwgdGltZSB6b25lIGlzOgoKYGBge3J9ClN5cy50aW1lem9uZSgpCmBgYAoKVGhlIHRpbWUgcG9pbnQgMTA6MDA6MDAgQU0gb24gQXByaWwgMTYsIDIwMjMgaW4gSW93YSBDaXR5IGNhbiBiZQpzcGVjaWZpZWQgYXMKCmBgYHtyfQoodG0gPC0gbWFrZV9kYXRldGltZSgyMDIzLCA0LCAxNiwgMTAsIHR6ID0gIkFtZXJpY2EvQ2hpY2FnbyIpKQpgYGAKClRpbWUgem9uZXMgb2YgZGF0ZS10aW1lIG9iamVjdHMgY2FuIGJlIGNoYW5nZWQgaW4gdHdvIHdheXM6CgoqIGB3aXRoX3R6YCBrZWVwcyB0aGUgaW5zdGFudCBpbiB0aW1lIGFuZCBjaGFuZ2VzIHRoZSB0aW1lIHpvbmUgdXNlZAogIGZvciBkaXNwbGF5LgoKKiBgZm9yY2VfdHpgIGNoYW5nZXMgdGhlIGluc3RhbnQgaW4gdGltZTsgdXNlIHRoaXMgaWYgdGhlIHRpbWUgem9uZSBpcwogIGluY29ycmVjdGx5IHNwZWNpZmllZCBidXQgdGhlIGNsb2NrIHRpbWUgaXMgY29ycmVjdC4KClRoZSBhdmFpbGFibGUgdGltZSB6b25lIHNwZWNpZmljYXRpb25zIGFyZSBjb250YWluZWQgaW4gYE9sc29uTmFtZXNgOgoKYGBge3J9CmhlYWQoT2xzb25OYW1lcygpKQpgYGAKClRoZSBpbnN0YW50IGB0bWAgaW4gc29tZSBvdGhlciB0aW1lIHpvbmVzOgoKYGBge3J9CndpdGhfdHoodG0sIHR6ID0gIlVUQyIpCmBgYAoKYGBge3J9CndpdGhfdHoodG0sIHR6ID0gIkFtZXJpY2EvTmV3X1lvcmsiKQpgYGAKCmBgYHtyfQp3aXRoX3R6KHRtLCB0eiA9ICJBc2lhL1NoYW5naGFpIikKYGBgCgpgYGB7cn0Kd2l0aF90eih0bSwgdHogPSAiUGFjaWZpYy9BdWNrbGFuZCIpCmBgYAoKYGBge3J9CndpdGhfdHoodG0sIHR6ID0gIkFzaWEvS29sa2F0YSIpCmBgYAoKYGBge3J9CndpdGhfdHoodG0sIHR6ID0gIkNhbmFkYS9OZXdmb3VuZGxhbmQiKQpgYGAKCmBgYHtyfQp3aXRoX3R6KHRtLCB0eiA9ICJBc2lhL0thdG1hbmR1IikKYGBgCgpTb21lIG1vcmUgZXhhbXBsZXM6CgpgYGB7ciwgZXZhbCA9IEZBTFNFfQojIyBBbGwgb2Zmc2V0cyB0aGF0IGFyZSBub3QgYSBmdWxsIGhvdXI6CmdldF9vZmZzZXQgPC0gZnVuY3Rpb24oeikKICAgIGFicyhtaW51dGUod2l0aF90eih0bSwgdHogPSB6KSkgLSBtaW51dGUodG0pKQpvZmZzZXRzIDwtIGRhdGEuZnJhbWUoem9uZSA9IE9sc29uTmFtZXMoKSkgJT4lCiAgICBtdXRhdGUob2Zmc2V0ID0gc2FwcGx5KHpvbmUsIGdldF9vZmZzZXQpKSAlPiUKICAgIGFycmFuZ2Uob2Zmc2V0KQpmaWx0ZXIob2Zmc2V0cywgb2Zmc2V0ICE9IDApCgojIyBPZmZzZXRzIGZvciBBdXN0cmFsaWE6CmZpbHRlcihvZmZzZXRzLCBncmVwbCgiQXVzdHJhbGlhIiwgem9uZSkpCmBgYAoKSWYgd2UgY3JlYXRlIHRoZSBgdGhgIHZhcmlhYmxlIGZvciB0aGUgZmxpZ2h0cyBkYXRhIGFzCgpgYGB7cn0KZmwgPC0gbXV0YXRlKGZsaWdodHMsIHRoID0gbWFrZV9kYXRldGltZSh5ZWFyLCBtb250aCwgZGF5LCBob3VyLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHR6ID0gIkFtZXJpY2EvTmV3X1lvcmsiKSkKYGBgCgp0aGVuIHRoZSByZXN1bHQgbWF0Y2hlcyB0aGUgYGRhdGVfdGltZWAgdmFyaWFibGU6CgpgYGB7cn0KaWRlbnRpY2FsKGZsJHRoLCBmbCR0aW1lX2hvdXIpCmBgYAoKVGhlIGB0aW1lX2hvdXJgIHZhcmlhYmxlIGluIHRoZSBgd2VhdGhlcmAgdGFibGUgcmVmbGVjdHMgYWN0dWFsIHBvaW50cwppbiB0aW1lIGFuZCwgdG9nZXRoZXIgd2l0aCBgb3JpZ2luYCwgY2FuIHNlcnZlIGFzIGEgcHJpbWFyeSBrZXk6CgpgYGB7cn0KZmlsdGVyKGNvdW50KHdlYXRoZXIsIG9yaWdpbiwgdGltZV9ob3VyKSwgbiA+IDEpCmBgYAoKVGhlIGBtb250aGAsIGBkYXlgLCBgaG91cmAgdmFyaWFibGVzIGFyZSBjb25mdXNlZCBieSB0aGUgdGltZQpjaGFuZ2UuCgpJbiBOb3ZlbWJlciB0aGVyZSBpcyBhIHJlcGVhdDoKCmBgYHtyfQpjb3VudCh3ZWF0aGVyLCBvcmlnaW4sIG1vbnRoLCBkYXksIGhvdXIpICU+JQogICAgZmlsdGVyKG4gPiAxKQpgYGAKCmFuZCB0aGVyZSBpcyBhIG1pc3NpbmcgaG91ciBpbiBNYXJjaDoKCmBgYHtyfQpzZWxlY3Qod2VhdGhlciwgb3JpZ2luLCBtb250aCwgZGF5LCBob3VyKSAlPiUKICAgIGZpbHRlcihvcmlnaW4gPT0gIkVXUiIsIG1vbnRoID09IDMsCiAgICAgICAgICAgZGF5ID09IDEwLCBob3VyIDw9IDMpCmBgYAoKCiMjIFRoaW5ncyB0byBMb29rIE91dCBGb3IKCkZvciBkYXRlczoKCiogTGFuZ3VhZ2UgdXNlZCBmb3IgbW9udGhzIGFuZCB3ZWVrZGF5cywgYW5kIHRoZWlyIGFiYnJldmlhdGlvbnMuCgoqIEFtYmlndW91cyBudW1lcmljYWwgY29udmVudGlvbnMgbGlrZSA0LzExLzIwMjM6IGlzIHRoaXMgQXByaWwgMTEgb3IKICBOb3ZlbWJlciA0PwoKKiBEYXkgMiBvZiB0aGUgd2VlazogaXMgdGhpcyBNb25kYXkgb3IgVHVlc2RheT8KCiogRm9yIGhpc3RvcmljYWwgZGF0YSwgd2hhdCBjYWxlbmRhciBpcyBiZWluZyB1c2VkPyAoVGhlIF9PY3RvYmVyCiAgUmV2b2x1dGlvbl8gaGFwcGVuZWQgb24gTm92ZW1iZXIgNi83LCAxOTE3IGJ5IHRoZSBjdXJyZW50IEdyZWdvcmlhbgogIGNhbGVuZGFyOyBPY3RvYmVyIDI0LzI1IGJ5IHRoZSBKdWxpYW4gY2FsZW5kYXIgUnVzc2lhIHdhcyBzdGlsbAogIHVzaW5nLikKCkZvciBkYXRlLXRpbWVzCgoqIEFsbCBvZiB0aGUgYWJvdmUuCgoqIERheWxpZ2h0IHNhdmluZyB0aW1lLgoKKiBUaW1lIHpvbmVzLgoKPCEtLQpMb2NhbGUgc3R1ZmYgbWF5IGZhaWwgb24gc29tZSBzeXN0ZW1zIChvbiBVYnVudHUgbWF5IG5lZWQgdG8gdXNlCmxvY2FsZS1nZW4KLS0+CjwhLS0KbGlicmFyeShsdWJyaWRhdGUpCmZsaWdodHMgPC0gbXV0YXRlKGZsaWdodHMsIHRoID0gbWFrZV9kYXRldGltZSh5ZWFyLCBtb250aCwgZGF5LCBob3VyLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHogPSAiQW1lcmljYS9OZXdfWW9yayIpKQp3ZWF0aGVyIDwtIG11dGF0ZSh3ZWF0aGVyLCB0aCA9IG1ha2VfZGF0ZXRpbWUoeWVhciwgbW9udGgsIGRheSwgaG91ciwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHR6ID0gIlVUQyIpKQoKZmwwIDwtIHNlbGVjdChmaWx0ZXIoZmxpZ2h0cywgb3JpZ2luID09ICJMR0EiKSwKICAgICAgICAgICAgICBkZXBfdGltZSwgdGltZV9ob3VyLCB0aCkKdzAgPC0gc2VsZWN0KGZpbHRlcih3ZWF0aGVyLCBvcmlnaW4gPT0gIkxHQSIpWy0oMSA6IDkpLF0sCiAgICAgICAgICAgICB0aW1lX2hvdXIsIHRoLCB0ZW1wKQoKZmwxIDwtIGxlZnRfam9pbihmbDAsIHNlbGVjdCh3MCwgLXRoKSwgInRpbWVfaG91ciIpCmZsMiA8LSBsZWZ0X2pvaW4oZmwwLCBzZWxlY3QodzAsIC10aW1lX2hvdXIpLCAidGgiKQotLT4KCgojIyBSZWFkaW5nCgpDaGFwdGVyIFtfRGF0ZXMgYW5kIFRpbWVzX10oaHR0cHM6Ly9yNGRzLmhhZC5jby5uei9kYXRlcy1hbmQtdGltZXMuaHRtbCkKaW4gW19SIGZvciBEYXRhIFNjaWVuY2VfXShodHRwczovL3I0ZHMuaGFkLmNvLm56LykuCgoKIyMgRXhlcmNpc2VzCgoxLiBVc2luZyB0aGUgTllDIGZsaWdodHMgZGF0YSwgaG93IG1hbnkgZmxpZ2h0cyB3ZXJlIHRoZXJlIG9uCiAgIFNhdHVyZGF5cyBmcm9tIE5ld2FyayAoRVdSKSB0byBDaWNhZ28gTydIYXJlIChPUkQpIGluIDIwMTM/CiAgIAogICAgYS4gNDEzCiAgICBiLiA1MjIKICAgIGMuIDYwMQogICAgZC4gNzMzCiAgIAoKMi4gV2hhdCBkYXkgb2YgdGhlIHdlZWsgd2lsbCBKdWx5IDQsIDIwMzAsIGZhbGwgb24/CgogICAgYS4gTW9uZGF5CiAgICBiLiBXZWRuZXNkYXkKICAgIGQuIFRodXJzZGF5CiAgICBjLiBTYXR1cmRheQo=