timechange/ 0000755 0001762 0000144 00000000000 14552166502 012364 5 ustar ligges users timechange/NAMESPACE 0000644 0001762 0000144 00000000441 14330467710 013601 0 ustar ligges users # Generated by roxygen2: do not edit by hand
export(time_add)
export(time_at_tz)
export(time_ceiling)
export(time_clock_at_tz)
export(time_floor)
export(time_force_tz)
export(time_get)
export(time_round)
export(time_subtract)
export(time_update)
useDynLib(timechange, .registration=TRUE)
timechange/README.md 0000644 0001762 0000144 00000002524 14552163750 013650 0 ustar ligges users
[](https://github.com/vspinu/timechange/actions)
[](https://github.com/vspinu/timechange/actions/workflows/R-CMD-check.yaml)
[](https://CRAN.R-project.org/package=timechange)
## timechange
Utilities for efficient manipulation of date-time objects while accounting for time-zones and day-light saving times. Supported date time classes are `Date`, `POSIXct`, `POSIXlt` ([`nanosecond`](https://cran.r-project.org/package=nanotime) is [planned](https://github.com/vspinu/timechange/issues/1)).
Currently implemented:
- __`time_get`__: get components (hour, day etc) of date-time objects
- __`time_update`__: update date-time objects
- __`time_round`__, __`time_ceiling`__ and __`time_floor`__: date-time rounding methods
- __`time_force_tz`__, __`time_at_tz`__ and __`time_clock_at_tz`__: updating of time-zones and time/clock extraction at different time-zones
- __`time_add`__,__`time_subtract`__: fast period arithmetic
When it makes sense functions provide a refined control of what happens in ambiguous situations through `roll_month` and `roll_dst` arguments.
timechange/man/ 0000755 0001762 0000144 00000000000 14330750137 013134 5 ustar ligges users timechange/man/time_update.Rd 0000644 0001762 0000144 00000011116 14345623600 015723 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/update.R
\name{time_update}
\alias{time_update}
\title{Update components of a date-time object}
\usage{
time_update(
time,
updates = NULL,
year = NULL,
month = NULL,
yday = NULL,
mday = NULL,
wday = NULL,
hour = NULL,
minute = NULL,
second = NULL,
tz = NULL,
roll_month = "preday",
roll_dst = c("boundary", "post"),
week_start = getOption("timechange.week_start", 1),
exact = FALSE
)
}
\arguments{
\item{time}{a date-time object}
\item{updates}{a named list of components}
\item{year, month, yday, wday, mday, hour, minute, second}{components of the date-time to be
updated. All components except \code{second} will be converted to integer. Components
are replicated according to \code{vctrs} semantics, i.e. vectors must be either of
length 1 or same length as \code{time} vector.}
\item{tz}{time zone component (a singleton character vector)}
\item{roll_month}{controls how addition of months and years behaves when standard
arithmetic rules exceed limits of the resulting date's month. Possible values are
"preday", "boundary", "postday", "full" and "NA". See "Details" or
\verb{[(timechange::time_add())} for further details.}
\item{roll_dst}{is a string vector of length one or two. When two values are
supplied they specify how to roll date-times when they fall into "skipped" and
"repeated" DST transitions respectively. A single value is replicated to the
length of two. Possible values are:
\if{html}{\out{
}}\preformatted{* `pre` - Use the time before the transition boundary.
* `boundary` - Use the time exactly at the boundary transition.
* `post` - Use the time after the boundary transition.
* `xfirst` - crossed-first: First time which occurred when crossing the
boundary. For addition with positive units pre interval is crossed first and
post interval last. With negative units post interval is crossed first, pre -
last. For subtraction the logic is reversed.
* `xlast` - crossed-last.
* `NA` - Produce NAs when the resulting time falls inside the problematic interval.
}\if{html}{\out{
}}
For example `roll_dst = c("NA", "pre") indicates that for skiped intervals
return NA and for repeated times return the earlier time.
When multiple units are supplied the meaning of "negative period" is determined by
the largest unit. For example \code{time_add(t, days = -1, hours = 2, roll_dst = "xfirst")} would operate as if with negative period, thus crossing the boundary
from the "post" to "pre" side and "xfirst" and hence resolving to "post"
time. As this might result in confusing behavior. See examples.
"xfirst" and "xlast" make sense for addition and subtraction only. An error is
raised if an attempt is made to use them with other functions.}
\item{week_start}{first day of the week (default is 1, Monday). Set
\code{timechange.week_start} option to change this globally.}
\item{exact}{logical (TRUE), whether the update should be exact. If set to \code{FALSE} no
rolling or unit-recycling is allowed and \code{NA} is produced whenever the units of the
end date-time don't match the provided units. This can occur when an end date falls
into a gap (e.g. DST or Feb.29) or when large components (e.g. \code{hour = 25}) are
supplied and result in crossing boundaries of higher units. When \code{exact = TRUE},
\code{roll_month} and \code{roll_dst} arguments are ignored.}
}
\value{
A date-time with the requested elements updated. Retain its original class
unless the original class is \code{Date} and at least one of the \code{hour}, \code{minute},
\code{second} or \code{tz} is supplied, in which case a \code{POSIXct} object is returned.
}
\description{
Update components of a date-time object
}
\examples{
date <- as.Date("2009-02-10")
time_update(date, year = 2010, month = 1, mday = 1)
time_update(date, year = 2010, month = 13, mday = 1)
time_update(date, minute = 10, second = 3)
time_update(date, minute = 10, second = 3, tz = "America/New_York")
time <- as.POSIXct("2015-02-03 01:02:03", tz = "America/New_York")
time_update(time, month = 2, mday = 31, roll_month = "preday")
time_update(time, month = 2, mday = 31, roll_month = "boundary")
time_update(time, month = 2, mday = 31, roll_month = "postday")
time_update(time, month = 2, mday = 31, exact = TRUE)
time_update(time, month = 2, mday = 31, exact = FALSE)
## DST skipped
time <- as.POSIXct("2015-02-03 01:02:03", tz = "America/New_York")
time_update(time, year = 2016, yday = 10)
time_update(time, year = 2016, yday = 10, tz = "Europe/Amsterdam")
time_update(time, second = 30, tz = "America/New_York")
}
\seealso{
\verb{[time_add()]}
}
timechange/man/timechange-package.Rd 0000644 0001762 0000144 00000001315 13266110470 017115 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/package.R
\docType{package}
\name{timechange-package}
\alias{timechange}
\alias{timechange-package}
\title{Package \code{timechange}}
\description{
Utilities for efficient updating of date-times components while accounting
for time-zones and day-light saving times. When it makes sense functions
provide a refined control of what happens in ambiguous situations through
\code{roll_month} and \code{roll_dst} arguments.
}
\seealso{
Useful links:
\itemize{
\item \url{https://github.com/vspinu/timechange/}
\item Report bugs at \url{https://github.com/vspinu/timechange/issues}
}
}
\author{
Vitalie Spinu (\email{spinuvit@gmail.com})
}
timechange/man/time_add.Rd 0000644 0001762 0000144 00000020350 14345623600 015171 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/addition.R
\name{time_add}
\alias{time_add}
\alias{time_subtract}
\title{Arithmetics with periods}
\usage{
time_add(
time,
periods = NULL,
year = NULL,
month = NULL,
week = NULL,
day = NULL,
hour = NULL,
minute = NULL,
second = NULL,
roll_month = "preday",
roll_dst = c("post", "pre"),
...
)
time_subtract(
time,
periods = NULL,
year = NULL,
month = NULL,
week = NULL,
day = NULL,
hour = NULL,
minute = NULL,
second = NULL,
roll_month = "preday",
roll_dst = c("pre", "post"),
...
)
}
\arguments{
\item{time}{date-time object}
\item{periods}{a named list of the form \code{list(year = 1, month = 2, ...)}.}
\item{year, month, week, day, hour, minute, second}{Units to be added to \code{time}. Units
except for seconds are converted to integer values. Components are replicated
according to \code{vctrs} semantics, i.e vectors must be either of length 1 or same
length as \code{time} vector.}
\item{roll_month}{controls how addition of months and years behaves when standard
arithmetic rules exceed limits of the resulting date's month. Possible values are
"preday", "boundary", "postday", "full" and "NA". See "Details" or
\verb{[(timechange::time_add())} for further details.}
\item{roll_dst}{is a string vector of length one or two. When two values are
supplied they specify how to roll date-times when they fall into "skipped" and
"repeated" DST transitions respectively. A single value is replicated to the
length of two. Possible values are:
\if{html}{\out{}}\preformatted{* `pre` - Use the time before the transition boundary.
* `boundary` - Use the time exactly at the boundary transition.
* `post` - Use the time after the boundary transition.
* `xfirst` - crossed-first: First time which occurred when crossing the
boundary. For addition with positive units pre interval is crossed first and
post interval last. With negative units post interval is crossed first, pre -
last. For subtraction the logic is reversed.
* `xlast` - crossed-last.
* `NA` - Produce NAs when the resulting time falls inside the problematic interval.
}\if{html}{\out{
}}
For example `roll_dst = c("NA", "pre") indicates that for skiped intervals
return NA and for repeated times return the earlier time.
When multiple units are supplied the meaning of "negative period" is determined by
the largest unit. For example \code{time_add(t, days = -1, hours = 2, roll_dst = "xfirst")} would operate as if with negative period, thus crossing the boundary
from the "post" to "pre" side and "xfirst" and hence resolving to "post"
time. As this might result in confusing behavior. See examples.
"xfirst" and "xlast" make sense for addition and subtraction only. An error is
raised if an attempt is made to use them with other functions.}
\item{...}{deprecated}
}
\description{
Add periods to date-time objects. Periods track the change in the
"clock time" between two civil times. They are measured in common civil time
units: years, months, days, hours, minutes, and seconds.
}
\details{
Arithmetic operations with multiple period units (years, months etc) are
applied in decreasing size order, from year to second. Thus \code{time_add(x, month = 1, days = 3)} first adds 1 month to \code{x}, then ads to the resulting date 3 days.
Generally period arithmetic is undefined due to the irregular nature of
civil time and complexities with DST transitions. \pkg{`timechange`} allows
for a refined control of what happens when an addition of irregular periods
(years, months, days) results in "unclear" date.
Let's start with an example. What happens when you add "1 month 3 days" to
"2000-01-31 01:02:03"? \pkg{`timechange`} operates by applying larger
periods first. First months are added\code{1 + 1 = February} which results in
non-existent time of \verb{2000-02-31 01:02:03}. Here the \code{roll_month} adjustment
kicks in. After the adjustment, the remaining 3 days are added.
\code{roll_month} can be one of the following:
\itemize{
\item \code{boundary} - if rolling over a month boundary occurred due to setting units
smaller than month, the date is adjusted to the beginning of the month (the
boundary). For example, \verb{2000-01-31 01:02:03 + 1 month = 2000-03-01 00:00:00}.
\item \code{preday} - roll back to the last valid day of the previous month (pre-boundary
day) preserving the H, M, S units. For example, \verb{2000-01-31 01:02:03 + 1 month = 2000-02-28 01:02:03}. This is the default.
\item \code{postday} - roll to the first day post-boundary preserving the H, M, S units. For
example, \verb{2000-01-31 01:02:03 + 1 month = 2000-03-01 01:02:03}.
\item \code{full} - full rolling. No adjustment is done to the simple arithmetic operations
(the gap is skipped as if it's not there). For example, \verb{2000-01-31 01:02:03 + 1 month + 3 days} is equivalent to \verb{2000-01-01 01:02:03 + 1 month + 31 days + 3 days}
resulting in \verb{2000-03-05 01:02:03}.
\item \code{NA} - if end result was rolled over the month boundary due to addition of units
smaller than month (day, hour, minute, second) produce NA.
\item \code{NAym} - if intermediate date resulting from first adding years and months ends in
a non-existing date (e.g. Feb 31) produce NA. This is how period addition in
lubridate works for historical reasons.
}
}
\examples{
# Addition
## Month gap
x <- as.POSIXct("2000-01-31 01:02:03", tz = "America/Chicago")
time_add(x, month = 1, roll_month = "postday")
time_add(x, month = 1, roll_month = "preday")
time_add(x, month = 1, roll_month = "boundary")
time_add(x, month = 1, roll_month = "full")
time_add(x, month = 1, roll_month = "NA")
time_add(x, month = 1, day = 3, roll_month = "postday")
time_add(x, month = 1, day = 3, roll_month = "preday")
time_add(x, month = 1, day = 3, roll_month = "boundary")
time_add(x, month = 1, day = 3, roll_month = "full")
time_add(x, month = 1, day = 3, roll_month = "NA")
## DST gap
x <- as.POSIXlt("2010-03-14 01:02:03", tz = "America/Chicago")
time_add(x, hour = 1, minute = 50, roll_dst = "pre")
time_add(x, hour = 1, minute = 50, roll_dst = "boundary")
time_add(x, hour = 1, minute = 50, roll_dst = "post")
##' time_add(x, hours = 1, minutes = 50, roll_dst = "NA")
## DST repeated time with cross-first and cross-last
(tt <- as.POSIXct(c("2014-11-02 00:15:00", "2014-11-02 02:15:00"), tz = "America/New_York"))
time_add(tt, hours = c(1, -1), roll_dst = "pre")
time_add(tt, hours = c(1, -1), roll_dst = "post")
time_add(tt, hours = c(1, -1), roll_dst = "xfirst")
time_add(tt, hours = c(1, -1), roll_dst = "xlast")
## DST skip with cross-first and cross-last
cst <- as.POSIXlt("2010-03-14 01:02:03", tz = "America/Chicago")
cdt <- as.POSIXlt("2010-03-14 03:02:03", tz = "America/Chicago")
time_add(cst, hour = 1, roll_dst = "xfirst")
time_add(cst, hour = 1, roll_dst = "xlast")
time_add(cdt, hour = -1, roll_dst = "xfirst")
time_add(cdt, hour = -1, roll_dst = "xlast")
# WARNING:
# In the following example the overall period is treated as a negative period
# because the largest unit (hour) is negative. Thus `xfirst` roll_dst results in the
# "post" time. To avoid such confusing behavior either avoid supplying multiple
# units with heterogeneous sign.
time_add(cst, hour = -1, minute = 170, roll_dst = "xfirst")
# SUBTRACTION
## Month gap
x <- as.POSIXct("2000-03-31 01:02:03", tz = "America/Chicago")
time_subtract(x, month = 1, roll_month = "postday")
time_subtract(x, month = 1, roll_month = "preday")
time_subtract(x, month = 1, roll_month = "boundary")
time_subtract(x, month = 1, roll_month = "full")
time_subtract(x, month = 1, roll_month = "NA")
time_subtract(x, month = 1, day = 0, roll_month = "postday")
time_subtract(x, month = 1, day = 3, roll_month = "postday")
time_subtract(x, month = 1, day = 0, roll_month = "preday")
time_subtract(x, month = 1, day = 3, roll_month = "preday")
time_subtract(x, month = 1, day = 3, roll_month = "boundary")
time_subtract(x, month = 1, day = 3, roll_month = "full")
time_subtract(x, month = 1, day = 3, roll_month = "NA")
## DST gap
y <- as.POSIXlt("2010-03-15 01:02:03", tz = "America/Chicago")
time_subtract(y, hour = 22, minute = 50, roll_dst = "pre")
time_subtract(y, hour = 22, minute = 50, roll_dst = "boundary")
time_subtract(y, hour = 22, minute = 50, roll_dst = "post")
time_subtract(y, hour = 22, minute = 50, roll_dst = "NA")
}
timechange/man/time-zones.Rd 0000644 0001762 0000144 00000007216 14330040167 015516 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/zones.R
\name{time-zones}
\alias{time-zones}
\alias{time_at_tz}
\alias{time_force_tz}
\alias{time_clock_at_tz}
\title{Time-zone manipulation}
\usage{
time_at_tz(time, tz = "UTC")
time_force_tz(
time,
tz = "UTC",
tzout = tz[[1]],
roll_dst = c("boundary", "post")
)
time_clock_at_tz(time, tz = NULL, units = "secs")
}
\arguments{
\item{time}{a date-time object (POSIXct, POSIXlt, Date) or a list of
date-time objects. When a list, all contained elements are updated the new
list is returned.}
\item{tz}{a character string containing the time zone to convert to. R must
recognize the name contained in the string as a time zone on your
system. For \code{time_force_tz} and \code{time_clock_at_tzs}, \code{tz} can be a vector
of heterogeneous time-zones, in which case \code{time} and \code{tz} arguments are
paired. If \code{time} and \code{tz} lengths differ, the smaller one is recycled
according with usual R conventions.}
\item{tzout}{timezone of the output date-time vector. Meaningful only when
\code{tz} argument is a vector of heterogenuous time-zones. This argument is
necessary because R date-time vectors cannot hold elements with different
time-zones.}
\item{roll_dst}{same as in \code{time_add} which see.}
\item{units}{passed directly to \code{\link[=as.difftime]{as.difftime()}}.}
}
\value{
a POSIXct object with the updated time zone
}
\description{
\code{time_at_tz} returns a date-time as it would appear in a
different time zone. The actual moment of time measured does not change,
just the time zone it is measured in. \code{time_at_tz} defaults to the
Universal Coordinated time zone (UTC) when an unrecognized time zone is
supplied.
\code{time_force_tz} returns the date-time that has the same clock
time as input time, but in the new time zone. Although the new date-time
has the same clock time (e.g. the same values in the seconds, minutes,
hours, etc.) it is a different moment of time than the input
date-time. Computation is vectorized over both \code{time} and \code{tz} arguments.
\code{time_clock_at_tz} retrieves day clock time in specified time
zones. Computation is vectorized over both \code{dt} and \code{tz} arguments, \code{tz}
defaults to the timezone of \code{time}.
}
\examples{
x <- as.POSIXct("2009-08-07 00:00:00", tz = "America/New_York")
time_at_tz(x, "UTC")
time_force_tz(x, "UTC")
time_force_tz(x, "Europe/Amsterdam")
## DST skip:
y <- as.POSIXct("2010-03-14 02:05:05", tz = "UTC")
time_force_tz(y, "America/New_York", roll = "boundary")
time_force_tz(y, "America/New_York", roll = "post")
time_force_tz(y, "America/New_York", roll = "pre")
time_force_tz(y, "America/New_York", roll = "NA")
## DST skipped and repeated
y <- as.POSIXct(c("2010-03-14 02:05:05 UTC", "2014-11-02 01:35:00"), tz = "UTC")
time_force_tz(y, "America/New_York", roll_dst = c("NA", "pre"))
time_force_tz(y, "America/New_York", roll_dst = c("boundary", "post"))
## Heterogeneous time-zones:
x <- as.POSIXct(c("2009-08-07 00:00:01", "2009-08-07 01:02:03"), tz = "UTC")
time_force_tz(x, tz = c("America/New_York", "Europe/Amsterdam"))
time_force_tz(x, tz = c("America/New_York", "Europe/Amsterdam"), tzout = "America/New_York")
x <- as.POSIXct("2009-08-07 00:00:01", tz = "UTC")
time_force_tz(x, tz = c("America/New_York", "Europe/Amsterdam"))
## Local clock:
x <- as.POSIXct(c("2009-08-07 01:02:03", "2009-08-07 10:20:30"), tz = "UTC")
time_clock_at_tz(x, units = "secs")
time_clock_at_tz(x, units = "hours")
time_clock_at_tz(x, "Europe/Amsterdam")
x <- as.POSIXct("2009-08-07 01:02:03", tz = "UTC")
time_clock_at_tz(x, tz = c("America/New_York", "Europe/Amsterdam", "Asia/Shanghai"), unit = "hours")
}
timechange/man/time_get.Rd 0000644 0001762 0000144 00000001532 13573202614 015221 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/get.R
\name{time_get}
\alias{time_get}
\title{Get components of a date-time object}
\usage{
time_get(
time,
components = c("year", "month", "yday", "mday", "wday", "hour", "minute", "second"),
week_start = getOption("timechange.week_start", 1)
)
}
\arguments{
\item{time}{a date-time object}
\item{components}{a character vector of components to return. Component is
one of "year", "month", "yday", "day", "mday", "wday", "hour", "minute",
"second" where "day" is the same as "mday".}
\item{week_start}{week starting day (Default is 1, Monday). Set
\code{timechange.week_start} option to change this globally.}
}
\value{
A data.frame of the requested components
}
\description{
Get components of a date-time object
}
\examples{
x <- as.POSIXct("2019-02-03")
time_get(x)
}
timechange/man/time_round.Rd 0000644 0001762 0000144 00000022646 14413264560 015604 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/round.R
\name{time_round}
\alias{time_round}
\alias{time_floor}
\alias{time_ceiling}
\title{Round, floor and ceiling for date-time objects}
\usage{
time_round(
time,
unit = "second",
week_start = getOption("timechange.week_start", 1),
origin = unix_origin
)
time_floor(
time,
unit = "seconds",
week_start = getOption("timechange.week_start", 1),
origin = unix_origin
)
time_ceiling(
time,
unit = "seconds",
change_on_boundary = inherits(time, "Date"),
week_start = getOption("timechange.week_start", 1),
origin = unix_origin
)
}
\arguments{
\item{time}{a date-time vector (\code{Date}, \code{POSIXct} or \code{POSIXlt})}
\item{unit}{a character string specifying a time unit or a multiple of a unit. Valid
base periods for civil time rounding are \code{second}, \code{minute}, \code{hour}, \code{day}, \code{week},
\code{month}, \code{bimonth}, \code{quarter}, \code{season}, \code{halfyear} and \code{year}. The only units for
absolute time rounding are \code{asecond}, \code{aminute} and \code{ahour}. Other absolute units
can be achieved with multiples of \code{asecond} (e.g. "24ah"). See "Details" and
examples. Arbitrary unique English abbreviations are allowed. One letter
abbreviations follow \code{strptime} formats "y", "m", "d", "M", "H", "S". Multi-unit
rounding of weeks is currently not supported.
Rounding for a unit is performed from the parent's unit origin. For example when
rounding to seconds origin is start of the minute. When rounding to days, origin is
first date of the month. See examples.
With fractional sub-unit (unit < 1) rounding with child unit is performed
instead. For example 0.5mins == 30secs, .2hours == 12min etc.
Please note that for fractions which don't match exactly to integer number of the
child units only the integer part is used for computation. For example .7days =
16.8hours will use 16 hours during the computation.}
\item{week_start}{When unit is \code{weeks}, this is the first day of the week. Defaults
to 1 (Monday).}
\item{origin}{Origin with respect to which to perform the rounding operation. For
absolute units only. Can be a vector of the same length as the input \code{time}
vector. Defaults to the Unix origin "1970-01-01 UTC".}
\item{change_on_boundary}{If NULL (the default) don't change instants on the boundary
(\code{time_ceiling(ymd_hms('2000-01-01 00:00:00'))} is \verb{2000-01-01 00:00:00}), but
round up \code{Date} objects to the next boundary (\code{time_ceiling(ymd("2000-01-01"), "month")} is \code{"2000-02-01"}). When \code{TRUE}, instants on the boundary are rounded up
to the next boundary. When \code{FALSE}, date-time on the boundary are never rounded up
(this was the default for \pkg{lubridate} prior to \code{v1.6.0}. See section \verb{Rounding Up Date Objects} below for more details.}
}
\value{
An object of the same class as the input object. When input is a \code{Date}
object and unit is smaller than \code{day} a \code{POSIXct} object is returned.
}
\description{
\pkg{timechange} provides rounding to the nearest unit or multiple of a
unit with fractional support whenever makes sense. Units can be specified flexibly
as strings. All common abbreviations are supported - secs, min, mins, 2 minutes, 3
years, 2s, 1d etc.
}
\section{Civil Time vs Absolute Time rounding}{
Rounding in civil time is done on actual clock time (ymdHMS) and is affected
by civil time irregularities like DST. One important characteristic of civil
time rounding is that floor (ceiling) does not produce civil times that are
bigger (smaller) than the original civil time.
Absolute time rounding (with \code{aseconds}, \code{aminutes} and \code{ahours}) is done on the
absolute time (number of seconds since origin), thus, allowing for fractional seconds
and arbitrary multi-units. See examples of rounding around DST transition where
rounding in civil time does not give the same result as rounding with the
corresponding absolute units. Also note that \code{round.POSIXt()} rounds on absolute
time.
Please note that absolute rounding to fractions smaller than 1ms will result in large
precision errors due to the floating point representation of the POSIXct objects.
}
\section{Note on \code{time_round()}}{
For rounding date-times which is exactly halfway between two consecutive units,
the convention is to round up. Note that this is in line with the behavior of R's
\code{\link[base:round.POSIXt]{base::round.POSIXt()}} function but does not follow the convention of the base
\code{\link[base:Round]{base::round()}} function which "rounds to the even digit" per IEC 60559.
}
\section{Ceiling of \code{Date} objects}{
By default rounding up \code{Date} objects follows 3 steps:
\enumerate{
\item Convert to an instant representing lower bound of the Date:
\code{2000-01-01} --> \verb{2000-01-01 00:00:00}
\item Round up to the \strong{next} closest rounding unit boundary. For example,
if the rounding unit is \code{month} then next closest boundary of \code{2000-01-01}
is \verb{2000-02-01 00:00:00}.
The motivation for this is that the "partial" \code{2000-01-01} is conceptually
an interval (\verb{2000-01-01 00:00:00} -- \verb{2000-01-02 00:00:00}) and the day
hasn't started clocking yet at the exact boundary \code{00:00:00}. Thus, it
seems wrong to round up a day to its lower boundary.
The behavior on the boundary can be changed by setting
\code{change_on_boundary} to a non-\code{NULL} value.
\item If rounding unit is smaller than a day, return the instant from step 2
(\code{POSIXct}), otherwise convert to and return a \code{Date} object.
}
}
\examples{
## print fractional seconds
options(digits.secs=6)
x <- as.POSIXct("2009-08-03 12:01:59.23")
time_round(x, ".5 asec")
time_round(x, "sec")
time_round(x, "second")
time_round(x, "asecond")
time_round(x, "minute")
time_round(x, "5 mins")
time_round(x, "5M") # "M" for minute "m" for month
time_round(x, "hour")
time_round(x, "2 hours")
time_round(x, "2H")
time_round(x, "day")
time_round(x, "week")
time_round(x, "month")
time_round(x, "bimonth")
time_round(x, "quarter") == time_round(x, "3 months")
time_round(x, "halfyear")
time_round(x, "year")
x <- as.POSIXct("2009-08-03 12:01:59.23")
time_floor(x, ".1 asec")
time_floor(x, "second")
time_floor(x, "minute")
time_floor(x, "M")
time_floor(x, "hour")
time_floor(x, ".2 ahour")
time_floor(x, "day")
time_floor(x, "week")
time_floor(x, "m")
time_floor(x, "month")
time_floor(x, "bimonth")
time_floor(x, "quarter")
time_floor(x, "season")
time_floor(x, "halfyear")
time_floor(x, "year")
x <- as.POSIXct("2009-08-03 12:01:59.23")
time_ceiling(x, ".1 asec")
time_ceiling(x, "second")
time_ceiling(x, "minute")
time_ceiling(x, "5 mins")
time_ceiling(x, "hour")
time_ceiling(x, ".2 ahour")
time_ceiling(x, "day")
time_ceiling(x, "week")
time_ceiling(x, "month")
time_ceiling(x, "bimonth") == time_ceiling(x, "2 months")
time_ceiling(x, "quarter")
time_ceiling(x, "season")
time_ceiling(x, "halfyear")
time_ceiling(x, "year")
## behavior on the boundary
x <- as.Date("2000-01-01")
time_ceiling(x, "month")
time_ceiling(x, "month", change_on_boundary = FALSE)
## As of R 3.4.2 POSIXct printing of fractional seconds is wrong
as.POSIXct("2009-08-03 12:01:59.3", tz = "UTC") ## -> "2009-08-03 12:01:59.2 UTC"
time_ceiling(x, ".1 asec") ## -> "2009-08-03 12:01:59.2 UTC"
## Civil Time vs Absolute Time Rounding
# "2014-11-02 01:59:59.5 EDT" before 1h backroll at 2AM
x <- .POSIXct(1414907999.5, tz = "America/New_York")
x
time_ceiling(x, "hour") # "2014-11-02 02:00:00 EST"
time_ceiling(x, "ahour") # "2014-11-02 01:00:00 EST"
time_ceiling(x, "minute")
time_ceiling(x, "aminute")
time_ceiling(x, "sec")
time_ceiling(x, "asec")
time_round(x, "hour") # "2014-11-02 01:00:00 EDT" !!
time_round(x, "ahour") # "2014-11-02 01:00:00 EST"
round.POSIXt(x, "hour") # "2014-11-02 01:00:00 EST"
# "2014-11-02 01:00:00.5 EST" .5s after 1h backroll at 2AM
x <- .POSIXct(1414908000.5, tz = "America/New_York")
x
time_floor(x, "hour") # "2014-11-02 01:00:00 EST"
time_floor(x, "ahour") # "2014-11-02 01:00:00 EST"
## Behavior on the boundary when rounding multi-units
x <- as.POSIXct("2009-08-28 22:56:59.23", tz = "UTC")
time_ceiling(x, "3.4 secs") # "2009-08-28 22:57:03.4"
time_ceiling(x, "50.5 secs") # "2009-08-28 22:57:50.5"
time_ceiling(x, "57 min") # "2009-08-28 22:57:00"
time_ceiling(x, "56 min") # "2009-08-28 23:56:00"
time_ceiling(x, "7h") # "2009-08-29 07:00:00"
time_ceiling(x, "7d") # "2009-08-29 00:00:00"
time_ceiling(x, "8d") # "2009-09-09 00:00:00"
time_ceiling(x, "8m") # "2009-09-01 00:00:00"
time_ceiling(x, "6m") # "2010-01-01 00:00:00"
time_ceiling(x, "7m") # "2010-08-01 00:00:00"
x <- as.POSIXct("2010-11-25 22:56:57", tz = "UTC")
time_ceiling(x, "6sec") # "2010-11-25 22:57:00"
time_ceiling(x, "60sec") # "2010-11-25 22:57:00"
time_ceiling(x, "6min") # "2010-11-25 23:00:00"
time_ceiling(x, "60min") # "2010-11-25 23:00:00"
time_ceiling(x, "4h") # "2010-11-26 00:00:00"
time_ceiling(x, "15d") # "2010-12-01 00:00:00"
time_ceiling(x, "15d") # "2010-12-01 00:00:00"
time_ceiling(x, "6m") # "2011-01-01 00:00:00"
## custom origin
x <- as.POSIXct(c("2010-10-01 01:00:01", "2010-11-02 02:00:01"), tz = "America/New_York")
# 50 minutes from the day or month start
time_floor(x, "50amin")
time_floor(x, "50amin", origin = time_floor(x, "day"))
time_floor(x, "50amin", origin = time_floor(x, "month"))
time_ceiling(x, "50amin")
time_ceiling(x, "50amin", origin = time_floor(x, "day"))
time_ceiling(x, "50amin", origin = time_floor(x, "month"))
}
\seealso{
\code{\link[base:Round]{base::round()}}
}
timechange/DESCRIPTION 0000644 0001762 0000144 00000002601 14552166502 014071 0 ustar ligges users Package: timechange
Title: Efficient Manipulation of Date-Times
Version: 0.3.0
Authors@R: c(person("Vitalie", "Spinu", email = "spinuvit@gmail.com", role = c("aut", "cre")),
person("Google Inc.", role = c("ctb", "cph")))
Description: Efficient routines for manipulation of date-time objects while
accounting for time-zones and daylight saving times. The package includes
utilities for updating of date-time components (year, month, day etc.),
modification of time-zones, rounding of date-times, period addition and
subtraction etc. Parts of the 'CCTZ' source code, released under the Apache
2.0 License, are included in this package. See
for more details.
Depends: R (>= 3.3)
License: GPL (>= 3)
Encoding: UTF-8
LinkingTo: cpp11 (>= 0.2.7)
Suggests: testthat (>= 0.7.1.99), knitr
SystemRequirements: A system with zoneinfo data (e.g.
/usr/share/zoneinfo) as well as a recent-enough C++11 compiler
(such as g++-4.8 or later). On Windows the zoneinfo included
with R is used.
BugReports: https://github.com/vspinu/timechange/issues
URL: https://github.com/vspinu/timechange/
RoxygenNote: 7.2.1
NeedsCompilation: yes
Packaged: 2024-01-18 08:57:24 UTC; vspinu
Author: Vitalie Spinu [aut, cre],
Google Inc. [ctb, cph]
Maintainer: Vitalie Spinu
Repository: CRAN
Date/Publication: 2024-01-18 09:20:02 UTC
timechange/tests/ 0000755 0001762 0000144 00000000000 14337670452 013533 5 ustar ligges users timechange/tests/testthat/ 0000755 0001762 0000144 00000000000 14552166502 015366 5 ustar ligges users timechange/tests/testthat/helpers.R 0000644 0001762 0000144 00000005235 14337670452 017165 0 ustar ligges users
ctutc <- function(x) {
as.POSIXct(x, tz = "UTC")
}
ctus <- function(x) {
as.POSIXct(x, tz = "America/New_York")
}
cteu <- function(x) {
as.POSIXct(x, tz = "Europe/Amsterdam")
}
ltutc <- function(x) {
as.POSIXlt(x, tz = "UTC")
}
ltus <- function(x) {
as.POSIXlt(x, tz = "America/New_York")
}
ltam <- ltus
lteu <- function(x) {
as.POSIXct(x, tz = "Europe/Amsterdam")
}
NAam <- .POSIXct(NA_real_, tz = "America/New_York")
NAem <- .POSIXct(NA_real_, tz = "Europe/Amsterdam")
NAutc <- .POSIXct(NA_real_, tz = "UTC")
ymd_hms <- function(..., tz = "UTC") {
x <- unlist(list(...))
out <- .POSIXct(rep_len(NA_real_, length(x)), tz = tz)
nna <- !is.na(x)
out[nna] <- as.POSIXct(x[nna], tz = tz)
out
}
origin <- as.POSIXct("1970-01-01 00:00:00", tz = "UTC")
ymd <- function(..., tz = NULL) {
x <- unlist(list(...))
if (is.null(tz)) {
out <- as.Date(rep_len(NA_real_, length(x)), origin = origin)
nna <- !is.na(x)
out[nna] <- as.Date(x[nna], tz = "UTC")
} else {
out <- .POSIXct(rep_len(NA_real_, length(x)), tz = tz)
nna <- !is.na(x)
out[nna] <- as.POSIXct(paste(x[nna], "00:00:00", sep = " "), tz = tz)
}
out
}
second <- function(x)
as.POSIXlt(x, tz = timechange:::tz(x))$sec
hour <- function(x)
as.POSIXlt(x, tz = timechange:::tz(x))$hour
minute <- function(x)
as.POSIXlt(x, tz = timechange:::tz(x))$min
month <- function(x, label = FALSE, abbr = TRUE, locale = Sys.getlocale("LC_TIME")) {
x <- as.POSIXlt(x, tz = timechange:::tz(x))$mon + 1
if (!label) return(x)
names <- .get_locale_regs(locale)$month_names
labels <- if (abbr) names$abr else names$full
ordered(x, levels = 1:12, labels = labels)
}
year <- function(x)
as.POSIXlt(x, tz = timechange:::tz(x))$year + 1900
mday <- function(x)
as.POSIXlt(x, tz = timechange:::tz(x))$mday
day <- function(x)
as.POSIXlt(x, tz = timechange:::tz(x))$mday
wday <- function(x, label = FALSE, abbr = TRUE,
week_start = 1, locale = Sys.getlocale("LC_TIME")) {
x <- as.POSIXlt(x, tz = timechange:::tz(x))$wday + 1
start <- as.integer(week_start)
if (start > 7 || start < 1) stop("Invalid 'week_start' argument; must be between 1 and 7")
if (start != 7) {
x <- 1 + (x + (6 - start)) %% 7
}
if (!label) {
return(x)
}
names <- .get_locale_regs(locale)$wday_names
labels <- if (abbr) names$abr else names$full
ordered(x, levels = 1:7, labels = .shift_wday_names(labels, week_start = start))
}
yday <- function(x)
as.POSIXlt(x, tz = timechange:::tz(x))$yday + 1
now <- function(tzone = "")
time_at_tz(Sys.time(), tzone)
NA_Date_ <- structure(NA_real_, class = "Date")
NA_POSIXct_ <- structure(NA_real_, class = c("POSIXct", "POSIXt"), tzone = "UTC")
timechange/tests/testthat/test-parse.R 0000644 0001762 0000144 00000004020 14357543044 017577 0 ustar ligges users context("Parser")
test_that("parse_unit works as expected", {
expect_identical(
parse_unit(c("1.2s", "1.2 s", "1.2S", "1.2 secs", " 1.2 seco", " 1.2 seconds ")),
list(n = rep.int(1.2, 6),
unit = rep.int("second", 6)))
expect_identical(
parse_unit(c("1M", "1 mi", "mi", "1 mins", " 1 minu", " minutes ")),
list(n = rep.int(1, 6),
unit = rep.int("minute", 6)))
expect_identical(
parse_unit(c("-1M", "-.1 d", "-1000.0000001y", "-1000.0000001years")),
list(n = c(-1, -.1, -1000.0000001, -1000.0000001),
unit = c("minute", "day", "year", "year")))
expect_identical(
parse_unit(c("-1sea", "-.1 seaso", "-1000.0000001seasons ")),
list(n = c(-1, -.1, -1000.0000001),
unit = c("season", "season", "season")))
expect_identical(
parse_unit(c("-1h", "-.1ha", "-1000.0000001se", "-1000.0000001sea")),
list(n = c(-1, -.1, -1000.0000001, -1000.0000001),
unit = c("hour", "halfyear", "second", "season")))
expect_identical(parse_unit("asecs"), list(n = 1, unit = "asecond"))
expect_identical(parse_unit("102.300003 amins"), list(n = 102.300003, unit = "aminute"))
expect_identical(parse_unit("ahours"), list(n = 1, unit = "ahour"))
expect_identical(parse_unit("2.3 ahours"), list(n = 2.3, unit = "ahour"))
expect_identical(parse_unit("as"), list(n = 1, unit = "asecond"))
expect_identical(parse_unit("am"), list(n = 1, unit = "aminute"))
expect_identical(parse_unit("ah"), list(n = 1, unit = "ahour"))
expect_identical(parse_unit("0H 3M 0S"), list(n = 3, unit = "minute"))
expect_identical(parse_unit("3M 0S 0mon"), list(n = 3, unit = "minute"))
})
test_that("parse_unit errors on invalid unit", {
expect_error(parse_unit("1 blabla"), "Invalid unit.*blabla")
expect_error(parse_unit("1 mm"), "Invalid unit.*mm")
expect_error(parse_unit("1"), "Invalid unit.*1")
expect_error(parse_unit("1 2"), "Invalid unit.*1 2")
expect_error(parse_unit("1 m m"), "Heterogeneous unit.*m m")
expect_error(parse_unit("3M 0S 1M"), "Heterogeneous unit.*1M")
})
timechange/tests/testthat/test-addition.R 0000644 0001762 0000144 00000036217 14356563775 020311 0 ustar ligges users context("Addition Operations")
test_that("Integer mode posixct input works", {
set.seed(100)
int <- seq(ymd("2020-08-13", tz = "UTC"), by = "hour", length.out = 50)
expect_true(storage.mode(int) == "integer")
dbl <- int
secs <- runif(length(int))
expect_equal(time_add(int, seconds = int), time_add(dbl, seconds = int))
expect_equal(time_add(int, seconds = as.integer(int)), time_add(dbl, seconds = as.integer(int)))
})
test_that("Non-finite date-times are handled correctly", {
expect_equal(time_add(.POSIXct(Inf), hours = 1), .POSIXct(Inf, tz = ""))
expect_equal(time_add(.POSIXct(-Inf), hours = 1), .POSIXct(-Inf, tz = ""))
expect_equal(time_add(.POSIXct(NA_real_), hours = 1), .POSIXct(NA_real_, tz = ""))
expect_equal(time_add(.Date(Inf), days = 1), .Date(Inf))
expect_equal(time_add(.Date(-Inf), days = 1), .Date(-Inf))
expect_equal(time_add(.POSIXct(NA_real_), days = 1), .POSIXct(NA_real_, tz = ""))
})
test_that("addition handles daylight savings time", {
x <- ctus("2010-03-14 01:02:03")
y <- ctus("2010-03-15 01:02:03")
expect_equal(time_add(x, days = 1), y)
expect_equal(time_add(x, hours = 1, minutes = 50, roll_dst = "post"), ctus("2010-03-14 03:52:03"))
expect_equal(time_add(x, hours = 1, minutes = 50, roll_dst = "pre"), ctus("2010-03-14 01:52:03"))
expect_equal(time_add(x, hours = 1, minutes = 50, roll_dst = "xlast"), ctus("2010-03-14 03:52:03"))
expect_equal(time_add(x, hours = 1, minutes = 50, roll_dst = "xfirst"), ctus("2010-03-14 01:52:03"))
expect_equal(time_add(x, hours = 1, minutes = 50, roll_dst = "boundary"), ctus("2010-03-14 03:00:00"))
expect_equal(time_add(x, hours = 1, minutes = 50, roll_dst = "NA"), NAam)
expect_equal(time_add(y, hours = -22, minutes = -50, roll_dst = "xfirst"), ctus("2010-03-14 03:12:03"))
expect_equal(time_add(y, hours = -22, minutes = -50, roll_dst = "xlast"), ctus("2010-03-14 01:12:03"))
expect_equal(time_add(y, hours = -23, minutes = 10, roll_dst = "xfirst"), ctus("2010-03-14 03:12:03"))
expect_equal(time_add(y, hours = -23, minutes = 10, roll_dst = "xlast"), ctus("2010-03-14 01:12:03"))
## negative period even though minutes is crossing the boundary from below
expect_equal(time_add(x, hours = -1, minutes = 170, roll_dst = "xfirst"), ctus("2010-03-14 03:52:03"))
expect_equal(time_add(x, hours = -1, minutes = 170, roll_dst = "xlast"), ctus("2010-03-14 01:52:03"))
expect_equal(time_subtract(y, hours = 22, minutes = 50, roll_dst = "post"), ctus("2010-03-14 03:12:03"))
expect_equal(time_subtract(y, hours = 22, minutes = 50, roll_dst = "pre"), ctus("2010-03-14 01:12:03"))
expect_equal(time_subtract(y, hours = 22, minutes = 50, roll_dst = "xfirst"), ctus("2010-03-14 03:12:03"))
expect_equal(time_subtract(y, hours = 22, minutes = 50, roll_dst = "xlast"), ctus("2010-03-14 01:12:03"))
expect_equal(time_subtract(y, hours = 22, minutes = 50, roll_dst = "boundary"), ctus("2010-03-14 03:00:00"))
expect_equal(time_subtract(y, hours = 22, minutes = 50, roll_dst = "NA"), NAam)
expect_equal(time_subtract(y, days = 1), x)
tt <- ymd_hms(c("2014-11-02 00:15:00", "2014-11-02 02:15:00"), tz = "America/New_York")
expect_equal(time_add(tt, hours = c(1, -1), roll_dst = "pre"), tt + c(3600, -2*3600))
expect_equal(time_add(tt, hours = c(1, -1), roll_dst = "post"), tt + c(2*3600, -3600))
expect_equal(time_add(tt, hours = c(1, -1), roll_dst = "xfirst"), tt + c(3600, -3600))
expect_equal(time_add(tt, hours = c(1, -1), roll_dst = "xlast"), tt + c(2*3600, -2*3600))
tt <- ymd_hms(c("2021-04-04 01:15:00", "2021-04-04 03:15:00"), tz = "Pacific/Auckland")
expect_equal(time_add(tt, hours = c(1, -1), roll_dst = "pre"), tt + c(3600, -2*3600))
expect_equal(time_add(tt, hours = c(1, -1), roll_dst = "post"), tt + c(2*3600, -3600))
expect_equal(time_add(tt, hours = c(1, -1), roll_dst = "xfirst"), tt + c(3600, -3600))
expect_equal(time_add(tt, hours = c(1, -1), roll_dst = "xlast"), tt + c(2*3600, -2*3600))
})
test_that("addition works as expected for instants", {
x <- ctutc("2008-01-01 00:00:00")
y <- ltutc("2008-01-01 00:00:00")
z <- as.Date("2008-01-01")
expect_equal(time_add(x, years = 1), ctutc("2009-01-01 00:00:00"))
expect_equal(time_add(y, years = 1), ltutc("2009-01-01 00:00:00"))
expect_equal(time_add(z, years = 1), as.Date("2009-01-01"))
x <- ctus("2008-01-01 00:00:00")
y <- ctus("2008-01-01 00:00:00")
expect_equal(time_add(x, years = 1), ctus("2009-01-01 00:00:00"))
expect_equal(time_add(y, years = 1), ctus("2009-01-01 00:00:00"))
expect_equal(time_add(x, years = 1, months = 2, days = 3, hours = 4, minutes = 5, seconds = 6.6),
ctus("2009-03-04 04:05:06.6"))
expect_equal(time_add(y, years = 1), ctus("2009-01-01 00:00:00"))
x <- ctus("2008-01-01 00:00:00")
y <- ctus("2008-01-01 00:00:00")
expect_equal(time_subtract(x, years = 1), ctus("2007-01-01 00:00:00"))
expect_equal(time_subtract(y, years = 1), ctus("2007-01-01 00:00:00"))
expect_equal(time_subtract(x, years = 1, months = 2, days = 3, hours = 4, minutes = 5, seconds = 6.6),
ctus("2006-10-28 19:54:53.4"))
})
test_that("addition works with month gap", {
x <- ctutc("2008-01-31 01:02:03")
y <- ltutc("2008-01-31 01:02:03")
z <- as.Date("2008-01-31")
expect_equal(time_add(x, months = 1), ctutc("2008-02-29 01:02:03"))
expect_equal(time_add(y, months = 1), ltutc("2008-02-29 01:02:03"))
expect_equal(time_add(z, months = 1), as.Date("2008-02-29"))
expect_equal(time_add(x, years = 2, months = 1, roll_month = "preday"), ctutc("2010-02-28 01:02:03"))
expect_equal(time_add(y, years = 2, months = 1, roll_month = "preday"), ltutc("2010-02-28 01:02:03"))
expect_equal(time_add(z, years = 2, months = 1, roll_month = "preday"), as.Date("2010-02-28"))
expect_equal(time_add(x, years = 2, months = 1, roll_month = "postday"), ctutc("2010-03-01 01:02:03"))
expect_equal(time_add(y, years = 2, months = 1, roll_month = "postday"), ltutc("2010-03-01 01:02:03"))
expect_equal(time_add(z, years = 2, months = 1, roll_month = "postday"), as.Date("2010-03-01"))
expect_equal(time_add(x, years = 2, months = 1, roll_month = "boundary"), ctutc("2010-03-01 00:00:00"))
expect_equal(time_add(y, years = 2, months = 1, roll_month = "boundary"), ltutc("2010-03-01 00:00:00"))
expect_equal(time_add(z, years = 2, months = 1, roll_month = "boundary"), as.Date("2010-03-01"))
expect_equal(time_add(x, years = 2, months = 1, roll_month = "NA"), NAutc)
expect_equal(time_add(y, years = 2, months = 1, roll_month = "NA"), as.POSIXlt(NAutc))
expect_equal(time_add(z, years = 2, months = 1, roll_month = "NA"), as.Date(NA))
x <- ctutc("2008-03-31 01:02:03")
y <- ltutc("2008-03-31 01:02:03")
z <- as.Date("2008-03-31")
expect_equal(time_subtract(x, months = 1), ctutc("2008-02-29 01:02:03"))
expect_equal(time_subtract(y, months = 1), ltutc("2008-02-29 01:02:03"))
expect_equal(time_subtract(z, months = 1), as.Date("2008-02-29"))
expect_equal(time_subtract(x, years = 2, months = 1, roll_month = "preday"), ctutc("2006-02-28 01:02:03"))
expect_equal(time_subtract(y, years = 2, months = 1, roll_month = "preday"), ltutc("2006-02-28 01:02:03"))
expect_equal(time_subtract(z, years = 2, months = 1, roll_month = "preday"), as.Date("2006-02-28"))
expect_equal(time_subtract(x, years = 2, months = 1, roll_month = "postday"), ctutc("2006-03-01 01:02:03"))
expect_equal(time_subtract(y, years = 2, months = 1, roll_month = "postday"), ltutc("2006-03-01 01:02:03"))
expect_equal(time_subtract(z, years = 2, months = 1, roll_month = "postday"), as.Date("2006-03-01"))
expect_equal(time_subtract(x, years = 2, months = 1, roll_month = "boundary"), ctutc("2006-03-01 00:00:00"))
expect_equal(time_subtract(y, years = 2, months = 1, roll_month = "boundary"), ltutc("2006-03-01 00:00:00"))
expect_equal(time_subtract(z, years = 2, months = 1, roll_month = "boundary"), as.Date("2006-03-01"))
expect_equal(time_subtract(x, years = 2, months = 1, roll_month = "NA"), NAutc)
expect_equal(time_subtract(y, years = 2, months = 1, roll_month = "NA"), as.POSIXlt(NAutc))
expect_equal(time_subtract(z, years = 2, months = 1, roll_month = "NA"), as.Date(NA))
})
test_that("adding vectors works as expected for instants", {
x <- ctutc(c("2008-01-01 00:00:00", "2009-01-01 00:00:00"))
y <- ltutc(c("2008-01-01 00:00:00", "2009-01-01 00:00:00"))
z <- c(as.Date("2008-01-01"), as.Date("2008-01-10"))
expect_equal(time_add(x, years = 1), ctutc(c("2009-01-01 00:00:00", "2010-01-01 00:00:00")))
expect_equal(time_add(y, years = 1), ltutc(c("2009-01-01 00:00:00", "2010-01-01 00:00:00")))
expect_equal(time_add(z, years = 1), as.Date(c("2009-01-01", "2009-01-10")))
expect_equal(time_subtract(x, years = 1), ctutc(c("2007-01-01 00:00:00", "2008-01-01 00:00:00")))
expect_equal(time_subtract(y, years = 1), ltutc(c("2007-01-01 00:00:00", "2008-01-01 00:00:00")))
expect_equal(time_subtract(z, years = 1), as.Date(c("2007-01-01", "2007-01-10")))
x <- ctutc(c("2008-01-31 01:02:03", "2009-01-30 01:02:03"))
y <- ltutc(c("2008-01-31 01:02:03", "2009-01-30 01:02:03"))
z <- c(as.Date("2008-01-31"), as.Date("2008-01-30"))
expect_equal(time_add(x, years = 1, month = 1, roll_month = "preday"),
ctutc(c("2009-02-28 01:02:03", "2010-02-28 01:02:03")))
expect_equal(time_add(y, years = 1, month = 1, roll_month = "preday"),
ltutc(c("2009-02-28 01:02:03", "2010-02-28 01:02:03")))
expect_equal(time_add(z, years = 1, month = 1, roll_month = "preday"),
as.Date(c("2009-02-28", "2009-02-28")))
expect_equal(time_add(x, years = 1, month = 1, roll_month = "postday"),
ctutc(c("2009-03-01 01:02:03", "2010-03-01 01:02:03")))
expect_equal(time_add(y, years = 1, month = 1, roll_month = "postday"),
ltutc(c("2009-03-01 01:02:03", "2010-03-01 01:02:03")))
expect_equal(time_add(z, years = 1, month = 1, roll_month = "postday"),
as.Date(c("2009-03-01", "2009-03-01")))
expect_equal(time_add(x, years = 1, month = 1, roll_month = "boundary"),
ctutc(c("2009-03-01 00:00:00", "2010-03-01 00:00:00")))
expect_equal(time_add(y, years = 1, month = 1, roll_month = "boundary"),
ltutc(c("2009-03-01 00:00:00", "2010-03-01 00:00:00")))
expect_equal(time_add(z, years = 1, month = 1, roll_month = "boundary"),
as.Date(c("2009-03-01", "2009-03-01")))
expect_equal(time_add(x, years = 1, month = 1, roll_month = "skip"),
ctutc(c("2009-03-03 01:02:03", "2010-03-02 01:02:03")))
expect_equal(time_add(y, years = 1, month = 1, roll_month = "skip"),
ltutc(c("2009-03-03 01:02:03", "2010-03-02 01:02:03")))
expect_equal(time_add(z, years = 1, month = 1, roll_month = "skip"),
as.Date(c("2009-03-03", "2009-03-02")))
})
test_that("addition and subtraction work with repeated DST", {
am1 <- .POSIXct(1414904400, tz = "America/New_York")
am2 <- am1 + 3600*2
expect_equal(time_subtract(am2, hours = 1, roll_dst = "post"), am1 + 3600)
expect_equal(time_subtract(am2, hours = 1, roll_dst = "pre"), am1)
expect_equal(time_subtract(am2, hours = 1, roll_dst = c("pre", "post")), am1 + 3600)
expect_equal(time_subtract(am2, hours = 1, minutes = 1, roll_dst = "pre"), am1 - 60)
expect_equal(time_subtract(am2 + 60, hours = 1, roll_dst = "post"), am1 + 3660)
expect_equal(time_subtract(am2 + 60, hours = 1, minutes = 1, roll_dst = "post"), am1 + 3600)
expect_equal(time_subtract(am2 + 60, hours = 1, minutes = 2, roll_dst = "pre"), am1 - 60)
expect_equal(time_subtract(am2 + 60, hours = 1, minutes = 1, seconds = 1, roll_dst = "pre"), am1 - 1)
expect_equal(time_add(am1, minutes = 2, roll_dst = "pre"), am1 + 120)
expect_equal(time_add(am1, minutes = 60, roll_dst = "pre"), am2)
expect_equal(time_add(am1 + 60, minutes = 1, roll_dst = "pre"), am1 + 120)
expect_equal(time_add(am1 + 60, hours = 1, roll_dst = "pre"), am2 + 60)
expect_equal(time_add(am1 + 60, minutes = 1, roll_dst = "pre"), am1 + 120)
expect_equal(time_add(am1 + 60, minutes = 60, roll_dst = "pre"), am2 + 60)
expect_equal(time_add(am1 + 60, minutes = 120, roll_dst = "pre"), am2 + 3660)
})
test_that("addition works on 'strange' DST gaps", {
## Midnight doesn't exist. DST spring forward happens at 2020-03-29 00:00:00
## and they spring forward to hour 1
y <- as.POSIXct("2020-03-29 01:00:00", tz = "Asia/Beirut")
x <- as.POSIXct("2020-03-28 00:00:00", tz = "Asia/Beirut")
expect_equal(y, time_add(x, days = 1))
expect_equal(time_add(y, minutes = 5), time_add(x, hours = 24, minutes = 5))
expect_equal(time_add(y, minutes = 5), time_add(x, hours = 23, minutes = 65))
})
test_that("addition errors on empty unit vectors", {
y <- ymd_hms("2020-03-29 01:00:00", tz = "Asia/Beirut")
expect_error(time_add(y, minute = integer()), "Incompatible size of 'minute' vector")
expect_error(time_add(y, hour = 1, minute = integer()), "Incompatible size of 'minute' vector")
})
test_that("Subtracting months to March 1 produces correct results", {
## https://github.com/tidyverse/lubridate/issues/1037
time <- ymd("2022-04-01", tz = "America/New_York")
expect_equal(time_add(time, months = -1), ymd("2022-03-01", tz = "America/New_York"))
time <- ymd("2022-05-01", tz = "America/New_York")
expect_equal(time_add(time, months = -2), ymd("2022-03-01", tz = "America/New_York"))
time <- ymd_hms("2022-04-02 04:01:01", tz = "America/New_York")
expect_equal(time_add(time, months = -1, days = -1, hours = -4, minutes = -1, seconds = -1),
ymd("2022-03-01", tz = "America/New_York"))
})
test_that("addition works correctly for DST transitions", {
ref <- ymd("2017-10-01", tz = "Australia/Melbourne")
expect_equal(time_add(ref, hours = 1:3, roll_dst = c("NA", "pre")),
ref + c(1, NA, 2)*3600)
ref <- ymd_hms(rep(c("2022-10-30 00:00:00", "2022-03-27 00:00:00"), each = 3), tz = "Europe/Amsterdam")
expect_equal(time_add(ref, hours = rep(1:3, 2), roll_dst = c("NA", "pre")),
ref + c(1, 2, 4, 1, NA, 2)*3600)
expect_equal(time_add(ref, hours = rep(1:3, 2), minutes = 1:6, roll_dst = c("NA", "pre")),
ref + c(1, 2, 4, 1, NA, 2)*3600 + 1:6*60)
expect_equal(time_add(ref, hours = rep(1:3, 2), roll_dst = c("NA", "NA")),
ref + c(1, NA, 4, 1, NA, 2)*3600)
expect_equal(time_add(ref, hours = rep(1:3, 2), minutes = 1:6, roll_dst = c("NA", "NA")),
ref + c(1, NA, 4, 1, NA, 2)*3600 + 1:6*60)
expect_equal(time_add(ref, hours = rep(1:3, 2), roll_dst = c("pre", "NA")),
ref + c(1, NA, 4, 1, 1, 2)*3600)
expect_equal(time_add(ref, hours = rep(1:3, 2), minutes = 1:6, roll_dst = c("pre", "NA")),
ref + c(1, NA, 4, 1, 1, 2)*3600 + 1:6*60)
expect_equal(time_add(ref, hours = rep(1:3, 2), roll_dst = c("post", "NA")),
ref + c(1, NA, 4, 1, 2, 2)*3600)
expect_equal(time_add(ref, hours = rep(1:3, 2), minutes = 1:6, roll_dst = c("post", "NA")),
ref + c(1, NA, 4, 1, 2, 2)*3600 + 1:6*60)
})
test_that("tzone attributes of Dates is preserved", {
d <- ymd("2020-01-01")
tzone <- "America/New_York"
attr(d, "tzone") <- tzone
expect_is(time_add(d, month = 2), "Date")
expect_is(time_add(d, hour = 2), "POSIXct")
expect_identical(time_add(d, month = 1), structure(ymd("2020-02-01"), tzone = tzone))
expect_identical(time_add(d, hour = 1), ymd_hms("2020-01-01 01:00:00", tz = tzone))
})
timechange/tests/testthat/test-get.R 0000644 0001762 0000144 00000004553 14356564260 017261 0 ustar ligges users context("Time Get")
test_that("time_get handles different date-time types correctly", {
date <- as.Date("1970-01-01")
datetime_ct <- as.POSIXct("1970-01-01 01:02:03", tz = "America/New_York")
datetime_lt <- as.POSIXlt(datetime_ct)
expect_identical(time_get(date), data.frame(year = 1970L, month = 1L,
yday = 1L, mday = 1L, wday = 3L,
hour = 0L, minute = 0L, second = 0))
expect_identical(time_get(datetime_ct, c("month", "year", "second", "hour")),
data.frame(month = 1L, year = 1970L, second = 3, hour = 1L))
expect_identical(time_get(datetime_ct, c("month", "year", "second", "hour")),
time_get(datetime_lt, c("month", "year", "second", "hour")))
## duplicates allowed
expect_identical(time_get(datetime_ct, c("month", "year", "month", "year")),
data.frame(month = 1L, year = 1970L, month = 1L, year = 1970L))
## day / mday return the same value but are different columns
expect_identical(time_get(datetime_ct, c("day", "mday")),
data.frame(day = 1L, mday = 1L))
expect_identical(time_get(datetime_ct, c("day", "mday")),
time_get(datetime_ct, c("day", "mday")))
})
test_that("tzone attributes of Dates is preserved", {
d <- ymd("2020-01-01")
tzone <- "America/New_York"
attr(d, "tzone") <- tzone
time_get(d, "month")
expect_is(time_update(d, hour = 2), "POSIXct")
expect_identical(time_update(d, month = 2), structure(ymd("2020-02-01"), tzone = tzone))
expect_identical(time_update(d, hour = 1), ymd_hms("2020-01-01 01:00:00", tz = tzone))
})
## speed tests
## library(microbenchmark)
## x <- .POSIXct(runif(1e5, -17987443200, 32503680000)) # random times between 1400 and 3000
## microbenchmark(y = timechange::time_get(x, "year"),
## w = timechange::time_get(x, "wday"),
## s = timechange::time_get(x, "second"),
## yhs = timechange::time_get(x, c("year", "hour", "second")),
## yhs_C = timechange:::C_time_get(x, c("year", "hour", "second")),
## ry = timerip::rip_year(x),
## rw = timerip::rip_wday(x),
## rs = timerip::rip_second(x),
## rall = timerip::rip_info(x),
## py = as.POSIXlt(x)$year + 1900L,
## times = 10)
timechange/tests/testthat/test-update.R 0000644 0001762 0000144 00000070604 14356563265 017770 0 ustar ligges users context("Time Update")
test_that("Integer mode posixct input works", {
set.seed(100)
int <- seq(ymd("2020-08-13", tz = "UTC"), by = "hour", length.out = 50)
expect_true(storage.mode(int) == "integer")
dbl <- int
secs <- runif(length(int))
expect_equal(time_update(int, second = int), time_update(dbl, second = int))
expect_equal(time_update(int, second = as.integer(int)), time_update(dbl, second = as.integer(int)))
})
test_that("Non-finite date-times are handled correctly", {
expect_equal(time_update(.POSIXct(Inf), hour = 1), .POSIXct(Inf))
expect_equal(time_add(.POSIXct(-Inf), hour = 1), .POSIXct(-Inf))
expect_equal(time_add(.POSIXct(NA_real_), hour = 1), .POSIXct(NA_real_))
expect_equal(time_add(.POSIXct(NA_real_), day = 1), .POSIXct(NA_real_))
expect_equal(time_add(.Date(Inf), day = 1), .Date(Inf))
expect_equal(time_add(.Date(-Inf), day = 1), .Date(-Inf))
})
## most of the tests are borrowed from lubridate
test_that("update.Date returns a date object", {
date <- as.Date("05/05/2010", "%m/%d/%Y")
expect_that(time_update(date, yday = 1), is_a("Date"))
expect_that(time_update(date, mday = 1), is_a("Date"))
expect_that(time_update(date, wday = 1), is_a("Date"))
expect_that(time_update(date, month = 1), is_a("Date"))
expect_that(time_update(date, year = 2001), is_a("Date"))
})
test_that("update.Date returns a posix object if time is manipulated", {
date <- as.Date("05/05/2010", "%m/%d/%Y")
expect_that(time_update(date, second = 1), is_a("POSIXct"))
expect_that(time_update(date, minute = 1), is_a("POSIXct"))
expect_that(time_update(date, hour = 1), is_a("POSIXct"))
expect_that(time_update(date, tz = "UTC"), is_a("POSIXct"))
expect_equal(time_update(date, second = 1, minute = 1, hour = 1),
as.POSIXct("2010-05-05 01:01:01", tz = "UTC"))
})
test_that("update.Date performs simple operation as expected", {
date <- as.Date("05/05/2010", "%m/%d/%Y")
expect_equal(second(time_update(date, second = 1)), 1)
expect_equal(minute(time_update(date, minute = 1)), 1)
expect_equal(hour(time_update(date, hour = 1)), 1)
expect_equal(mday(time_update(date, mday = 1)), 1)
expect_equal(wday(time_update(date, mday = 1)), 6)
expect_equal(yday(time_update(date, mday = 1)), 121)
expect_equal(yday(time_update(date, yday = 1)), 1)
expect_equal(mday(time_update(date, yday = 1)), 1)
expect_equal(wday(time_update(date, yday = 1)), 5)
expect_equal(wday(time_update(date, wday = 1)), 1)
expect_equal(yday(time_update(date, wday = 1)), 123)
expect_equal(mday(time_update(date, wday = 1)), 3)
expect_equal(month(time_update(date, month = 1)), 1)
expect_equal(year(time_update(date, year = 2000)), 2000)
expect_equal(timechange:::tz(time_update(date, tz = "UTC")), "UTC")
})
test_that("update.POSIXlt returns a POSIXlt object", {
poslt <- as.POSIXlt("2010-02-03 13:45:59", tz = "GMT", format = "%Y-%m-%d %H:%M:%S")
expect_that(time_update(poslt, second = 1), is_a("POSIXlt"))
expect_that(time_update(poslt, minute = 1), is_a("POSIXlt"))
expect_that(time_update(poslt, hour = 1), is_a("POSIXlt"))
expect_that(time_update(poslt, yday = 1), is_a("POSIXlt"))
expect_that(time_update(poslt, mday = 1), is_a("POSIXlt"))
expect_that(time_update(poslt, wday = 1), is_a("POSIXlt"))
expect_that(time_update(poslt, month = 1), is_a("POSIXlt"))
expect_that(time_update(poslt, year = 2001), is_a("POSIXlt"))
expect_that(time_update(poslt, tz = "UTC"), is_a("POSIXlt"))
})
test_that("update.POSIXct returns a POSIXct object", {
posct <- as.POSIXct("2010-02-03 13:45:59",
tz = "GMT", format = "%Y-%m-%d %H:%M:%S")
expect_that(time_update(posct, second = 1), is_a("POSIXct"))
expect_that(time_update(posct, minute = 1), is_a("POSIXct"))
expect_that(time_update(posct, hour = 1), is_a("POSIXct"))
expect_that(time_update(posct, yday = 1), is_a("POSIXct"))
expect_that(time_update(posct, mday = 1), is_a("POSIXct"))
expect_that(time_update(posct, wday = 1), is_a("POSIXct"))
expect_that(time_update(posct, month = 1), is_a("POSIXct"))
expect_that(time_update(posct, year = 2001), is_a("POSIXct"))
expect_that(time_update(posct, tz = "UTC"), is_a("POSIXct"))
})
test_that("update.POSIXt performs simple operation as expected", {
poslt <- as.POSIXlt("2010-02-03 13:45:59", tz = "GMT", format = "%Y-%m-%d %H:%M:%S")
posct <- as.POSIXct("2010-02-03 13:45:59", tz = "GMT", format = "%Y-%m-%d %H:%M:%S")
expect_equal(second(time_update(poslt, second = 1)), 1)
expect_equal(minute(time_update(poslt, minute = 1)), 1)
expect_equal(hour(time_update(poslt, hour = 1)), 1)
expect_equal(mday(time_update(poslt, mday = 1)), 1)
expect_equal(wday(time_update(poslt, mday = 1)), 1)
expect_equal(yday(time_update(poslt, mday = 1)), 32)
expect_equal(yday(time_update(poslt, yday = 1)), 1)
expect_equal(mday(time_update(poslt, yday = 1)), 1)
expect_equal(wday(time_update(poslt, yday = 1)), 5)
expect_equal(wday(time_update(poslt, wday = 1)), 1)
expect_equal(yday(time_update(poslt, wday = 1)), 32)
expect_equal(mday(time_update(poslt, wday = 1)), 1)
expect_equal(month(time_update(poslt, month = 1)), 1)
expect_equal(year(time_update(poslt, year = 2000)), 2000)
expect_equal(second(time_update(posct, second = 1)), 1)
expect_equal(minute(time_update(posct, minute = 1)), 1)
expect_equal(hour(time_update(posct, hour = 1)), 1)
expect_equal(mday(time_update(posct, mday = 1)), 1)
expect_equal(wday(time_update(posct, mday = 1)), 1)
expect_equal(yday(time_update(posct, mday = 1)), 32)
expect_equal(yday(time_update(posct, yday = 1)), 1)
expect_equal(mday(time_update(posct, yday = 1)), 1)
expect_equal(wday(time_update(posct, yday = 1)), 5)
expect_equal(wday(time_update(posct, wday = 1), week_start = 1), 1)
expect_equal(yday(time_update(posct, wday = 1, week_start = 7)), 31)
expect_equal(mday(time_update(posct, wday = 1, week_start = 7)), 31)
expect_equal(month(time_update(posct, month = 1)), 1)
expect_equal(year(time_update(posct, year = 2000)), 2000)
expect_equal(timechange:::tz(time_update(poslt, tz = "UTC")), "UTC")
expect_equal(timechange:::tz(time_update(posct, tz = "UTC")), "UTC")
})
test_that("update works with fractional second", {
poslt <- ltutc("2010-02-03 13:45:59.234")
posct <- ltutc("2010-02-03 13:45:59.323")
expect_equal(time_update(poslt, year = 2002, minute = 3), ltutc("2002-02-03 13:03:59.234"))
expect_equal(time_update(poslt, mday = 1, minute = 1, hour = 3), ltutc("2010-02-01 03:01:59.234"))
expect_equal(time_update(poslt, year = 2002, minute = 3, second = 0.5), ltutc("2002-02-03 13:03:00.5"))
expect_equal(time_update(poslt, mday = 1, minute = 1, hour = 3, second = 0.5), ltutc("2010-02-01 03:01:00.5"))
})
## test_that("update works with non-existent dates", {
## ct <- as.POSIXct("2010-01-31 13:45:59.234", tz = "UTC")
## time_update(ct, month = 2)
## })
test_that("update.POSIXt works on wdays", {
date <- ymd("2017-05-07") ## sunday
ct <- as.POSIXct("2010-02-03 13:45:59", tz = "America/New_York", format = "%Y-%m-%d %H:%M:%S") ## Wednesday
expect_equal(wday(time_update(ct, wday = 1)), 1)
expect_equal(wday(time_update(ct, wday = 2)), 2)
expect_equal(wday(time_update(ct, wday = 5)), 5)
expect_equal(wday(time_update(ct, wday = 7)), 7)
expect_equal(wday(time_update(date, wday = 1)), 1)
expect_equal(wday(time_update(date, wday = 2)), 2)
expect_equal(wday(time_update(date, wday = 5)), 5)
expect_equal(wday(time_update(date, wday = 7)), 7)
ws <- 1
expect_equal(wday(time_update(ct, wday = 1, week_start = ws)), 1)
expect_equal(wday(time_update(ct, wday = 2, week_start = ws)), 2)
expect_equal(wday(time_update(ct, wday = 5, week_start = ws)), 5)
expect_equal(wday(time_update(ct, wday = 7, week_start = ws)), 7)
expect_equal(wday(time_update(date, wday = 1, week_start = ws)), 1)
expect_equal(wday(time_update(date, wday = 2, week_start = ws)), 2)
expect_equal(wday(time_update(date, wday = 5, week_start = ws)), 5)
expect_equal(wday(time_update(date, wday = 7, week_start = ws)), 7)
ws <- 1
expect_equal(wday(time_update(ct, wday = 1, week_start = ws), week_start = ws), 1)
expect_equal(wday(time_update(ct, wday = 2, week_start = ws), week_start = ws), 2)
expect_equal(wday(time_update(ct, wday = 5, week_start = ws), week_start = ws), 5)
expect_equal(wday(time_update(ct, wday = 7, week_start = ws), week_start = ws), 7)
expect_equal(wday(time_update(date, wday = 1, week_start = ws), week_start = ws), 1)
expect_equal(wday(time_update(date, wday = 2, week_start = ws), week_start = ws), 2)
expect_equal(wday(time_update(date, wday = 5, week_start = ws), week_start = ws), 5)
expect_equal(wday(time_update(date, wday = 7, week_start = ws), week_start = ws), 7)
ws <- 3
expect_equal(wday(time_update(ct, wday = 1, week_start = ws), week_start = ws), 1)
expect_equal(wday(time_update(ct, wday = 2, week_start = ws), week_start = ws), 2)
expect_equal(wday(time_update(ct, wday = 5, week_start = ws), week_start = ws), 5)
expect_equal(wday(time_update(ct, wday = 7, week_start = ws), week_start = ws), 7)
expect_equal(wday(time_update(date, wday = 1, week_start = ws), week_start = ws), 1)
expect_equal(wday(time_update(date, wday = 2, week_start = ws), week_start = ws), 2)
expect_equal(wday(time_update(date, wday = 5, week_start = ws), week_start = ws), 5)
expect_equal(wday(time_update(date, wday = 7, week_start = ws), week_start = ws), 7)
})
test_that("Updates on ydays works correctly with leap year", {
expect_equal(time_update(ymd("1915-02-03", tz = "UTC"), year = 2000, yday = 1),
ymd("2000-01-01", tz = "UTC"))
expect_equal(time_update(ymd("1915-02-03", tz = "UTC"), year = 2015, yday = 1),
ymd("2015-01-01", tz = "UTC"))
expect_equal(time_update(ymd("1915-02-03", tz = "UTC"), year = 2016, yday = 10),
ymd("2016-01-10", tz = "UTC"))
expect_equal(time_update(ymd("1915-02-03", tz = "America/New_York"), year = 2000, yday = 1),
ymd("2000-01-01", tz = "America/New_York"))
expect_equal(time_update(ymd("1915-02-03", tz = "America/New_York"), year = 2015, yday = 1),
ymd("2015-01-01", tz = "America/New_York"))
expect_equal(time_update(ymd("1915-02-03", tz = "America/New_York"), year = 2016, yday = 10),
ymd("2016-01-10", tz = "America/New_York"))
expect_equal(time_update(ymd(c("2016-02-29", "2016-03-01")), yday = 1),
ymd(c("2016-01-01", "2016-01-01")))
expect_equal(time_update(ymd(c("2016-02-29", "2016-03-01"), tz = "America/New_York"), yday = 1),
ymd(c("2016-01-01", "2016-01-01"), tz = "America/New_York"))
expect_equal(time_update(ymd_hms(c("2016-02-29 1:2:3", "2016-03-01 10:20:30")), yday = 1),
ymd_hms(c("2016-01-01 1:2:3", "2016-01-01 10:20:30")))
expect_equal(time_update(ymd_hms(c("2016-02-29 1:2:3", "2016-03-01 10:20:30"), tz = "America/New_York"), yday = 1),
ymd_hms(c("2016-01-01 1:2:3", "2016-01-01 10:20:30"), tz = "America/New_York"))
})
test_that("update performs roll overs correctly for Date objects", {
date <- ymd("2010-05-05")
expect_equal(time_update(date, second = 61), ymd_hms("2010-05-05 00:01:01"))
expect_equal(time_update(date, mday = 29, second = 61), ymd_hms("2010-05-29 00:01:01"))
date <- ymd("2001-02-20")
expect_equal(time_update(date, mday = 29, roll_month = "preday"), ymd("2001-02-28"))
expect_equal(time_update(date, mday = 29, roll_month = "postday"), ymd("2001-03-01"))
expect_equal(time_update(date, mday = 28, hour = 25, second = 2, roll_month = "postday"),
ymd_hms("2001-03-01 01:00:02"))
expect_equal(time_update(date, mday = 28, hour = 25, second = 2, roll_month = "preday"),
ymd_hms("2001-02-28 01:00:02"))
expect_equal(time_update(date, mday = 28, hour = 25, second = 2, roll_month = "boundary"),
ymd_hms("2001-03-01 00:00:00"))
expect_equal(time_update(date, hour = 25), ymd_hms("2001-02-21 01:00:00"))
expect_equal(time_update(date, hour = 240), ymd_hms("2001-02-28 00:00:00"))
expect_equal(time_update(date, hour = 241, roll_month = "postday"), ymd_hms("2001-03-01 01:00:00"))
expect_equal(time_update(date, hour = 241, roll_month = "boundary"), ymd_hms("2001-03-01 00:00:00"))
expect_equal(time_update(date, hour = 241, roll_month = "NA"), NA_POSIXct_)
expect_equal(time_update(date, hour = 241, exact = T), NA_POSIXct_)
date <- ymd("2010-05-05")
## setting yday is exactly what we want so roll_month has no effect
expect_equal(time_update(date, yday = 365, exact = T), ymd("2010-12-31"))
expect_equal(time_update(date, yday = 370, exact = T), NA_Date_)
expect_equal(time_update(date, yday = 370, roll_month = "postday"), ymd("2011-01-05"))
expect_equal(time_update(date, yday = 370, roll_month = "preday"), ymd("2011-01-05"))
expect_equal(time_update(date, yday = 370, roll_month = "boundary"), ymd("2011-01-05"))
expect_equal(time_update(date, month = 13, roll_month = "postday"), ymd("2011-01-05"))
expect_equal(time_update(date, month = 13, roll_month = "preday"), ymd("2011-01-05"))
expect_equal(time_update(date, month = 13, roll_month = "boundary"), ymd("2011-01-05"))
expect_equal(timechange:::tz(time_update(date, month = 13)), "UTC")
})
test_that("update performs roll overs correctly for POSIXlt objects", {
poslt <- ymd_hms("2010-05-05 00:00:00")
expect_equal(second(time_update(poslt, second = 61)), 1)
expect_equal(minute(time_update(poslt, second = 61)), 1)
expect_equal(minute(time_update(poslt, minute = 61)), 1)
expect_equal(hour(time_update(poslt, minute = 61)), 1)
expect_equal(hour(time_update(poslt, hour = 25)), 1)
expect_equal(mday(time_update(poslt, hour = 25)), 6)
expect_equal(yday(time_update(poslt, hour = 25)), 126)
expect_equal(wday(time_update(poslt, hour = 25)), 4)
expect_equal(mday(time_update(poslt, mday = 32)), 31)
expect_equal(mday(time_update(poslt, mday = 32, roll_month = "postday")), 1)
expect_equal(month(time_update(poslt, mday = 32)), 5)
expect_equal(wday(time_update(poslt, wday = 31)), 3)
expect_equal(month(time_update(poslt, wday = 31)), 6)
expect_equal(yday(time_update(poslt, yday = 366)), 1)
expect_equal(month(time_update(poslt, yday = 366)), 1)
expect_equal(month(time_update(poslt, month = 13)), 1)
expect_equal(year(time_update(poslt, month = 13)), 2011)
expect_equal(timechange:::tz(time_update(poslt, month = 13)), "UTC")
})
test_that("update performs roll overs correctly for POSIXct objects", {
posct <- as.POSIXct("2010-05-05 00:00:00", tz = "UTC", format = "%Y-%m-%d %H:%M:%S")
expect_equal(second(time_update(posct, second = 61)), 1)
expect_equal(minute(time_update(posct, second = 61)), 1)
expect_equal(minute(time_update(posct, minute = 61)), 1)
expect_equal(hour(time_update(posct, minute = 61)), 1)
expect_equal(hour(time_update(posct, hour = 25)), 1)
expect_equal(mday(time_update(posct, hour = 25)), 6)
expect_equal(yday(time_update(posct, hour = 25)), 126)
expect_equal(wday(time_update(posct, hour = 25)), 4)
expect_equal(mday(time_update(posct, mday = 32)), 31)
expect_equal(time_update(posct, mday = 32, exact = T), NA_POSIXct_)
expect_equal(month(time_update(posct, mday = 32)), 5)
expect_equal(wday(time_update(posct, wday = 31)), 3)
expect_equal(month(time_update(posct, wday = 31)), 6)
expect_equal(yday(time_update(posct, yday = 366)), 1)
expect_equal(month(time_update(posct, yday = 366)), 1)
expect_equal(month(time_update(posct, month = 13)), 1)
expect_equal(year(time_update(posct, month = 13)), 2011)
expect_equal(timechange:::tz(time_update(posct, month = 13)), "UTC")
})
test_that("update performs consecutive roll overs correctly for
Date objects regardless of order", {
expect_equal(time_update(ymd("2010-01-11"),
month = 13, mday = 32, hour = 25,
minute = 61, second = 61),
ymd_hms("2011-01-31 02:02:01"))
expect_equal(time_update(ymd("2010-01-11"),
month = 13, mday = 32, hour = 25,
minute = 61, second = 61,
roll_month = "boundary"),
ymd_hms("2011-02-01 00:00:00"))
expect_equal(time_update(ymd("2010-01-11"),
month = 13, mday = 32, hour = 25,
minute = 61, second = 61,
roll_month = "NA"),
NA_POSIXct_)
})
test_that("update performs consecutive roll overs correctly for POSIXlt objects", {
posl <- as.POSIXlt("2010-11-01 00:00:00",
tz = "GMT", format = "%Y-%m-%d %H:%M:%S")
poslt <- time_update(posl, month = 13, mday = 32, hour = 25,
minute = 61, second = 61,
roll_month = "skip")
expect_equal(second(poslt), 1)
expect_equal(minute(poslt), 2)
expect_equal(hour(poslt), 2)
expect_equal(mday(poslt), 2)
expect_equal(wday(poslt), 3)
expect_equal(yday(poslt), 33)
expect_equal(month(poslt), 2)
expect_equal(year(poslt), 2011)
expect_equal(timechange:::tz(poslt), "GMT")
poslt2 <- time_update(posl, second = 61, minute = 61, hour = 25,
mday = 32, month = 13, roll_month = "skip")
expect_equal(second(poslt2), 1)
expect_equal(minute(poslt2), 2)
expect_equal(hour(poslt2), 2)
expect_equal(mday(poslt2), 2)
expect_equal(wday(poslt2), 3)
expect_equal(yday(poslt2), 33)
expect_equal(month(poslt2), 2)
expect_equal(year(poslt2), 2011)
expect_equal(timechange:::tz(poslt2), "GMT")
})
test_that("update performs consecutive roll overs correctly for POSIXct objects", {
posc <- as.POSIXct("2010-11-01 00:00:00",
tz = "GMT", format = "%Y-%m-%d %H:%M:%S")
posct <- time_update(posc, month = 13, mday = 32, hour = 25,
minute = 61, second = 61, roll_month = "skip")
expect_equal(second(posct), 1)
expect_equal(minute(posct), 2)
expect_equal(hour(posct), 2)
expect_equal(mday(posct), 2)
expect_equal(wday(posct), 3)
expect_equal(yday(posct), 33)
expect_equal(month(posct), 2)
expect_equal(year(posct), 2011)
expect_equal(timechange:::tz(posct), "GMT")
posct2 <- time_update(posc, second = 61, minute = 61, hour = 25,
mday = 32, month = 13, roll_month = "skip")
expect_equal(second(posct2), 1)
expect_equal(minute(posct2), 2)
expect_equal(hour(posct2), 2)
expect_equal(mday(posct2), 2)
expect_equal(wday(posct2), 3)
expect_equal(yday(posct2), 33)
expect_equal(month(posct2), 2)
expect_equal(year(posct2), 2011)
expect_equal(timechange:::tz(posct2), "GMT")
})
test_that("update returns NA for date-times in the spring dst gap", {
poslt <- as.POSIXlt("2010-03-14 01:59:59", tz = "UTC", format = "%Y-%m-%d %H:%M:%S")
poslt <- time_force_tz(poslt, tz = "America/New_York")
expect_true(is.na(time_update(poslt, second = 65, roll_dst = "NA")))
expect_true(is.na(time_update(poslt, minute = 65, roll_dst = "NA")))
expect_true(is.na(time_update(poslt, hour = 2, roll_dst = "NA")))
poslt <- as.POSIXlt("2010-03-13 02:59:59", tz = "UTC", format = "%Y-%m-%d %H:%M:%S")
poslt <- time_force_tz(poslt, tz = "America/New_York")
expect_true(is.na(time_update(poslt, mday = 14, roll_dst = "NA")))
expect_true(is.na(time_update(poslt, wday = 7, roll_dst = "NA")))
expect_true(is.na(time_update(poslt, yday = 73, roll_dst = "NA")))
poslt <- as.POSIXlt("2010-02-14 02:59:59", tz = "UTC", format = "%Y-%m-%d %H:%M:%S")
poslt <- time_force_tz(poslt, tz = "America/New_York")
expect_true(is.na(time_update(poslt, month = 3, roll_dst = "NA")))
poslt <- as.POSIXlt("2009-03-14 02:59:59", tz = "UTC", format = "%Y-%m-%d %H:%M:%S")
poslt <- time_force_tz(poslt, tz = "America/New_York")
expect_true(is.na(time_update(poslt, year = 2010, roll_dst = "NA")))
poslt <- as.POSIXlt("2010-03-14 02:59:59", tz = "UTC", format = "%Y-%m-%d %H:%M:%S")
expect_true(is.na(time_update(poslt, tz = "America/New_York", roll_dst = "NA")))
})
test_that("time_update roll_dst specs work correctly", {
## DST repeat 2022-10-29 02:00:00 CEST -- 2022-10-29 03:00:00 CET"
repref <- ymd_hms("2022-10-30 01:00:00", tz = "Europe/Amsterdam")
rep <- ymd_hms("2022-10-29 00:01:02", tz = "Europe/Amsterdam")
expect_equal(time_update(rep, mday = 30, hour = 2, minute = 3, roll_dst = "pre"),
repref + 3600 + 3*60 + 2)
expect_equal(time_update(rep, mday = 30, hour = 2, minute = 3, roll_dst = "boundary"),
repref + 2*3600)
expect_equal(time_update(rep, mday = 30, hour = 2, minute = 3, roll_dst = "post"),
repref + 2*3600 + 3*60 + 2)
expect_equal(time_update(rep, mday = 30, hour = 2, second = 3.35, roll_dst = "pre"),
repref + 3600 + 60 + 3.35)
expect_equal(time_update(rep, mday = 30, hour = 2, second = 3.35, roll_dst = "boundary"),
repref + 2*3600)
expect_equal(time_update(rep, mday = 30, hour = 2, second = 3.35, roll_dst = "post"),
repref + 2*3600 + 60 + 3.35)
## DST gap 2022-03-27 01:00:00 CET -- 2022-03-27 02:00:00 CEST
gapref <- ymd_hms("2022-03-27 00:00:00", tz = "Europe/Amsterdam")
gap <- ymd_hms("2022-03-26 00:00:00", tz = "Europe/Amsterdam")
### FIXME: finish these tests
})
test_that("update with roll_dst = 'boundary' works in dst gap", {
poslt <- ltus("2010-03-14 01:59:59")
boundary <- ltus("2010-03-14 03:00:00")
expect_equal(time_update(poslt, second = 65, roll_dst = "boundary"), boundary)
expect_equal(time_update(poslt, minute = 65, roll_dst = "boundary"), boundary)
expect_equal(time_update(poslt, hour = 2, roll_dst = "boundary"), boundary)
poslt <- ltus("2010-03-13 02:59:59")
expect_equal(time_update(poslt, mday = 14, roll_dst = "boundary"), boundary)
expect_equal(time_update(poslt, wday = 7, roll_dst = "boundary"), boundary)
expect_equal(time_update(poslt, yday = 73, roll_dst = "boundary"), boundary)
poslt <- ltus("2010-02-14 02:59:59")
expect_equal(time_update(poslt, month = 3, roll_dst = "boundary"), boundary)
poslt <- ltus("2009-03-14 02:59:59")
expect_equal(time_update(poslt, year = 2010, roll_dst = "boundary"), boundary)
poslt <- ltutc("2010-03-14 02:59:59")
expect_equal(time_update(poslt, tz = "America/New_York", roll_dst = "boundary"), boundary)
})
test_that("update with roll_dst = 'next' works in dst gap", {
poslt <- ltus("2010-03-14 01:59:59")
boundary <- ltus("2010-03-14 03:00:00")
expect_equal(time_update(poslt, second = 65, roll_dst = "post"), boundary + 5)
expect_equal(time_update(poslt, minute = 65, roll_dst = "post"), boundary + 5 * 60 + 59)
expect_equal(time_update(poslt, hour = 2, roll_dst = "post"), boundary + 59*60 + 59)
poslt <- ltus("2010-03-13 02:59:59")
expect_equal(time_update(poslt, mday = 14, roll_dst = "post"), boundary + 59*60 + 59)
expect_equal(time_update(poslt, wday = 7, roll_dst = "post"), boundary + 59*60 + 59)
expect_equal(time_update(poslt, yday = 73, roll_dst = "post"), boundary + 59*60 + 59)
poslt <- ltus("2010-02-14 02:59:59")
expect_equal(time_update(poslt, month = 3, roll_dst = "post"), boundary + 59*60 + 59)
poslt <- ltus("2009-03-14 02:59:59")
expect_equal(time_update(poslt, year = 2010, roll_dst = "post"), boundary + 59*60 + 59)
poslt <- ltutc("2010-03-14 02:59:59")
expect_equal(time_update(poslt, tz = "America/New_York", roll_dst = "post"), boundary + 59*60 + 59)
})
test_that("update with roll_dst = 'prev' works in dst gap", {
poslt <- ltus("2010-03-14 01:59:59")
boundary <- ltus("2010-03-14 01:00:00")
expect_equal(time_update(poslt, second = 65, roll_dst = "pre"), boundary + 5)
expect_equal(time_update(poslt, minute = 65, roll_dst = "pre"), boundary + 5 * 60 + 59)
expect_equal(time_update(poslt, hour = 2, roll_dst = "pre"), boundary + 59*60 + 59)
poslt <- ltus("2010-03-13 02:59:59")
expect_equal(time_update(poslt, mday = 14, roll_dst = "pre"), boundary + 59*60 + 59)
expect_equal(time_update(poslt, wday = 7, roll_dst = "pre"), boundary + 59*60 + 59)
expect_equal(time_update(poslt, yday = 73, roll_dst = "pre"), boundary + 59*60 + 59)
poslt <- ltus("2010-02-14 02:59:59")
expect_equal(time_update(poslt, month = 3, roll_dst = "pre"), boundary + 59*60 + 59)
poslt <- ltus("2009-03-14 02:59:59")
expect_equal(time_update(poslt, year = 2010, roll_dst = "pre"), boundary + 59*60 + 59)
poslt <- ltutc("2010-03-14 02:59:59")
expect_equal(time_update(poslt, tz = "America/New_York", roll_dst = "pre"), boundary + 59*60 + 59)
})
test_that("update handles vectors of dates", {
poslt <- as.POSIXlt(c("2010-02-14 01:59:59", "2010-02-15 01:59:59", "2010-02-16 01:59:59"),
tz = "UTC", format = "%Y-%m-%d %H:%M:%S")
posct <- as.POSIXct(poslt)
date <- as.Date(poslt)
expect_equal(second(time_update(poslt, second = 1)), c(1, 1, 1))
expect_equal(second(time_update(posct, second = 1)), c(1, 1, 1))
expect_equal(day(time_update(date, mday = 1)), c(1, 1, 1))
})
test_that("update handles vectors of dates and conformable vector of inputs", {
poslt <- as.POSIXlt(c("2010-02-14 01:59:59", "2010-02-15 01:59:59", "2010-02-16
01:59:59"), tz = "UTC", format = "%Y-%m-%d %H:%M:%S")
posct <- as.POSIXct(poslt)
date <- as.Date(poslt)
expect_equal(second(time_update(poslt, second = c(1, 2, 3))), c(1, 2, 3))
expect_equal(second(time_update(posct, second = c(1, 2, 3))), c(1, 2, 3))
expect_equal(day(time_update(date, mday = c(1, 2, 3))), c(1, 2, 3))
})
test_that("update handles single vector of inputs", {
poslt <- as.POSIXlt("2010-03-14 01:59:59", tz = "UTC", format = "%Y-%m-%d %H:%M:%S")
posct <- as.POSIXct(poslt)
date <- as.Date(poslt)
expect_equal(second(time_update(poslt, second = c(1, 2, 3))), c(1, 2, 3))
expect_equal(second(time_update(posct, second = c(1, 2, 3))), c(1, 2, 3))
expect_equal(day(time_update(date, mday = c(1, 2, 3))), c(1, 2, 3))
})
test_that("update follows vctrs replication rules", {
poslt <- as.POSIXlt("2010-03-10 01:59:59", tz = "UTC", format = "%Y-%m-%d %H:%M:%S")
posct <- as.POSIXct(poslt)
date <- as.Date(poslt)
expect_error(time_update(poslt, second = c(1, 2), minute = c(1, 2, 3, 4)), "Incompatible")
expect_error(time_update(posct, second = c(1, 2), minute = c(1, 2, 3, 4)), "Incompatible")
expect_error(time_update(date, mday = c(1, 2), month = c(1, 2, 3, 4)), "Incompatible")
})
test_that("update.POSIXct returns input of length zero when given input of length zero", {
x <- structure(vector(mode = "numeric"), class = c("POSIXct", "POSIXt"))
expect_equal(time_update(x, mday = 1), x)
})
test_that("update.POSIXlt returns input of length zero when given input of length zero", {
x <- as.POSIXlt(structure(vector(mode = "numeric"), class = c("POSIXct", "POSIXt")))
expect_equal(time_update(x, mday = 1), x)
})
test_that("update correctly works for yday", {
x <- ymd("2020-01-01")
expect_equal(time_update(x, yday = 45), ymd("2020-02-14"))
## FIXME: with NA rolling check the boundary
## expect_equal(time_update(x, yday = 1000, roll_month = "NA"), NA_Date_)
## expect_equal(time_update(x, wday = 10, roll_month = "NA"), NA_Date_)
x <- ymd_hms("2020-01-01 01:02:03")
expect_equal(time_update(x, yday = 45), ymd_hms("2020-02-14 01:02:03"))
## FIXME: with NA rolling check the boundary
## expect_equal(time_update(x, yday = 1000, roll_month = "NA"), NA_POSIXct_)
## expect_equal(time_update(x, wday = 10, roll_month = "NA"), NA_POSIXct_)
})
test_that("update errors on empty unit vectors", {
y <- ymd_hms("2020-03-29 01:00:00", tz = "Asia/Beirut")
expect_error(time_update(y, minute = integer()), "Incompatible size of 'minute' vector")
expect_error(time_update(y, hour = 1, minute = integer()), "Incompatible size of 'minute' vector")
})
test_that("updating Dates with 0 hms units produces POSIXct.", {
expect_equal(time_update(ymd("2020-03-03"), hour = 0, second = 0), ymd("2020-03-03", tz = "UTC"))
expect_equal(time_update(ymd("2020-03-03"), hour = NULL), ymd("2020-03-03"))
})
test_that("NAs propagate in update", {
expect_equal(time_update(ymd("2020-03-03"), year = NA), NA_Date_)
expect_equal(time_update(ymd("2020-03-03"), year = NA, second = 1), NA_POSIXct_)
expect_equal(time_update(ymd("2020-03-03"), second = NA), NA_POSIXct_)
expect_equal(time_update(ymd("2020-03-03"), year = c(2021, NA), second = 1),
c(ymd_hms("2021-03-03 00:00:01 UTC", NA)))
expect_equal(time_update(ymd("2020-03-03"), year = c(2021, NA), second = c(NA, 10)),
c(NA_POSIXct_, NA_POSIXct_))
})
test_that("tzone attributes of Dates is preserved", {
d <- ymd("2020-01-01")
tzone <- "America/New_York"
attr(d, "tzone") <- tzone
expect_is(time_update(d, month = 2), "Date")
expect_is(time_update(d, hour = 2), "POSIXct")
expect_identical(time_update(d, month = 2), structure(ymd("2020-02-01"), tzone = tzone))
expect_identical(time_update(d, hour = 1), ymd_hms("2020-01-01 01:00:00", tz = tzone))
})
timechange/tests/testthat/test-time-zones.R 0000644 0001762 0000144 00000025313 14327530005 020555 0 ustar ligges users context("Time Zones")
test_that("time_at_tz works as expected", {
x <- ctus("2008-08-03 10:01:59")
y <- as.POSIXlt(x)
expect_equal(time_at_tz(x, "UTC"),
as.POSIXct(format(as.POSIXct(x), tz = "UTC"), tz = "UTC"))
expect_equal(time_at_tz(y, "UTC"),
as.POSIXlt(format(as.POSIXct(x), tz = "UTC"), tz = "UTC"))
})
test_that("time_at_tz handles vectors", {
x <- as.POSIXct(c("2008-08-03 13:01:59", "2009-08-03 10:01:59"), tz = "America/New_York")
y <- as.POSIXlt(x)
expect_equal(time_at_tz(x, "UTC"),
as.POSIXct(format(as.POSIXct(x), tz = "UTC"), tz = "UTC"))
expect_equal(time_at_tz(y, "UTC"),
as.POSIXlt(format(as.POSIXct(x), tz = "UTC"), tz = "UTC"))
})
test_that("time_at_tz handles various date-time classes", {
x <- as.POSIXct("2008-08-03 13:01:59", tz = "America/New_York")
expect_equal(time_at_tz(as.POSIXlt(x), "UTC"),
as.POSIXlt(format(as.POSIXct(x), tz = "UTC"), tz = "UTC"))
})
test_that("time_at_tz handles data.frames", {
x <- as.POSIXct("2008-08-03 10:01:59", tz = "America/New_York")
df <- data.frame(x = x, y = as.POSIXlt(x), z = "blabla")
df <- time_at_tz(df, "UTC")
x_out <- as.POSIXct(format(as.POSIXct(x), tz = "UTC"), tz = "UTC")
expect_equal(df$x, x_out)
expect_equal(df$y, x_out)
})
test_that("time_force_tz works as expected", {
x <- ctutc(c("2009-08-07 00:00:01", "2009-08-07 00:00:01"))
expect_equal(time_force_tz(x, tz = c("America/New_York", "Europe/Amsterdam"), tzout = "UTC"),
ymd_hms("2009-08-07 04:00:01 UTC", "2009-08-06 22:00:01 UTC"))
expect_equal(time_force_tz(x, tz = c("America/New_York", "Europe/Amsterdam"), tzout = "America/New_York"),
ymd_hms("2009-08-07 00:00:01 EDT", "2009-08-06 18:00:01 EDT", tz = "America/New_York"))
## recycling
expect_equal(time_force_tz(x, tz = "America/New_York", tzout = "UTC"),
ymd_hms("2009-08-07 04:00:01 UTC", "2009-08-07 04:00:01 UTC"))
x <- ymd_hms("2009-08-07 00:00:01")
expect_equal(time_force_tz(x, tz = c("America/New_York", "Europe/Amsterdam"), tzout = "UTC"),
ymd_hms("2009-08-07 04:00:01 UTC", "2009-08-06 22:00:01 UTC"))
expect_equal(time_force_tz(x, tz = c("America/New_York", "Europe/Amsterdam"), tzout = "America/New_York"),
ymd_hms("2009-08-07 00:00:01 EDT", "2009-08-06 18:00:01 EDT", tz = "America/New_York"))
})
test_that("time_force_tzs is robusts against overflow", {
s <- c("2038-01-19 03:14:06", "2038-01-19 03:14:07", "2038-01-19 03:14:08",
"2038-01-19 03:14:09", "2038-01-19 03:14:10")
expect_equal(time_force_tz(ymd_hms(s), tz = "America/New_York"),
ymd_hms(s, tz = "America/New_York"))
expect_equal(time_force_tz(ymd_hms(s, tz = "Europe/Helsinki"), tz = "America/New_York"),
ymd_hms(s, tz = "America/New_York"))
})
test_that("time_clock_at_tz works as expected", {
x <- ymd_hms(c("2009-08-07 01:02:03", "2009-08-07 10:20:30"))
expect_equal(time_clock_at_tz(x, units = "secs"),
as.difftime(c(3723, 37230), units = "secs"))
expect_equal(time_clock_at_tz(x, units = "hours"),
as.difftime(c(3723, 37230)/3600, units = "hours"))
expect_equal(time_clock_at_tz(x, "Europe/Amsterdam"),
time_clock_at_tz(time_at_tz(x, "Europe/Amsterdam")))
x <- ymd_hms("2009-08-07 01:02:03")
expect_equal(time_clock_at_tz(x, c("America/New_York", "Europe/Amsterdam", "Asia/Shanghai")),
c(time_clock_at_tz(time_at_tz(x, "America/New_York")),
time_clock_at_tz(time_at_tz(x, "Europe/Amsterdam")),
time_clock_at_tz(time_at_tz(x, "Asia/Shanghai"))))
})
test_that("time_at_tz throws warning on unrecognized time zones", {
expect_warning(time_at_tz(now(), "blablabla"))
expect_silent(time_at_tz(now(), "UTC"))
expect_silent(time_at_tz(now(), ""))
expect_silent(time_at_tz(now(), "America/New_York"))
})
test_that("time_force_tz works as expected", {
x <- as.POSIXct("2008-08-03 10:01:59", tz = "America/New_York")
expect_equal(time_force_tz(x, "UTC"),
as.POSIXct(format(as.POSIXct(x)), tz = "UTC"))
})
test_that("time_force_tz handles vectors", {
x <- as.POSIXct(c("2008-08-03 13:01:59", "2009-08-03 10:01:59"), tz = "America/New_York")
expect_equal(time_force_tz(x, "UTC"),
as.POSIXct(format(as.POSIXct(x)), tz = "UTC"))
})
test_that("time_force_tz handles various date-time classes", {
x <- as.POSIXct("2008-12-03 13:01:59", tz = "America/New_York")
expect_equal(time_force_tz(as.POSIXlt(x), "UTC"),
as.POSIXlt(format(x), tz = "UTC"))
x <- as.Date("2008-12-03")
expect_equal(time_force_tz(x, "UTC"),
as.POSIXlt(format(x), tz = "UTC"))
expect_equal(time_force_tz(x, "America/New_York"),
as.POSIXlt(format(x), tz = "America/New_York"))
})
test_that("time_force_tz handles data.frames", {
x <- as.POSIXct("2008-08-03 10:01:59", tz = "America/New_York")
x_out <- as.POSIXct(format(as.POSIXct(x)), tz = "UTC")
df <- data.frame(x = x, y = as.POSIXlt(x), z = "blabla")
df <- time_force_tz(df, "UTC")
expect_equal(df$x, x_out)
expect_equal(df$y, x_out)
})
test_that("time_force_tz doesn't return NA just because new time zone uses DST", {
poslt <- as.POSIXlt("2009-03-14 02:59:59", tz = "UTC", format
= "%Y-%m-%d %H:%M:%S")
poslt2 <- time_force_tz(poslt, tz = "America/New_York")
expect_true(!is.na(poslt2))
})
## test_that("olson_time_zones returns a non-trivial character vector", {
## tz_olson <- olson_time_zones()
## expect_true(length(tz_olson) > 0)
## expect_is(tz_olson, "character")
## })
test_that("DST times are forced_tz correctly", {
## DST repeated "2014-11-02 01:35:00 CST"
ref <- structure(1414913700, tzone = "America/Chicago", class = c("POSIXct", "POSIXt"))
expect_equal(C_force_tz(ymd_hms("2014-11-02 01:35:00"), tz = "America/Chicago", "post"),
ref)
expect_equal(C_force_tz(ymd_hms("2014-11-02 01:35:00"), tz = "America/Chicago", "pre"),
ref - 3600)
expect_equal(C_force_tz(ymd_hms("2014-11-02 01:35:00"), tz = "America/Chicago", "boundary"),
ref - 35*60)
ref <- ymd_hms("2014-11-02 00:00:00", tz = "America/Los_Angeles")
tt <- ref + 95*60
expect_equal(
time_force_tz(time_force_tz(tt, "America/Chicago", roll_dst = "post"),
"America/Los_Angeles", roll_dst = "post"),
ref + 95*60)
expect_equal(
time_force_tz(ymd_hms("2014-11-02 01:35:00"), "America/Los_Angeles", roll_dst = "post"),
ref + 155*60)
expect_equal(
time_force_tz(ymd_hms("2014-11-02 01:35:00"), "America/Los_Angeles", roll_dst = "pre"),
ref + 95*60)
})
test_that("If original post/pre is known, ignore roll_DST", {
tt <- ymd_hms("2014-11-02 01:35:00")
chpst <- time_force_tz(tt, "America/Chicago", roll_dst = "post")
chpdt <- time_force_tz(tt, "America/Chicago", roll_dst = "pre")
lapst <- time_force_tz(tt, "America/Los_Angeles", roll_dst = "post")
lapdt <- time_force_tz(tt, "America/Los_Angeles", roll_dst = "pre")
expect_equal(lapst, time_force_tz(chpst, "America/Los_Angeles", roll_dst = "pre"))
expect_equal(lapst, time_force_tz(chpst, "America/Los_Angeles", roll_dst = "post"))
expect_equal(lapst, time_force_tz(chpst, "America/Los_Angeles", roll_dst = "boundary"))
expect_equal(lapdt, time_force_tz(chpdt, "America/Los_Angeles", roll_dst = "pre"))
expect_equal(lapdt, time_force_tz(chpdt, "America/Los_Angeles", roll_dst = "post"))
expect_equal(lapdt, time_force_tz(chpdt, "America/Los_Angeles", roll_dst = "boundary"))
## expect_equal(lapst, time_force_tzs(chpst, "America/Los_Angeles", roll_dst = "pre"))
## expect_equal(lapst, time_force_tzs(chpst, "America/Los_Angeles", roll_dst = "post"))
## expect_equal(lapst, time_force_tzs(chpst, "America/Los_Angeles", roll_dst = "boundary"))
## expect_equal(lapst, time_force_tzs(chpst, "America/Los_Angeles", roll_dst = "pre"))
## expect_equal(lapst, time_force_tzs(chpst, "America/Los_Angeles", roll_dst = "post"))
## expect_equal(lapst, time_force_tzs(chpst, "America/Los_Angeles", roll_dst = "boundary"))
expect_equal(time_at_tz(c(tt, chpdt, lapdt), "America/Chicago"),
time_force_tz(tt, #c(tt, chpdt, lapdt),
c("UTC", "America/Chicago", "America/Los_Angeles"),
tzout = "America/Chicago",
roll_dst = "pre"))
expect_equal(time_at_tz(c(tt, chpst, lapst), "America/Chicago"),
time_force_tz(tt, #c(tt, chpdt, lapdt),
c("UTC", "America/Chicago", "America/Los_Angeles"),
tzout = "America/Chicago",
roll_dst = "post"))
expect_equal(time_at_tz(c(tt, chpdt, lapdt), "America/Chicago"),
time_force_tz(chpdt,
c("UTC", "America/Chicago", "America/Los_Angeles"),
tzout = "America/Chicago",
roll_dst = "pre"))
expect_equal(time_at_tz(c(tt, chpdt, lapdt), "America/Chicago"),
time_force_tz(chpdt,
c("UTC", "America/Chicago", "America/Los_Angeles"),
tzout = "America/Chicago",
roll_dst = "post"))
expect_equal(time_at_tz(c(tt, chpst, lapst), "America/Chicago"),
time_force_tz(chpst,
c("UTC", "America/Chicago", "America/Los_Angeles"),
tzout = "America/Chicago",
roll_dst = "post"))
expect_equal(time_at_tz(c(tt, chpst + 100, chpst + 3600), "America/Chicago"),
time_force_tz(time_at_tz(c(chpst, chpst + 100, chpdt), "America/Chicago"),
c("UTC", "America/Chicago", "America/Los_Angeles"),
tzout = "America/Chicago",
roll_dst = "post"))
})
test_that("force_tz works within repeated DST", {
## "2014-11-02 00:00:00 CST"
ch <- ymd_hms("2014-11-02 00:00:00", tz = "America/Chicago")
eu <- ymd_hms("2014-11-02 00:00:00", tz = "Europe/Berlin")
expect_equal(
time_force_tz(eu + (95*60), "America/Chicago", roll_dst = "post"),
ch + (155*60))
expect_equal(
time_force_tz(eu + (95*60), "America/Chicago", roll_dst = "pre"),
ch + (95*60))
expect_equal(
time_force_tz(eu + (95*60), "America/Chicago", roll_dst = "boundary"),
ch + (120*60))
expect_equal(
time_force_tz(ch + (95*60), "Europe/Berlin", roll_dst = "post"),
eu + (95*60))
expect_equal(
time_force_tz(ch + (95*60), "Europe/Berlin", roll_dst = "pre"),
eu + (95*60))
expect_equal(
time_force_tz(ch + (95*60), "Europe/Berlin", roll_dst = "boundary"),
eu + (95*60))
expect_equal(
time_force_tz(ch + (155*60), "Europe/Berlin", roll_dst = "post"),
eu + (95*60))
})
timechange/tests/testthat/test-round.R 0000644 0001762 0000144 00000130415 14413263324 017615 0 ustar ligges users context("Rounding")
test_that("time_floor works for each time element", {
x <- as.POSIXct("2009-08-03 12:01:59.84", tz = "UTC")
expect_identical(time_floor(x, "second"), as.POSIXct("2009-08-03 12:01:59", tz = "UTC"))
expect_identical(time_floor(x, "minute"), as.POSIXct("2009-08-03 12:01:00", tz = "UTC"))
expect_identical(time_floor(x, "hour"), as.POSIXct("2009-08-03 12:00:00", tz = "UTC"))
expect_identical(time_floor(x, "day"), as.POSIXct("2009-08-03 00:00:00", tz = "UTC"))
expect_identical(time_floor(x, "week"), as.POSIXct("2009-08-03 00:00:00", tz = "UTC"))
expect_identical(time_floor(x, "month"), as.POSIXct("2009-08-01 00:00:00", tz = "UTC"))
expect_identical(time_floor(x, "bimonth"), as.POSIXct("2009-07-01 00:00:00", tz = "UTC"))
expect_identical(time_floor(x, "quarter"), as.POSIXct("2009-07-01 00:00:00", tz = "UTC"))
expect_identical(time_floor(x, "halfyear"), as.POSIXct("2009-07-01 00:00:00", tz = "UTC"))
expect_identical(time_floor(x, "year"), as.POSIXct("2009-01-01 00:00:00", tz = "UTC"))
})
test_that("time_floor and time_round throw on invalid multi-unit spec", {
x <- ymd_hms("2009-08-03 12:01:57.11")
expect_silent(time_floor(x, "120 asec"))
expect_error(time_ceiling(x, "120 sec"))
expect_error(time_floor(x, "120 min"))
expect_error(time_floor(x, "25 h"))
expect_error(time_floor(x, "32 days"))
expect_error(time_ceiling(x, "120 min"))
expect_error(time_ceiling(x, "25 h"))
expect_error(time_ceiling(x, "32 days"))
})
test_that("time_floor works for multi-units", {
x <- cteu("2009-08-03 12:01:59.23")
expect_identical(time_floor(x, "2 secs"), cteu("2009-08-03 12:01:58"))
expect_identical(time_floor(x, "2s"), cteu("2009-08-03 12:01:58"))
expect_identical(time_floor(x, "2 mins"), cteu("2009-08-03 12:00:00"))
expect_identical(time_floor(x, "2M"), cteu("2009-08-03 12:00:00"))
expect_identical(time_floor(x, "2 hours"), cteu("2009-08-03 12:00:00"))
expect_identical(time_floor(x, "2h"), cteu("2009-08-03 12:00:00"))
expect_identical(time_floor(x, "2 days"), cteu("2009-08-03 00:00:00"))
expect_identical(time_floor(x, "3 days"), cteu("2009-08-01 00:00:00"))
expect_identical(time_floor(x, "10 days"), cteu("2009-08-01 00:00:00"))
expect_identical(time_floor(x, "10d"), cteu("2009-08-01 00:00:00"))
expect_identical(time_floor(x, "2 month"), cteu("2009-07-01 00:00:00"))
expect_identical(time_floor(x, "2m"), cteu("2009-07-01 00:00:00"))
expect_identical(time_floor(x, "2 bimonth"), time_floor(x, "4 months"))
expect_identical(time_floor(x, "2 quarter"), time_floor(x, "6 months"))
expect_identical(time_floor(x, "2 halfyear"), time_floor(x, "year"))
expect_identical(time_floor(x, "2 year"), cteu("2008-01-01 00:00:00"))
})
test_that("time_floor works for fractional units", {
x <- as.POSIXct("2009-08-03 12:01:59.23001", tz = "UTC")
expect_equal(time_floor(x, ".2 secs"), time_floor(x, ".2 asec"))
expect_equal(time_floor(x, ".2 secs"), ymd_hms("2009-08-03 12:01:59.2 UTC"))
expect_equal(time_floor(x, ".5 mins"), ymd_hms("2009-08-03 12:01:30"))
expect_identical(time_floor(x, ".2asec"), ymd_hms("2009-08-03 12:01:59.2"))
expect_identical(time_floor(x, ".1asec"), ymd_hms("2009-08-03 12:01:59.2"))
expect_equal(time_floor(x, ".05as"), ymd_hms("2009-08-03 12:01:59.2"))
expect_equal(time_floor(x, ".01as"), ymd_hms("2009-08-03 12:01:59.23"))
expect_equal(time_floor(x, ".005as"), ymd_hms("2009-08-03 12:01:59.23"))
})
test_that("time_floor fails with fractional multi-units", {
x <- as.POSIXct("2009-08-03 12:01:59.23001", tz = "UTC")
expect_error(time_floor(x, "10.2m"), "Rounding")
expect_error(time_floor(x, "10.2h"), "Rounding")
expect_error(time_floor(x, "10.5M"), "Rounding")
expect_error(time_floor(x, "10.00005min"), "Rounding")
})
test_that("multi-unit rounding works the same for POSIX and Date objects", {
px <- ymd("2009-08-01", tz = "UTC")
dt <- ymd("2009-08-01")
expect_identical(time_floor(px, "5 mins"), time_floor(dt, "5 mins"))
expect_identical(time_floor(px, "5 mins"), time_floor(dt, "5 mins"))
expect_identical(time_ceiling(px + 0.0001, "5 mins"), time_ceiling(dt, "5 mins"))
expect_identical(time_ceiling(px, "5 mins", change_on_boundary = T),
time_ceiling(dt, "5 mins", change_on_boundary = T))
expect_identical(time_ceiling(px + 0.001, "5 hours"), time_ceiling(dt, "5 hours"))
expect_identical(time_ceiling(px + 0.0001, "5 hours", change_on_boundary = T), time_ceiling(dt, "5 hours", change_on_boundary = T))
expect_identical(time_ceiling(px + 0.001, "2 hours"), time_ceiling(dt, "2 hours"))
expect_identical(as.Date(time_floor(px, "2 days")), time_floor(dt, "2 days"))
expect_identical(as.Date(time_ceiling(px + 0.001, "2 days")), time_ceiling(dt, "2 days"))
expect_identical(as.Date(time_floor(px, "5 days")), time_floor(dt, "5 days"))
expect_identical(as.Date(time_ceiling(px + 0.001, "5 days")), time_ceiling(dt, "5 days"))
expect_identical(as.Date(time_floor(px, "2 months")), time_floor(dt, "2 months"))
expect_identical(as.Date(time_ceiling(px, "2 months")), time_ceiling(dt, "2 months"))
expect_identical(as.Date(time_floor(px, "5 months")), time_floor(dt, "5 months"))
expect_identical(as.Date(time_ceiling(px, "5 months")), time_ceiling(dt, "5 months"))
})
test_that("time_ceiling works for multi-units", {
x <- as.POSIXct("2009-08-03 12:01:59.23", tz = "UTC")
y <- as.POSIXct("2009-08-03 12:01:30.23", tz = "UTC")
z <- as.POSIXct("2009-08-24 12:01:30.23", tz = "UTC")
d <- as.Date("2009-01-01")
p1 <- as.POSIXct("2009-01-01 00:00:00", tz = "UTC")
d5 <- as.Date("2009-05-01")
p5 <- as.POSIXct("2009-05-01 00:00:00", tz = "Europe/Helsinki")
expect_identical(time_ceiling(x, "2 secs"), as.POSIXct("2009-08-03 12:02:00", tz = "UTC"))
expect_identical(time_ceiling(x, "3 secs"), as.POSIXct("2009-08-03 12:02:00", tz = "UTC"))
expect_identical(time_ceiling(x, "5 secs"), as.POSIXct("2009-08-03 12:02:00", tz = "UTC"))
expect_identical(time_ceiling(y, "2 secs"), as.POSIXct("2009-08-03 12:01:32", tz = "UTC"))
expect_identical(time_ceiling(y, "3 secs"), as.POSIXct("2009-08-03 12:01:33", tz = "UTC"))
expect_identical(time_ceiling(y, "5 secs"), as.POSIXct("2009-08-03 12:01:35", tz = "UTC"))
expect_identical(time_ceiling(x, "2 mins"), as.POSIXct("2009-08-03 12:02:00", tz = "UTC"))
expect_identical(time_ceiling(x, "3 mins"), as.POSIXct("2009-08-03 12:03:00", tz = "UTC"))
expect_identical(time_ceiling(x, "5 mins"), as.POSIXct("2009-08-03 12:05:00", tz = "UTC"))
expect_identical(time_ceiling(x, "2 hours"), as.POSIXct("2009-08-03 14:00:00", tz = "UTC"))
expect_identical(time_ceiling(x, "3 hours"), as.POSIXct("2009-08-03 15:00:00", tz = "UTC"))
expect_identical(time_ceiling(x, "2 days"), as.POSIXct("2009-08-05 00:00:00", tz = "UTC"))
expect_identical(time_ceiling(x, "3 days"), as.POSIXct("2009-08-04 00:00:00", tz = "UTC"))
expect_identical(time_ceiling(x, "10 days"), as.POSIXct("2009-08-11 00:00:00", tz = "UTC"))
expect_identical(time_ceiling(z, "5 days"), as.POSIXct("2009-08-26 00:00:00", tz = "UTC"))
expect_identical(time_ceiling(z, "10 days"), as.POSIXct("2009-08-31 00:00:00", tz = "UTC"))
expect_identical(time_ceiling(x, "2 month"), as.POSIXct("2009-09-01 00:00:00", tz = "UTC"))
expect_identical(time_ceiling(x, "2 bimonth"), time_ceiling(x, "4 months"))
expect_identical(time_ceiling(x, "2 quarter"), time_ceiling(x, "6 months"))
expect_identical(time_ceiling(x, "2 halfyear"), time_ceiling(x, "year"))
expect_identical(time_ceiling(x, "2 year"), as.POSIXct("2010-01-01 00:00:00", tz = "UTC"))
expect_identical(time_ceiling(d, "days", change_on_boundary = F), as.Date("2009-01-01"))
expect_identical(time_ceiling(d, "days", change_on_boundary = T), as.Date("2009-01-02"))
expect_identical(time_ceiling(d, "2 days", change_on_boundary = F), as.Date("2009-01-01"))
expect_identical(time_ceiling(d, "2 days", change_on_boundary = T), as.Date("2009-01-03"))
expect_identical(time_ceiling(d5, "days", change_on_boundary = F), as.Date("2009-05-01"))
expect_identical(time_ceiling(d5, "days", change_on_boundary = T), as.Date("2009-05-02"))
expect_identical(time_ceiling(d5, "2 days", change_on_boundary = F), as.Date("2009-05-01"))
expect_identical(time_ceiling(d5, "2 days", change_on_boundary = T), as.Date("2009-05-03"))
expect_identical(time_ceiling(p1, "2 days", change_on_boundary = T), as.POSIXct("2009-01-03 00:00:00", tz = "UTC"))
expect_identical(time_ceiling(p1, "2 days", change_on_boundary = F), as.POSIXct("2009-01-01 00:00:00", tz = "UTC"))
expect_identical(time_ceiling(p5, "2 days", change_on_boundary = T), as.POSIXct("2009-05-03 00:00:00", tz = "Europe/Helsinki"))
expect_identical(time_ceiling(p5, "2 days", change_on_boundary = F), as.POSIXct("2009-05-01 00:00:00", tz = "Europe/Helsinki"))
})
test_that("time_ceiling works correctly on parent unit wrap", {
x <- as.POSIXct("2009-08-28 22:56:59.23", tz = "UTC")
expect_identical(time_ceiling(x, "3.4 secs"), as.POSIXct("2009-08-28 22:57:03.4", tz = "UTC"))
expect_identical(time_ceiling(x, "50.5 secs"), as.POSIXct("2009-08-28 22:57:50.5", tz = "UTC"))
expect_identical(time_ceiling(x, "57 min"), as.POSIXct("2009-08-28 22:57:00", tz = "UTC"))
expect_identical(time_ceiling(x, "56 min"), as.POSIXct("2009-08-28 23:56:00", tz = "UTC"))
expect_identical(time_ceiling(x, "7h"), as.POSIXct("2009-08-29 07:00:00", tz = "UTC"))
expect_identical(time_ceiling(x, "7d"), as.POSIXct("2009-08-29 00:00:00", tz = "UTC"))
expect_identical(time_ceiling(x, "8d"), as.POSIXct("2009-09-09 00:00:00", tz = "UTC"))
expect_identical(time_ceiling(x, "8m"), as.POSIXct("2009-09-01 00:00:00", tz = "UTC"))
expect_identical(time_ceiling(x, "6m"), as.POSIXct("2010-01-01 00:00:00", tz = "UTC"))
expect_identical(time_ceiling(x, "7m"), as.POSIXct("2010-08-01 00:00:00", tz = "UTC"))
x <- ymd_hms("2010-11-25 22:56:57")
expect_identical(time_ceiling(x, "6sec"), ymd_hms("2010-11-25 22:57:00"))
expect_identical(time_ceiling(x, "60sec"), ymd_hms("2010-11-25 22:57:00"))
expect_identical(time_ceiling(x, "6min"), ymd_hms("2010-11-25 23:00:00"))
expect_identical(time_ceiling(x, "60min"), ymd_hms("2010-11-25 23:00:00"))
expect_identical(time_ceiling(x, "4h"), ymd_hms("2010-11-26 00:00:00"))
expect_identical(time_ceiling(x, "15d"), ymd_hms("2010-12-01 00:00:00"))
expect_identical(time_ceiling(x, "15d"), ymd_hms("2010-12-01 00:00:00"))
expect_identical(time_ceiling(x, "6m"), ymd_hms("2011-01-01 00:00:00"))
})
x <- as.POSIXct("2009-08-28 22:56:59.23", tz = "UTC")
time_ceiling(x, "3.4 secs") # "2009-08-28 22:57:03.4"
time_ceiling(x, "50.5 secs") # "2009-08-28 22:57:50.5"
time_ceiling(x, "57 min") # "2009-08-28 22:57:00"
time_ceiling(x, "56 min") # "2009-08-28 23:56:00"
time_ceiling(x, "7h") # "2009-08-29 07:00:00"
time_ceiling(x, "7d") # "2009-08-29 00:00:00"
time_ceiling(x, "8d") # "2009-09-09 00:00:00"
time_ceiling(x, "8m") # "2009-09-01 00:00:00"
time_ceiling(x, "6m") # "2010-01-01 00:00:00"
time_ceiling(x, "7m") # "2010-08-01 00:00:00"
x <- as.POSIXct("2010-11-25 22:56:57", tz = "UTC")
time_ceiling(x, "6sec") # "2010-11-25 22:57:00"
time_ceiling(x, "60sec") # "2010-11-25 22:57:00"
time_ceiling(x, "6min") # "2010-11-25 23:00:00"
time_ceiling(x, "60min") # "2010-11-25 23:00:00"
time_ceiling(x, "4h") # "2010-11-26 00:00:00"
time_ceiling(x, "15d") # "2010-12-01 00:00:00"
time_ceiling(x, "15d") # "2010-12-01 00:00:00"
time_ceiling(x, "6m") # "2011-01-01 00:00:00"
test_that("time_ceiling works for fractional multi-units", {
x <- as.POSIXct("2009-08-03 12:01:59.23", tz = "UTC")
expect_identical(time_ceiling(x, ".2 asecs"), as.POSIXct("2009-08-03 12:01:59.4", tz = "UTC"))
expect_identical(time_ceiling(x, ".2 secs"), as.POSIXct("2009-08-03 12:01:59.4", tz = "UTC"))
expect_identical(time_ceiling(x, "2.5 asecs"), as.POSIXct("2009-08-03 12:02:00", tz = "UTC"))
expect_identical(time_ceiling(x, "2.5 secs"), as.POSIXct("2009-08-03 12:02:00", tz = "UTC"))
expect_identical(time_ceiling(x, "3.4 asecs"), .POSIXct(ceiling(unclass(x)/3.4)*3.4, tz = "UTC"))
expect_identical(time_ceiling(x, "3.4 secs"), as.POSIXct("2009-08-03 12:02:03.4", tz = "UTC"))
expect_identical(time_ceiling(x, ".1a"), as.POSIXct("2009-08-03 12:01:59.3", tz = "UTC"))
expect_equal(time_ceiling(x, ".05as"), as.POSIXct("2009-08-03 12:01:59.25", tz = "UTC"))
expect_equal(time_ceiling(x, ".5 mins"), as.POSIXct("2009-08-03 12:02:00", tz = "UTC"))
expect_equal(time_ceiling(x, "2mins"), as.POSIXct("2009-08-03 12:02:00", tz = "UTC"))
expect_equal(time_ceiling(x, "2hours"), as.POSIXct("2009-08-03 14:00:00", tz = "UTC"))
expect_equal(time_ceiling(ymd_hms("2009-08-03 00:56:00"), "10 mins"), ymd_hms("2009-08-03 01:00:00"))
expect_equal(time_ceiling(ymd_hms("2009-08-03 00:56:00"), "7 mins"), ymd_hms("2009-08-03 01:07:00"))
})
test_that("time_ceiling works for fractional multi-units", {
x <- as.POSIXct("2009-08-03 12:01:59.230001", tz = "UTC")
expect_identical(time_ceiling(x, ".2 secs"), as.POSIXct("2009-08-03 12:01:59.4", tz = "UTC"))
expect_identical(time_ceiling(x, ".1s"), as.POSIXct("2009-08-03 12:01:59.3", tz = "UTC"))
expect_identical(time_ceiling(x, ".05s"), as.POSIXct("2009-08-03 12:01:59.25", tz = "UTC"))
expect_identical(time_ceiling(x, ".01s"), as.POSIXct("2009-08-03 12:01:59.24", tz = "UTC"))
expect_identical(time_ceiling(x, ".005s"), as.POSIXct("2009-08-03 12:01:59.235", tz = "UTC"))
expect_identical(time_ceiling(x, ".5 mins"), as.POSIXct("2009-08-03 12:02:00", tz = "UTC"))
})
test_that("time_floor works for fractional multi-units", {
x <- as.POSIXct("2009-08-03 12:01:59.230001", tz = "UTC")
expect_identical(time_floor(x, ".2 secs"), as.POSIXct("2009-08-03 12:01:59.2", tz = "UTC"))
expect_identical(time_floor(x, ".1s"), as.POSIXct("2009-08-03 12:01:59.2", tz = "UTC"))
expect_identical(time_floor(x, ".05s"), as.POSIXct("2009-08-03 12:01:59.2", tz = "UTC"))
expect_identical(time_floor(x, ".01s"), as.POSIXct("2009-08-03 12:01:59.23", tz = "UTC"))
expect_identical(time_floor(x, ".005s"), as.POSIXct("2009-08-03 12:01:59.23", tz = "UTC"))
expect_identical(time_floor(x, ".5 mins"), as.POSIXct("2009-08-03 12:01:30", tz = "UTC"))
})
test_that("time_floor works for fractional asecons", {
x <- as.POSIXct("2009-08-03 12:01:59.23", tz = "UTC")
expect_identical(time_floor(x, ".2a"), as.POSIXct("2009-08-03 12:01:59.2", tz = "UTC"))
expect_identical(time_floor(x, ".1a"), as.POSIXct("2009-08-03 12:01:59.2", tz = "UTC"))
expect_identical(time_floor(x, ".05a"), as.POSIXct("2009-08-03 12:01:59.2", tz = "UTC"))
expect_identical(time_floor(x, ".01a"), as.POSIXct("2009-08-03 12:01:59.23", tz = "UTC"))
expect_identical(time_floor(x, ".005a"), as.POSIXct("2009-08-03 12:01:59.23", tz = "UTC"))
})
test_that("time_ceiling fails with fractional multi-units", {
x <- as.POSIXct("2009-08-03 12:01:59.23001", tz = "UTC")
expect_error(time_ceiling(x, "10.2m"), "Rounding")
expect_error(time_ceiling(x, "10.2h"), "Rounding")
expect_error(time_ceiling(x, "10.5M"), "Rounding")
expect_error(time_ceiling(x, "10.00005min"), "Rounding")
})
test_that("time_round works for each time element", {
x <- ctus("2009-08-03 12:01:59.23")
expect_equal(time_round(x, "second"), ctus("2009-08-03 12:01:59"))
expect_equal(time_round(x, "minute"), ctus("2009-08-03 12:02:00"))
expect_equal(time_round(x, "hour"), ctus("2009-08-03 12:00:00"))
expect_equal(time_round(x, "day"), ctus("2009-08-04 00:00:00"))
expect_equal(time_round(x, "week"), ctus("2009-08-03 00:00:00"))
expect_equal(time_round(x, "month"), ctus("2009-08-01 00:00:00"))
expect_equal(time_round(x, "bimonth"), ctus("2009-09-01 00:00:00"))
expect_equal(time_round(x, "quarter"), ctus("2009-07-01 00:00:00"))
expect_equal(time_round(x, "halfyear"), ctus("2009-07-01 00:00:00"))
expect_equal(time_round(x, "year"), ctus("2010-01-01 00:00:00"))
})
test_that("time_round fails with fractional multi-units", {
x <- as.POSIXct("2009-08-03 12:01:59.23001", tz = "UTC")
expect_error(time_round(x, "10.2m"), "Rounding")
expect_error(time_round(x, "10.2h"), "Rounding")
expect_error(time_round(x, "10.5M"), "Rounding")
expect_error(time_round(x, "10.00005min"), "Rounding")
})
test_that("time_floor and time_ceiling work with leap years", {
expect_equal(time_floor(ymd_hms(c("2016-02-29 1:2:3", "2016-03-01 10:20:30")), "year"),
ymd_hms(c("2016-01-01 0:0:0", "2016-01-01 0:0:0")))
expect_equal(time_floor(ymd_hms(c("2016-02-29 1:2:3", "2016-03-01 10:20:30"), tz = "America/New_York"), "year"),
ymd_hms(c("2016-01-01 0:0:0", "2016-01-01 0:0:0"), tz = "America/New_York"))
expect_equal(time_ceiling(ymd_hms(c("2016-02-29 1:2:3", "2016-03-01 10:20:30")), "year"),
ymd_hms(c("2017-01-01 0:0:0", "2017-01-01 0:0:0")))
expect_equal(time_ceiling(ymd_hms(c("2016-02-29 1:2:3", "2016-03-01 10:20:30"), tz = "America/New_York"), "year"),
ymd_hms(c("2017-01-01 0:0:0", "2017-01-01 0:0:0"), tz = "America/New_York"))
})
test_that("time_round works for multi-units", {
x <- ctutc("2009-08-03 12:01:59.23")
expect_equal(time_round(x, "2 second"), ctutc("2009-08-03 12:02:00"))
expect_equal(time_round(x, "2 minute"), ctutc("2009-08-03 12:02:00"))
expect_equal(time_round(x, "3 mins"), ctutc("2009-08-03 12:03:00"))
expect_equal(time_round(x, "5 mins"), ctutc("2009-08-03 12:00:00"))
expect_equal(time_round(x, "2 hour"), ctutc("2009-08-03 12:00:00"))
expect_equal(time_round(x, "5 hour"), ctutc("2009-08-03 10:00:00"))
expect_equal(time_round(x, "2 days"), ctutc("2009-08-03 00:00:00"))
expect_equal(time_round(x, "2 months"), ctutc("2009-09-01 00:00:00"))
expect_equal(time_round(x, "bimonth"), time_round(x, "2 months"))
expect_equal(time_round(x, "bimonth"), time_round(x, "4 months"))
expect_equal(time_round(x, "quarter"), time_round(x, "3 months"))
expect_equal(time_round(x, "halfyear"), time_round(x, "6 months"))
expect_equal(time_round(x, "3 years"), ctutc("2010-01-01 00:00:00"))
expect_equal(time_round(x, "4 years"), ctutc("2008-01-01 00:00:00"))
})
test_that("time_round works for fractional multi-units", {
x <- ctus("2009-08-03 12:01:59.23")
expect_equal(time_round(x, ".2 asecs"), ctus("2009-08-03 12:01:59.2"))
expect_equal(time_round(x, ".1as"), ctus("2009-08-03 12:01:59.2"))
expect_equal(time_round(x, ".05as"), ctus("2009-08-03 12:01:59.25"))
expect_equal(time_round(x, ".5 mins"), ctus("2009-08-03 12:02:00"))
})
test_that("time_floor handles vectors", {
x <- as.POSIXct(c("2009-08-03 12:01:59.23", "2010-08-03 12:01:59.23"), tz = "UTC")
expect_identical(time_floor(x, "second"),
as.POSIXct(c("2009-08-03 12:01:59", "2010-08-03 12:01:59"), tz = "UTC"))
expect_identical(time_floor(x, "minute"),
as.POSIXct(c("2009-08-03 12:01:00", "2010-08-03 12:01:00"), tz = "UTC"))
expect_identical(time_floor(x, "hour"),
as.POSIXct(c("2009-08-03 12:00:00", "2010-08-03 12:00:00"), tz = "UTC"))
expect_identical(time_floor(x, "day"),
as.POSIXct(c("2009-08-03 00:00:00", "2010-08-03 00:00:00"), tz = "UTC"))
expect_identical(time_floor(x, "week"),
as.POSIXct(c("2009-08-03 00:00:00", "2010-08-02 00:00:00"), tz = "UTC"))
expect_identical(time_floor(x, "month"),
as.POSIXct(c("2009-08-01 00:00:00", "2010-08-01 00:00:00"), tz = "UTC"))
expect_identical(time_floor(x, "year"),
as.POSIXct(c("2009-01-01 00:00:00", "2010-01-01 00:00:00"), tz = "UTC"))
})
test_that("time_ceiling handles vectors", {
x <- as.POSIXct(c("2009-08-03 12:01:59.23", "2010-08-03 12:01:59.23"), tz = "UTC")
expect_identical(time_ceiling(x, "second"),
as.POSIXct(c("2009-08-03 12:02:00", "2010-08-03 12:02:00"), tz = "UTC"))
expect_identical(time_ceiling(x, "minute"),
as.POSIXct(c("2009-08-03 12:02:00", "2010-08-03 12:02:00"), tz = "UTC"))
expect_identical(time_ceiling(x, "hour"),
as.POSIXct(c("2009-08-03 13:00:00", "2010-08-03 13:00:00"), tz = "UTC"))
expect_identical(time_ceiling(x, "day"),
as.POSIXct(c("2009-08-04 00:00:00", "2010-08-04 00:00:00"), tz = "UTC"))
expect_identical(time_ceiling(x, "week"),
as.POSIXct(c("2009-08-10 00:00:00", "2010-08-09 00:00:00"), tz = "UTC"))
expect_identical(time_ceiling(x, "month"),
as.POSIXct(c("2009-09-01 00:00:00", "2010-09-01 00:00:00"), tz = "UTC"))
expect_identical(time_ceiling(x, "year"),
as.POSIXct(c("2010-01-01 00:00:00", "2011-01-01 00:00:00"), tz = "UTC"))
})
test_that("time_ceiling handles days, weeks and months correctly on boundary", {
x <- as.POSIXct(c("2009-08-03 00:00:00", "2010-08-02 00:00:00"), tz = "UTC")
d <- as.Date(x)
expect_identical(time_ceiling(x, "week", change_on_boundary = T),
as.POSIXct(c("2009-08-10 00:00:00", "2010-08-09 00:00:00"), tz = "UTC"))
expect_identical(time_ceiling(x, "week", change_on_boundary = FALSE), x)
expect_identical(time_ceiling(d, "week", change_on_boundary = T),
as.Date(c("2009-08-10", "2010-08-09")))
expect_identical(time_ceiling(d, "week", change_on_boundary = F), d)
expect_identical(time_ceiling(x, "day", change_on_boundary = T),
as.POSIXct(c("2009-08-04 00:00:00", "2010-08-03 00:00:00"), tz = "UTC"))
expect_identical(time_ceiling(x, "day", change_on_boundary = FALSE), x)
expect_identical(time_ceiling(d, "day", change_on_boundary = T),
as.Date(c("2009-08-04", "2010-08-03")))
expect_identical(time_ceiling(d, "day", change_on_boundary = F), d)
expect_identical(time_ceiling(x, "month", change_on_boundary = T),
as.POSIXct(c("2009-09-01 00:00:00", "2010-09-01 00:00:00"), tz = "UTC"))
expect_identical(time_ceiling(x, "month", change_on_boundary = F),
as.POSIXct(c("2009-09-01 00:00:00", "2010-09-01 00:00:00"), tz = "UTC"))
expect_identical(time_ceiling(d, "month", change_on_boundary = T),
as.Date(c("2009-09-01", "2010-09-01")))
expect_identical(time_ceiling(d, "month", change_on_boundary = F),
as.Date(c("2009-09-01", "2010-09-01")))
x <- as.POSIXct(c("2009-08-01 00:00:00", "2010-08-01 00:00:00"), tz = "UTC")
d <- as.Date(x)
expect_identical(time_ceiling(x, "month", change_on_boundary = T),
as.POSIXct(c("2009-09-01 00:00:00", "2010-09-01 00:00:00"), tz = "UTC"))
expect_identical(time_ceiling(x, "month", change_on_boundary = F),
as.POSIXct(c("2009-08-01 00:00:00", "2010-08-01 00:00:00"), tz = "UTC"))
expect_identical(time_ceiling(d, "month", change_on_boundary = T),
as.Date(c("2009-09-01", "2010-09-01")))
expect_identical(time_ceiling(d, "month", change_on_boundary = F),
as.Date(c("2009-08-01", "2010-08-01")))
})
test_that("time_round handles vectors", {
x <- as.POSIXct(c("2009-08-03 12:01:59.23", "2010-08-03 12:01:59.23"), tz = "UTC")
expect_equal(time_round(x, "second"),
as.POSIXct(c("2009-08-03 12:01:59",
"2010-08-03 12:01:59"), tz = "UTC"))
expect_equal(time_round(x, "minute"),
as.POSIXct(c("2009-08-03 12:02:00",
"2010-08-03 12:02:00"), tz = "UTC"))
expect_equal(time_round(x, "hour"),
as.POSIXct(c("2009-08-03 12:00:00",
"2010-08-03 12:00:00"), tz = "UTC"))
expect_equal(time_round(x, "day"),
as.POSIXct(c("2009-08-04 00:00:00",
"2010-08-04 00:00:00"), tz = "UTC"))
expect_equal(time_round(x, "week"),
as.POSIXct(c("2009-08-03 00:00:00",
"2010-08-02 00:00:00"), tz = "UTC"))
expect_equal(time_round(x, "month"),
as.POSIXct(c("2009-08-01 00:00:00",
"2010-08-01 00:00:00"), tz = "UTC"))
expect_equal(time_round(x, "year"),
as.POSIXct(c("2010-01-01 00:00:00",
"2011-01-01 00:00:00"), tz = "UTC"))
})
test_that("time_floor works for a variety of formats", {
x <- as.POSIXct("2009-08-03 12:01:59", tz = "UTC")
expect_equal(time_floor(x, "minute"),
as.POSIXct("2009-08-03 12:01:00", tz = "UTC"))
expect_equal(time_floor(as.Date(x), "month"),
as.Date("2009-08-01"))
expect_equal(time_floor(as.Date(x), "bimonth"),
ymd("2009-07-01"))
expect_equal(time_floor(as.Date(x), "quarter"),
ymd("2009-07-01"))
expect_equal(time_floor(as.Date(x), "halfyear"),
ymd("2009-07-01"))
expect_equal(time_floor(as.POSIXlt(x), "minute"),
as.POSIXlt(as.POSIXct("2009-08-03 12:01:00", tz = "UTC")))
})
test_that("time_ceiling works for a variety of formats", {
x <- as.POSIXct("2009-08-03 12:01:59", tz = "UTC")
expect_equal(time_ceiling(x, "minute"),
as.POSIXct("2009-08-03 12:02:00", tz = "UTC"))
expect_equal(time_ceiling(as.Date(x), "month"),
as.Date("2009-09-01"))
expect_equal(time_ceiling(as.Date(x), "bimonth"),
ymd("2009-09-01"))
expect_equal(time_ceiling(as.Date(x), "quarter"),
ymd("2009-10-01"))
expect_equal(time_ceiling(as.Date(x), "halfyear"),
ymd("2010-01-01"))
expect_equal(time_ceiling(as.POSIXlt(x), "minute"),
as.POSIXlt(as.POSIXct("2009-08-03 12:02:00", tz = "UTC")))
})
test_that("time_round works for a variety of formats", {
x <- as.POSIXct("2009-08-03 12:01:59", tz = "UTC")
expect_equal(time_round(x, "minute"), as.POSIXct("2009-08-03 12:02:00", tz = "UTC"))
expect_equal(time_round(as.Date(x), "month"), as.Date("2009-08-01"))
expect_equal(time_round(as.POSIXlt(x), "minute"), as.POSIXlt(as.POSIXct("2009-08-03 12:02:00", tz = "UTC")))
})
test_that("rounding works across DST", {
## https://github.com/hadley/lubridate/issues/399
tt <- ymd("2016-03-27", tz = "Europe/Helsinki");
expect_equal(time_ceiling(tt, "month"), as.POSIXct("2016-04-01", tz = "Europe/Helsinki"))
expect_equal(time_ceiling(tt, "day"), as.POSIXct("2016-03-27", tz = "Europe/Helsinki"))
tt <- ymd("2016-03-28", tz = "Europe/Helsinki");
expect_equal(time_floor(tt, "month"), as.POSIXct("2016-03-01", tz = "Europe/Helsinki"))
tt <- ymd_hms("2016-03-27 05:00:00", tz = "Europe/Helsinki");
expect_equal(time_floor(tt, "day"), as.POSIXct("2016-03-27", tz = "Europe/Helsinki"))
## https://github.com/tidyverse/lubridate/issues/605
x <- ymd_hms("2017-11-05 23:59:03", tz = 'America/New_York')
expect_equal(time_ceiling(x, "day"), as.POSIXct("2017-11-06", tz = "America/New_York"))
})
test_that("Ceiling for partials (Date) rounds up on boundary", {
expect_identical(time_ceiling(as.Date("2012-09-27"), "day"), ymd("2012-09-28"))
expect_identical(time_ceiling(as.Date("2012-09-01"), "day"), ymd("2012-09-02"))
expect_identical(time_ceiling(as.Date("2012-09-01"), "2 days"), ymd("2012-09-03"))
})
test_that("Ceiling for Date returns date when unit level is higher than day", {
expect_true(is.Date(time_ceiling(ymd("2016-09-27"), "year")))
expect_true(is.Date(time_ceiling(ymd("2016-09-27"), "halfyear")))
expect_true(is.Date(time_ceiling(ymd("2016-09-27"), "quarter")))
expect_true(is.Date(time_ceiling(ymd("2016-09-27"), "bimonth")))
expect_true(is.Date(time_ceiling(ymd("2016-09-27"), "month")))
expect_true(is.Date(time_ceiling(ymd("2016-09-27"), "week")))
expect_true(is.Date(time_ceiling(ymd("2016-09-27"), "day")))
expect_true(is.POSIXct(time_ceiling(ymd("2016-09-27"), "hour")))
expect_true(is.POSIXct(time_ceiling(ymd("2016-09-27"), "minute")))
expect_true(is.POSIXct(time_ceiling(ymd("2016-09-27"), "second")))
})
test_that("Ceiling for POSIXct always returns POSIXct", {
expect_true(is.POSIXct(time_ceiling(ymd_hms("2016-09-27 00:00:00"), "year")))
expect_true(is.POSIXct(time_ceiling(ymd_hms("2016-09-27 00:00:00"), "halfyear")))
expect_true(is.POSIXct(time_ceiling(ymd_hms("2016-09-27 00:00:00"), "quarter")))
expect_true(is.POSIXct(time_ceiling(ymd_hms("2016-09-27 00:00:00"), "bimonth")))
expect_true(is.POSIXct(time_ceiling(ymd_hms("2016-09-27 00:00:00"), "month")))
expect_true(is.POSIXct(time_ceiling(ymd_hms("2016-09-27 00:00:00"), "week")))
expect_true(is.POSIXct(time_ceiling(ymd_hms("2016-09-27 00:00:00"), "day")))
expect_true(is.POSIXct(time_ceiling(ymd_hms("2016-09-27 00:00:00"), "hour")))
expect_true(is.POSIXct(time_ceiling(ymd_hms("2016-09-27 00:00:00"), "minute")))
expect_true(is.POSIXct(time_ceiling(ymd_hms("2016-09-27 00:00:00"), "second")))
})
test_that("time_ceiling does not round up dates that are already on a boundary", {
expect_equal(time_ceiling(ymd_hms("2012-09-01 00:00:00"), "month"), as.POSIXct("2012-09-01", tz = "UTC"))
expect_equal(time_ceiling(ymd_hms("2012-01-01 00:00:00"), "year"), as.POSIXct("2012-01-01", tz = "UTC"))
expect_equal(time_ceiling(ymd_hms("2012-01-01 00:00:00"), "2 year"), as.POSIXct("2012-01-01", tz = "UTC"))
expect_equal(time_ceiling(ymd_hms("2012-01-01 00:00:00"), "3 year"), as.POSIXct("2013-01-01", tz = "UTC"))
expect_equal(time_ceiling(ymd_hms("2012-01-01 00:00:00"), "5 year"), as.POSIXct("2015-01-01", tz = "UTC"))
expect_equal(time_ceiling(ymd_hms("2012-01-01 01:00:00"), "second"), ymd_hms("2012-01-01 01:00:00"))
expect_equal(time_ceiling(ymd_hms("2012-01-01 01:00:00"), "2 second"), ymd_hms("2012-01-01 01:00:00"))
expect_equal(time_ceiling(ymd_hms("2012-01-01 01:00:00"), "2 second", change_on_boundary = T),
ymd_hms("2012-01-01 01:00:02"))
expect_equal(time_ceiling(ymd_hms("2012-01-01 01:00:00"), "5 second"), ymd_hms("2012-01-01 01:00:00"))
expect_equal(time_ceiling(ymd_hms("2012-01-01 00:00:00"), "bimonth"), ymd_hms("2012-01-01 00:00:00"))
})
test_that("time_ceiling does round up dates on a boundary with change_on_boundary=TRUE", {
expect_equal(time_ceiling(as.Date("2012-09-27"), "day", change_on_boundary = TRUE), as.Date("2012-09-28"))
expect_equal(time_ceiling(as.Date("2012-09-01"), "month", change_on_boundary = TRUE), as.Date("2012-10-01"))
expect_equal(time_ceiling(ymd_hms("2012-09-01 00:00:00"), "month", change_on_boundary = TRUE), ymd("2012-10-01", tz = "UTC"))
expect_equal(time_ceiling(ymd_hms("2012-09-01 00:00:00"), "bimonth", change_on_boundary = TRUE), ymd("2012-11-01", tz = "UTC"))
expect_equal(time_ceiling(ymd_hms("2012-01-01 00:00:00"), "year", change_on_boundary = TRUE), as.POSIXct("2013-01-01", tz = "UTC"))
expect_equal(time_ceiling(ymd_hms("2012-01-01 01:00:00"), "hour", change_on_boundary = TRUE), ymd_hms("2012-01-01 02:00:00"))
expect_equal(time_ceiling(ymd_hms("2012-01-01 00:00:00"), "day", change_on_boundary = TRUE), ymd("2012-01-02", tz = "UTC"))
expect_equal(time_ceiling(ymd_hms("2012-01-01 01:00:00"), "second", change_on_boundary = TRUE), ymd_hms("2012-01-01 01:00:01"))
})
test_that("time_floor does not round down dates that are already on a boundary", {
expect_equal(time_floor(as.Date("2012-09-27"), "day"), as.Date("2012-09-27"))
})
test_that("time_round does not round dates that are already on a boundary", {
expect_equal(time_round(as.Date("2012-09-27"), "day"), as.Date("2012-09-27"))
})
test_that("time_ceiling returns input of length zero when given input of length zero", {
x <- structure(vector(mode = "numeric"), class = c("POSIXct", "POSIXt"))
expect_equal(time_ceiling(x), x)
})
test_that("time_floor returns input of length zero when given input of length zero", {
x <- structure(vector(mode = "numeric"), class = c("POSIXct", "POSIXt"))
expect_equal(time_floor(x), x)
})
test_that("time_round returns input of length zero when given input of length zero", {
x <- structure(vector(mode = "numeric"), class = c("POSIXct", "POSIXt"))
expect_equal(time_round(x), x)
})
test_that("time_round behaves correctly on 60th second", {
## (bug #217)
x <- ymd_hms("2013-12-01 23:59:59.9999")
expect_equal(time_round(x, unit = "second"),
ymd("2013-12-02", tz = "UTC"))
time_update(x, second = 60)
expect_equal(x, ymd("2013-12-02", tz = "UTC"))
})
test_that("time_round and time_ceiling skip day time gap", {
tz <- "Europe/Amsterdam"
ref <- ymd_hms("2013-03-31 01:00:00 CET", tz = "Europe/Amsterdam")
times <- ref + c(0, 15*60, 30*60, 45*60, 3600, 3600 + 15*60)
floor <- ref + c(0, 0, 0, 0, 3600, 3600)
ceiling <- ref + c(0, 3600, 3600, 3600, 3600, 2*3600)
round <- ref + c(0, 0, 3600, 3600, 3600, 3600)
expect_equal(time_ceiling(times, "hour"), ceiling)
expect_equal(time_floor(times, "hour"), floor)
expect_equal(time_round(times, "hour"), round)
tz <- "America/Chicago"
x <- ymd_hms(c("2014-03-09 00:00:00", "2014-03-09 00:29:59", "2014-03-09 00:30:00",
"2014-03-09 00:59:59", "2014-03-09 01:35:00", "2014-03-09 03:15:00"),
tz = tz)
y <- as.POSIXct(c("2014-03-09 00:00:00", "2014-03-09 00:00:00", "2014-03-09 01:00:00",
"2014-03-09 01:00:00", "2014-03-09 03:00:00", "2014-03-09 03:00:00"),
tz = tz)
expect_equal(time_round(x, "hour"), y)
})
test_that("time rounding with hours works with repeated DST transitions", {
am1 <- .POSIXct(1414904400, tz = "America/New_York")
expect_equal(time_floor(am1 + 3600, "hour"), am1 + 3600)
## rounding is done in civil time for units > seconds
expect_equal(time_ceiling(ctus("2014-11-02 00:30:00"), "hour"), am1)
expect_equal(time_ceiling(ctus("2014-11-02 01:35:00"), "hour"), ctus("2014-11-02 02:00:00")) ## EST (1.5h)
expect_equal(time_ceiling(ctus("2014-11-02 02:15:00"), "hour"), ctus("2014-11-02 03:00:00")) ## EST (45m)
x <- ctus("2014-11-02 00:30:00")
expect_equal(as.numeric(difftime(time_ceiling(x, "hour"), ctus(x), units = "min")), 30)
x <- ctus("2014-11-02 01:30:00")
expect_equal(as.numeric(difftime(time_ceiling(x, "hour"), ctus(x), units = "min")), 90)
x <- ctus("2014-11-02 02:15:00")
expect_equal(as.numeric(difftime(time_ceiling(x, "hour"), ctus(x), units = "min")), 45)
## rounding is done in absolute time for seconds
expect_equal(time_ceiling(ctus("2014-11-02 00:30:00"), "ahour"), am1) ## EDT (.5h)
x <- ctus("2014-11-02 00:30:00")
expect_equal(as.numeric(difftime(time_ceiling(x, "ahour"), ctus(x), units = "min")), 30)
x <- time_add(am1, minutes = 30) ## EDT
expect_equal(as.numeric(difftime(time_ceiling(x, "ahour"), ctus(x), units = "min")), 30)
## rounding is done in civil time for units > seconds
expect_equal(time_round(ctus("2014-11-02 00:30:00"), "hour"), am1) ## EDT (30m)
x <- time_add(am1, minutes = 35) ## EDT
expect_equal(as.numeric(difftime(time_round(x, "hour"), x, units = "min")), -35)
expect_equal(as.numeric(difftime(time_round(x, "ahour"), x, units = "min")), 25)
expect_equal(as.numeric(difftime(time_round(x, "3600a"), x, units = "min")), 25)
x <- ctus("2014-11-02 02:15:00")
expect_equal(time_round(x, "hour"), ctus("2014-11-02 02:00:00")) ## EST (15m)
expect_equal(as.numeric(difftime(time_round(x, "hour"), x, units = "min")), -15)
expect_equal(as.numeric(difftime(time_round(x, "3600a"), x, units = "min")), -15)
## rounding is done in civil time for units > seconds
expect_equal(time_round(ctus("2014-11-02 00:30:00"), "hour"), am1) ## EDT (30m)
x <- .POSIXct(1414909500, tz = "America/New_York") # "2014-11-02 01:25:00 EST"
expect_equal(as.numeric(difftime(time_floor(x, "hour"), x, units = "min")), -25)
expect_equal(as.numeric(difftime(time_floor(x, "3600a"), x, units = "min")), -25)
x <- ctus("2014-11-02 02:15:00") ## EST
expect_equal(time_floor(x, "hour"), ctus("2014-11-02 02:00:00")) ## EST (15m)
expect_equal(as.numeric(difftime(time_floor(x, "3600a"), ctus(x), units = "min")), -15)
})
test_that("time rounding with minutes works with repeated DST transitions", {
am1 <- .POSIXct(1414904400, tz = "America/New_York")
expect_equal(time_ceiling(am1 + 30, "min"), am1 + 60)
expect_equal(time_ceiling(am1 + 3630, "min"), am1 + 3660)
expect_equal(time_ceiling(am1 + 3690, "min"), am1 + 3720)
expect_equal(time_ceiling(am1 + 30, "min"), am1 + 60)
expect_equal(time_floor(am1 + 30, "min"), am1)
expect_equal(time_floor(am1 + 90, "min"), am1 + 60)
expect_equal(time_floor(am1 + 3600, "min"), am1 + 3600)
expect_equal(time_floor(am1 + 3600, "sec"), am1 + 3600)
expect_equal(time_floor(am1 + 3690, "min"), am1 + 3660)
expect_equal(time_floor(am1 + 3690, "5min"), am1 + 3600)
expect_equal(time_floor(am1 + 3690, "hour"), am1 + 3600)
expect_equal(time_floor(am1 + 3690, "hour"), am1 + 3600)
expect_equal(time_floor(am1 + 3690, "day"), am1 - 3600)
## rounding is done in civil time for units > seconds
x <- .POSIXct(1414909530, tz = "America/New_York") # "2014-11-02 01:25:30 EST"
expect_equal(as.numeric(difftime(time_floor(x, "minute"), x, units = "secs")), -30)
expect_equal(as.numeric(difftime(time_floor(x, "60s"), x, units = "secs")), -30)
x <- am1 + 1890
expect_equal(time_floor(x, "minute"), am1 + 1860)
expect_equal(time_floor(x, "3minute"), am1 + 1800)
x <- ctus("2014-11-02 02:15:00") ## EST
expect_equal(time_floor(x, "minute"), ctus("2014-11-02 02:15:00"))
expect_equal(time_floor(x, "5minute"), ctus("2014-11-02 02:15:00"))
expect_equal(time_floor(x, "4minute"), ctus("2014-11-02 02:12:00"))
expect_equal(as.numeric(difftime(time_floor(x, "3600a"), ctus(x), units = "min")), -15)
})
test_that("rounding works on 'strange' DST gaps", {
## Midnight doesn't exist. DST spring forward happens at 2020-03-29 00:00:00
## and they spring forward to hour 1
y <- ymd_hms("2020-03-29 01:00:01", tz = "Asia/Beirut")
expect_equal(time_floor(y, "day"),
ymd_hms("2020-03-29 01:00:00", tz = "Asia/Beirut"))
})
test_that("time_ceiling, time_round and time_floor behave correctly with NA", {
am1 <- .POSIXct(1414904400, tz = "America/New_York")
## (bug lubridate #486)
x <- time_add(ymd_hms("2009-08-03 12:01:59.23", tz = "UTC"), days = 0:1)
x[2] <- NA
expect_equal(time_ceiling(x, unit = "day"), ymd(c("2009-08-04", NA), tz = "UTC"))
expect_equal(time_ceiling(x, unit = "seconds"), ymd_hms(c("2009-08-03 12:02:00", NA), tz = "UTC"))
expect_equal(time_ceiling(x, unit = "months"), ymd(c("2009-09-01", NA), tz = "UTC"))
expect_equal(time_floor(x, unit = "day"), ymd(c("2009-08-03", NA), tz = "UTC"))
expect_equal(time_floor(x, unit = "months"), ymd(c("2009-08-01", NA), tz = "UTC"))
expect_equal(time_round(x, unit = "minute"), ymd_hms(c("2009-08-03 12:02:00", NA), tz = "UTC"))
})
test_that("time_floor works for seasons", {
dts <- ymd_hms(sprintf("2017-%d-02 0:34:3", 1:12))
expect_equal(month(time_floor(dts, "season")), c(12, 12, 3, 3, 3, 6, 6, 6, 9, 9, 9, 12))
dts <- time_force_tz(dts, "America/New_York")
expect_equal(month(time_floor(dts, "season")), c(12, 12, 3, 3, 3, 6, 6, 6, 9, 9, 9, 12))
})
test_that("time_ceiling works for seasons", {
dts <- ymd_hms(sprintf("2017-%d-02 0:34:3", 1:12))
expect_equal(month(time_ceiling(dts, "season")), c(3, 3, 6, 6, 6, 9, 9, 9, 12, 12, 12, 3))
dts <- time_force_tz(dts, "America/New_York")
expect_equal(month(time_ceiling(dts, "season")), c(3, 3, 6, 6, 6, 9, 9, 9, 12, 12, 12, 3))
})
test_that("round on week respects week_start", {
date <- ymd("2017-05-07") ## sunday
ct <- as.POSIXct("2010-02-03 13:45:59", tz = "America/New_York", format = "%Y-%m-%d %H:%M:%S") ## Wednesday
expect_equal(wday(time_floor(ct, "week", week_start = 1)), 1)
expect_equal(wday(time_floor(ct, "week", week_start = 2)), 2)
expect_equal(wday(time_floor(ct, "week", week_start = 5)), 5)
expect_equal(wday(time_floor(ct, "week", week_start = 7)), 7)
expect_equal(wday(time_floor(date, "week", week_start = 1)), 1)
expect_equal(wday(time_floor(date, "week", week_start = 2)), 2)
expect_equal(wday(time_floor(date, "week", week_start = 5)), 5)
expect_equal(wday(time_floor(date, "week", week_start = 7)), 7)
expect_equal(wday(time_ceiling(ct, "week", week_start = 1)), 1)
expect_equal(wday(time_ceiling(ct, "week", week_start = 2)), 2)
expect_equal(wday(time_ceiling(ct, "week", week_start = 5)), 5)
expect_equal(wday(time_ceiling(ct, "week", week_start = 7)), 7)
expect_equal(wday(time_ceiling(date, "week", week_start = 1)), 1)
expect_equal(wday(time_ceiling(date, "week", week_start = 2)), 2)
expect_equal(wday(time_ceiling(date, "week", week_start = 5)), 5)
expect_equal(wday(time_ceiling(date, "week", week_start = 7)), 7)
})
test_that("rounding works for dates < 1970", {
expect_equal(time_round(ymd_hms("1957-12-01 14:00:00 UTC"), "month"),
ymd("1957-12-01", tz = "UTC"))
expect_equal(time_round(ymd_hms("1958-01-31 09:59:59 UTC"), "month"),
ymd("1958-02-01", tz = "UTC"))
})
test_that("rounding works with custom origin", {
x <- ymd_hms(c("2010-10-01 01:00:01", "2010-11-02 02:00:01"), tz = "America/New_York")
expect_equal(time_floor(x, "3000a", origin = time_floor(x, "day")),
ymd_hms(c("2010-10-01 00:50:00", "2010-11-02 01:40:00"), tz = "America/New_York"))
expect_equal(time_ceiling(x, "3000a", origin = time_floor(x, "day")),
ymd_hms(c("2010-10-01 01:40:00", "2010-11-02 02:30:00"), tz = "America/New_York"))
expect_equal(time_floor(x, "3000a", origin = time_floor(x, "month")),
ymd_hms(c("2010-10-01 00:50:00", "2010-11-02 01:50:00"), tz = "America/New_York"))
expect_equal(time_ceiling(x, "3000a", origin = time_floor(x, "month")),
ymd_hms(c("2010-10-01 01:40:00", "2010-11-02 02:40:00"), tz = "America/New_York"))
expect_equal(time_ceiling(x, "50AM", origin = time_floor(x, "month")),
ymd_hms(c("2010-10-01 01:40:00", "2010-11-02 02:40:00"), tz = "America/New_York"))
expect_equal(time_ceiling(x, "50AM", origin = time_floor(x, "month")),
time_ceiling(x, "50amins", origin = time_floor(x, "month")))
expect_equal(time_ceiling(x, "30amins", origin = time_floor(x, "month")),
time_ceiling(x, ".5ahours", origin = time_floor(x, "month")))
expect_equal(time_round(x, "3000a", origin = time_floor(x, "month")),
ymd_hms(c("2010-10-01 00:50:00", "2010-11-02 01:50:00"), tz = "America/New_York"))
expect_equal(time_round(x, "3000a", origin = time_floor(x, "month")),
ymd_hms(c("2010-10-01 00:50:00", "2010-11-02 01:50:00"), tz = "America/New_York"))
})
test_that("rounding with custom origin respects change_on_boundary", {
x <- ymd_hms(c("2010-10-01 01:40:00", "2010-11-02 02:30:00"), tz = "America/New_York")
expect_equal(time_ceiling(x, "3000a", change_on_boundary = FALSE, origin = time_floor(x, "day")),
ymd_hms(c("2010-10-01 01:40:00", "2010-11-02 02:30:00"), tz = "America/New_York"))
expect_equal(time_ceiling(x, "50amins", change_on_boundary = TRUE, origin = time_floor(x, "day")),
ymd_hms(c("2010-10-01 02:30:00", "2010-11-02 03:20:00"), tz = "America/New_York"))
})
test_that("tzone attributes of Dates is preserved", {
#23
d <- ymd("2020-01-01")
tzone <- "America/New_York"
attr(d, "tzone") <- tzone
expect_is(time_floor(d, "month"), "Date")
expect_is(time_floor(d, "hour"), "POSIXct")
expect_identical(time_floor(d, "month"), structure(ymd("2020-01-01"), tzone = tzone))
expect_identical(time_floor(d, "hour"), ymd("2020-01-01", tz = tzone))
expect_identical(time_ceiling(d, "hour"), ymd_hms("2020-01-01 01:00:00", tz = tzone))
expect_identical(time_ceiling(d, "hour", change_on_boundary = FALSE), ymd_hms("2020-01-01 00:00:00", tz = tzone))
})
test_that("Rounding infinite dates works as expected", {
tinf <- .POSIXct(c(Inf, -Inf))
dinf <- as.Date(c(Inf, -Inf))
expect_equal(time_floor(tinf), tinf)
expect_equal(as.numeric(time_floor(tinf)), floor(as.numeric(tinf)))
expect_equal(time_ceiling(tinf), tinf)
expect_equal(as.numeric(time_ceiling(tinf)), ceiling(as.numeric(tinf)))
expect_equal(time_round(tinf), tinf)
expect_equal(as.numeric(time_round(tinf)), round(as.numeric(tinf)))
expect_equal(time_floor(tinf, "month"), tinf)
expect_equal(as.numeric(time_floor(tinf)), floor(as.numeric(tinf)))
expect_equal(time_ceiling(tinf, "month"), tinf)
expect_equal(as.numeric(time_ceiling(tinf, "month")), ceiling(as.numeric(tinf)))
expect_equal(time_round(tinf, "month"), tinf)
expect_equal(as.numeric(time_round(tinf, "month")), round(as.numeric(tinf)))
expect_equal(time_floor(dinf, "day"), dinf)
expect_equal(as.numeric(time_floor(dinf, "day")), floor(as.numeric(dinf)))
expect_equal(time_ceiling(dinf, "day"), dinf)
expect_equal(as.numeric(time_ceiling(dinf, "day")), ceiling(as.numeric(dinf)))
expect_equal(time_round(dinf, "day"), dinf)
expect_equal(as.numeric(time_round(dinf, "day")), round(as.numeric(dinf)))
expect_equal(time_floor(dinf, "month"), dinf)
expect_equal(as.numeric(time_floor(dinf)), floor(as.numeric(dinf)))
expect_equal(time_ceiling(dinf, "month"), dinf)
expect_equal(as.numeric(time_ceiling(dinf, "month")), ceiling(as.numeric(dinf)))
expect_equal(time_round(dinf, "month"), dinf)
expect_equal(as.numeric(time_round(dinf, "month")), round(as.numeric(dinf)))
})
timechange/tests/testthat.R 0000644 0001762 0000144 00000000055 14337670452 015516 0 ustar ligges users
library(testthat)
test_check("timechange")
timechange/src/ 0000755 0001762 0000144 00000000000 14552163764 013162 5 ustar ligges users timechange/src/tzone.cpp 0000644 0001762 0000144 00000004451 14337670452 015027 0 ustar ligges users
#include "tzone.h"
#include "cpp11/logicals.hpp"
namespace chrono = std::chrono;
const char* tz_from_R_tzone(SEXP tz) {
if (Rf_isNull(tz)) {
return "";
} else {
if (!Rf_isString(tz))
Rf_error("'tz' is not a character vector");
const char* tz0 = CHAR(STRING_ELT(tz, 0));
if (strlen(tz0) == 0) {
if (LENGTH(tz) > 1) {
return CHAR(STRING_ELT(tz, 1));
}
}
return tz0;
}
}
const char* tz_from_tzone_attr(SEXP x){
return tz_from_R_tzone(Rf_getAttrib(x, Rf_install("tzone")));
}
const char* system_tz() {
auto sys_timezone = cpp11::package("base")["Sys.timezone"];
SEXP sys_tz = STRING_ELT(sys_timezone(), 0);
if (sys_tz == NA_STRING || strlen(CHAR(sys_tz)) == 0) {
Rf_warning("System timezone name is unknown. Please set environment variable TZ. Using UTC.");
return "UTC";
} else {
return CHAR(sys_tz);
}
}
const char* local_tz() {
// initialize once per session
static const char* SYS_TZ = strdup(system_tz());
const char* tz_env = std::getenv("TZ");
if (tz_env == NULL) {
return SYS_TZ;
} else if (strlen(tz_env) == 0) {
// If set but empty, R behaves in a system specific way and there is no way
// to infer local time zone.
Rf_warning("Environment variable TZ is set to \"\". Using system TZ.");
return SYS_TZ;
} else {
return tz_env;
}
}
bool load_tz(std::string tzstr, cctz::time_zone& tz) {
// return `true` if loaded, else false
if (tzstr.empty()) {
// CCTZ doesn't work on windows https://github.com/google/cctz/issues/53
/* std::cout << "Local TZ: " << local_tz() << std::endl; */
return cctz::load_time_zone(local_tz(), &tz);
} else {
if (!cctz::load_time_zone(tzstr, &tz)) {
auto el = TZMAP.find(tzstr);
if (el != TZMAP.end()) {
tz = cctz::fixed_time_zone(chrono::hours(el->second));
} else {
return false;
}
}
return true;
}
}
void load_tz_or_fail(std::string tzstr, cctz::time_zone& tz, std::string error_msg) {
if (!load_tz(tzstr, tz)) {
Rf_error(error_msg.c_str(), tzstr.c_str());
}
}
[[cpp11::register]]
cpp11::strings C_local_tz() {
return Rf_mkString(local_tz());
}
[[cpp11::register]]
bool C_valid_tz(const cpp11::strings tz_name) {
cctz::time_zone tz;
std::string tzstr(tz_name[0]);
return load_tz(tzstr, tz);
}
timechange/src/update.cpp 0000644 0001762 0000144 00000050473 14345622261 015151 0 ustar ligges users // CIVIL TIME:
// https://github.com/google/cctz/blob/master/include/cctz/civil_time.h
// https://github.com/devjgm/papers/blob/master/d0215r1.md
// TIME ZONES:
// https://github.com/google/cctz/blob/master/include/cctz/time_zone.h
// https://github.com/devjgm/papers/blob/master/d0216r1.md
// https://raw.githubusercontent.com/devjgm/papers/master/resources/struct-civil_lookup.png
// https://en.wikipedia.org/wiki/Tz_database
// https://github.com/eggert/tz/blob/2018d/etcetera#L36-L42
// R's timezone registry:
// https://github.com/SurajGupta/r-source/blob/master/src/extra/tzone/registryTZ.c
// C++20 date/calendar proposal: https://github.com/HowardHinnant/date
#include "common.h"
#include "cpp11/doubles.hpp"
#include "cpp11/function.hpp"
#include "cpp11/integers.hpp"
#include "cpp11/strings.hpp"
#include "tzone.h"
#include "R_ext/Print.h"
#define SET_NEGATIVE(x) if (do_negative) { is_negative = x < 0; do_negative = false; }
[[cpp11::register]]
cpp11::writable::doubles C_time_update(const cpp11::doubles dt,
const cpp11::list updates,
const SEXP tz,
const std::string roll_month,
const cpp11::strings roll_dst,
const int week_start = 1,
const bool exact = false) {
if (dt.empty())
// FIXME: ignored tz argument
return(posixct(tz_from_tzone_attr(dt)));
RollMonth rmonth = parse_month_roll(roll_month);
DST rdst(roll_dst);
cpp11::integers year, month, yday, mday, wday, hour, minute;
cpp11::doubles second;
bool do_year = updates.contains("year"),
do_month = updates.contains("month"),
do_yday = updates.contains("yday"),
do_mday = updates.contains("mday"),
do_wday = updates.contains("wday"),
do_hour = updates.contains("hour"),
do_minute = updates.contains("minute"),
do_second = updates.contains("second");
if (do_year) year = to_integers(updates["year"]);
if (do_month) month = to_integers(updates["month"]);
if (do_yday) yday = to_integers(updates["yday"]);
if (do_mday) mday = to_integers(updates["mday"]);
if (do_wday) wday = to_integers(updates["wday"]);
if (do_hour) hour = to_integers(updates["hour"]);
if (do_minute) minute = to_integers(updates["minute"]);
if (do_second) second = to_doubles(updates["second"]);
std::vector sizes {
year.size(), month.size(), yday.size(), mday.size(),
wday.size(), hour.size(), minute.size(), second.size()
};
// tz is always there, so the output is at least length 1
R_xlen_t N = std::max(*std::max_element(sizes.begin(), sizes.end()), dt.size());
bool loop_year = sizes[0] == N, loop_month = sizes[1] == N,
loop_yday = sizes[2] == N, loop_mday = sizes[3] == N, loop_wday = sizes[4] == N,
loop_hour = sizes[5] == N, loop_minute = sizes[6] == N, loop_second = sizes[7] == N,
loop_dt = dt.size() == N;
// fixme: more informative message
if (do_year && sizes[0] != 1 && !loop_year) Rf_error("time_update: Incompatible size of 'year' vector");
if (do_month && sizes[1] != 1 && !loop_month) Rf_error("time_update: Incompatible size of 'month' vector");
if (do_yday && sizes[2] != 1 && !loop_yday) Rf_error("time_update: Incompatible size of 'yday' vector");
if (do_mday && sizes[3] != 1 && !loop_mday) Rf_error("time_update: Incompatible size of 'mday' vector");
if (do_wday && sizes[4] != 1 && !loop_wday) Rf_error("time_update: Incompatible size of 'wday' vector");
if (do_hour && sizes[5] != 1 && !loop_hour) Rf_error("time_update: Incompatible size of 'hour' vector");
if (do_minute && sizes[6] != 1 && !loop_minute) Rf_error("time_update: Incompatible size of 'minute' vector");
if (do_second && sizes[7] != 1 && !loop_second) Rf_error("time_update: Incompatible size of 'second' vector");
if (dt.size() > 1 && !loop_dt)
Rf_error("time_update: Incompatible length of time and update vectors");
if (do_yday + do_mday + do_wday > 1)
Rf_error("Conflicting days input, only one of yday, mday and wday must be supplied");
if (do_yday && (do_month || do_mday))
Rf_error("Setting `yday` in combination with `month` or `mday` is not supported");
if (do_wday && (do_year || do_month || do_mday))
Rf_error("Setting `yday` in combination with `year`, `month` or `mday` is not supported");
if ((do_yday && do_wday))
Rf_error("Setting both `yday` and `wday` is not supported");
std::string tzfrom = tz_from_tzone_attr(dt);
cctz::time_zone otzone;
load_tz_or_fail(tzfrom, otzone, "CCTZ: Invalid timezone of the input vector: \"%s\"");
std::string tzto;
cctz::time_zone ntzone;
if (Rf_isNull(tz)) {
tzto = tzfrom;
} else {
tzto = tz_from_R_tzone(tz);
}
load_tz_or_fail(tzto, ntzone, "CCTZ: Unrecognized tzone: \"%s\"");
cpp11::writable::doubles out(N);
init_posixct(out, tzto.c_str());
// all vectors are either size N or 1
for (R_xlen_t i = 0; i < N; i++)
{
double dti = loop_dt ? dt[i] : dt[0];
int_fast64_t secs = floor_to_int64(dti);
if (ISNAN(dti) || secs == NA_INT64) {
if (dti == R_PosInf)
out[i] = R_PosInf;
else if (dti == R_NegInf)
out[i] = R_NegInf;
else
out[i] = NA_REAL;
continue;
}
double rem = dti - secs;
sys_seconds ss(secs);
time_point otp(ss);
cctz::civil_second ocs = cctz::convert(otp, otzone);
int_fast64_t
y = ocs.year(), m = ocs.month(), d = ocs.day(),
H = ocs.hour(), M = ocs.minute(), S = ocs.second();
/* Rprintf("dti: %f sec:%ld H:%d M:%d S:%d\n", dti, secs, H, M, S); */
if (do_year) {
y = loop_year ? year[i] : year[0];
if (y == NA_INT32) { out[i] = NA_REAL; continue; }
}
if (do_month) {
m = loop_month ? month[i] : month[0];
if (m == NA_INT32) { out[i] = NA_REAL; continue; }
}
if (do_mday) {
d = loop_mday ? mday[i] : mday[0];
if (d == NA_INT32) { out[i] = NA_REAL; continue; }
}
cctz::civil_month cm = cctz::civil_month(y, m);
if (rmonth == RollMonth::NAym) {
// lubridate historical case of returning NA when intermediate month+year result
// in a invalid date
if (d != cctz::civil_day(y, m, d).day()) { out[i] = NA_REAL; continue; }
}
if (do_yday || do_wday) {
cctz::civil_day cd = cctz::civil_day(y, m, d);
d = cd.day();
// - yda and wday are recycled
// - yday and wday apply after y,m,d computaiton and is always valid
if (do_yday) {
// yday and d are 1 based
d = d - cctz::get_yearday(cd);
if (loop_yday) d += yday[i]; else d += yday[0];
}
if (do_wday) {
// wday is 1 based and starts on week_start
int cur_wday = (static_cast(cctz::get_weekday(cd)) + 8 - week_start) % 7;
d = d - cur_wday - 1;
if (loop_wday) d += wday[i]; else d += wday[0];
}
}
if (do_hour) {
H = loop_hour ? hour[i] : hour[0];
if (H == NA_INT32) { out[i] = NA_REAL; continue; }
}
if (do_minute) {
M = loop_minute ? minute[i] : minute[0];
if (M == NA_INT32) { out[i] = NA_REAL; continue; }
}
if (do_second) {
double s = loop_second ? second[i] : second[0];
if (ISNAN(s)) { out[i] = NA_REAL; continue; }
S = floor_to_int64(s);
if (S == NA_INT64) { out[i] = NA_REAL; continue; }
rem = s - S;
}
cctz::civil_second ncs(y, m, d, H, M, S);
if (exact) {
bool invalid = false;
if (do_yday || do_wday) {
if (do_yday) {
invalid = invalid ||
(cctz::get_yearday(ncs) != (loop_yday ? yday[i] : yday[0]));
}
// FIXME: fix this logic
/* if (do_wday) { */
/* int new_wday = static_cast(cctz::get_weekday(ncs)) + 1; */
/* invalid = invalid || */
/* new_wday != (loop_yday ? wday[i] : wday[0]); */
/* } */
} else {
invalid =
ncs.year() != y || ncs.month() != m || ncs.day() != d ||
ncs.hour() != H || ncs.minute() != M || ncs.second() != S;
}
if (invalid) {
out[i] = NA_REAL;
continue;
}
} else {
// Month recycling - yda and wday are incompatible with year,month,mday units
// and month recycling does not make sense
if (!(do_yday || do_wday)) {
// If day is not expected we roll. Rolling can be triggered by recycling
// d,H,M,S units or by falling into a non-existing day by virtue of setting
// year or month.
if (ncs.day() != d && ncs.month() != cm.month()) {
switch(rmonth) {
case RollMonth::FULL: break;
case RollMonth::NA:
out[i] = NA_REAL;
continue;
case RollMonth::NAym: break;
case RollMonth::BOUNDARY: {
cctz::civil_day cd = cctz::civil_day(cctz::civil_month(ncs));
ncs = cctz::civil_second(cd.year(), cd.month(), cd.day(), 0, 0, 0);
rem = 0.0;
break;
}
case RollMonth::POSTDAY:
ncs = cctz::civil_second(ncs.year(), ncs.month(), 1, ncs.hour(), ncs.minute(), ncs.second());
break;
case RollMonth::PREDAY: {
cctz::civil_day cd = cctz::civil_day(cctz::civil_month(ncs)) - 1;
ncs = cctz::civil_second(cd.year(), cd.month(), cd.day(), ncs.hour(), ncs.minute(), ncs.second());
break;
}
}
}
}
}
const cctz::time_zone::civil_lookup ncl = ntzone.lookup(ncs);
out[i] = civil_lookup_to_posix(ncl, otzone, otp, ocs, rdst, rem);
}
return out;
}
[[cpp11::register]]
cpp11::writable::doubles C_time_add(const cpp11::doubles& dt,
const cpp11::list periods,
const std::string roll_month,
const cpp11::strings roll_dst) {
if (dt.size() == 0)
// FIXME: ignored tz argument
return(posixct(tz_from_tzone_attr(dt)));
RollMonth rmonth = parse_month_roll(roll_month);
DST rdst(roll_dst, true);
bool
do_year = periods.contains("year"),
do_month = periods.contains("month"),
do_day = periods.contains("day"),
do_week = periods.contains("week"),
do_hour = periods.contains("hour"),
do_minute = periods.contains("minute"),
do_second = periods.contains("second");
cpp11::integers year, month, week, day, hour, minute;
cpp11::doubles second;
if (do_year) year = to_integers(periods["year"]);
if (do_month) month = to_integers(periods["month"]);
if (do_week) week = to_integers(periods["week"]);
if (do_day) day = to_integers(periods["day"]);
if (do_hour) hour = to_integers(periods["hour"]);
if (do_minute) minute = to_integers(periods["minute"]);
if (do_second) second = to_doubles(periods["second"]);
std::vector sizes {
year.size(), month.size(), week.size(), day.size(),
hour.size(), minute.size(), second.size()
};
// tz is always there, so the output is at least length 1
R_xlen_t N = std::max(*std::max_element(sizes.begin(), sizes.end()), dt.size());
bool loop_year = year.size() == N, loop_month = month.size() == N,
loop_week = week.size() == N, loop_day = day.size() == N,
loop_hour = hour.size() == N, loop_minute = minute.size() == N,
loop_second = second.size() == N, loop_dt = dt.size() == N;
// fixme: provide vec size info in the message
if (do_year && year.size() != 1 && !loop_year) Rf_error("time_add: Incompatible size of 'year' vector");
if (do_month && month.size() != 1 && !loop_month) Rf_error("time_add: Incompatible size of 'month' vector");
if (do_week && week.size() != 1 && !loop_week) Rf_error("time_add: Incompatible size of 'week' vector");
if (do_day && day.size() != 1 && !loop_day) Rf_error("time_add: Incompatible size of 'day' or 'mday' vector");
if (do_hour && hour.size() != 1 && !loop_hour) Rf_error("time_add: Incompatible size of 'hour' vector");
if (do_minute && minute.size() != 1 && !loop_minute) Rf_error("time_add: Incompatible size of 'minute' vector");
if (do_second && second.size() != 1 && !loop_second) Rf_error("time_add: Incompatible size of 'second' vector");
if (dt.size() > 1 && !loop_dt)
Rf_error("time_add: length of datetime vector must be 1 or match the length of updating vectors");
std::string tz_name = tz_from_tzone_attr(dt);
cctz::time_zone tz;
load_tz_or_fail(tz_name, tz, "CCTZ: Invalid timezone of the input vector: \"%s\"");
cpp11::writable::doubles out(N);
init_posixct(out, tz_name.c_str());
int y = 0, m = 0, w = 0, d = 0, H = 0, M = 0;
double s = 0.0;
int_fast64_t S = 0;
cctz::civil_year cy;
cctz::civil_month cm;
cctz::civil_day cd;
cctz::civil_hour cH;
cctz::civil_minute cM;
cctz::civil_second cS;
// all vectors are either size N or 1
for (R_xlen_t i = 0; i < N; i++)
{
bool is_negative = false;
bool do_negative = true;
double dti = loop_dt ? dt[i] : dt[0];
int_fast64_t secs = floor_to_int64(dti);
if (ISNAN(dti) || secs == NA_INT64) {
if (dti == R_PosInf)
out[i] = R_PosInf;
else if (dti == R_NegInf)
out[i] = R_NegInf;
else
out[i] = NA_REAL;
continue;
}
bool add_my_hms = true;
double rem = dti - secs;
sys_seconds ss(secs);
time_point tp(ss);
cctz::civil_second cs = cctz::convert(tp, tz);
int_fast64_t
ty = cs.year(), tm = cs.month(), td = cs.day(),
tH = cs.hour(), tM = cs.minute(), tS = cs.second();
cy = cctz::civil_year(ty);
if (do_year) {
y = loop_year ? year[i] : year[0];
if (y == NA_INT32) { out[i] = NA_REAL; continue; }
SET_NEGATIVE(y)
cy += y;
}
cm = cctz::civil_month(cy) + (tm -1);
if (do_month) {
m = loop_month ? month[i] : month[0];
if (m == NA_INT32) { out[i] = NA_REAL; continue; }
SET_NEGATIVE(m)
cm += m;
}
cd = cctz::civil_day(cm) + (td - 1);
if (cd.day() != td) {
// month rolling kicks in
switch(rmonth) {
case RollMonth::FULL: break;
case RollMonth::PREDAY:
cd = cctz::civil_day(cctz::civil_month(cd)) - 1;
break;
case RollMonth::BOUNDARY:
cd = cctz::civil_day(cctz::civil_month(cd));
add_my_hms = false;
break;
case RollMonth::POSTDAY:
cd = cctz::civil_day(cctz::civil_month(cd));
break;
case RollMonth::NA:
out[i] = NA_REAL;
continue;
case RollMonth::NAym: break;
}
}
if (do_week) {
w = loop_week ? week[i] : week[0];
if (w == NA_INT32) { out[i] = NA_REAL; continue; }
SET_NEGATIVE(w)
cd += w * 7;
}
if (do_day) {
d = loop_day ? day[i] : day[0];
if (d == NA_INT32) { out[i] = NA_REAL; continue; }
SET_NEGATIVE(d)
cd += d;
}
cH = cctz::civil_hour(cd);
if (add_my_hms) cH += tH;
if (do_hour) {
H = loop_hour ? hour[i] : hour[0];
if (H == NA_INT32) { out[i] = NA_REAL; continue; }
SET_NEGATIVE(H)
cH += H;
}
cM = cctz::civil_minute(cH);
if (add_my_hms)
cM += tM;
if (do_minute) {
M = loop_minute ? minute[i] : minute[0];
if (M == NA_INT32) { out[i] = NA_REAL; continue; }
SET_NEGATIVE(M)
cM += M;
}
cS = cctz::civil_second(cM);
if (add_my_hms) cS += tS;
if (do_second) {
s = loop_second ? second[i] : second[0];
if (ISNAN(s)) { out[i] = NA_REAL; continue; }
S = floor_to_int64(s);
if (S == NA_INT64) { out[i] = NA_REAL; continue; }
SET_NEGATIVE(S)
rem += s - S;
cS += S;
}
s = civil_lookup_to_posix(tz.lookup(cS), rdst, is_negative);
out[i] = s + rem;
}
return out;
}
[[cpp11::register]]
cpp11::writable::doubles C_force_tz(const cpp11::doubles dt,
const cpp11::strings tz,
const cpp11::strings roll_dst) {
// roll: logical, if `true`, and `time` falls into the DST-break, assume the
// next valid civil time, otherwise return NA
DST rdst(roll_dst);
if (tz.size() != 1)
Rf_error("`tz` argument must be a single character string");
std::string tzfrom_name = tz_from_tzone_attr(dt);
std::string tzto_name(tz[0]);
cctz::time_zone tzfrom, tzto;
load_tz_or_fail(tzfrom_name, tzfrom, "CCTZ: Unrecognized timezone of the input vector: \"%s\"");
load_tz_or_fail(tzto_name, tzto, "CCTZ: Unrecognized output timezone: \"%s\"");
/* std::cout << "TZ from:" << tzfrom.name() << std::endl; */
/* std::cout << "TZ to:" << tzto.name() << std::endl; */
size_t n = dt.size();
cpp11::writable::doubles out(n);
init_posixct(out, tzto_name.c_str());
for (size_t i = 0; i < n; i++)
{
int_fast64_t secs = floor_to_int64(dt[i]);
/* printf("na: %i na64: %+" PRIiFAST64 " secs: %+" PRIiFAST64 " dt: %f\n", NA_INTEGER, INT_FAST64_MIN, secs, dt[i]); */
if (secs == NA_INT64) {out[i] = NA_REAL; continue; }
double rem = dt[i] - secs;
sys_seconds secsfrom(secs);
time_point tpfrom(secsfrom);
cctz::civil_second ct1 = cctz::convert(tpfrom, tzfrom);
const cctz::time_zone::civil_lookup cl2 = tzto.lookup(ct1);
out[i] = civil_lookup_to_posix(cl2, tzfrom, tpfrom, ct1, rdst, rem);
}
return out;
}
[[cpp11::register]]
cpp11::writable::doubles C_force_tzs(const cpp11::doubles dt,
const cpp11::strings tzs,
const cpp11::strings tz_out,
const cpp11::strings roll_dst) {
// roll: logical, if `true`, and `time` falls into the DST-break, assume the
// next valid civil time, otherwise return NA
DST rdst(roll_dst);
if (tz_out.size() != 1)
Rf_error("In 'tzout' argument must be of length 1");
if (tzs.size() != dt.size())
Rf_error("In 'C_force_tzs' tzs and dt arguments must be of the same length");
std::string tzfrom_name = tz_from_tzone_attr(dt);
std::string tzout_name(tz_out[0]);
cctz::time_zone tzfrom, tzto, tzout;
load_tz_or_fail(tzfrom_name, tzfrom, "CCTZ: Unrecognized timezone of input vector: \"%s\"");
load_tz_or_fail(tzout_name, tzout, "CCTZ: Unrecognized timezone: \"%s\"");
std::string tzto_old_name("not-a-tz");
size_t n = dt.size();
cpp11::writable::doubles out(n);
init_posixct(out, tzout_name.c_str());
for (size_t i = 0; i < n; i++)
{
std::string tzto_name(tzs[i]);
if (tzto_name != tzto_old_name) {
load_tz_or_fail(tzto_name, tzto, "CCTZ: Unrecognized timezone: \"%s\"");
tzto_old_name = tzto_name;
}
int_fast64_t secs = floor_to_int64(dt[i]);
if (secs == NA_INT64) { out[i] = NA_REAL; continue; }
double rem = dt[i] - secs;
sys_seconds secsfrom(secs);
time_point tpfrom(secsfrom);
cctz::civil_second csfrom = cctz::convert(tpfrom, tzfrom);
const cctz::time_zone::civil_lookup clto = tzto.lookup(csfrom);
out[i] = civil_lookup_to_posix(clto, tzfrom, tpfrom, csfrom, rdst, rem);
}
return out;
}
[[cpp11::register]]
cpp11::writable::doubles C_local_clock(const cpp11::doubles dt,
const cpp11::strings tzs) {
if (tzs.size() != dt.size())
Rf_error("`tzs` and `dt` arguments must be of the same length");
std::string tzfrom_name = tz_from_tzone_attr(dt);
std::string tzto_old_name("not-a-tz");
cctz::time_zone tzto;
size_t n = dt.size();
cpp11::writable::doubles out(n);
for (size_t i = 0; i < n; i++)
{
std::string tzto_name(tzs[i]);
if (tzto_name != tzto_old_name) {
load_tz_or_fail(tzto_name, tzto, "CCTZ: Unrecognized timezone: \"%s\"");
tzto_old_name = tzto_name;
}
int_fast64_t secs = floor_to_int64(dt[i]);
if (secs == NA_INT64) { out[i] = NA_REAL; continue; }
double rem = dt[i] - secs;
sys_seconds secsfrom(secs);
time_point tpfrom(secsfrom);
cctz::civil_second cs = cctz::convert(tpfrom, tzto);
cctz::civil_second cs_floor = cctz::civil_second(cctz::civil_day(cs));
out[i] = cs - cs_floor + rem;
}
return out;
}
timechange/src/get.cpp 0000644 0001762 0000144 00000010515 14330477622 014442 0 ustar ligges users
#include "common.h"
#include "cpp11/data_frame.hpp"
// CIVIL TIME:
// https://github.com/google/cctz/blob/master/include/cctz/civil_time.h
// https://github.com/devjgm/papers/blob/master/d0215r1.md
// TIME ZONES:
// https://github.com/google/cctz/blob/master/include/cctz/time_zone.h
// https://github.com/devjgm/papers/blob/master/d0216r1.md
// https://raw.githubusercontent.com/devjgm/papers/master/resources/struct-civil_lookup.png
// R's timezone registry:
// https://github.com/SurajGupta/r-source/blob/master/src/extra/tzone/registryTZ.c
// C++20 date/calendar proposal: https://github.com/HowardHinnant/date
bool charvec_contains(const cpp11::strings vec, const std::string& elt) {
return std::find(vec.begin(), vec.end(), elt) != vec.end();
}
[[cpp11::register]]
cpp11::writable::list C_time_get(const cpp11::doubles& dt,
const cpp11::strings& components,
const int week_start = 1) {
bool
do_year = false, do_month = false, do_yday = false,
do_mday = false, do_wday = false, do_hour = false,
do_minute = false, do_second = false;
R_xlen_t N = dt.size(), N_comps = components.size();
for (std::string comp: components) {
if (comp == "year") { do_year = true; continue; };
if (comp == "month") { do_month = true; continue; };
if (comp == "yday") { do_yday = true; continue; };
if (comp == "day" || comp == "mday") { do_mday = true; continue; };
if (comp == "wday") { do_wday = true; continue; };
if (comp == "hour") { do_hour = true; continue; };
if (comp == "minute") { do_minute = true; continue; };
if (comp == "second") { do_second = true; continue; };
Rf_error("Invalid datetime component '%s'", comp.c_str());
}
cpp11::writable::integers year(do_year ? N : 0);
cpp11::writable::integers month(do_month ? N :0);
cpp11::writable::integers yday(do_yday ? N : 0);
cpp11::writable::integers mday(do_mday ? N : 0);
cpp11::writable::integers wday(do_wday ? N : 0);
cpp11::writable::integers hour(do_hour ? N : 0);
cpp11::writable::integers minute(do_minute ? N : 0);
cpp11::writable::doubles second(do_second ? N : 0);
std::string tz = tz_from_tzone_attr(dt);
cctz::time_zone tzone;
load_tz_or_fail(tz, tzone, "CCTZ: Invalid timezone of the input vector: \"%s\"");
for (R_xlen_t i = 0; i < N; i++)
{
double dti = dt[i];
int_fast64_t secs = floor_to_int64(dti);
double rem = dti - secs;
if (ISNAN(dti) || secs == NA_INT64) {
if (do_year) year[i] = NA_INTEGER;
if (do_month) month[i] = NA_INTEGER;
if (do_yday) yday[i] = NA_INTEGER;
if (do_mday) mday[i] = NA_INTEGER;
if (do_wday) wday[i] = NA_INTEGER;
if (do_hour) hour[i] = NA_INTEGER;
if (do_minute) minute[i] = NA_INTEGER;
if (do_second) second[i] = NA_REAL;
continue;
}
sys_seconds ss(secs);
time_point tp(ss);
cctz::civil_second ct = cctz::convert(tp, tzone);
if (do_year) year[i] = ct.year();
if (do_month) month[i] = ct.month();
if (do_yday) yday[i] = cctz::get_yearday(cctz::civil_day(ct));
if (do_mday) mday[i] = ct.day();
if (do_wday) {
// wday is 1 based and starts on week_start
int cur_wday = (static_cast(cctz::get_weekday(cctz::civil_day(ct))) + 8 - week_start) % 7;
wday[i] = cur_wday;
}
if (do_hour) hour[i] = ct.hour();
if (do_minute) minute[i] = ct.minute();
if (do_second) second[i] = ct.second() + rem;
}
cpp11::writable::list out(N_comps);
cpp11::writable::strings names(N_comps);
R_len_t pos = 0;
for (std::string comp: components) {
if (comp == "year") { out[pos] = year; names[pos] = "year"; pos++; };
if (comp == "month") { out[pos] = month; names[pos] = "month"; pos++; };
if (comp == "yday") { out[pos] = yday; names[pos] = "yday"; pos++; };
if (comp == "day") { out[pos] = mday; names[pos] = "day"; pos++; };
if (comp == "mday") { out[pos] = mday; names[pos] = "mday"; pos++; }
if (comp == "wday") { out[pos] = wday; names[pos] = "wday"; pos++; };
if (comp == "hour") { out[pos] = hour; names[pos] = "hour"; pos++; };
if (comp == "minute") { out[pos] = minute; names[pos] = "minute"; pos++; };
if (comp == "second") { out[pos] = second; names[pos] = "second"; pos++; };
}
out.attr("names") = names;
return out;
}
timechange/src/common.cpp 0000644 0001762 0000144 00000012123 14413257337 015151 0 ustar ligges users
#include "common.h"
#include "cpp11/doubles.hpp"
int_fast64_t NA_INT32 = static_cast(NA_INTEGER);
int_fast64_t NA_INT64 = std::numeric_limits::min();
double fINT64_MAX = static_cast(std::numeric_limits::max());
double fINT64_MIN = static_cast(std::numeric_limits::min());
int_fast64_t floor_to_int64(double x) {
// maybe fixme: no warning yet on integer overflow
if (ISNAN(x))
return NA_INT64;
x = std::floor(x);
if (x > fINT64_MAX || x <= fINT64_MIN) {
return NA_INT64;
}
return static_cast(x);
}
double civil_lookup_to_posix(const cctz::time_zone::civil_lookup& cl,
const DST& dst,
const bool is_negative) noexcept {
time_point tp;
switch (cl.kind) {
case cctz::time_zone::civil_lookup::SKIPPED:
// meaning of pre/post in CCTZ is not the same as here. It's inverted.
switch (dst.skipped) {
case RollDST::PRE: tp = cl.post; break;
case RollDST::BOUNDARY: tp = cl.trans; break;
case RollDST::POST: tp = cl.pre; break;
case RollDST::XFIRST: tp = is_negative ? cl.pre : cl.post; break;
case RollDST::XLAST: tp = is_negative ? cl.post : cl.pre; break;
case RollDST::NA: return NA_REAL;
}
break;
case cctz::time_zone::civil_lookup::REPEATED:
switch (dst.repeated) {
case RollDST::PRE: tp = cl.pre; break;
case RollDST::BOUNDARY: tp = cl.trans; break;
case RollDST::POST: tp = cl.post; break;
case RollDST::XFIRST: tp = is_negative ? cl.post : cl.pre; break;
case RollDST::XLAST: tp = is_negative ? cl.pre : cl.post; break;
case RollDST::NA: return NA_REAL;
}
break;
case cctz::time_zone::civil_lookup::UNIQUE:
tp = cl.pre;
}
return tp.time_since_epoch().count();
}
// Helper for conversion functions. Get seconds from civil_lookup, but relies on
// original time pre/post time if cl_new falls in repeated interval.
double civil_lookup_to_posix(const cctz::time_zone::civil_lookup& cl_new, // new lookup
const cctz::time_zone& tz_old, // original time zone
const time_point& tp_old, // original time point
const cctz::civil_second& cs_old, // original time in secs
const DST& dst,
double remainder) noexcept {
if (cl_new.kind == cctz::time_zone::civil_lookup::REPEATED) {
if (dst.repeated == RollDST::BOUNDARY)
remainder = 0.0;
// match pre or post time of original time
time_point tp_new;
const cctz::time_zone::civil_lookup cl_old = tz_old.lookup(cs_old);
if (cl_old.kind == cctz::time_zone::civil_lookup::REPEATED) {
if (tp_old >= cl_old.trans) {
tp_new = cl_new.post;
} else {
tp_new = cl_new.pre;
}
return tp_new.time_since_epoch().count() + remainder;
}
} else if (cl_new.kind == cctz::time_zone::civil_lookup::SKIPPED) {
if (dst.repeated == RollDST::BOUNDARY)
remainder = 0.0;
}
return civil_lookup_to_posix(cl_new, dst) + remainder;
}
cpp11::integers to_integers(SEXP x) {
if (TYPEOF(x) == INTSXP) {
return cpp11::as_cpp(x);
} else if (TYPEOF(x) == LGLSXP) {
cpp11::logicals xn = cpp11::as_cpp(x);
R_xlen_t len = xn.size();
cpp11::writable::integers ret(len);
for (R_xlen_t i = 0; i < len; ++i) {
int el = xn[i];
if (cpp11::is_na(el)) {
ret[i] = cpp11::na();
} else {
ret[i] = static_cast(el);
}
}
return ret;
} else if (TYPEOF(x) == REALSXP) {
cpp11::doubles xn = cpp11::as_cpp(x);
R_xlen_t len = xn.size();
cpp11::writable::integers ret(len);
for (R_xlen_t i = 0; i < len; ++i) {
double el = xn[i];
if (cpp11::is_na(el)) {
ret[i] = cpp11::na();
} else if (is_convertable_without_loss_to_integer(el)) {
ret[i] = static_cast(el);
} else {
throw std::runtime_error("All elements must be integer-like");
}
}
return ret;
}
throw cpp11::type_error(INTSXP, TYPEOF(x));
}
cpp11::doubles to_doubles(SEXP x) {
if (TYPEOF(x) == REALSXP) {
return cpp11::as_cpp(x);
} else if (TYPEOF(x) == LGLSXP) {
cpp11::logicals xn = cpp11::as_cpp(x);
R_xlen_t len = xn.size();
cpp11::writable::doubles ret(len);
for (R_xlen_t i = 0; i < len; ++i) {
int el = xn[i];
if (cpp11::is_na(el)) {
ret[i] = cpp11::na();
} else {
ret[i] = static_cast(el);
}
}
return ret;
} else if (TYPEOF(x) == INTSXP) {
cpp11::integers xn = cpp11::as_cpp(x);
R_xlen_t len = xn.size();
cpp11::writable::doubles ret(len);
for (R_xlen_t i = 0; i < len; ++i) {
int el = xn[i];
if (cpp11::is_na(el)) {
ret[i] = cpp11::na();
} else {
ret[i] = static_cast(el);
}
}
return ret;
}
throw cpp11::type_error(REALSXP, TYPEOF(x));
}
timechange/src/parse.c 0000644 0001762 0000144 00000010436 14357542700 014436 0 ustar ligges users #include "R_ext/Error.h"
#include "R_ext/Print.h"
#include
#include
#include
#include
#define ALPHA(X) (((X) >= 'a' && (X) <= 'z') || ((X) >= 'A' && (X) <= 'Z'))
#define DIGIT(X) ((X) >= '0' && (X) <= '9')
// Find maximal partial match in `strings`.
//
// Increment *c and return index in 0..(length(strings)-1) if match was found,
// -1 if not. Matching starts from *c, with all non-alpha-numeric characters
// pre-skipped.
//
// - *c: pointer to a character in a C string (incremented by side effect)
// - *stings: pointer to an array of C strings to be matched to
// - strings_len: length of strings array
int parse_alphanum(char **c, const char **strings, const int strings_len,
const bool ignore_case){
// tracking array: all valid objects are marked with 1, invalid with 0
int track[strings_len];
for (int i = 0; i < strings_len; i++){
track[i] = 1;
}
int j = 0, out = -1, good_tracks = strings_len;
while (**c && !ALPHA(**c) && !DIGIT(**c)) (*c)++;
while (**c && good_tracks) {
// stop when all tracks have been exhausted
for (int i = 0; i < strings_len; i++) {
// keep going while at least one valid track
if (track[i]){
if (strings[i][j]) {
if (**c == strings[i][j] || (ignore_case && (tolower(**c) == strings[i][j]))) {
out = i;
} else { // invalidate track i if not matching
track[i] = 0;
good_tracks--;
}
} else { // reached to the end of string i; return it if it was the last track
good_tracks--;
if (good_tracks == 0) {
out = i;
}
}
}
}
if (good_tracks) {
(*c)++;
j++;
}
}
return out;
}
// Latter values have precedence.
static const char *UNITS[] = {
"bimonths", "quarters", "halfyears", "seasons",
"AH", "ahours",
"AM", "amins", "aminutes",
"AS", "asecs", "aseconds",
"S", "secs", "seconds",
"M", "mins", "minutes",
"H", "hours",
"D", "days",
"W", "weeks",
"months",
"Y", "years"
};
static const char *CANONICAL_UNITS[] = {
"bimonth", "quarter", "halfyear", "season",
"ahour", "ahour",
"aminute", "aminute", "aminute",
"asecond", "asecond", "asecond",
"second", "second", "second",
"minute", "minute", "minute",
"hour", "hour",
"day", "day",
"week", "week",
"month",
"year", "year"
};
#define N_UNITS 27
typedef struct {
int ix;
double n;
} Unit ;
Unit parse_unit(const char *el, char **c) {
Unit unit = {-1, -1};
double v = strtod(el, c);
bool parsed_n = false;
if (*c != el) {
unit.n = v;
parsed_n = true;
}
if (**c) {
unit.ix = parse_alphanum(c, UNITS, N_UNITS, false);
if (unit.ix >=0 && !parsed_n)
unit.n = 1; // units without numeric (like "month")
}
if (parsed_n && unit.ix < 0)
Rf_error("Invalid unit specification '%s'\n", el);
return unit;
}
SEXP C_parse_unit(SEXP str) {
if (TYPEOF(str) != STRSXP)
error("STR argument must be a character vector");
int n = LENGTH(str);
const char* names[] = {"n", "unit", ""};
// store parsed units in a N_PERIOD_UNITS x n matrix
SEXP out = PROTECT(mkNamed(VECSXP, names));
SEXP val = PROTECT(allocVector(REALSXP, n));
SEXP unit = PROTECT(allocVector(STRSXP, n));
double *real_val = REAL(val);
for (int i = 0; i < n; i++) {
const char *el0 = CHAR(STRING_ELT(str, i));
const char *el = el0;
char *c;
Unit u = {-1, -1};
Unit tu = parse_unit(el, &c);
while (el != c) {
/* Rprintf("0:el:'%s' c:'%s' tu:%f/%d u:%f/%d\n", el, c, tu.n, tu.ix, u.n, u.ix); */
el = c;
// ignore 0 units as in "2d 0H 0S" which is lubridate's string representation of periods
if (tu.ix >= 0 && tu.n != 0) {
if (u.n !=0 && u.ix >= 0)
Rf_error("Heterogeneous unit in '%s'\n", el0);
u = tu;
}
// enforce non-alpha separators (avoid monthmonth)
if (*c && ALPHA(*c))
Rf_error("Invalid unit specification '%s' (at %s)\n", el0, c);
tu = parse_unit(el, &c);
}
if (u.ix < 0) {
Rf_error("Invalid unit specification '%s'\n", el0);
} else {
SET_STRING_ELT(unit, i, mkChar(CANONICAL_UNITS[u.ix]));
real_val[i] = u.n;
}
}
SET_VECTOR_ELT(out, 0, val);
SET_VECTOR_ELT(out, 1, unit);
UNPROTECT(3);
return out;
}
timechange/src/tzone.h 0000644 0001762 0000144 00000001163 14435137263 014466 0 ustar ligges users #ifndef TIMECHANGE_TZONE_H
#define TIMECHANGE_TZONE_H
#include
#include "cctz/time_zone.h"
#include
const std::unordered_map TZMAP {
{"GMT", 0},
{"CEST", 2}, {"CET", 1}, {"EDT", -4}, {"EEST", 3}, {"EET", 2}, {"EST", -5},
{"PDT", -7}, {"PST", -8}, {"WEST", 1}, {"WET", 0}
};
const char* tz_from_R_tzone(SEXP tz);
const char* tz_from_tzone_attr(SEXP x);
const char* system_tz();
const char* local_tz();
bool load_tz(std::string tzstr, cctz::time_zone& tz);
void load_tz_or_fail(std::string tzstr, cctz::time_zone& tz, std::string error_msg);
#endif // TIMECHANGE_TZONE_H
timechange/src/cctz/ 0000755 0001762 0000144 00000000000 14552163764 014125 5 ustar ligges users timechange/src/cctz/civil_time_detail.h 0000644 0001762 0000144 00000047645 14326763410 017755 0 ustar ligges users // Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef CCTZ_CIVIL_TIME_DETAIL_H_
#define CCTZ_CIVIL_TIME_DETAIL_H_
#include
#include
#include
#include
// Disable constexpr support unless we are in C++14 mode.
#if __cpp_constexpr >= 201304 || (defined(_MSC_VER) && _MSC_VER >= 1910)
#define CONSTEXPR_D constexpr // data
#define CONSTEXPR_F constexpr // function
#define CONSTEXPR_M constexpr // member
#else
#define CONSTEXPR_D const
#define CONSTEXPR_F inline
#define CONSTEXPR_M
#endif
namespace cctz {
// Support years that at least span the range of 64-bit time_t values.
using year_t = std::int_fast64_t;
// Type alias that indicates an argument is not normalized (e.g., the
// constructor parameters and operands/results of addition/subtraction).
using diff_t = std::int_fast64_t;
namespace detail {
// Type aliases that indicate normalized argument values.
using month_t = std::int_fast8_t; // [1:12]
using day_t = std::int_fast8_t; // [1:31]
using hour_t = std::int_fast8_t; // [0:23]
using minute_t = std::int_fast8_t; // [0:59]
using second_t = std::int_fast8_t; // [0:59]
// Normalized civil-time fields: Y-M-D HH:MM:SS.
struct fields {
CONSTEXPR_M fields(year_t year, month_t month, day_t day,
hour_t hour, minute_t minute, second_t second)
: y(year), m(month), d(day), hh(hour), mm(minute), ss(second) {}
std::int_least64_t y;
std::int_least8_t m;
std::int_least8_t d;
std::int_least8_t hh;
std::int_least8_t mm;
std::int_least8_t ss;
};
struct second_tag {};
struct minute_tag : second_tag {};
struct hour_tag : minute_tag {};
struct day_tag : hour_tag {};
struct month_tag : day_tag {};
struct year_tag : month_tag {};
////////////////////////////////////////////////////////////////////////
// Field normalization (without avoidable overflow).
namespace impl {
CONSTEXPR_F bool is_leap_year(year_t y) noexcept {
return y % 4 == 0 && (y % 100 != 0 || y % 400 == 0);
}
CONSTEXPR_F int year_index(year_t y, month_t m) noexcept {
const int yi = static_cast((y + (m > 2)) % 400);
return yi < 0 ? yi + 400 : yi;
}
CONSTEXPR_F int days_per_century(int yi) noexcept {
return 36524 + (yi == 0 || yi > 300);
}
CONSTEXPR_F int days_per_4years(int yi) noexcept {
return 1460 + (yi == 0 || yi > 300 || (yi - 1) % 100 < 96);
}
CONSTEXPR_F int days_per_year(year_t y, month_t m) noexcept {
return is_leap_year(y + (m > 2)) ? 366 : 365;
}
CONSTEXPR_F int days_per_month(year_t y, month_t m) noexcept {
CONSTEXPR_D int k_days_per_month[1 + 12] = {
-1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 // non leap year
};
return k_days_per_month[m] + (m == 2 && is_leap_year(y));
}
CONSTEXPR_F fields n_day(year_t y, month_t m, diff_t d, diff_t cd,
hour_t hh, minute_t mm, second_t ss) noexcept {
year_t ey = y % 400;
const year_t oey = ey;
ey += (cd / 146097) * 400;
cd %= 146097;
if (cd < 0) {
ey -= 400;
cd += 146097;
}
ey += (d / 146097) * 400;
d = d % 146097 + cd;
if (d > 0) {
if (d > 146097) {
ey += 400;
d -= 146097;
}
} else {
if (d > -365) {
// We often hit the previous year when stepping a civil time backwards,
// so special case it to avoid counting up by 100/4/1-year chunks.
ey -= 1;
d += days_per_year(ey, m);
} else {
ey -= 400;
d += 146097;
}
}
if (d > 365) {
int yi = year_index(ey, m); // Index into Gregorian 400 year cycle.
for (;;) {
int n = days_per_century(yi);
if (d <= n) break;
d -= n;
ey += 100;
yi += 100;
if (yi >= 400) yi -= 400;
}
for (;;) {
int n = days_per_4years(yi);
if (d <= n) break;
d -= n;
ey += 4;
yi += 4;
if (yi >= 400) yi -= 400;
}
for (;;) {
int n = days_per_year(ey, m);
if (d <= n) break;
d -= n;
++ey;
}
}
if (d > 28) {
for (;;) {
int n = days_per_month(ey, m);
if (d <= n) break;
d -= n;
if (++m > 12) {
++ey;
m = 1;
}
}
}
return fields(y + (ey - oey), m, static_cast(d), hh, mm, ss);
}
CONSTEXPR_F fields n_mon(year_t y, diff_t m, diff_t d, diff_t cd,
hour_t hh, minute_t mm, second_t ss) noexcept {
if (m != 12) {
y += m / 12;
m %= 12;
if (m <= 0) {
y -= 1;
m += 12;
}
}
return n_day(y, static_cast(m), d, cd, hh, mm, ss);
}
CONSTEXPR_F fields n_hour(year_t y, diff_t m, diff_t d, diff_t cd,
diff_t hh, minute_t mm, second_t ss) noexcept {
cd += hh / 24;
hh %= 24;
if (hh < 0) {
cd -= 1;
hh += 24;
}
return n_mon(y, m, d, cd, static_cast(hh), mm, ss);
}
CONSTEXPR_F fields n_min(year_t y, diff_t m, diff_t d, diff_t hh, diff_t ch,
diff_t mm, second_t ss) noexcept {
ch += mm / 60;
mm %= 60;
if (mm < 0) {
ch -= 1;
mm += 60;
}
return n_hour(y, m, d, hh / 24 + ch / 24, hh % 24 + ch % 24,
static_cast(mm), ss);
}
CONSTEXPR_F fields n_sec(year_t y, diff_t m, diff_t d, diff_t hh, diff_t mm,
diff_t ss) noexcept {
// Optimization for when (non-constexpr) fields are already normalized.
if (0 <= ss && ss < 60) {
const second_t nss = static_cast(ss);
if (0 <= mm && mm < 60) {
const minute_t nmm = static_cast(mm);
if (0 <= hh && hh < 24) {
const hour_t nhh = static_cast(hh);
if (1 <= d && d <= 28 && 1 <= m && m <= 12) {
const day_t nd = static_cast(d);
const month_t nm = static_cast(m);
return fields(y, nm, nd, nhh, nmm, nss);
}
return n_mon(y, m, d, 0, nhh, nmm, nss);
}
return n_hour(y, m, d, hh / 24, hh % 24, nmm, nss);
}
return n_min(y, m, d, hh, mm / 60, mm % 60, nss);
}
diff_t cm = ss / 60;
ss %= 60;
if (ss < 0) {
cm -= 1;
ss += 60;
}
return n_min(y, m, d, hh, mm / 60 + cm / 60, mm % 60 + cm % 60,
static_cast(ss));
}
} // namespace impl
////////////////////////////////////////////////////////////////////////
// Increments the indicated (normalized) field by "n".
CONSTEXPR_F fields step(second_tag, fields f, diff_t n) noexcept {
return impl::n_sec(f.y, f.m, f.d, f.hh, f.mm + n / 60, f.ss + n % 60);
}
CONSTEXPR_F fields step(minute_tag, fields f, diff_t n) noexcept {
return impl::n_min(f.y, f.m, f.d, f.hh + n / 60, 0, f.mm + n % 60, f.ss);
}
CONSTEXPR_F fields step(hour_tag, fields f, diff_t n) noexcept {
return impl::n_hour(f.y, f.m, f.d + n / 24, 0, f.hh + n % 24, f.mm, f.ss);
}
CONSTEXPR_F fields step(day_tag, fields f, diff_t n) noexcept {
return impl::n_day(f.y, f.m, f.d, n, f.hh, f.mm, f.ss);
}
CONSTEXPR_F fields step(month_tag, fields f, diff_t n) noexcept {
return impl::n_mon(f.y + n / 12, f.m + n % 12, f.d, 0, f.hh, f.mm, f.ss);
}
CONSTEXPR_F fields step(year_tag, fields f, diff_t n) noexcept {
return fields(f.y + n, f.m, f.d, f.hh, f.mm, f.ss);
}
////////////////////////////////////////////////////////////////////////
namespace impl {
// Returns (v * f + a) but avoiding intermediate overflow when possible.
CONSTEXPR_F diff_t scale_add(diff_t v, diff_t f, diff_t a) noexcept {
return (v < 0) ? ((v + 1) * f + a) - f : ((v - 1) * f + a) + f;
}
// Map a (normalized) Y/M/D to the number of days before/after 1970-01-01.
// Probably overflows for years outside [-292277022656:292277026595].
CONSTEXPR_F diff_t ymd_ord(year_t y, month_t m, day_t d) noexcept {
const diff_t eyear = (m <= 2) ? y - 1 : y;
const diff_t era = (eyear >= 0 ? eyear : eyear - 399) / 400;
const diff_t yoe = eyear - era * 400;
const diff_t doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1;
const diff_t doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
return era * 146097 + doe - 719468;
}
// Returns the difference in days between two normalized Y-M-D tuples.
// ymd_ord() will encounter integer overflow given extreme year values,
// yet the difference between two such extreme values may actually be
// small, so we take a little care to avoid overflow when possible by
// exploiting the 146097-day cycle.
CONSTEXPR_F diff_t day_difference(year_t y1, month_t m1, day_t d1,
year_t y2, month_t m2, day_t d2) noexcept {
const diff_t a_c4_off = y1 % 400;
const diff_t b_c4_off = y2 % 400;
diff_t c4_diff = (y1 - a_c4_off) - (y2 - b_c4_off);
diff_t delta = ymd_ord(a_c4_off, m1, d1) - ymd_ord(b_c4_off, m2, d2);
if (c4_diff > 0 && delta < 0) {
delta += 2 * 146097;
c4_diff -= 2 * 400;
} else if (c4_diff < 0 && delta > 0) {
delta -= 2 * 146097;
c4_diff += 2 * 400;
}
return (c4_diff / 400 * 146097) + delta;
}
} // namespace impl
// Returns the difference between fields structs using the indicated unit.
CONSTEXPR_F diff_t difference(year_tag, fields f1, fields f2) noexcept {
return f1.y - f2.y;
}
CONSTEXPR_F diff_t difference(month_tag, fields f1, fields f2) noexcept {
return impl::scale_add(difference(year_tag{}, f1, f2), 12, (f1.m - f2.m));
}
CONSTEXPR_F diff_t difference(day_tag, fields f1, fields f2) noexcept {
return impl::day_difference(f1.y, f1.m, f1.d, f2.y, f2.m, f2.d);
}
CONSTEXPR_F diff_t difference(hour_tag, fields f1, fields f2) noexcept {
return impl::scale_add(difference(day_tag{}, f1, f2), 24, (f1.hh - f2.hh));
}
CONSTEXPR_F diff_t difference(minute_tag, fields f1, fields f2) noexcept {
return impl::scale_add(difference(hour_tag{}, f1, f2), 60, (f1.mm - f2.mm));
}
CONSTEXPR_F diff_t difference(second_tag, fields f1, fields f2) noexcept {
return impl::scale_add(difference(minute_tag{}, f1, f2), 60, f1.ss - f2.ss);
}
////////////////////////////////////////////////////////////////////////
// Aligns the (normalized) fields struct to the indicated field.
CONSTEXPR_F fields align(second_tag, fields f) noexcept {
return f;
}
CONSTEXPR_F fields align(minute_tag, fields f) noexcept {
return fields{f.y, f.m, f.d, f.hh, f.mm, 0};
}
CONSTEXPR_F fields align(hour_tag, fields f) noexcept {
return fields{f.y, f.m, f.d, f.hh, 0, 0};
}
CONSTEXPR_F fields align(day_tag, fields f) noexcept {
return fields{f.y, f.m, f.d, 0, 0, 0};
}
CONSTEXPR_F fields align(month_tag, fields f) noexcept {
return fields{f.y, f.m, 1, 0, 0, 0};
}
CONSTEXPR_F fields align(year_tag, fields f) noexcept {
return fields{f.y, 1, 1, 0, 0, 0};
}
////////////////////////////////////////////////////////////////////////
template
class civil_time {
public:
explicit CONSTEXPR_M civil_time(year_t y, diff_t m = 1, diff_t d = 1,
diff_t hh = 0, diff_t mm = 0,
diff_t ss = 0) noexcept
: civil_time(impl::n_sec(y, m, d, hh, mm, ss)) {}
CONSTEXPR_M civil_time() noexcept : f_{1970, 1, 1, 0, 0, 0} {}
civil_time(const civil_time&) = default;
civil_time& operator=(const civil_time&) = default;
// Conversion between civil times of different alignment. Conversion to
// a more precise alignment is allowed implicitly (e.g., day -> hour),
// but conversion where information is discarded must be explicit
// (e.g., second -> minute).
template
using preserves_data =
typename std::enable_if::value>::type;
template
CONSTEXPR_M civil_time(const civil_time& ct,
preserves_data* = nullptr) noexcept
: civil_time(ct.f_) {}
template
explicit CONSTEXPR_M civil_time(const civil_time& ct,
preserves_data* = nullptr) noexcept
: civil_time(ct.f_) {}
// Factories for the maximum/minimum representable civil_time.
static CONSTEXPR_F civil_time (max)() {
const auto max_year = (std::numeric_limits::max)();
return civil_time(max_year, 12, 31, 23, 59, 59);
}
static CONSTEXPR_F civil_time (min)() {
const auto min_year = (std::numeric_limits::min)();
return civil_time(min_year, 1, 1, 0, 0, 0);
}
// Field accessors. Note: All but year() return an int.
CONSTEXPR_M year_t year() const noexcept { return f_.y; }
CONSTEXPR_M int month() const noexcept { return f_.m; }
CONSTEXPR_M int day() const noexcept { return f_.d; }
CONSTEXPR_M int hour() const noexcept { return f_.hh; }
CONSTEXPR_M int minute() const noexcept { return f_.mm; }
CONSTEXPR_M int second() const noexcept { return f_.ss; }
// Assigning arithmetic.
CONSTEXPR_M civil_time& operator+=(diff_t n) noexcept {
return *this = *this + n;
}
CONSTEXPR_M civil_time& operator-=(diff_t n) noexcept {
return *this = *this - n;
}
CONSTEXPR_M civil_time& operator++() noexcept {
return *this += 1;
}
CONSTEXPR_M civil_time operator++(int) noexcept {
const civil_time a = *this;
++*this;
return a;
}
CONSTEXPR_M civil_time& operator--() noexcept {
return *this -= 1;
}
CONSTEXPR_M civil_time operator--(int) noexcept {
const civil_time a = *this;
--*this;
return a;
}
// Binary arithmetic operators.
friend CONSTEXPR_F civil_time operator+(civil_time a, diff_t n) noexcept {
return civil_time(step(T{}, a.f_, n));
}
friend CONSTEXPR_F civil_time operator+(diff_t n, civil_time a) noexcept {
return a + n;
}
friend CONSTEXPR_F civil_time operator-(civil_time a, diff_t n) noexcept {
return n != (std::numeric_limits::min)()
? civil_time(step(T{}, a.f_, -n))
: civil_time(step(T{}, step(T{}, a.f_, -(n + 1)), 1));
}
friend CONSTEXPR_F diff_t operator-(civil_time lhs, civil_time rhs) noexcept {
return difference(T{}, lhs.f_, rhs.f_);
}
private:
// All instantiations of this template are allowed to call the following
// private constructor and access the private fields member.
template
friend class civil_time;
// The designated constructor that all others eventually call.
explicit CONSTEXPR_M civil_time(fields f) noexcept : f_(align(T{}, f)) {}
fields f_;
};
// Disallows difference between differently aligned types.
// auto n = civil_day(...) - civil_hour(...); // would be confusing.
template
CONSTEXPR_F diff_t operator-(civil_time, civil_time) = delete;
using civil_year = civil_time;
using civil_month = civil_time;
using civil_day = civil_time;
using civil_hour = civil_time;
using civil_minute = civil_time;
using civil_second = civil_time;
////////////////////////////////////////////////////////////////////////
// Relational operators that work with differently aligned objects.
// Always compares all six fields.
template
CONSTEXPR_F bool operator<(const civil_time& lhs,
const civil_time& rhs) noexcept {
return (lhs.year() < rhs.year() ||
(lhs.year() == rhs.year() &&
(lhs.month() < rhs.month() ||
(lhs.month() == rhs.month() &&
(lhs.day() < rhs.day() ||
(lhs.day() == rhs.day() &&
(lhs.hour() < rhs.hour() ||
(lhs.hour() == rhs.hour() &&
(lhs.minute() < rhs.minute() ||
(lhs.minute() == rhs.minute() &&
(lhs.second() < rhs.second())))))))))));
}
template
CONSTEXPR_F bool operator<=(const civil_time& lhs,
const civil_time& rhs) noexcept {
return !(rhs < lhs);
}
template
CONSTEXPR_F bool operator>=(const civil_time& lhs,
const civil_time& rhs) noexcept {
return !(lhs < rhs);
}
template
CONSTEXPR_F bool operator>(const civil_time& lhs,
const civil_time& rhs) noexcept {
return rhs < lhs;
}
template
CONSTEXPR_F bool operator==(const civil_time& lhs,
const civil_time& rhs) noexcept {
return lhs.year() == rhs.year() && lhs.month() == rhs.month() &&
lhs.day() == rhs.day() && lhs.hour() == rhs.hour() &&
lhs.minute() == rhs.minute() && lhs.second() == rhs.second();
}
template
CONSTEXPR_F bool operator!=(const civil_time& lhs,
const civil_time& rhs) noexcept {
return !(lhs == rhs);
}
////////////////////////////////////////////////////////////////////////
enum class weekday {
monday,
tuesday,
wednesday,
thursday,
friday,
saturday,
sunday,
};
CONSTEXPR_F weekday get_weekday(const civil_second& cs) noexcept {
CONSTEXPR_D weekday k_weekday_by_mon_off[13] = {
weekday::monday, weekday::tuesday, weekday::wednesday,
weekday::thursday, weekday::friday, weekday::saturday,
weekday::sunday, weekday::monday, weekday::tuesday,
weekday::wednesday, weekday::thursday, weekday::friday,
weekday::saturday,
};
CONSTEXPR_D int k_weekday_offsets[1 + 12] = {
-1, 0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4,
};
year_t wd = 2400 + (cs.year() % 400) - (cs.month() < 3);
wd += wd / 4 - wd / 100 + wd / 400;
wd += k_weekday_offsets[cs.month()] + cs.day();
return k_weekday_by_mon_off[wd % 7 + 6];
}
////////////////////////////////////////////////////////////////////////
CONSTEXPR_F civil_day next_weekday(civil_day cd, weekday wd) noexcept {
CONSTEXPR_D weekday k_weekdays_forw[14] = {
weekday::monday, weekday::tuesday, weekday::wednesday,
weekday::thursday, weekday::friday, weekday::saturday,
weekday::sunday, weekday::monday, weekday::tuesday,
weekday::wednesday, weekday::thursday, weekday::friday,
weekday::saturday, weekday::sunday,
};
weekday base = get_weekday(cd);
for (int i = 0;; ++i) {
if (base == k_weekdays_forw[i]) {
for (int j = i + 1;; ++j) {
if (wd == k_weekdays_forw[j]) {
return cd + (j - i);
}
}
}
}
}
CONSTEXPR_F civil_day prev_weekday(civil_day cd, weekday wd) noexcept {
CONSTEXPR_D weekday k_weekdays_back[14] = {
weekday::sunday, weekday::saturday, weekday::friday,
weekday::thursday, weekday::wednesday, weekday::tuesday,
weekday::monday, weekday::sunday, weekday::saturday,
weekday::friday, weekday::thursday, weekday::wednesday,
weekday::tuesday, weekday::monday,
};
weekday base = get_weekday(cd);
for (int i = 0;; ++i) {
if (base == k_weekdays_back[i]) {
for (int j = i + 1;; ++j) {
if (wd == k_weekdays_back[j]) {
return cd - (j - i);
}
}
}
}
}
CONSTEXPR_F int get_yearday(const civil_second& cs) noexcept {
CONSTEXPR_D int k_month_offsets[1 + 12] = {
-1, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334,
};
const int feb29 = (cs.month() > 2 && impl::is_leap_year(cs.year()));
return k_month_offsets[cs.month()] + feb29 + cs.day();
}
////////////////////////////////////////////////////////////////////////
std::ostream& operator<<(std::ostream& os, const civil_year& y);
std::ostream& operator<<(std::ostream& os, const civil_month& m);
std::ostream& operator<<(std::ostream& os, const civil_day& d);
std::ostream& operator<<(std::ostream& os, const civil_hour& h);
std::ostream& operator<<(std::ostream& os, const civil_minute& m);
std::ostream& operator<<(std::ostream& os, const civil_second& s);
std::ostream& operator<<(std::ostream& os, weekday wd);
} // namespace detail
} // namespace cctz
#undef CONSTEXPR_M
#undef CONSTEXPR_F
#undef CONSTEXPR_D
#endif // CCTZ_CIVIL_TIME_DETAIL_H_
timechange/src/cctz/time_zone.h 0000644 0001762 0000144 00000045606 14326763410 016273 0 ustar ligges users // Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// A library for translating between absolute times (represented by
// std::chrono::time_points of the std::chrono::system_clock) and civil
// times (represented by cctz::civil_second) using the rules defined by
// a time zone (cctz::time_zone).
#ifndef CCTZ_TIME_ZONE_H_
#define CCTZ_TIME_ZONE_H_
#include
#include
#include
#include
#include
#include "cctz/civil_time.h"
namespace cctz {
// Convenience aliases. Not intended as public API points.
template
using time_point = std::chrono::time_point;
using seconds = std::chrono::duration;
using sys_seconds = seconds; // Deprecated. Use cctz::seconds instead.
namespace detail {
template
std::pair, D> split_seconds(const time_point& tp);
std::pair, seconds> split_seconds(
const time_point& tp);
} // namespace detail
// cctz::time_zone is an opaque, small, value-type class representing a
// geo-political region within which particular rules are used for mapping
// between absolute and civil times. Time zones are named using the TZ
// identifiers from the IANA Time Zone Database, such as "America/Los_Angeles"
// or "Australia/Sydney". Time zones are created from factory functions such
// as load_time_zone(). Note: strings like "PST" and "EDT" are not valid TZ
// identifiers.
//
// Example:
// cctz::time_zone utc = cctz::utc_time_zone();
// cctz::time_zone pst = cctz::fixed_time_zone(std::chrono::hours(-8));
// cctz::time_zone loc = cctz::local_time_zone();
// cctz::time_zone lax;
// if (!cctz::load_time_zone("America/Los_Angeles", &lax)) { ... }
//
// See also:
// - http://www.iana.org/time-zones
// - https://en.wikipedia.org/wiki/Zoneinfo
class time_zone {
public:
time_zone() : time_zone(nullptr) {} // Equivalent to UTC
time_zone(const time_zone&) = default;
time_zone& operator=(const time_zone&) = default;
std::string name() const;
// An absolute_lookup represents the civil time (cctz::civil_second) within
// this time_zone at the given absolute time (time_point). There are
// additionally a few other fields that may be useful when working with
// older APIs, such as std::tm.
//
// Example:
// const cctz::time_zone tz = ...
// const auto tp = std::chrono::system_clock::now();
// const cctz::time_zone::absolute_lookup al = tz.lookup(tp);
struct absolute_lookup {
civil_second cs;
// Note: The following fields exist for backward compatibility with older
// APIs. Accessing these fields directly is a sign of imprudent logic in
// the calling code. Modern time-related code should only access this data
// indirectly by way of cctz::format().
int offset; // civil seconds east of UTC
bool is_dst; // is offset non-standard?
const char* abbr; // time-zone abbreviation (e.g., "PST")
};
absolute_lookup lookup(const time_point& tp) const;
template
absolute_lookup lookup(const time_point& tp) const {
return lookup(detail::split_seconds(tp).first);
}
// A civil_lookup represents the absolute time(s) (time_point) that
// correspond to the given civil time (cctz::civil_second) within this
// time_zone. Usually the given civil time represents a unique instant
// in time, in which case the conversion is unambiguous. However,
// within this time zone, the given civil time may be skipped (e.g.,
// during a positive UTC offset shift), or repeated (e.g., during a
// negative UTC offset shift). To account for these possibilities,
// civil_lookup is richer than just a single time_point.
//
// In all cases the civil_lookup::kind enum will indicate the nature
// of the given civil-time argument, and the pre, trans, and post
// members will give the absolute time answers using the pre-transition
// offset, the transition point itself, and the post-transition offset,
// respectively (all three times are equal if kind == UNIQUE). If any
// of these three absolute times is outside the representable range of a
// time_point the field is set to its maximum/minimum value.
//
// Example:
// cctz::time_zone lax;
// if (!cctz::load_time_zone("America/Los_Angeles", &lax)) { ... }
//
// // A unique civil time.
// auto jan01 = lax.lookup(cctz::civil_second(2011, 1, 1, 0, 0, 0));
// // jan01.kind == cctz::time_zone::civil_lookup::UNIQUE
// // jan01.pre is 2011/01/01 00:00:00 -0800
// // jan01.trans is 2011/01/01 00:00:00 -0800
// // jan01.post is 2011/01/01 00:00:00 -0800
//
// // A Spring DST transition, when there is a gap in civil time.
// auto mar13 = lax.lookup(cctz::civil_second(2011, 3, 13, 2, 15, 0));
// // mar13.kind == cctz::time_zone::civil_lookup::SKIPPED
// // mar13.pre is 2011/03/13 03:15:00 -0700
// // mar13.trans is 2011/03/13 03:00:00 -0700
// // mar13.post is 2011/03/13 01:15:00 -0800
//
// // A Fall DST transition, when civil times are repeated.
// auto nov06 = lax.lookup(cctz::civil_second(2011, 11, 6, 1, 15, 0));
// // nov06.kind == cctz::time_zone::civil_lookup::REPEATED
// // nov06.pre is 2011/11/06 01:15:00 -0700
// // nov06.trans is 2011/11/06 01:00:00 -0800
// // nov06.post is 2011/11/06 01:15:00 -0800
struct civil_lookup {
enum civil_kind {
UNIQUE, // the civil time was singular (pre == trans == post)
SKIPPED, // the civil time did not exist (pre >= trans > post)
REPEATED, // the civil time was ambiguous (pre < trans <= post)
} kind;
time_point pre; // uses the pre-transition offset
time_point trans; // instant of civil-offset change
time_point post; // uses the post-transition offset
};
civil_lookup lookup(const civil_second& cs) const;
// Finds the time of the next/previous offset change in this time zone.
//
// By definition, next_transition(tp, &trans) returns false when tp has
// its maximum value, and prev_transition(tp, &trans) returns false
// when tp has its minimum value. If the zone has no transitions, the
// result will also be false no matter what the argument.
//
// Otherwise, when tp has its minimum value, next_transition(tp, &trans)
// returns true and sets trans to the first recorded transition. Chains
// of calls to next_transition()/prev_transition() will eventually return
// false, but it is unspecified exactly when next_transition(tp, &trans)
// jumps to false, or what time is set by prev_transition(tp, &trans) for
// a very distant tp.
//
// Note: Enumeration of time-zone transitions is for informational purposes
// only. Modern time-related code should not care about when offset changes
// occur.
//
// Example:
// cctz::time_zone nyc;
// if (!cctz::load_time_zone("America/New_York", &nyc)) { ... }
// const auto now = std::chrono::system_clock::now();
// auto tp = cctz::time_point::min();
// cctz::time_zone::civil_transition trans;
// while (tp <= now && nyc.next_transition(tp, &trans)) {
// // transition: trans.from -> trans.to
// tp = nyc.lookup(trans.to).trans;
// }
struct civil_transition {
civil_second from; // the civil time we jump from
civil_second to; // the civil time we jump to
};
bool next_transition(const time_point& tp,
civil_transition* trans) const;
template
bool next_transition(const time_point& tp,
civil_transition* trans) const {
return next_transition(detail::split_seconds(tp).first, trans);
}
bool prev_transition(const time_point& tp,
civil_transition* trans) const;
template
bool prev_transition(const time_point& tp,
civil_transition* trans) const {
return prev_transition(detail::split_seconds(tp).first, trans);
}
// version() and description() provide additional information about the
// time zone. The content of each of the returned strings is unspecified,
// however, when the IANA Time Zone Database is the underlying data source
// the version() string will be in the familar form (e.g, "2018e") or
// empty when unavailable.
//
// Note: These functions are for informational or testing purposes only.
std::string version() const; // empty when unknown
std::string description() const;
// Relational operators.
friend bool operator==(time_zone lhs, time_zone rhs) {
return &lhs.effective_impl() == &rhs.effective_impl();
}
friend bool operator!=(time_zone lhs, time_zone rhs) {
return !(lhs == rhs);
}
class Impl;
private:
explicit time_zone(const Impl* impl) : impl_(impl) {}
const Impl& effective_impl() const; // handles implicit UTC
const Impl* impl_;
};
// Loads the named time zone. May perform I/O on the initial load.
// If the name is invalid, or some other kind of error occurs, returns
// false and "*tz" is set to the UTC time zone.
bool load_time_zone(const std::string& name, time_zone* tz);
// Returns a time_zone representing UTC. Cannot fail.
time_zone utc_time_zone();
// Returns a time zone that is a fixed offset (seconds east) from UTC.
// Note: If the absolute value of the offset is greater than 24 hours
// you'll get UTC (i.e., zero offset) instead.
time_zone fixed_time_zone(const seconds& offset);
// Returns a time zone representing the local time zone. Falls back to UTC.
// Note: local_time_zone.name() may only be something like "localtime".
time_zone local_time_zone();
// Returns the civil time (cctz::civil_second) within the given time zone at
// the given absolute time (time_point). Since the additional fields provided
// by the time_zone::absolute_lookup struct should rarely be needed in modern
// code, this convert() function is simpler and should be preferred.
template
inline civil_second convert(const time_point& tp, const time_zone& tz) {
return tz.lookup(tp).cs;
}
// Returns the absolute time (time_point) that corresponds to the given civil
// time within the given time zone. If the civil time is not unique (i.e., if
// it was either repeated or non-existent), then the returned time_point is
// the best estimate that preserves relative order. That is, this function
// guarantees that if cs1 < cs2, then convert(cs1, tz) <= convert(cs2, tz).
inline time_point convert(const civil_second& cs,
const time_zone& tz) {
const time_zone::civil_lookup cl = tz.lookup(cs);
if (cl.kind == time_zone::civil_lookup::SKIPPED) return cl.trans;
return cl.pre;
}
namespace detail {
using femtoseconds = std::chrono::duration;
std::string format(const std::string&, const time_point&,
const femtoseconds&, const time_zone&);
bool parse(const std::string&, const std::string&, const time_zone&,
time_point*, femtoseconds*, std::string* err = nullptr);
template
bool join_seconds(
const time_point& sec, const femtoseconds& fs,
time_point>>* tpp);
template
bool join_seconds(
const time_point& sec, const femtoseconds& fs,
time_point>>* tpp);
template
bool join_seconds(
const time_point& sec, const femtoseconds& fs,
time_point>>* tpp);
bool join_seconds(const time_point& sec, const femtoseconds&,
time_point* tpp);
} // namespace detail
// Formats the given time_point in the given cctz::time_zone according to
// the provided format string. Uses strftime()-like formatting options,
// with the following extensions:
//
// - %Ez - RFC3339-compatible numeric UTC offset (+hh:mm or -hh:mm)
// - %E*z - Full-resolution numeric UTC offset (+hh:mm:ss or -hh:mm:ss)
// - %E#S - Seconds with # digits of fractional precision
// - %E*S - Seconds with full fractional precision (a literal '*')
// - %E#f - Fractional seconds with # digits of precision
// - %E*f - Fractional seconds with full precision (a literal '*')
// - %E4Y - Four-character years (-999 ... -001, 0000, 0001 ... 9999)
// - %ET - The RFC3339 "date-time" separator "T"
//
// Note that %E0S behaves like %S, and %E0f produces no characters. In
// contrast %E*f always produces at least one digit, which may be '0'.
//
// Note that %Y produces as many characters as it takes to fully render the
// year. A year outside of [-999:9999] when formatted with %E4Y will produce
// more than four characters, just like %Y.
//
// Tip: Format strings should include the UTC offset (e.g., %z, %Ez, or %E*z)
// so that the resulting string uniquely identifies an absolute time.
//
// Example:
// cctz::time_zone lax;
// if (!cctz::load_time_zone("America/Los_Angeles", &lax)) { ... }
// auto tp = cctz::convert(cctz::civil_second(2013, 1, 2, 3, 4, 5), lax);
// std::string f = cctz::format("%H:%M:%S", tp, lax); // "03:04:05"
// f = cctz::format("%H:%M:%E3S", tp, lax); // "03:04:05.000"
template
inline std::string format(const std::string& fmt, const time_point& tp,
const time_zone& tz) {
const auto p = detail::split_seconds(tp);
const auto n = std::chrono::duration_cast(p.second);
return detail::format(fmt, p.first, n, tz);
}
// Parses an input string according to the provided format string and
// returns the corresponding time_point. Uses strftime()-like formatting
// options, with the same extensions as cctz::format(), but with the
// exceptions that %E#S is interpreted as %E*S, and %E#f as %E*f. %Ez
// and %E*z also accept the same inputs, which (along with %z) includes
// 'z' and 'Z' as synonyms for +00:00. %ET accepts either 'T' or 't'.
//
// %Y consumes as many numeric characters as it can, so the matching data
// should always be terminated with a non-numeric. %E4Y always consumes
// exactly four characters, including any sign.
//
// Unspecified fields are taken from the default date and time of ...
//
// "1970-01-01 00:00:00.0 +0000"
//
// For example, parsing a string of "15:45" (%H:%M) will return a time_point
// that represents "1970-01-01 15:45:00.0 +0000".
//
// Note that parse() returns time instants, so it makes most sense to parse
// fully-specified date/time strings that include a UTC offset (%z, %Ez, or
// %E*z).
//
// Note also that parse() only heeds the fields year, month, day, hour,
// minute, (fractional) second, and UTC offset. Other fields, like weekday (%a
// or %A), while parsed for syntactic validity, are ignored in the conversion.
//
// Date and time fields that are out-of-range will be treated as errors rather
// than normalizing them like cctz::civil_second() would do. For example, it
// is an error to parse the date "Oct 32, 2013" because 32 is out of range.
//
// A second of ":60" is normalized to ":00" of the following minute with
// fractional seconds discarded. The following table shows how the given
// seconds and subseconds will be parsed:
//
// "59.x" -> 59.x // exact
// "60.x" -> 00.0 // normalized
// "00.x" -> 00.x // exact
//
// Errors are indicated by returning false.
//
// Example:
// const cctz::time_zone tz = ...
// std::chrono::system_clock::time_point tp;
// if (cctz::parse("%Y-%m-%d", "2015-10-09", tz, &tp)) {
// ...
// }
template
inline bool parse(const std::string& fmt, const std::string& input,
const time_zone& tz, time_point* tpp) {
time_point sec;
detail::femtoseconds fs;
return detail::parse(fmt, input, tz, &sec, &fs) &&
detail::join_seconds(sec, fs, tpp);
}
namespace detail {
// Split a time_point into a time_point and a D subseconds.
// Undefined behavior if time_point is not of sufficient range.
// Note that this means it is UB to call cctz::time_zone::lookup(tp) or
// cctz::format(fmt, tp, tz) with a time_point that is outside the range
// of a 64-bit std::time_t.
template
std::pair, D> split_seconds(const time_point& tp) {
auto sec = std::chrono::time_point_cast(tp);
auto sub = tp - sec;
if (sub.count() < 0) {
sec -= seconds(1);
sub += seconds(1);
}
return {sec, std::chrono::duration_cast(sub)};
}
inline std::pair, seconds> split_seconds(
const time_point& tp) {
return {tp, seconds::zero()};
}
// Join a time_point and femto subseconds into a time_point.
// Floors to the resolution of time_point. Returns false if time_point
// is not of sufficient range.
template
bool join_seconds(
const time_point& sec, const femtoseconds& fs,
time_point>>* tpp) {
using D = std::chrono::duration>;
// TODO(#199): Return false if result unrepresentable as a time_point.
*tpp = std::chrono::time_point_cast(sec);
*tpp += std::chrono::duration_cast(fs);
return true;
}
template
bool join_seconds(
const time_point& sec, const femtoseconds&,
time_point>>* tpp) {
using D = std::chrono::duration>;
auto count = sec.time_since_epoch().count();
if (count >= 0 || count % Num == 0) {
count /= Num;
} else {
count /= Num;
count -= 1;
}
if (count > (std::numeric_limits::max)()) return false;
if (count < (std::numeric_limits::min)()) return false;
*tpp = time_point() + D{static_cast(count)};
return true;
}
template
bool join_seconds(
const time_point& sec, const femtoseconds&,
time_point>>* tpp) {
using D = std::chrono::duration>;
auto count = sec.time_since_epoch().count();
if (count > (std::numeric_limits::max)()) return false;
if (count < (std::numeric_limits::min)()) return false;
*tpp = time_point() + D{static_cast(count)};
return true;
}
inline bool join_seconds(const time_point& sec, const femtoseconds&,
time_point* tpp) {
*tpp = sec;
return true;
}
} // namespace detail
} // namespace cctz
#endif // CCTZ_TIME_ZONE_H_
timechange/src/cctz/zone_info_source.h 0000644 0001762 0000144 00000006415 13573273654 017654 0 ustar ligges users // Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef CCTZ_ZONE_INFO_SOURCE_H_
#define CCTZ_ZONE_INFO_SOURCE_H_
#include
#include
#include
#include
namespace cctz {
// A stdio-like interface for providing zoneinfo data for a particular zone.
class ZoneInfoSource {
public:
virtual ~ZoneInfoSource();
virtual std::size_t Read(void* ptr, std::size_t size) = 0; // like fread()
virtual int Skip(std::size_t offset) = 0; // like fseek()
// Until the zoneinfo data supports versioning information, we provide
// a way for a ZoneInfoSource to indicate it out-of-band. The default
// implementation returns an empty string.
virtual std::string Version() const;
};
} // namespace cctz
namespace cctz_extension {
// A function-pointer type for a factory that returns a ZoneInfoSource
// given the name of a time zone and a fallback factory. Returns null
// when the data for the named zone cannot be found.
using ZoneInfoSourceFactory =
std::unique_ptr (*)(
const std::string&,
const std::function(
const std::string&)>&);
// The user can control the mapping of zone names to zoneinfo data by
// providing a definition for cctz_extension::zone_info_source_factory.
// For example, given functions my_factory() and my_other_factory() that
// can return a ZoneInfoSource for a named zone, we could inject them into
// cctz::load_time_zone() with:
//
// namespace cctz_extension {
// namespace {
// std::unique_ptr CustomFactory(
// const std::string& name,
// const std::function(
// const std::string& name)>& fallback_factory) {
// if (auto zip = my_factory(name)) return zip;
// if (auto zip = fallback_factory(name)) return zip;
// if (auto zip = my_other_factory(name)) return zip;
// return nullptr;
// }
// } // namespace
// ZoneInfoSourceFactory zone_info_source_factory = CustomFactory;
// } // namespace cctz_extension
//
// This might be used, say, to use zoneinfo data embedded in the program,
// or read from a (possibly compressed) file archive, or both.
//
// cctz_extension::zone_info_source_factory() will be called:
// (1) from the same thread as the cctz::load_time_zone() call,
// (2) only once for any zone name, and
// (3) serially (i.e., no concurrent execution).
//
// The fallback factory obtains zoneinfo data by reading files in ${TZDIR},
// and it is used automatically when no zone_info_source_factory definition
// is linked into the program.
extern ZoneInfoSourceFactory zone_info_source_factory;
} // namespace cctz_extension
#endif // CCTZ_ZONE_INFO_SOURCE_H_
timechange/src/cctz/civil_time.h 0000644 0001762 0000144 00000032063 13736317460 016423 0 ustar ligges users // Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef CCTZ_CIVIL_TIME_H_
#define CCTZ_CIVIL_TIME_H_
#include "cctz/civil_time_detail.h"
namespace cctz {
// The term "civil time" refers to the legally recognized human-scale time
// that is represented by the six fields YYYY-MM-DD hh:mm:ss. Modern-day civil
// time follows the Gregorian Calendar and is a time-zone-independent concept.
// A "date" is perhaps the most common example of a civil time (represented in
// this library as cctz::civil_day). This library provides six classes and a
// handful of functions that help with rounding, iterating, and arithmetic on
// civil times while avoiding complications like daylight-saving time (DST).
//
// The following six classes form the core of this civil-time library:
//
// * civil_second
// * civil_minute
// * civil_hour
// * civil_day
// * civil_month
// * civil_year
//
// Each class is a simple value type with the same interface for construction
// and the same six accessors for each of the civil fields (year, month, day,
// hour, minute, and second, aka YMDHMS). These classes differ only in their
// alignment, which is indicated by the type name and specifies the field on
// which arithmetic operates.
//
// Each class can be constructed by passing up to six optional integer
// arguments representing the YMDHMS fields (in that order) to the
// constructor. Omitted fields are assigned their minimum valid value. Hours,
// minutes, and seconds will be set to 0, month and day will be set to 1, and
// since there is no minimum valid year, it will be set to 1970. So, a
// default-constructed civil-time object will have YMDHMS fields representing
// "1970-01-01 00:00:00". Fields that are out-of-range are normalized (e.g.,
// October 32 -> November 1) so that all civil-time objects represent valid
// values.
//
// Each civil-time class is aligned to the civil-time field indicated in the
// class's name after normalization. Alignment is performed by setting all the
// inferior fields to their minimum valid value (as described above). The
// following are examples of how each of the six types would align the fields
// representing November 22, 2015 at 12:34:56 in the afternoon. (Note: the
// string format used here is not important; it's just a shorthand way of
// showing the six YMDHMS fields.)
//
// civil_second 2015-11-22 12:34:56
// civil_minute 2015-11-22 12:34:00
// civil_hour 2015-11-22 12:00:00
// civil_day 2015-11-22 00:00:00
// civil_month 2015-11-01 00:00:00
// civil_year 2015-01-01 00:00:00
//
// Each civil-time type performs arithmetic on the field to which it is
// aligned. This means that adding 1 to a civil_day increments the day field
// (normalizing as necessary), and subtracting 7 from a civil_month operates
// on the month field (normalizing as necessary). All arithmetic produces a
// valid civil time. Difference requires two similarly aligned civil-time
// objects and returns the scalar answer in units of the objects' alignment.
// For example, the difference between two civil_hour objects will give an
// answer in units of civil hours.
//
// In addition to the six civil-time types just described, there are
// a handful of helper functions and algorithms for performing common
// calculations. These are described below.
//
// Note: In C++14 and later, this library is usable in a constexpr context.
//
// CONSTRUCTION:
//
// Each of the civil-time types can be constructed in two ways: by directly
// passing to the constructor up to six (optional) integers representing the
// YMDHMS fields, or by copying the YMDHMS fields from a differently aligned
// civil-time type.
//
// civil_day default_value; // 1970-01-01 00:00:00
//
// civil_day a(2015, 2, 3); // 2015-02-03 00:00:00
// civil_day b(2015, 2, 3, 4, 5, 6); // 2015-02-03 00:00:00
// civil_day c(2015); // 2015-01-01 00:00:00
//
// civil_second ss(2015, 2, 3, 4, 5, 6); // 2015-02-03 04:05:06
// civil_minute mm(ss); // 2015-02-03 04:05:00
// civil_hour hh(mm); // 2015-02-03 04:00:00
// civil_day d(hh); // 2015-02-03 00:00:00
// civil_month m(d); // 2015-02-01 00:00:00
// civil_year y(m); // 2015-01-01 00:00:00
//
// m = civil_month(y); // 2015-01-01 00:00:00
// d = civil_day(m); // 2015-01-01 00:00:00
// hh = civil_hour(d); // 2015-01-01 00:00:00
// mm = civil_minute(hh); // 2015-01-01 00:00:00
// ss = civil_second(mm); // 2015-01-01 00:00:00
//
// ALIGNMENT CONVERSION:
//
// The alignment of a civil-time object cannot change, but the object may be
// used to construct a new object with a different alignment. This is referred
// to as "realigning". When realigning to a type with the same or more
// precision (e.g., civil_day -> civil_second), the conversion may be
// performed implicitly since no information is lost. However, if information
// could be discarded (e.g., civil_second -> civil_day), the conversion must
// be explicit at the call site.
//
// void fun(const civil_day& day);
//
// civil_second cs;
// fun(cs); // Won't compile because data may be discarded
// fun(civil_day(cs)); // OK: explicit conversion
//
// civil_day cd;
// fun(cd); // OK: no conversion needed
//
// civil_month cm;
// fun(cm); // OK: implicit conversion to civil_day
//
// NORMALIZATION:
//
// Integer arguments passed to the constructor may be out-of-range, in which
// case they are normalized to produce a valid civil-time object. This enables
// natural arithmetic on constructor arguments without worrying about the
// field's range. Normalization guarantees that there are no invalid
// civil-time objects.
//
// civil_day d(2016, 10, 32); // Out-of-range day; normalized to 2016-11-01
//
// Note: If normalization is undesired, you can signal an error by comparing
// the constructor arguments to the normalized values returned by the YMDHMS
// properties.
//
// PROPERTIES:
//
// All civil-time types have accessors for all six of the civil-time fields:
// year, month, day, hour, minute, and second. Recall that fields inferior to
// the type's alignment will be set to their minimum valid value.
//
// civil_day d(2015, 6, 28);
// // d.year() == 2015
// // d.month() == 6
// // d.day() == 28
// // d.hour() == 0
// // d.minute() == 0
// // d.second() == 0
//
// COMPARISON:
//
// Comparison always considers all six YMDHMS fields, regardless of the type's
// alignment. Comparison between differently aligned civil-time types is
// allowed.
//
// civil_day feb_3(2015, 2, 3); // 2015-02-03 00:00:00
// civil_day mar_4(2015, 3, 4); // 2015-03-04 00:00:00
// // feb_3 < mar_4
// // civil_year(feb_3) == civil_year(mar_4)
//
// civil_second feb_3_noon(2015, 2, 3, 12, 0, 0); // 2015-02-03 12:00:00
// // feb_3 < feb_3_noon
// // feb_3 == civil_day(feb_3_noon)
//
// // Iterates all the days of February 2015.
// for (civil_day d(2015, 2, 1); d < civil_month(2015, 3); ++d) {
// // ...
// }
//
// STREAMING:
//
// Each civil-time type may be sent to an output stream using operator<<().
// The output format follows the pattern "YYYY-MM-DDThh:mm:ss" where fields
// inferior to the type's alignment are omitted.
//
// civil_second cs(2015, 2, 3, 4, 5, 6);
// std::cout << cs << "\n"; // Outputs: 2015-02-03T04:05:06
//
// civil_day cd(cs);
// std::cout << cd << "\n"; // Outputs: 2015-02-03
//
// civil_year cy(cs);
// std::cout << cy << "\n"; // Outputs: 2015
//
// ARITHMETIC:
//
// Civil-time types support natural arithmetic operators such as addition,
// subtraction, and difference. Arithmetic operates on the civil-time field
// indicated in the type's name. Difference requires arguments with the same
// alignment and returns the answer in units of the alignment.
//
// civil_day a(2015, 2, 3);
// ++a; // 2015-02-04 00:00:00
// --a; // 2015-02-03 00:00:00
// civil_day b = a + 1; // 2015-02-04 00:00:00
// civil_day c = 1 + b; // 2015-02-05 00:00:00
// int n = c - a; // n = 2 (civil days)
// int m = c - civil_month(c); // Won't compile: different types.
//
// EXAMPLE: Adding a month to January 31.
//
// One of the classic questions that arises when considering a civil-time
// library (or a date library or a date/time library) is this: "What happens
// when you add a month to January 31?" This is an interesting question
// because there could be a number of possible answers:
//
// 1. March 3 (or 2 if a leap year). This may make sense if the operation
// wants the equivalent of February 31.
// 2. February 28 (or 29 if a leap year). This may make sense if the operation
// wants the last day of January to go to the last day of February.
// 3. Error. The caller may get some error, an exception, an invalid date
// object, or maybe false is returned. This may make sense because there is
// no single unambiguously correct answer to the question.
//
// Practically speaking, any answer that is not what the programmer intended
// is the wrong answer.
//
// This civil-time library avoids the problem by making it impossible to ask
// ambiguous questions. All civil-time objects are aligned to a particular
// civil-field boundary (such as aligned to a year, month, day, hour, minute,
// or second), and arithmetic operates on the field to which the object is
// aligned. This means that in order to "add a month" the object must first be
// aligned to a month boundary, which is equivalent to the first day of that
// month.
//
// Of course, there are ways to compute an answer the question at hand using
// this civil-time library, but they require the programmer to be explicit
// about the answer they expect. To illustrate, let's see how to compute all
// three of the above possible answers to the question of "Jan 31 plus 1
// month":
//
// const civil_day d(2015, 1, 31);
//
// // Answer 1:
// // Add 1 to the month field in the constructor, and rely on normalization.
// const auto ans_normalized = civil_day(d.year(), d.month() + 1, d.day());
// // ans_normalized == 2015-03-03 (aka Feb 31)
//
// // Answer 2:
// // Add 1 to month field, capping to the end of next month.
// const auto next_month = civil_month(d) + 1;
// const auto last_day_of_next_month = civil_day(next_month + 1) - 1;
// const auto ans_capped = std::min(ans_normalized, last_day_of_next_month);
// // ans_capped == 2015-02-28
//
// // Answer 3:
// // Signal an error if the normalized answer is not in next month.
// if (civil_month(ans_normalized) != next_month) {
// // error, month overflow
// }
//
using civil_year = detail::civil_year;
using civil_month = detail::civil_month;
using civil_day = detail::civil_day;
using civil_hour = detail::civil_hour;
using civil_minute = detail::civil_minute;
using civil_second = detail::civil_second;
// An enum class with members monday, tuesday, wednesday, thursday, friday,
// saturday, and sunday. These enum values may be sent to an output stream
// using operator<<(). The result is the full weekday name in English with a
// leading capital letter.
//
// weekday wd = weekday::thursday;
// std::cout << wd << "\n"; // Outputs: Thursday
//
using detail::weekday;
// Returns the weekday for the given civil-time value.
//
// civil_day a(2015, 8, 13);
// weekday wd = get_weekday(a); // wd == weekday::thursday
//
using detail::get_weekday;
// Returns the civil_day that strictly follows or precedes the given
// civil_day, and that falls on the given weekday.
//
// For example, given:
//
// August 2015
// Su Mo Tu We Th Fr Sa
// 1
// 2 3 4 5 6 7 8
// 9 10 11 12 13 14 15
// 16 17 18 19 20 21 22
// 23 24 25 26 27 28 29
// 30 31
//
// civil_day a(2015, 8, 13); // get_weekday(a) == weekday::thursday
// civil_day b = next_weekday(a, weekday::thursday); // b = 2015-08-20
// civil_day c = prev_weekday(a, weekday::thursday); // c = 2015-08-06
//
// civil_day d = ...
// // Gets the following Thursday if d is not already Thursday
// civil_day thurs1 = next_weekday(d - 1, weekday::thursday);
// // Gets the previous Thursday if d is not already Thursday
// civil_day thurs2 = prev_weekday(d + 1, weekday::thursday);
//
using detail::next_weekday;
using detail::prev_weekday;
// Returns the day-of-year for the given civil-time value.
//
// civil_day a(2015, 1, 1);
// int yd_jan_1 = get_yearday(a); // yd_jan_1 = 1
// civil_day b(2015, 12, 31);
// int yd_dec_31 = get_yearday(b); // yd_dec_31 = 365
//
using detail::get_yearday;
} // namespace cctz
#endif // CCTZ_CIVIL_TIME_H_
timechange/src/cctz/src/ 0000755 0001762 0000144 00000000000 14552163764 014714 5 ustar ligges users timechange/src/cctz/src/time_zone_if.h 0000644 0001762 0000144 00000004654 14326763405 017542 0 ustar ligges users // Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef CCTZ_TIME_ZONE_IF_H_
#define CCTZ_TIME_ZONE_IF_H_
#include
#include
#include
#include
#include "cctz/civil_time.h"
#include "cctz/time_zone.h"
namespace cctz {
// A simple interface used to hide time-zone complexities from time_zone::Impl.
// Subclasses implement the functions for civil-time conversions in the zone.
class TimeZoneIf {
public:
// A factory function for TimeZoneIf implementations.
static std::unique_ptr Load(const std::string& name);
virtual ~TimeZoneIf();
virtual time_zone::absolute_lookup BreakTime(
const time_point& tp) const = 0;
virtual time_zone::civil_lookup MakeTime(
const civil_second& cs) const = 0;
virtual bool NextTransition(const time_point& tp,
time_zone::civil_transition* trans) const = 0;
virtual bool PrevTransition(const time_point& tp,
time_zone::civil_transition* trans) const = 0;
virtual std::string Version() const = 0;
virtual std::string Description() const = 0;
protected:
TimeZoneIf() {}
};
// Convert between time_point and a count of seconds since the
// Unix epoch. We assume that the std::chrono::system_clock and the
// Unix clock are second aligned, and that the results are representable.
// (That is, that they share an epoch, which is required since C++20.)
inline std::int_fast64_t ToUnixSeconds(const time_point& tp) {
return (tp - std::chrono::time_point_cast(
std::chrono::system_clock::from_time_t(0))).count();
}
inline time_point FromUnixSeconds(std::int_fast64_t t) {
return std::chrono::time_point_cast(
std::chrono::system_clock::from_time_t(0)) + seconds(t);
}
} // namespace cctz
#endif // CCTZ_TIME_ZONE_IF_H_
timechange/src/cctz/src/zone_info_source.cc 0000644 0001762 0000144 00000007747 14326763406 020606 0 ustar ligges users // Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "cctz/zone_info_source.h"
namespace cctz {
// Defined out-of-line to avoid emitting a weak vtable in all TUs.
ZoneInfoSource::~ZoneInfoSource() {}
std::string ZoneInfoSource::Version() const { return std::string(); }
} // namespace cctz
namespace cctz_extension {
namespace {
// A default for cctz_extension::zone_info_source_factory, which simply
// defers to the fallback factory.
std::unique_ptr DefaultFactory(
const std::string& name,
const std::function(
const std::string& name)>& fallback_factory) {
return fallback_factory(name);
}
} // namespace
// A "weak" definition for cctz_extension::zone_info_source_factory.
// The user may override this with their own "strong" definition (see
// zone_info_source.h).
#if !defined(__has_attribute)
#define __has_attribute(x) 0
#endif
// MinGW is GCC on Windows, so while it asserts __has_attribute(weak), the
// Windows linker cannot handle that. Nor does the MinGW compiler know how to
// pass "#pragma comment(linker, ...)" to the Windows linker.
#if (__has_attribute(weak) || defined(__GNUC__)) && !defined(__MINGW32__)
ZoneInfoSourceFactory zone_info_source_factory
__attribute__((weak)) = DefaultFactory;
#elif defined(_MSC_VER) && !defined(__MINGW32__) && !defined(_LIBCPP_VERSION)
extern ZoneInfoSourceFactory zone_info_source_factory;
extern ZoneInfoSourceFactory default_factory;
ZoneInfoSourceFactory default_factory = DefaultFactory;
#if defined(_M_IX86) || defined(_M_ARM)
#pragma comment( \
linker, \
"/alternatename:?zone_info_source_factory@cctz_extension@@3P6A?AV?$unique_ptr@VZoneInfoSource@cctz@@U?$default_delete@VZoneInfoSource@cctz@@@std@@@std@@ABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@3@ABV?$function@$$A6A?AV?$unique_ptr@VZoneInfoSource@cctz@@U?$default_delete@VZoneInfoSource@cctz@@@std@@@std@@ABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@2@@Z@3@@ZA=?default_factory@cctz_extension@@3P6A?AV?$unique_ptr@VZoneInfoSource@cctz@@U?$default_delete@VZoneInfoSource@cctz@@@std@@@std@@ABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@3@ABV?$function@$$A6A?AV?$unique_ptr@VZoneInfoSource@cctz@@U?$default_delete@VZoneInfoSource@cctz@@@std@@@std@@ABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@2@@Z@3@@ZA")
#elif defined(_M_IA_64) || defined(_M_AMD64) || defined(_M_ARM64)
#pragma comment( \
linker, \
"/alternatename:?zone_info_source_factory@cctz_extension@@3P6A?AV?$unique_ptr@VZoneInfoSource@cctz@@U?$default_delete@VZoneInfoSource@cctz@@@std@@@std@@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@3@AEBV?$function@$$A6A?AV?$unique_ptr@VZoneInfoSource@cctz@@U?$default_delete@VZoneInfoSource@cctz@@@std@@@std@@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@2@@Z@3@@ZEA=?default_factory@cctz_extension@@3P6A?AV?$unique_ptr@VZoneInfoSource@cctz@@U?$default_delete@VZoneInfoSource@cctz@@@std@@@std@@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@3@AEBV?$function@$$A6A?AV?$unique_ptr@VZoneInfoSource@cctz@@U?$default_delete@VZoneInfoSource@cctz@@@std@@@std@@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@2@@Z@3@@ZEA")
#else
#error Unsupported MSVC platform
#endif // _M_
#else
// Make it a "strong" definition if we have no other choice.
ZoneInfoSourceFactory zone_info_source_factory = DefaultFactory;
#endif
} // namespace cctz_extension
timechange/src/cctz/src/time_zone_libc.h 0000644 0001762 0000144 00000003232 13573274232 020042 0 ustar ligges users // Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef CCTZ_TIME_ZONE_LIBC_H_
#define CCTZ_TIME_ZONE_LIBC_H_
#include
#include "time_zone_if.h"
namespace cctz {
// A time zone backed by gmtime_r(3), localtime_r(3), and mktime(3),
// and which therefore only supports UTC and the local time zone.
// TODO: Add support for fixed offsets from UTC.
class TimeZoneLibC : public TimeZoneIf {
public:
explicit TimeZoneLibC(const std::string& name);
// TimeZoneIf implementations.
time_zone::absolute_lookup BreakTime(
const time_point& tp) const override;
time_zone::civil_lookup MakeTime(
const civil_second& cs) const override;
bool NextTransition(const time_point& tp,
time_zone::civil_transition* trans) const override;
bool PrevTransition(const time_point& tp,
time_zone::civil_transition* trans) const override;
std::string Version() const override;
std::string Description() const override;
private:
const bool local_; // localtime or UTC
};
} // namespace cctz
#endif // CCTZ_TIME_ZONE_LIBC_H_
timechange/src/cctz/src/time_zone_lookup_test.cc 0000644 0001762 0000144 00000140023 14327324721 021634 0 ustar ligges users // Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "cctz/time_zone.h"
#include
#include
#include
#include
#include
#include
#include
#include
#if defined(__linux__)
#include
#endif
#include "cctz/civil_time.h"
#include "gtest/gtest.h"
namespace chrono = std::chrono;
namespace cctz {
namespace {
// A list of known time-zone names.
const char* const kTimeZoneNames[] = {
"Africa/Abidjan",
"Africa/Accra",
"Africa/Addis_Ababa",
"Africa/Algiers",
"Africa/Asmara",
"Africa/Asmera",
"Africa/Bamako",
"Africa/Bangui",
"Africa/Banjul",
"Africa/Bissau",
"Africa/Blantyre",
"Africa/Brazzaville",
"Africa/Bujumbura",
"Africa/Cairo",
"Africa/Casablanca",
"Africa/Ceuta",
"Africa/Conakry",
"Africa/Dakar",
"Africa/Dar_es_Salaam",
"Africa/Djibouti",
"Africa/Douala",
"Africa/El_Aaiun",
"Africa/Freetown",
"Africa/Gaborone",
"Africa/Harare",
"Africa/Johannesburg",
"Africa/Juba",
"Africa/Kampala",
"Africa/Khartoum",
"Africa/Kigali",
"Africa/Kinshasa",
"Africa/Lagos",
"Africa/Libreville",
"Africa/Lome",
"Africa/Luanda",
"Africa/Lubumbashi",
"Africa/Lusaka",
"Africa/Malabo",
"Africa/Maputo",
"Africa/Maseru",
"Africa/Mbabane",
"Africa/Mogadishu",
"Africa/Monrovia",
"Africa/Nairobi",
"Africa/Ndjamena",
"Africa/Niamey",
"Africa/Nouakchott",
"Africa/Ouagadougou",
"Africa/Porto-Novo",
"Africa/Sao_Tome",
"Africa/Timbuktu",
"Africa/Tripoli",
"Africa/Tunis",
"Africa/Windhoek",
"America/Adak",
"America/Anchorage",
"America/Anguilla",
"America/Antigua",
"America/Araguaina",
"America/Argentina/Buenos_Aires",
"America/Argentina/Catamarca",
"America/Argentina/ComodRivadavia",
"America/Argentina/Cordoba",
"America/Argentina/Jujuy",
"America/Argentina/La_Rioja",
"America/Argentina/Mendoza",
"America/Argentina/Rio_Gallegos",
"America/Argentina/Salta",
"America/Argentina/San_Juan",
"America/Argentina/San_Luis",
"America/Argentina/Tucuman",
"America/Argentina/Ushuaia",
"America/Aruba",
"America/Asuncion",
"America/Atikokan",
"America/Atka",
"America/Bahia",
"America/Bahia_Banderas",
"America/Barbados",
"America/Belem",
"America/Belize",
"America/Blanc-Sablon",
"America/Boa_Vista",
"America/Bogota",
"America/Boise",
"America/Buenos_Aires",
"America/Cambridge_Bay",
"America/Campo_Grande",
"America/Cancun",
"America/Caracas",
"America/Catamarca",
"America/Cayenne",
"America/Cayman",
"America/Chicago",
"America/Chihuahua",
"America/Coral_Harbour",
"America/Cordoba",
"America/Costa_Rica",
"America/Creston",
"America/Cuiaba",
"America/Curacao",
"America/Danmarkshavn",
"America/Dawson",
"America/Dawson_Creek",
"America/Denver",
"America/Detroit",
"America/Dominica",
"America/Edmonton",
"America/Eirunepe",
"America/El_Salvador",
"America/Ensenada",
"America/Fort_Nelson",
"America/Fort_Wayne",
"America/Fortaleza",
"America/Glace_Bay",
"America/Godthab",
"America/Goose_Bay",
"America/Grand_Turk",
"America/Grenada",
"America/Guadeloupe",
"America/Guatemala",
"America/Guayaquil",
"America/Guyana",
"America/Halifax",
"America/Havana",
"America/Hermosillo",
"America/Indiana/Indianapolis",
"America/Indiana/Knox",
"America/Indiana/Marengo",
"America/Indiana/Petersburg",
"America/Indiana/Tell_City",
"America/Indiana/Vevay",
"America/Indiana/Vincennes",
"America/Indiana/Winamac",
"America/Indianapolis",
"America/Inuvik",
"America/Iqaluit",
"America/Jamaica",
"America/Jujuy",
"America/Juneau",
"America/Kentucky/Louisville",
"America/Kentucky/Monticello",
"America/Knox_IN",
"America/Kralendijk",
"America/La_Paz",
"America/Lima",
"America/Los_Angeles",
"America/Louisville",
"America/Lower_Princes",
"America/Maceio",
"America/Managua",
"America/Manaus",
"America/Marigot",
"America/Martinique",
"America/Matamoros",
"America/Mazatlan",
"America/Mendoza",
"America/Menominee",
"America/Merida",
"America/Metlakatla",
"America/Mexico_City",
"America/Miquelon",
"America/Moncton",
"America/Monterrey",
"America/Montevideo",
"America/Montreal",
"America/Montserrat",
"America/Nassau",
"America/New_York",
"America/Nipigon",
"America/Nome",
"America/Noronha",
"America/North_Dakota/Beulah",
"America/North_Dakota/Center",
"America/North_Dakota/New_Salem",
"America/Nuuk",
"America/Ojinaga",
"America/Panama",
"America/Pangnirtung",
"America/Paramaribo",
"America/Phoenix",
"America/Port-au-Prince",
"America/Port_of_Spain",
"America/Porto_Acre",
"America/Porto_Velho",
"America/Puerto_Rico",
"America/Punta_Arenas",
"America/Rainy_River",
"America/Rankin_Inlet",
"America/Recife",
"America/Regina",
"America/Resolute",
"America/Rio_Branco",
"America/Rosario",
"America/Santa_Isabel",
"America/Santarem",
"America/Santiago",
"America/Santo_Domingo",
"America/Sao_Paulo",
"America/Scoresbysund",
"America/Shiprock",
"America/Sitka",
"America/St_Barthelemy",
"America/St_Johns",
"America/St_Kitts",
"America/St_Lucia",
"America/St_Thomas",
"America/St_Vincent",
"America/Swift_Current",
"America/Tegucigalpa",
"America/Thule",
"America/Thunder_Bay",
"America/Tijuana",
"America/Toronto",
"America/Tortola",
"America/Vancouver",
"America/Virgin",
"America/Whitehorse",
"America/Winnipeg",
"America/Yakutat",
"America/Yellowknife",
"Antarctica/Casey",
"Antarctica/Davis",
"Antarctica/DumontDUrville",
"Antarctica/Macquarie",
"Antarctica/Mawson",
"Antarctica/McMurdo",
"Antarctica/Palmer",
"Antarctica/Rothera",
"Antarctica/South_Pole",
"Antarctica/Syowa",
"Antarctica/Troll",
"Antarctica/Vostok",
"Arctic/Longyearbyen",
"Asia/Aden",
"Asia/Almaty",
"Asia/Amman",
"Asia/Anadyr",
"Asia/Aqtau",
"Asia/Aqtobe",
"Asia/Ashgabat",
"Asia/Ashkhabad",
"Asia/Atyrau",
"Asia/Baghdad",
"Asia/Bahrain",
"Asia/Baku",
"Asia/Bangkok",
"Asia/Barnaul",
"Asia/Beirut",
"Asia/Bishkek",
"Asia/Brunei",
"Asia/Calcutta",
"Asia/Chita",
"Asia/Choibalsan",
"Asia/Chongqing",
"Asia/Chungking",
"Asia/Colombo",
"Asia/Dacca",
"Asia/Damascus",
"Asia/Dhaka",
"Asia/Dili",
"Asia/Dubai",
"Asia/Dushanbe",
"Asia/Famagusta",
"Asia/Gaza",
"Asia/Harbin",
"Asia/Hebron",
"Asia/Ho_Chi_Minh",
"Asia/Hong_Kong",
"Asia/Hovd",
"Asia/Irkutsk",
"Asia/Istanbul",
"Asia/Jakarta",
"Asia/Jayapura",
"Asia/Jerusalem",
"Asia/Kabul",
"Asia/Kamchatka",
"Asia/Karachi",
"Asia/Kashgar",
"Asia/Kathmandu",
"Asia/Katmandu",
"Asia/Khandyga",
"Asia/Kolkata",
"Asia/Krasnoyarsk",
"Asia/Kuala_Lumpur",
"Asia/Kuching",
"Asia/Kuwait",
"Asia/Macao",
"Asia/Macau",
"Asia/Magadan",
"Asia/Makassar",
"Asia/Manila",
"Asia/Muscat",
"Asia/Nicosia",
"Asia/Novokuznetsk",
"Asia/Novosibirsk",
"Asia/Omsk",
"Asia/Oral",
"Asia/Phnom_Penh",
"Asia/Pontianak",
"Asia/Pyongyang",
"Asia/Qatar",
"Asia/Qostanay",
"Asia/Qyzylorda",
"Asia/Rangoon",
"Asia/Riyadh",
"Asia/Saigon",
"Asia/Sakhalin",
"Asia/Samarkand",
"Asia/Seoul",
"Asia/Shanghai",
"Asia/Singapore",
"Asia/Srednekolymsk",
"Asia/Taipei",
"Asia/Tashkent",
"Asia/Tbilisi",
"Asia/Tehran",
"Asia/Tel_Aviv",
"Asia/Thimbu",
"Asia/Thimphu",
"Asia/Tokyo",
"Asia/Tomsk",
"Asia/Ujung_Pandang",
"Asia/Ulaanbaatar",
"Asia/Ulan_Bator",
"Asia/Urumqi",
"Asia/Ust-Nera",
"Asia/Vientiane",
"Asia/Vladivostok",
"Asia/Yakutsk",
"Asia/Yangon",
"Asia/Yekaterinburg",
"Asia/Yerevan",
"Atlantic/Azores",
"Atlantic/Bermuda",
"Atlantic/Canary",
"Atlantic/Cape_Verde",
"Atlantic/Faeroe",
"Atlantic/Faroe",
"Atlantic/Jan_Mayen",
"Atlantic/Madeira",
"Atlantic/Reykjavik",
"Atlantic/South_Georgia",
"Atlantic/St_Helena",
"Atlantic/Stanley",
"Australia/ACT",
"Australia/Adelaide",
"Australia/Brisbane",
"Australia/Broken_Hill",
"Australia/Canberra",
"Australia/Currie",
"Australia/Darwin",
"Australia/Eucla",
"Australia/Hobart",
"Australia/LHI",
"Australia/Lindeman",
"Australia/Lord_Howe",
"Australia/Melbourne",
"Australia/NSW",
"Australia/North",
"Australia/Perth",
"Australia/Queensland",
"Australia/South",
"Australia/Sydney",
"Australia/Tasmania",
"Australia/Victoria",
"Australia/West",
"Australia/Yancowinna",
"Brazil/Acre",
"Brazil/DeNoronha",
"Brazil/East",
"Brazil/West",
"CET",
"CST6CDT",
"Canada/Atlantic",
"Canada/Central",
"Canada/Eastern",
"Canada/Mountain",
"Canada/Newfoundland",
"Canada/Pacific",
"Canada/Saskatchewan",
"Canada/Yukon",
"Chile/Continental",
"Chile/EasterIsland",
"Cuba",
"EET",
"EST",
"EST5EDT",
"Egypt",
"Eire",
"Etc/GMT",
"Etc/GMT+0",
"Etc/GMT+1",
"Etc/GMT+10",
"Etc/GMT+11",
"Etc/GMT+12",
"Etc/GMT+2",
"Etc/GMT+3",
"Etc/GMT+4",
"Etc/GMT+5",
"Etc/GMT+6",
"Etc/GMT+7",
"Etc/GMT+8",
"Etc/GMT+9",
"Etc/GMT-0",
"Etc/GMT-1",
"Etc/GMT-10",
"Etc/GMT-11",
"Etc/GMT-12",
"Etc/GMT-13",
"Etc/GMT-14",
"Etc/GMT-2",
"Etc/GMT-3",
"Etc/GMT-4",
"Etc/GMT-5",
"Etc/GMT-6",
"Etc/GMT-7",
"Etc/GMT-8",
"Etc/GMT-9",
"Etc/GMT0",
"Etc/Greenwich",
"Etc/UCT",
"Etc/UTC",
"Etc/Universal",
"Etc/Zulu",
"Europe/Amsterdam",
"Europe/Andorra",
"Europe/Astrakhan",
"Europe/Athens",
"Europe/Belfast",
"Europe/Belgrade",
"Europe/Berlin",
"Europe/Bratislava",
"Europe/Brussels",
"Europe/Bucharest",
"Europe/Budapest",
"Europe/Busingen",
"Europe/Chisinau",
"Europe/Copenhagen",
"Europe/Dublin",
"Europe/Gibraltar",
"Europe/Guernsey",
"Europe/Helsinki",
"Europe/Isle_of_Man",
"Europe/Istanbul",
"Europe/Jersey",
"Europe/Kaliningrad",
"Europe/Kiev",
"Europe/Kirov",
"Europe/Kyiv",
"Europe/Lisbon",
"Europe/Ljubljana",
"Europe/London",
"Europe/Luxembourg",
"Europe/Madrid",
"Europe/Malta",
"Europe/Mariehamn",
"Europe/Minsk",
"Europe/Monaco",
"Europe/Moscow",
"Europe/Nicosia",
"Europe/Oslo",
"Europe/Paris",
"Europe/Podgorica",
"Europe/Prague",
"Europe/Riga",
"Europe/Rome",
"Europe/Samara",
"Europe/San_Marino",
"Europe/Sarajevo",
"Europe/Saratov",
"Europe/Simferopol",
"Europe/Skopje",
"Europe/Sofia",
"Europe/Stockholm",
"Europe/Tallinn",
"Europe/Tirane",
"Europe/Tiraspol",
"Europe/Ulyanovsk",
"Europe/Uzhgorod",
"Europe/Vaduz",
"Europe/Vatican",
"Europe/Vienna",
"Europe/Vilnius",
"Europe/Volgograd",
"Europe/Warsaw",
"Europe/Zagreb",
"Europe/Zaporozhye",
"Europe/Zurich",
"Factory",
"GB",
"GB-Eire",
"GMT",
"GMT+0",
"GMT-0",
"GMT0",
"Greenwich",
"HST",
"Hongkong",
"Iceland",
"Indian/Antananarivo",
"Indian/Chagos",
"Indian/Christmas",
"Indian/Cocos",
"Indian/Comoro",
"Indian/Kerguelen",
"Indian/Mahe",
"Indian/Maldives",
"Indian/Mauritius",
"Indian/Mayotte",
"Indian/Reunion",
"Iran",
"Israel",
"Jamaica",
"Japan",
"Kwajalein",
"Libya",
"MET",
"MST",
"MST7MDT",
"Mexico/BajaNorte",
"Mexico/BajaSur",
"Mexico/General",
"NZ",
"NZ-CHAT",
"Navajo",
"PRC",
"PST8PDT",
"Pacific/Apia",
"Pacific/Auckland",
"Pacific/Bougainville",
"Pacific/Chatham",
"Pacific/Chuuk",
"Pacific/Easter",
"Pacific/Efate",
"Pacific/Enderbury",
"Pacific/Fakaofo",
"Pacific/Fiji",
"Pacific/Funafuti",
"Pacific/Galapagos",
"Pacific/Gambier",
"Pacific/Guadalcanal",
"Pacific/Guam",
"Pacific/Honolulu",
"Pacific/Johnston",
"Pacific/Kanton",
"Pacific/Kiritimati",
"Pacific/Kosrae",
"Pacific/Kwajalein",
"Pacific/Majuro",
"Pacific/Marquesas",
"Pacific/Midway",
"Pacific/Nauru",
"Pacific/Niue",
"Pacific/Norfolk",
"Pacific/Noumea",
"Pacific/Pago_Pago",
"Pacific/Palau",
"Pacific/Pitcairn",
"Pacific/Pohnpei",
"Pacific/Ponape",
"Pacific/Port_Moresby",
"Pacific/Rarotonga",
"Pacific/Saipan",
"Pacific/Samoa",
"Pacific/Tahiti",
"Pacific/Tarawa",
"Pacific/Tongatapu",
"Pacific/Truk",
"Pacific/Wake",
"Pacific/Wallis",
"Pacific/Yap",
"Poland",
"Portugal",
"ROC",
"ROK",
"Singapore",
"Turkey",
"UCT",
"US/Alaska",
"US/Aleutian",
"US/Arizona",
"US/Central",
"US/East-Indiana",
"US/Eastern",
"US/Hawaii",
"US/Indiana-Starke",
"US/Michigan",
"US/Mountain",
"US/Pacific",
"US/Samoa",
"UTC",
"Universal",
"W-SU",
"WET",
"Zulu",
nullptr
};
// Helper to return a loaded time zone by value (UTC on error).
time_zone LoadZone(const std::string& name) {
time_zone tz;
load_time_zone(name, &tz);
return tz;
}
// This helper is a macro so that failed expectations show up with the
// correct line numbers.
#define ExpectTime(tp, tz, y, m, d, hh, mm, ss, off, isdst, zone) \
do { \
time_zone::absolute_lookup al = tz.lookup(tp); \
EXPECT_EQ(y, al.cs.year()); \
EXPECT_EQ(m, al.cs.month()); \
EXPECT_EQ(d, al.cs.day()); \
EXPECT_EQ(hh, al.cs.hour()); \
EXPECT_EQ(mm, al.cs.minute()); \
EXPECT_EQ(ss, al.cs.second()); \
EXPECT_EQ(off, al.offset); \
EXPECT_TRUE(isdst == al.is_dst); \
/* EXPECT_STREQ(zone, al.abbr); */ \
} while (0)
// These tests sometimes run on platforms that have zoneinfo data so old
// that the transition we are attempting to check does not exist, most
// notably Android emulators. Fortunately, AndroidZoneInfoSource supports
// time_zone::version() so, in cases where we've learned that it matters,
// we can make the check conditionally.
int VersionCmp(time_zone tz, const std::string& target) {
std::string version = tz.version();
if (version.empty() && !target.empty()) return 1; // unknown > known
return version.compare(target);
}
} // namespace
TEST(TimeZones, LoadZonesConcurrently) {
std::promise ready_promise;
std::shared_future ready_future(ready_promise.get_future());
auto load_zones = [ready_future](std::promise* started,
std::set* failures) {
started->set_value();
ready_future.wait();
for (const char* const* np = kTimeZoneNames; *np != nullptr; ++np) {
std::string zone = *np;
time_zone tz;
if (load_time_zone(zone, &tz)) {
EXPECT_EQ(zone, tz.name());
} else {
failures->insert(zone);
}
}
};
const std::size_t n_threads = 128;
std::vector threads;
std::vector> thread_failures(n_threads);
for (std::size_t i = 0; i != n_threads; ++i) {
std::promise started;
threads.emplace_back(load_zones, &started, &thread_failures[i]);
started.get_future().wait();
}
ready_promise.set_value();
for (auto& thread : threads) {
thread.join();
}
// Allow a small number of failures to account for skew between
// the contents of kTimeZoneNames and the zoneinfo data source.
#if defined(__ANDROID__)
// Cater to the possibility of using an even older zoneinfo data
// source when running on Android, where it is difficult to override
// the bionic tzdata provided by the test environment.
const std::size_t max_failures = 20;
#else
const std::size_t max_failures = 3;
#endif
std::set failures;
for (const auto& thread_failure : thread_failures) {
failures.insert(thread_failure.begin(), thread_failure.end());
}
EXPECT_LE(failures.size(), max_failures) << testing::PrintToString(failures);
}
TEST(TimeZone, UTC) {
const time_zone utc = utc_time_zone();
time_zone loaded_utc;
EXPECT_TRUE(load_time_zone("UTC", &loaded_utc));
EXPECT_EQ(loaded_utc, utc);
time_zone loaded_utc0;
EXPECT_TRUE(load_time_zone("UTC0", &loaded_utc0));
EXPECT_EQ(loaded_utc0, utc);
}
TEST(TimeZone, NamedTimeZones) {
const time_zone utc = utc_time_zone();
EXPECT_EQ("UTC", utc.name());
const time_zone nyc = LoadZone("America/New_York");
EXPECT_EQ("America/New_York", nyc.name());
const time_zone syd = LoadZone("Australia/Sydney");
EXPECT_EQ("Australia/Sydney", syd.name());
const time_zone fixed0 = fixed_time_zone(cctz::seconds::zero());
EXPECT_EQ("UTC", fixed0.name());
const time_zone fixed_pos = fixed_time_zone(
chrono::hours(3) + chrono::minutes(25) + chrono::seconds(45));
EXPECT_EQ("Fixed/UTC+03:25:45", fixed_pos.name());
const time_zone fixed_neg = fixed_time_zone(
-(chrono::hours(12) + chrono::minutes(34) + chrono::seconds(56)));
EXPECT_EQ("Fixed/UTC-12:34:56", fixed_neg.name());
}
TEST(TimeZone, Failures) {
time_zone tz;
EXPECT_FALSE(load_time_zone(":America/Los_Angeles", &tz));
tz = LoadZone("America/Los_Angeles");
EXPECT_FALSE(load_time_zone("Invalid/TimeZone", &tz));
EXPECT_EQ(chrono::system_clock::from_time_t(0),
convert(civil_second(1970, 1, 1, 0, 0, 0), tz)); // UTC
// Ensures that the load still fails on a subsequent attempt.
tz = LoadZone("America/Los_Angeles");
EXPECT_FALSE(load_time_zone("Invalid/TimeZone", &tz));
EXPECT_EQ(chrono::system_clock::from_time_t(0),
convert(civil_second(1970, 1, 1, 0, 0, 0), tz)); // UTC
// Loading an empty string timezone should fail.
tz = LoadZone("America/Los_Angeles");
EXPECT_FALSE(load_time_zone("", &tz));
EXPECT_EQ(chrono::system_clock::from_time_t(0),
convert(civil_second(1970, 1, 1, 0, 0, 0), tz)); // UTC
}
TEST(TimeZone, Equality) {
const time_zone a;
const time_zone b;
EXPECT_EQ(a, b);
EXPECT_EQ(a.name(), b.name());
const time_zone implicit_utc;
const time_zone explicit_utc = utc_time_zone();
EXPECT_EQ(implicit_utc, explicit_utc);
EXPECT_EQ(implicit_utc.name(), explicit_utc.name());
const time_zone fixed_zero = fixed_time_zone(cctz::seconds::zero());
EXPECT_EQ(fixed_zero, LoadZone(fixed_zero.name()));
EXPECT_EQ(fixed_zero, explicit_utc);
const time_zone fixed_utc = LoadZone("Fixed/UTC+00:00:00");
EXPECT_EQ(fixed_utc, LoadZone(fixed_utc.name()));
EXPECT_EQ(fixed_utc, explicit_utc);
const time_zone fixed_pos = fixed_time_zone(
chrono::hours(3) + chrono::minutes(25) + chrono::seconds(45));
EXPECT_EQ(fixed_pos, LoadZone(fixed_pos.name()));
EXPECT_NE(fixed_pos, explicit_utc);
const time_zone fixed_neg = fixed_time_zone(
-(chrono::hours(12) + chrono::minutes(34) + chrono::seconds(56)));
EXPECT_EQ(fixed_neg, LoadZone(fixed_neg.name()));
EXPECT_NE(fixed_neg, explicit_utc);
const time_zone fixed_lim = fixed_time_zone(chrono::hours(24));
EXPECT_EQ(fixed_lim, LoadZone(fixed_lim.name()));
EXPECT_NE(fixed_lim, explicit_utc);
const time_zone fixed_ovfl =
fixed_time_zone(chrono::hours(24) + chrono::seconds(1));
EXPECT_EQ(fixed_ovfl, LoadZone(fixed_ovfl.name()));
EXPECT_EQ(fixed_ovfl, explicit_utc);
EXPECT_EQ(fixed_time_zone(chrono::seconds(1)),
fixed_time_zone(chrono::seconds(1)));
const time_zone local = local_time_zone();
EXPECT_EQ(local, LoadZone(local.name()));
time_zone la = LoadZone("America/Los_Angeles");
time_zone nyc = LoadZone("America/New_York");
EXPECT_NE(la, nyc);
}
TEST(StdChronoTimePoint, TimeTAlignment) {
// Ensures that the Unix epoch and the system clock epoch are an integral
// number of seconds apart. This simplifies conversions to/from time_t.
auto diff = chrono::system_clock::time_point() -
chrono::system_clock::from_time_t(0);
EXPECT_EQ(chrono::system_clock::time_point::duration::zero(),
diff % chrono::seconds(1));
}
TEST(BreakTime, TimePointResolution) {
const time_zone utc = utc_time_zone();
const auto t0 = chrono::system_clock::from_time_t(0);
ExpectTime(chrono::time_point_cast(t0), utc,
1970, 1, 1, 0, 0, 0, 0, false, "UTC");
ExpectTime(chrono::time_point_cast(t0), utc,
1970, 1, 1, 0, 0, 0, 0, false, "UTC");
ExpectTime(chrono::time_point_cast(t0), utc,
1970, 1, 1, 0, 0, 0, 0, false, "UTC");
ExpectTime(chrono::time_point_cast(t0), utc,
1970, 1, 1, 0, 0, 0, 0, false, "UTC");
ExpectTime(chrono::time_point_cast(t0), utc,
1970, 1, 1, 0, 0, 0, 0, false, "UTC");
ExpectTime(chrono::time_point_cast(t0), utc,
1970, 1, 1, 0, 0, 0, 0, false, "UTC");
ExpectTime(chrono::time_point_cast(t0), utc,
1970, 1, 1, 0, 0, 0, 0, false, "UTC");
}
TEST(BreakTime, LocalTimeInUTC) {
const time_zone tz = utc_time_zone();
const auto tp = chrono::system_clock::from_time_t(0);
ExpectTime(tp, tz, 1970, 1, 1, 0, 0, 0, 0, false, "UTC");
EXPECT_EQ(weekday::thursday, get_weekday(convert(tp, tz)));
}
TEST(BreakTime, LocalTimeInUTCUnaligned) {
const time_zone tz = utc_time_zone();
const auto tp =
chrono::system_clock::from_time_t(0) - chrono::milliseconds(500);
ExpectTime(tp, tz, 1969, 12, 31, 23, 59, 59, 0, false, "UTC");
EXPECT_EQ(weekday::wednesday, get_weekday(convert(tp, tz)));
}
TEST(BreakTime, LocalTimePosix) {
// See IEEE Std 1003.1-1988 B.2.3 General Terms, Epoch.
const time_zone tz = utc_time_zone();
const auto tp = chrono::system_clock::from_time_t(536457599);
ExpectTime(tp, tz, 1986, 12, 31, 23, 59, 59, 0, false, "UTC");
EXPECT_EQ(weekday::wednesday, get_weekday(convert(tp, tz)));
}
TEST(TimeZoneImpl, LocalTimeInFixed) {
const cctz::seconds offset =
-(chrono::hours(8) + chrono::minutes(33) + chrono::seconds(47));
const time_zone tz = fixed_time_zone(offset);
const auto tp = chrono::system_clock::from_time_t(0);
ExpectTime(tp, tz, 1969, 12, 31, 15, 26, 13, offset.count(), false,
"-083347");
EXPECT_EQ(weekday::wednesday, get_weekday(convert(tp, tz)));
}
TEST(BreakTime, LocalTimeInNewYork) {
const time_zone tz = LoadZone("America/New_York");
const auto tp = chrono::system_clock::from_time_t(45);
ExpectTime(tp, tz, 1969, 12, 31, 19, 0, 45, -5 * 60 * 60, false, "EST");
EXPECT_EQ(weekday::wednesday, get_weekday(convert(tp, tz)));
}
TEST(BreakTime, LocalTimeInMTV) {
const time_zone tz = LoadZone("America/Los_Angeles");
const auto tp = chrono::system_clock::from_time_t(1380855729);
ExpectTime(tp, tz, 2013, 10, 3, 20, 2, 9, -7 * 60 * 60, true, "PDT");
EXPECT_EQ(weekday::thursday, get_weekday(convert(tp, tz)));
}
TEST(BreakTime, LocalTimeInSydney) {
const time_zone tz = LoadZone("Australia/Sydney");
const auto tp = chrono::system_clock::from_time_t(90);
ExpectTime(tp, tz, 1970, 1, 1, 10, 1, 30, 10 * 60 * 60, false, "AEST");
EXPECT_EQ(weekday::thursday, get_weekday(convert(tp, tz)));
}
TEST(MakeTime, TimePointResolution) {
const time_zone utc = utc_time_zone();
const time_point tp_ns =
convert(civil_second(2015, 1, 2, 3, 4, 5), utc);
EXPECT_EQ("04:05", format("%M:%E*S", tp_ns, utc));
const time_point tp_us =
convert(civil_second(2015, 1, 2, 3, 4, 5), utc);
EXPECT_EQ("04:05", format("%M:%E*S", tp_us, utc));
const time_point tp_ms =
convert(civil_second(2015, 1, 2, 3, 4, 5), utc);
EXPECT_EQ("04:05", format("%M:%E*S", tp_ms, utc));
const time_point tp_s =
convert(civil_second(2015, 1, 2, 3, 4, 5), utc);
EXPECT_EQ("04:05", format("%M:%E*S", tp_s, utc));
const time_point tp_s64 =
convert(civil_second(2015, 1, 2, 3, 4, 5), utc);
EXPECT_EQ("04:05", format("%M:%E*S", tp_s64, utc));
// These next two require chrono::time_point_cast because the conversion
// from a resolution of seconds (the return value of convert()) to a
// coarser resolution requires an explicit cast.
const time_point tp_m =
chrono::time_point_cast(
convert(civil_second(2015, 1, 2, 3, 4, 5), utc));
EXPECT_EQ("04:00", format("%M:%E*S", tp_m, utc));
const time_point tp_h =
chrono::time_point_cast(
convert(civil_second(2015, 1, 2, 3, 4, 5), utc));
EXPECT_EQ("00:00", format("%M:%E*S", tp_h, utc));
}
TEST(MakeTime, Normalization) {
const time_zone tz = LoadZone("America/New_York");
const auto tp = convert(civil_second(2009, 2, 13, 18, 31, 30), tz);
EXPECT_EQ(chrono::system_clock::from_time_t(1234567890), tp);
// Now requests for the same time_point but with out-of-range fields.
EXPECT_EQ(tp, convert(civil_second(2008, 14, 13, 18, 31, 30), tz)); // month
EXPECT_EQ(tp, convert(civil_second(2009, 1, 44, 18, 31, 30), tz)); // day
EXPECT_EQ(tp, convert(civil_second(2009, 2, 12, 42, 31, 30), tz)); // hour
EXPECT_EQ(tp, convert(civil_second(2009, 2, 13, 17, 91, 30), tz)); // minute
EXPECT_EQ(tp, convert(civil_second(2009, 2, 13, 18, 30, 90), tz)); // second
}
// NOTE: Run this with -ftrapv to detect overflow problems.
TEST(MakeTime, SysSecondsLimits) {
const char RFC3339[] = "%Y-%m-%d%ET%H:%M:%S%Ez";
const time_zone utc = utc_time_zone();
const time_zone east = fixed_time_zone(chrono::hours(14));
const time_zone west = fixed_time_zone(-chrono::hours(14));
time_point tp;
// Approach the maximal time_point value from below.
tp = convert(civil_second(292277026596, 12, 4, 15, 30, 6), utc);
EXPECT_EQ("292277026596-12-04T15:30:06+00:00", format(RFC3339, tp, utc));
tp = convert(civil_second(292277026596, 12, 4, 15, 30, 7), utc);
EXPECT_EQ("292277026596-12-04T15:30:07+00:00", format(RFC3339, tp, utc));
EXPECT_EQ(time_point::max(), tp);
tp = convert(civil_second(292277026596, 12, 4, 15, 30, 8), utc);
EXPECT_EQ(time_point::max(), tp);
tp = convert(civil_second::max(), utc);
EXPECT_EQ(time_point::max(), tp);
// Checks that we can also get the maximal value for a far-east zone.
tp = convert(civil_second(292277026596, 12, 5, 5, 30, 7), east);
EXPECT_EQ("292277026596-12-05T05:30:07+14:00", format(RFC3339, tp, east));
EXPECT_EQ(time_point::max(), tp);
tp = convert(civil_second(292277026596, 12, 5, 5, 30, 8), east);
EXPECT_EQ(time_point::max(), tp);
tp = convert(civil_second::max(), east);
EXPECT_EQ(time_point::max(), tp);
// Checks that we can also get the maximal value for a far-west zone.
tp = convert(civil_second(292277026596, 12, 4, 1, 30, 7), west);
EXPECT_EQ("292277026596-12-04T01:30:07-14:00", format(RFC3339, tp, west));
EXPECT_EQ(time_point::max(), tp);
tp = convert(civil_second(292277026596, 12, 4, 7, 30, 8), west);
EXPECT_EQ(time_point::max(), tp);
tp = convert(civil_second::max(), west);
EXPECT_EQ(time_point::max(), tp);
// Approach the minimal time_point value from above.
tp = convert(civil_second(-292277022657, 1, 27, 8, 29, 53), utc);
EXPECT_EQ("-292277022657-01-27T08:29:53+00:00", format(RFC3339, tp, utc));
tp = convert(civil_second(-292277022657, 1, 27, 8, 29, 52), utc);
EXPECT_EQ("-292277022657-01-27T08:29:52+00:00", format(RFC3339, tp, utc));
EXPECT_EQ(time_point::min(), tp);
tp = convert(civil_second(-292277022657, 1, 27, 8, 29, 51), utc);
EXPECT_EQ(time_point