timechange/0000755000176200001440000000000014552166502012364 5ustar liggesuserstimechange/NAMESPACE0000644000176200001440000000044114330467710013601 0ustar liggesusers# 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.md0000644000176200001440000000252414552163750013650 0ustar liggesusers [![R build status](https://github.com/vspinu/timechange/workflows/R-CMD-check/badge.svg)](https://github.com/vspinu/timechange/actions) [![R-CMD-check](https://github.com/vspinu/timechange/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/vspinu/timechange/actions/workflows/R-CMD-check.yaml) [![CRAN status](https://www.r-pkg.org/badges/version/timechange)](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/0000755000176200001440000000000014330750137013134 5ustar liggesuserstimechange/man/time_update.Rd0000644000176200001440000001111614345623600015723 0ustar liggesusers% 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.Rd0000644000176200001440000000131513266110470017115 0ustar liggesusers% 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.Rd0000644000176200001440000002035014345623600015171 0ustar liggesusers% 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.Rd0000644000176200001440000000721614330040167015516 0ustar liggesusers% 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.Rd0000644000176200001440000000153213573202614015221 0ustar liggesusers% 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.Rd0000644000176200001440000002264614413264560015604 0ustar liggesusers% 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/DESCRIPTION0000644000176200001440000000260114552166502014071 0ustar liggesusersPackage: 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/0000755000176200001440000000000014337670452013533 5ustar liggesuserstimechange/tests/testthat/0000755000176200001440000000000014552166502015366 5ustar liggesuserstimechange/tests/testthat/helpers.R0000644000176200001440000000523514337670452017165 0ustar liggesusers 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.R0000644000176200001440000000402014357543044017577 0ustar liggesuserscontext("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.R0000644000176200001440000003621714356563775020311 0ustar liggesuserscontext("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.R0000644000176200001440000000455314356564260017261 0ustar liggesuserscontext("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.R0000644000176200001440000007060414356563265017770 0ustar liggesuserscontext("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.R0000644000176200001440000002531314327530005020555 0ustar liggesuserscontext("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.R0000644000176200001440000013041514413263324017615 0ustar liggesuserscontext("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.R0000644000176200001440000000005514337670452015516 0ustar liggesusers library(testthat) test_check("timechange") timechange/src/0000755000176200001440000000000014552163764013162 5ustar liggesuserstimechange/src/tzone.cpp0000644000176200001440000000445114337670452015027 0ustar liggesusers #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.cpp0000644000176200001440000005047314345622261015151 0ustar liggesusers// 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.cpp0000644000176200001440000001051514330477622014442 0ustar liggesusers #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.cpp0000644000176200001440000001212314413257337015151 0ustar liggesusers #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.c0000644000176200001440000001043614357542700014436 0ustar liggesusers#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.h0000644000176200001440000000116314435137263014466 0ustar liggesusers#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/0000755000176200001440000000000014552163764014125 5ustar liggesuserstimechange/src/cctz/civil_time_detail.h0000644000176200001440000004764514326763410017755 0ustar liggesusers// 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.h0000644000176200001440000004560614326763410016273 0ustar liggesusers// 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.h0000644000176200001440000000641513573273654017654 0ustar liggesusers// 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.h0000644000176200001440000003206313736317460016423 0ustar liggesusers// 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/0000755000176200001440000000000014552163764014714 5ustar liggesuserstimechange/src/cctz/src/time_zone_if.h0000644000176200001440000000465414326763405017542 0ustar liggesusers// 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.cc0000644000176200001440000000774714326763406020606 0ustar liggesusers// 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.h0000644000176200001440000000323213573274232020042 0ustar liggesusers// 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.cc0000644000176200001440000014002314327324721021634 0ustar liggesusers// 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::min(), tp); tp = convert(civil_second::min(), utc); EXPECT_EQ(time_point::min(), tp); // Checks that we can also get the minimal value for a far-east zone. tp = convert(civil_second(-292277022657, 1, 27, 22, 29, 52), east); EXPECT_EQ("-292277022657-01-27T22:29:52+14:00", format(RFC3339, tp, east)); EXPECT_EQ(time_point::min(), tp); tp = convert(civil_second(-292277022657, 1, 27, 22, 29, 51), east); EXPECT_EQ(time_point::min(), tp); tp = convert(civil_second::min(), east); EXPECT_EQ(time_point::min(), tp); // Checks that we can also get the minimal value for a far-west zone. tp = convert(civil_second(-292277022657, 1, 26, 18, 29, 52), west); EXPECT_EQ("-292277022657-01-26T18:29:52-14:00", format(RFC3339, tp, west)); EXPECT_EQ(time_point::min(), tp); tp = convert(civil_second(-292277022657, 1, 26, 18, 29, 51), west); EXPECT_EQ(time_point::min(), tp); tp = convert(civil_second::min(), west); EXPECT_EQ(time_point::min(), tp); // Some similar checks for the "libc" time-zone implementation. if (sizeof(std::time_t) >= 8) { // Checks that "tm_year + 1900", as used by the "libc" implementation, // can produce year values beyond the range on an int without overflow. #if defined(_WIN32) || defined(_WIN64) // localtime_s() and gmtime_s() don't believe in years outside [1970:3000]. #else const time_zone cut = LoadZone("libc:UTC"); const year_t max_tm_year = year_t{std::numeric_limits::max()} + 1900; tp = convert(civil_second(max_tm_year, 12, 31, 23, 59, 59), cut); #if defined(__FreeBSD__) || defined(__OpenBSD__) // The BSD gmtime_r() fails on extreme positive tm_year values. #else EXPECT_EQ("2147485547-12-31T23:59:59+00:00", format(RFC3339, tp, cut)); #endif const year_t min_tm_year = year_t{std::numeric_limits::min()} + 1900; tp = convert(civil_second(min_tm_year, 1, 1, 0, 0, 0), cut); #if defined(__Fuchsia__) // Fuchsia's gmtime_r() fails on extreme negative values (fxbug.dev/78527). #else EXPECT_EQ("-2147481748-01-01T00:00:00+00:00", format(RFC3339, tp, cut)); #endif #endif } } TEST(MakeTime, LocalTimeLibC) { // Checks that cctz and libc agree on transition points in [1970:2037]. // // We limit this test case to environments where: // 1) we know how to change the time zone used by localtime()/mktime(), // 2) cctz and localtime()/mktime() will use similar-enough tzdata, and // 3) we have some idea about how mktime() behaves during transitions. #if defined(__linux__) && defined(__GLIBC__) && !defined(__ANDROID__) const char* const ep = getenv("TZ"); std::string tz_name = (ep != nullptr) ? ep : ""; for (const char* const* np = kTimeZoneNames; *np != nullptr; ++np) { ASSERT_EQ(0, setenv("TZ", *np, 1)); // change what "localtime" means const auto zi = local_time_zone(); const auto lc = LoadZone("libc:localtime"); time_zone::civil_transition transition; for (auto tp = zi.lookup(civil_second()).trans; zi.next_transition(tp, &transition); tp = zi.lookup(transition.to).trans) { const auto fcl = zi.lookup(transition.from); const auto tcl = zi.lookup(transition.to); civil_second cs; // compare cs in zi and lc if (fcl.kind == time_zone::civil_lookup::UNIQUE) { if (tcl.kind == time_zone::civil_lookup::UNIQUE) { // Both unique; must be an is_dst or abbr change. ASSERT_EQ(transition.from, transition.to); const auto trans = fcl.trans; const auto tal = zi.lookup(trans); const auto tprev = trans - cctz::seconds(1); const auto pal = zi.lookup(tprev); if (pal.is_dst == tal.is_dst) { ASSERT_STRNE(pal.abbr, tal.abbr); } continue; } ASSERT_EQ(time_zone::civil_lookup::REPEATED, tcl.kind); cs = transition.to; } else { ASSERT_EQ(time_zone::civil_lookup::UNIQUE, tcl.kind); ASSERT_EQ(time_zone::civil_lookup::SKIPPED, fcl.kind); cs = transition.from; } if (cs.year() > 2037) break; // limit test time (and to 32-bit time_t) const auto cl_zi = zi.lookup(cs); if (zi.lookup(cl_zi.pre).is_dst == zi.lookup(cl_zi.post).is_dst) { // The "libc" implementation cannot correctly classify transitions // that don't change the "tm_isdst" flag. In Europe/Volgograd, for // example, there is a SKIPPED transition from +03 to +04 with dst=F // on both sides ... // 1540681199 = 2018-10-28 01:59:59 +03:00:00 [dst=F off=10800] // 1540681200 = 2018-10-28 03:00:00 +04:00:00 [dst=F off=14400] // but std::mktime(2018-10-28 02:00:00, tm_isdst=0) fails, unlike, // say, the similar Europe/Chisinau transition from +02 to +03 ... // 1521935999 = 2018-03-25 01:59:59 +02:00:00 [dst=F off=7200] // 1521936000 = 2018-03-25 03:00:00 +03:00:00 [dst=T off=10800] // where std::mktime(2018-03-25 02:00:00, tm_isdst=0) succeeds and // returns 1521936000. continue; } if (cs == civil_second(2037, 10, 4, 2, 0, 0)) { const std::string tzname = *np; if (tzname == "Africa/Casablanca" || tzname == "Africa/El_Aaiun") { // The "libc" implementation gets this transition wrong (at least // until 2018g when it was removed), returning an offset of 3600 // instead of 0. TODO: Revert this when 2018g is ubiquitous. continue; } } const auto cl_lc = lc.lookup(cs); SCOPED_TRACE(testing::Message() << "For " << cs << " in " << *np); EXPECT_EQ(cl_zi.kind, cl_lc.kind); EXPECT_EQ(cl_zi.pre, cl_lc.pre); EXPECT_EQ(cl_zi.trans, cl_lc.trans); EXPECT_EQ(cl_zi.post, cl_lc.post); } } if (ep == nullptr) { ASSERT_EQ(0, unsetenv("TZ")); } else { ASSERT_EQ(0, setenv("TZ", tz_name.c_str(), 1)); } #endif } TEST(NextTransition, UTC) { const auto tz = utc_time_zone(); time_zone::civil_transition trans; auto tp = time_point::min(); EXPECT_FALSE(tz.next_transition(tp, &trans)); tp = time_point::max(); EXPECT_FALSE(tz.next_transition(tp, &trans)); } TEST(PrevTransition, UTC) { const auto tz = utc_time_zone(); time_zone::civil_transition trans; auto tp = time_point::max(); EXPECT_FALSE(tz.prev_transition(tp, &trans)); tp = time_point::min(); EXPECT_FALSE(tz.prev_transition(tp, &trans)); } TEST(NextTransition, AmericaNewYork) { const auto tz = LoadZone("America/New_York"); time_zone::civil_transition trans; auto tp = convert(civil_second(2018, 6, 30, 0, 0, 0), tz); EXPECT_TRUE(tz.next_transition(tp, &trans)); EXPECT_EQ(civil_second(2018, 11, 4, 2, 0, 0), trans.from); EXPECT_EQ(civil_second(2018, 11, 4, 1, 0, 0), trans.to); tp = time_point::max(); EXPECT_FALSE(tz.next_transition(tp, &trans)); tp = time_point::min(); EXPECT_TRUE(tz.next_transition(tp, &trans)); if (trans.from == civil_second(1918, 3, 31, 2, 0, 0)) { // It looks like the tzdata is only 32 bit (probably macOS), // which bottoms out at 1901-12-13T20:45:52+00:00. EXPECT_EQ(civil_second(1918, 3, 31, 3, 0, 0), trans.to); } else { EXPECT_EQ(civil_second(1883, 11, 18, 12, 3, 58), trans.from); EXPECT_EQ(civil_second(1883, 11, 18, 12, 0, 0), trans.to); } } TEST(PrevTransition, AmericaNewYork) { const auto tz = LoadZone("America/New_York"); time_zone::civil_transition trans; auto tp = convert(civil_second(2018, 6, 30, 0, 0, 0), tz); EXPECT_TRUE(tz.prev_transition(tp, &trans)); EXPECT_EQ(civil_second(2018, 3, 11, 2, 0, 0), trans.from); EXPECT_EQ(civil_second(2018, 3, 11, 3, 0, 0), trans.to); tp = time_point::min(); EXPECT_FALSE(tz.prev_transition(tp, &trans)); tp = time_point::max(); EXPECT_TRUE(tz.prev_transition(tp, &trans)); // We have a transition but we don't know which one. } TEST(NextTransition, Scan) { for (const char* const* np = kTimeZoneNames; *np != nullptr; ++np) { SCOPED_TRACE(testing::Message() << "In " << *np); time_zone tz; // EXPECT_TRUE(load_time_zone(*np, &tz)); if (!load_time_zone(*np, &tz)) { continue; // tolerate kTimeZoneNames/zoneinfo skew } auto tp = time_point::min(); time_zone::civil_transition trans; while (tz.next_transition(tp, &trans)) { time_zone::civil_lookup from_cl = tz.lookup(trans.from); EXPECT_NE(from_cl.kind, time_zone::civil_lookup::REPEATED); time_zone::civil_lookup to_cl = tz.lookup(trans.to); EXPECT_NE(to_cl.kind, time_zone::civil_lookup::SKIPPED); auto trans_tp = to_cl.trans; time_zone::absolute_lookup trans_al = tz.lookup(trans_tp); EXPECT_EQ(trans_al.cs, trans.to); auto pre_trans_tp = trans_tp - cctz::seconds(1); time_zone::absolute_lookup pre_trans_al = tz.lookup(pre_trans_tp); EXPECT_EQ(pre_trans_al.cs + 1, trans.from); auto offset_delta = trans_al.offset - pre_trans_al.offset; EXPECT_EQ(offset_delta, trans.to - trans.from); if (offset_delta == 0) { // This "transition" is only an is_dst or abbr change. EXPECT_EQ(to_cl.kind, time_zone::civil_lookup::UNIQUE); if (trans_al.is_dst == pre_trans_al.is_dst) { EXPECT_STRNE(trans_al.abbr, pre_trans_al.abbr); } } tp = trans_tp; // continue scan from transition } } } TEST(TimeZoneEdgeCase, AmericaNewYork) { const time_zone tz = LoadZone("America/New_York"); // Spring 1:59:59 -> 3:00:00 auto tp = convert(civil_second(2013, 3, 10, 1, 59, 59), tz); ExpectTime(tp, tz, 2013, 3, 10, 1, 59, 59, -5 * 3600, false, "EST"); tp += cctz::seconds(1); ExpectTime(tp, tz, 2013, 3, 10, 3, 0, 0, -4 * 3600, true, "EDT"); // Fall 1:59:59 -> 1:00:00 tp = convert(civil_second(2013, 11, 3, 1, 59, 59), tz); ExpectTime(tp, tz, 2013, 11, 3, 1, 59, 59, -4 * 3600, true, "EDT"); tp += cctz::seconds(1); ExpectTime(tp, tz, 2013, 11, 3, 1, 0, 0, -5 * 3600, false, "EST"); } TEST(TimeZoneEdgeCase, AmericaLosAngeles) { const time_zone tz = LoadZone("America/Los_Angeles"); // Spring 1:59:59 -> 3:00:00 auto tp = convert(civil_second(2013, 3, 10, 1, 59, 59), tz); ExpectTime(tp, tz, 2013, 3, 10, 1, 59, 59, -8 * 3600, false, "PST"); tp += cctz::seconds(1); ExpectTime(tp, tz, 2013, 3, 10, 3, 0, 0, -7 * 3600, true, "PDT"); // Fall 1:59:59 -> 1:00:00 tp = convert(civil_second(2013, 11, 3, 1, 59, 59), tz); ExpectTime(tp, tz, 2013, 11, 3, 1, 59, 59, -7 * 3600, true, "PDT"); tp += cctz::seconds(1); ExpectTime(tp, tz, 2013, 11, 3, 1, 0, 0, -8 * 3600, false, "PST"); } TEST(TimeZoneEdgeCase, ArizonaNoTransition) { const time_zone tz = LoadZone("America/Phoenix"); // No transition in Spring. auto tp = convert(civil_second(2013, 3, 10, 1, 59, 59), tz); ExpectTime(tp, tz, 2013, 3, 10, 1, 59, 59, -7 * 3600, false, "MST"); tp += cctz::seconds(1); ExpectTime(tp, tz, 2013, 3, 10, 2, 0, 0, -7 * 3600, false, "MST"); // No transition in Fall. tp = convert(civil_second(2013, 11, 3, 1, 59, 59), tz); ExpectTime(tp, tz, 2013, 11, 3, 1, 59, 59, -7 * 3600, false, "MST"); tp += cctz::seconds(1); ExpectTime(tp, tz, 2013, 11, 3, 2, 0, 0, -7 * 3600, false, "MST"); } TEST(TimeZoneEdgeCase, AsiaKathmandu) { const time_zone tz = LoadZone("Asia/Kathmandu"); // A non-DST offset change from +0530 to +0545 // // 504901799 == Tue, 31 Dec 1985 23:59:59 +0530 (+0530) // 504901800 == Wed, 1 Jan 1986 00:15:00 +0545 (+0545) auto tp = convert(civil_second(1985, 12, 31, 23, 59, 59), tz); ExpectTime(tp, tz, 1985, 12, 31, 23, 59, 59, 5.5 * 3600, false, "+0530"); tp += cctz::seconds(1); ExpectTime(tp, tz, 1986, 1, 1, 0, 15, 0, 5.75 * 3600, false, "+0545"); } TEST(TimeZoneEdgeCase, PacificChatham) { const time_zone tz = LoadZone("Pacific/Chatham"); // One-hour DST offset changes, but at atypical values // // 1365256799 == Sun, 7 Apr 2013 03:44:59 +1345 (+1345) // 1365256800 == Sun, 7 Apr 2013 02:45:00 +1245 (+1245) auto tp = convert(civil_second(2013, 4, 7, 3, 44, 59), tz); ExpectTime(tp, tz, 2013, 4, 7, 3, 44, 59, 13.75 * 3600, true, "+1345"); tp += cctz::seconds(1); ExpectTime(tp, tz, 2013, 4, 7, 2, 45, 0, 12.75 * 3600, false, "+1245"); // 1380376799 == Sun, 29 Sep 2013 02:44:59 +1245 (+1245) // 1380376800 == Sun, 29 Sep 2013 03:45:00 +1345 (+1345) tp = convert(civil_second(2013, 9, 29, 2, 44, 59), tz); ExpectTime(tp, tz, 2013, 9, 29, 2, 44, 59, 12.75 * 3600, false, "+1245"); tp += cctz::seconds(1); ExpectTime(tp, tz, 2013, 9, 29, 3, 45, 0, 13.75 * 3600, true, "+1345"); } TEST(TimeZoneEdgeCase, AustraliaLordHowe) { const time_zone tz = LoadZone("Australia/Lord_Howe"); // Half-hour DST offset changes // // 1365260399 == Sun, 7 Apr 2013 01:59:59 +1100 (+11) // 1365260400 == Sun, 7 Apr 2013 01:30:00 +1030 (+1030) auto tp = convert(civil_second(2013, 4, 7, 1, 59, 59), tz); ExpectTime(tp, tz, 2013, 4, 7, 1, 59, 59, 11 * 3600, true, "+11"); tp += cctz::seconds(1); ExpectTime(tp, tz, 2013, 4, 7, 1, 30, 0, 10.5 * 3600, false, "+1030"); // 1380986999 == Sun, 6 Oct 2013 01:59:59 +1030 (+1030) // 1380987000 == Sun, 6 Oct 2013 02:30:00 +1100 (+11) tp = convert(civil_second(2013, 10, 6, 1, 59, 59), tz); ExpectTime(tp, tz, 2013, 10, 6, 1, 59, 59, 10.5 * 3600, false, "+1030"); tp += cctz::seconds(1); ExpectTime(tp, tz, 2013, 10, 6, 2, 30, 0, 11 * 3600, true, "+11"); } TEST(TimeZoneEdgeCase, PacificApia) { const time_zone tz = LoadZone("Pacific/Apia"); // At the end of December 2011, Samoa jumped forward by one day, // skipping 30 December from the local calendar, when the nation // moved to the west of the International Date Line. // // A one-day, non-DST offset change // // 1325239199 == Thu, 29 Dec 2011 23:59:59 -1000 (-10) // 1325239200 == Sat, 31 Dec 2011 00:00:00 +1400 (+14) auto tp = convert(civil_second(2011, 12, 29, 23, 59, 59), tz); ExpectTime(tp, tz, 2011, 12, 29, 23, 59, 59, -10 * 3600, true, "-10"); EXPECT_EQ(363, get_yearday(convert(tp, tz))); tp += cctz::seconds(1); ExpectTime(tp, tz, 2011, 12, 31, 0, 0, 0, 14 * 3600, true, "+14"); EXPECT_EQ(365, get_yearday(convert(tp, tz))); } TEST(TimeZoneEdgeCase, AfricaCairo) { const time_zone tz = LoadZone("Africa/Cairo"); if (VersionCmp(tz, "2014c") >= 0) { // An interesting case of midnight not existing. // // 1400191199 == Thu, 15 May 2014 23:59:59 +0200 (EET) // 1400191200 == Fri, 16 May 2014 01:00:00 +0300 (EEST) auto tp = convert(civil_second(2014, 5, 15, 23, 59, 59), tz); ExpectTime(tp, tz, 2014, 5, 15, 23, 59, 59, 2 * 3600, false, "EET"); tp += cctz::seconds(1); ExpectTime(tp, tz, 2014, 5, 16, 1, 0, 0, 3 * 3600, true, "EEST"); } } TEST(TimeZoneEdgeCase, AfricaMonrovia) { const time_zone tz = LoadZone("Africa/Monrovia"); if (VersionCmp(tz, "2017b") >= 0) { // Strange offset change -00:44:30 -> +00:00:00 (non-DST) // // 63593069 == Thu, 6 Jan 1972 23:59:59 -0044 (MMT) // 63593070 == Fri, 7 Jan 1972 00:44:30 +0000 (GMT) auto tp = convert(civil_second(1972, 1, 6, 23, 59, 59), tz); ExpectTime(tp, tz, 1972, 1, 6, 23, 59, 59, -44.5 * 60, false, "MMT"); tp += cctz::seconds(1); ExpectTime(tp, tz, 1972, 1, 7, 0, 44, 30, 0 * 60, false, "GMT"); } } TEST(TimeZoneEdgeCase, AmericaJamaica) { // Jamaica discontinued DST transitions in 1983, and is now at a // constant -0500. This makes it an interesting edge-case target. // Note that the 32-bit times used in a (tzh_version == 0) zoneinfo // file cannot represent the abbreviation-only transition of 1890, // so we ignore the abbreviation by expecting what we received. const time_zone tz = LoadZone("America/Jamaica"); // Before the first transition. if (!tz.version().empty() && VersionCmp(tz, "2018d") >= 0) { // We avoid the expectations on the -18430 offset below unless we are // certain we have commit 907241e (Fix off-by-1 error for Jamaica and // T&C before 1913) from 2018d. TODO: Remove the "version() not empty" // part when 2018d is generally available from /usr/share/zoneinfo. auto tp = convert(civil_second(1889, 12, 31, 0, 0, 0), tz); ExpectTime(tp, tz, 1889, 12, 31, 0, 0, 0, -18430, false, tz.lookup(tp).abbr); // Over the first (abbreviation-change only) transition. // -2524503170 == Tue, 31 Dec 1889 23:59:59 -0507 (LMT) // -2524503169 == Wed, 1 Jan 1890 00:00:00 -0507 (KMT) tp = convert(civil_second(1889, 12, 31, 23, 59, 59), tz); ExpectTime(tp, tz, 1889, 12, 31, 23, 59, 59, -18430, false, tz.lookup(tp).abbr); tp += cctz::seconds(1); ExpectTime(tp, tz, 1890, 1, 1, 0, 0, 0, -18430, false, "KMT"); } // Over the last (DST) transition. // 436341599 == Sun, 30 Oct 1983 01:59:59 -0400 (EDT) // 436341600 == Sun, 30 Oct 1983 01:00:00 -0500 (EST) auto tp = convert(civil_second(1983, 10, 30, 1, 59, 59), tz); ExpectTime(tp, tz, 1983, 10, 30, 1, 59, 59, -4 * 3600, true, "EDT"); tp += cctz::seconds(1); ExpectTime(tp, tz, 1983, 10, 30, 1, 0, 0, -5 * 3600, false, "EST"); // After the last transition. tp = convert(civil_second(1983, 12, 31, 23, 59, 59), tz); ExpectTime(tp, tz, 1983, 12, 31, 23, 59, 59, -5 * 3600, false, "EST"); } TEST(TimeZoneEdgeCase, WET) { // Cover some non-existent times within forward transitions. const time_zone tz = LoadZone("WET"); // Before the first transition. auto tp = convert(civil_second(1977, 1, 1, 0, 0, 0), tz); ExpectTime(tp, tz, 1977, 1, 1, 0, 0, 0, 0, false, "WET"); // Over the first transition. // 228877199 == Sun, 3 Apr 1977 00:59:59 +0000 (WET) // 228877200 == Sun, 3 Apr 1977 02:00:00 +0100 (WEST) tp = convert(civil_second(1977, 4, 3, 0, 59, 59), tz); ExpectTime(tp, tz, 1977, 4, 3, 0, 59, 59, 0, false, "WET"); tp += cctz::seconds(1); ExpectTime(tp, tz, 1977, 4, 3, 2, 0, 0, 1 * 3600, true, "WEST"); // A non-existent time within the first transition. time_zone::civil_lookup cl1 = tz.lookup(civil_second(1977, 4, 3, 1, 15, 0)); EXPECT_EQ(time_zone::civil_lookup::SKIPPED, cl1.kind); ExpectTime(cl1.pre, tz, 1977, 4, 3, 2, 15, 0, 1 * 3600, true, "WEST"); ExpectTime(cl1.trans, tz, 1977, 4, 3, 2, 0, 0, 1 * 3600, true, "WEST"); ExpectTime(cl1.post, tz, 1977, 4, 3, 0, 15, 0, 0 * 3600, false, "WET"); // A non-existent time within the second forward transition. time_zone::civil_lookup cl2 = tz.lookup(civil_second(1978, 4, 2, 1, 15, 0)); EXPECT_EQ(time_zone::civil_lookup::SKIPPED, cl2.kind); ExpectTime(cl2.pre, tz, 1978, 4, 2, 2, 15, 0, 1 * 3600, true, "WEST"); ExpectTime(cl2.trans, tz, 1978, 4, 2, 2, 0, 0, 1 * 3600, true, "WEST"); ExpectTime(cl2.post, tz, 1978, 4, 2, 0, 15, 0, 0 * 3600, false, "WET"); } TEST(TimeZoneEdgeCase, FixedOffsets) { const time_zone gmtm5 = LoadZone("Etc/GMT+5"); // -0500 auto tp = convert(civil_second(1970, 1, 1, 0, 0, 0), gmtm5); ExpectTime(tp, gmtm5, 1970, 1, 1, 0, 0, 0, -5 * 3600, false, "-05"); EXPECT_EQ(chrono::system_clock::from_time_t(5 * 3600), tp); const time_zone gmtp5 = LoadZone("Etc/GMT-5"); // +0500 tp = convert(civil_second(1970, 1, 1, 0, 0, 0), gmtp5); ExpectTime(tp, gmtp5, 1970, 1, 1, 0, 0, 0, 5 * 3600, false, "+05"); EXPECT_EQ(chrono::system_clock::from_time_t(-5 * 3600), tp); } TEST(TimeZoneEdgeCase, NegativeYear) { // Tests transition from year 0 (aka 1BCE) to year -1. const time_zone tz = utc_time_zone(); auto tp = convert(civil_second(0, 1, 1, 0, 0, 0), tz); ExpectTime(tp, tz, 0, 1, 1, 0, 0, 0, 0 * 3600, false, "UTC"); EXPECT_EQ(weekday::saturday, get_weekday(convert(tp, tz))); tp -= cctz::seconds(1); ExpectTime(tp, tz, -1, 12, 31, 23, 59, 59, 0 * 3600, false, "UTC"); EXPECT_EQ(weekday::friday, get_weekday(convert(tp, tz))); } TEST(TimeZoneEdgeCase, UTC32bitLimit) { const time_zone tz = utc_time_zone(); // Limits of signed 32-bit time_t // // 2147483647 == Tue, 19 Jan 2038 03:14:07 +0000 (UTC) // 2147483648 == Tue, 19 Jan 2038 03:14:08 +0000 (UTC) auto tp = convert(civil_second(2038, 1, 19, 3, 14, 7), tz); ExpectTime(tp, tz, 2038, 1, 19, 3, 14, 7, 0 * 3600, false, "UTC"); tp += cctz::seconds(1); ExpectTime(tp, tz, 2038, 1, 19, 3, 14, 8, 0 * 3600, false, "UTC"); } TEST(TimeZoneEdgeCase, UTC5DigitYear) { const time_zone tz = utc_time_zone(); // Rollover to 5-digit year // // 253402300799 == Fri, 31 Dec 9999 23:59:59 +0000 (UTC) // 253402300800 == Sat, 1 Jan 1000 00:00:00 +0000 (UTC) auto tp = convert(civil_second(9999, 12, 31, 23, 59, 59), tz); ExpectTime(tp, tz, 9999, 12, 31, 23, 59, 59, 0 * 3600, false, "UTC"); tp += cctz::seconds(1); ExpectTime(tp, tz, 10000, 1, 1, 0, 0, 0, 0 * 3600, false, "UTC"); } } // namespace cctz timechange/src/cctz/src/time_zone_impl.cc0000644000176200001440000000663113736317364020243 0ustar liggesusers// 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 "time_zone_impl.h" #include #include #include #include #include #include #include "time_zone_fixed.h" namespace cctz { namespace { // time_zone::Impls are linked into a map to support fast lookup by name. using TimeZoneImplByName = std::unordered_map; TimeZoneImplByName* time_zone_map = nullptr; // Mutual exclusion for time_zone_map. std::mutex& TimeZoneMutex() { // This mutex is intentionally "leaked" to avoid the static deinitialization // order fiasco (std::mutex's destructor is not trivial on many platforms). static std::mutex* time_zone_mutex = new std::mutex; return *time_zone_mutex; } } // namespace time_zone time_zone::Impl::UTC() { return time_zone(UTCImpl()); } bool time_zone::Impl::LoadTimeZone(const std::string& name, time_zone* tz) { const Impl* const utc_impl = UTCImpl(); // Check for UTC (which is never a key in time_zone_map). auto offset = seconds::zero(); if (FixedOffsetFromName(name, &offset) && offset == seconds::zero()) { *tz = time_zone(utc_impl); return true; } // Check whether the time zone has already been loaded. { std::lock_guard lock(TimeZoneMutex()); if (time_zone_map != nullptr) { TimeZoneImplByName::const_iterator itr = time_zone_map->find(name); if (itr != time_zone_map->end()) { *tz = time_zone(itr->second); return itr->second != utc_impl; } } } // Load the new time zone (outside the lock). std::unique_ptr new_impl(new Impl(name)); // Add the new time zone to the map. std::lock_guard lock(TimeZoneMutex()); if (time_zone_map == nullptr) time_zone_map = new TimeZoneImplByName; const Impl*& impl = (*time_zone_map)[name]; if (impl == nullptr) { // this thread won any load race impl = new_impl->zone_ ? new_impl.release() : utc_impl; } *tz = time_zone(impl); return impl != utc_impl; } void time_zone::Impl::ClearTimeZoneMapTestOnly() { std::lock_guard lock(TimeZoneMutex()); if (time_zone_map != nullptr) { // Existing time_zone::Impl* entries are in the wild, so we can't delete // them. Instead, we move them to a private container, where they are // logically unreachable but not "leaked". Future requests will result // in reloading the data. static auto* cleared = new std::deque; for (const auto& element : *time_zone_map) { cleared->push_back(element.second); } time_zone_map->clear(); } } time_zone::Impl::Impl(const std::string& name) : name_(name), zone_(TimeZoneIf::Load(name_)) {} const time_zone::Impl* time_zone::Impl::UTCImpl() { static const Impl* utc_impl = new Impl("UTC"); // never fails return utc_impl; } } // namespace cctz timechange/src/cctz/src/time_zone_libc.cc0000644000176200001440000002343114326763406020206 0ustar liggesusers// 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. #if defined(_WIN32) || defined(_WIN64) #define _CRT_SECURE_NO_WARNINGS 1 #endif #include "time_zone_libc.h" #include #include #include #include #include "cctz/civil_time.h" #include "cctz/time_zone.h" #if defined(_AIX) extern "C" { extern long altzone; } #endif namespace cctz { namespace { #if defined(_WIN32) || defined(_WIN64) // Uses the globals: '_timezone', '_dstbias' and '_tzname'. auto tm_gmtoff(const std::tm& tm) -> decltype(_timezone + _dstbias) { const bool is_dst = tm.tm_isdst > 0; return _timezone + (is_dst ? _dstbias : 0); } auto tm_zone(const std::tm& tm) -> decltype(_tzname[0]) { const bool is_dst = tm.tm_isdst > 0; return _tzname[is_dst]; } #elif defined(__sun) || defined(_AIX) // Uses the globals: 'timezone', 'altzone' and 'tzname'. auto tm_gmtoff(const std::tm& tm) -> decltype(timezone) { const bool is_dst = tm.tm_isdst > 0; return is_dst ? altzone : timezone; } auto tm_zone(const std::tm& tm) -> decltype(tzname[0]) { const bool is_dst = tm.tm_isdst > 0; return tzname[is_dst]; } #elif defined(__native_client__) || defined(__myriad2__) || \ defined(__EMSCRIPTEN__) // Uses the globals: 'timezone' and 'tzname'. auto tm_gmtoff(const std::tm& tm) -> decltype(_timezone + 0) { const bool is_dst = tm.tm_isdst > 0; return _timezone + (is_dst ? 60 * 60 : 0); } auto tm_zone(const std::tm& tm) -> decltype(tzname[0]) { const bool is_dst = tm.tm_isdst > 0; return tzname[is_dst]; } #else // Adapt to different spellings of the struct std::tm extension fields. #if defined(tm_gmtoff) auto tm_gmtoff(const std::tm& tm) -> decltype(tm.tm_gmtoff) { return tm.tm_gmtoff; } #elif defined(__tm_gmtoff) auto tm_gmtoff(const std::tm& tm) -> decltype(tm.__tm_gmtoff) { return tm.__tm_gmtoff; } #else template auto tm_gmtoff(const T& tm) -> decltype(tm.tm_gmtoff) { return tm.tm_gmtoff; } template auto tm_gmtoff(const T& tm) -> decltype(tm.__tm_gmtoff) { return tm.__tm_gmtoff; } #endif // tm_gmtoff #if defined(tm_zone) auto tm_zone(const std::tm& tm) -> decltype(tm.tm_zone) { return tm.tm_zone; } #elif defined(__tm_zone) auto tm_zone(const std::tm& tm) -> decltype(tm.__tm_zone) { return tm.__tm_zone; } #else template auto tm_zone(const T& tm) -> decltype(tm.tm_zone) { return tm.tm_zone; } template auto tm_zone(const T& tm) -> decltype(tm.__tm_zone) { return tm.__tm_zone; } #endif // tm_zone #endif inline std::tm* gm_time(const std::time_t *timep, std::tm *result) { #if defined(_WIN32) || defined(_WIN64) return gmtime_s(result, timep) ? nullptr : result; #else return gmtime_r(timep, result); #endif } inline std::tm* local_time(const std::time_t *timep, std::tm *result) { #if defined(_WIN32) || defined(_WIN64) return localtime_s(result, timep) ? nullptr : result; #else return localtime_r(timep, result); #endif } // Converts a civil second and "dst" flag into a time_t and UTC offset. // Returns false if time_t cannot represent the requested civil second. // Caller must have already checked that cs.year() will fit into a tm_year. bool make_time(const civil_second& cs, int is_dst, std::time_t* t, int* off) { std::tm tm; tm.tm_year = static_cast(cs.year() - year_t{1900}); tm.tm_mon = cs.month() - 1; tm.tm_mday = cs.day(); tm.tm_hour = cs.hour(); tm.tm_min = cs.minute(); tm.tm_sec = cs.second(); tm.tm_isdst = is_dst; *t = std::mktime(&tm); if (*t == std::time_t{-1}) { std::tm tm2; const std::tm* tmp = local_time(t, &tm2); if (tmp == nullptr || tmp->tm_year != tm.tm_year || tmp->tm_mon != tm.tm_mon || tmp->tm_mday != tm.tm_mday || tmp->tm_hour != tm.tm_hour || tmp->tm_min != tm.tm_min || tmp->tm_sec != tm.tm_sec) { // A true error (not just one second before the epoch). return false; } } *off = static_cast(tm_gmtoff(tm)); return true; } // Find the least time_t in [lo:hi] where local time matches offset, given: // (1) lo doesn't match, (2) hi does, and (3) there is only one transition. std::time_t find_trans(std::time_t lo, std::time_t hi, int offset) { std::tm tm; while (lo + 1 != hi) { const std::time_t mid = lo + (hi - lo) / 2; std::tm* tmp = local_time(&mid, &tm); if (tmp != nullptr) { if (tm_gmtoff(*tmp) == offset) { hi = mid; } else { lo = mid; } } else { // If std::tm cannot hold some result we resort to a linear search, // ignoring all failed conversions. Slow, but never really happens. while (++lo != hi) { tmp = local_time(&lo, &tm); if (tmp != nullptr) { if (tm_gmtoff(*tmp) == offset) break; } } return lo; } } return hi; } } // namespace TimeZoneLibC::TimeZoneLibC(const std::string& name) : local_(name == "localtime") {} time_zone::absolute_lookup TimeZoneLibC::BreakTime( const time_point& tp) const { time_zone::absolute_lookup al; al.offset = 0; al.is_dst = false; al.abbr = "-00"; const std::int_fast64_t s = ToUnixSeconds(tp); // If std::time_t cannot hold the input we saturate the output. if (s < std::numeric_limits::min()) { al.cs = civil_second::min(); return al; } if (s > std::numeric_limits::max()) { al.cs = civil_second::max(); return al; } const std::time_t t = static_cast(s); std::tm tm; std::tm* tmp = local_ ? local_time(&t, &tm) : gm_time(&t, &tm); // If std::tm cannot hold the result we saturate the output. if (tmp == nullptr) { al.cs = (s < 0) ? civil_second::min() : civil_second::max(); return al; } const year_t year = tmp->tm_year + year_t{1900}; al.cs = civil_second(year, tmp->tm_mon + 1, tmp->tm_mday, tmp->tm_hour, tmp->tm_min, tmp->tm_sec); al.offset = static_cast(tm_gmtoff(*tmp)); al.abbr = local_ ? tm_zone(*tmp) : "UTC"; // as expected by cctz al.is_dst = tmp->tm_isdst > 0; return al; } time_zone::civil_lookup TimeZoneLibC::MakeTime(const civil_second& cs) const { if (!local_) { // If time_point cannot hold the result we saturate. static const civil_second min_tp_cs = civil_second() + ToUnixSeconds(time_point::min()); static const civil_second max_tp_cs = civil_second() + ToUnixSeconds(time_point::max()); const time_point tp = (cs < min_tp_cs) ? time_point::min() : (cs > max_tp_cs) ? time_point::max() : FromUnixSeconds(cs - civil_second()); return {time_zone::civil_lookup::UNIQUE, tp, tp, tp}; } // If tm_year cannot hold the requested year we saturate the result. if (cs.year() < 0) { if (cs.year() < std::numeric_limits::min() + year_t{1900}) { const time_point tp = time_point::min(); return {time_zone::civil_lookup::UNIQUE, tp, tp, tp}; } } else { if (cs.year() - year_t{1900} > std::numeric_limits::max()) { const time_point tp = time_point::max(); return {time_zone::civil_lookup::UNIQUE, tp, tp, tp}; } } // We probe with "is_dst" values of 0 and 1 to try to distinguish unique // civil seconds from skipped or repeated ones. This is not always possible // however, as the "dst" flag does not change over some offset transitions. // We are also subject to the vagaries of mktime() implementations. std::time_t t0, t1; int offset0, offset1; if (make_time(cs, 0, &t0, &offset0) && make_time(cs, 1, &t1, &offset1)) { if (t0 == t1) { // The civil time was singular (pre == trans == post). const time_point tp = FromUnixSeconds(t0); return {time_zone::civil_lookup::UNIQUE, tp, tp, tp}; } if (t0 > t1) { std::swap(t0, t1); std::swap(offset0, offset1); } const std::time_t tt = find_trans(t0, t1, offset1); const time_point trans = FromUnixSeconds(tt); if (offset0 < offset1) { // The civil time did not exist (pre >= trans > post). const time_point pre = FromUnixSeconds(t1); const time_point post = FromUnixSeconds(t0); return {time_zone::civil_lookup::SKIPPED, pre, trans, post}; } // The civil time was ambiguous (pre < trans <= post). const time_point pre = FromUnixSeconds(t0); const time_point post = FromUnixSeconds(t1); return {time_zone::civil_lookup::REPEATED, pre, trans, post}; } // make_time() failed somehow so we saturate the result. const time_point tp = (cs < civil_second()) ? time_point::min() : time_point::max(); return {time_zone::civil_lookup::UNIQUE, tp, tp, tp}; } bool TimeZoneLibC::NextTransition(const time_point&, time_zone::civil_transition*) const { return false; } bool TimeZoneLibC::PrevTransition(const time_point&, time_zone::civil_transition*) const { return false; } std::string TimeZoneLibC::Version() const { return std::string(); // unknown } std::string TimeZoneLibC::Description() const { return local_ ? "localtime" : "UTC"; } } // namespace cctz timechange/src/cctz/src/time_zone_posix.cc0000644000176200001440000001102713573274232020432 0ustar liggesusers// 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 "time_zone_posix.h" #include #include #include #include namespace cctz { namespace { const char kDigits[] = "0123456789"; const char* ParseInt(const char* p, int min, int max, int* vp) { int value = 0; const char* op = p; const int kMaxInt = std::numeric_limits::max(); for (; const char* dp = strchr(kDigits, *p); ++p) { int d = static_cast(dp - kDigits); if (d >= 10) break; // '\0' if (value > kMaxInt / 10) return nullptr; value *= 10; if (value > kMaxInt - d) return nullptr; value += d; } if (p == op || value < min || value > max) return nullptr; *vp = value; return p; } // abbr = <.*?> | [^-+,\d]{3,} const char* ParseAbbr(const char* p, std::string* abbr) { const char* op = p; if (*p == '<') { // special zoneinfo <...> form while (*++p != '>') { if (*p == '\0') return nullptr; } abbr->assign(op + 1, static_cast(p - op) - 1); return ++p; } while (*p != '\0') { if (strchr("-+,", *p)) break; if (strchr(kDigits, *p)) break; ++p; } if (p - op < 3) return nullptr; abbr->assign(op, static_cast(p - op)); return p; } // offset = [+|-]hh[:mm[:ss]] (aggregated into single seconds value) const char* ParseOffset(const char* p, int min_hour, int max_hour, int sign, std::int_fast32_t* offset) { if (p == nullptr) return nullptr; if (*p == '+' || *p == '-') { if (*p++ == '-') sign = -sign; } int hours = 0; int minutes = 0; int seconds = 0; p = ParseInt(p, min_hour, max_hour, &hours); if (p == nullptr) return nullptr; if (*p == ':') { p = ParseInt(p + 1, 0, 59, &minutes); if (p == nullptr) return nullptr; if (*p == ':') { p = ParseInt(p + 1, 0, 59, &seconds); if (p == nullptr) return nullptr; } } *offset = sign * ((((hours * 60) + minutes) * 60) + seconds); return p; } // datetime = ( Jn | n | Mm.w.d ) [ / offset ] const char* ParseDateTime(const char* p, PosixTransition* res) { if (p != nullptr && *p == ',') { if (*++p == 'M') { int month = 0; if ((p = ParseInt(p + 1, 1, 12, &month)) != nullptr && *p == '.') { int week = 0; if ((p = ParseInt(p + 1, 1, 5, &week)) != nullptr && *p == '.') { int weekday = 0; if ((p = ParseInt(p + 1, 0, 6, &weekday)) != nullptr) { res->date.fmt = PosixTransition::M; res->date.m.month = static_cast(month); res->date.m.week = static_cast(week); res->date.m.weekday = static_cast(weekday); } } } } else if (*p == 'J') { int day = 0; if ((p = ParseInt(p + 1, 1, 365, &day)) != nullptr) { res->date.fmt = PosixTransition::J; res->date.j.day = static_cast(day); } } else { int day = 0; if ((p = ParseInt(p, 0, 365, &day)) != nullptr) { res->date.fmt = PosixTransition::N; res->date.n.day = static_cast(day); } } } if (p != nullptr) { res->time.offset = 2 * 60 * 60; // default offset is 02:00:00 if (*p == '/') p = ParseOffset(p + 1, -167, 167, 1, &res->time.offset); } return p; } } // namespace // spec = std offset [ dst [ offset ] , datetime , datetime ] bool ParsePosixSpec(const std::string& spec, PosixTimeZone* res) { const char* p = spec.c_str(); if (*p == ':') return false; p = ParseAbbr(p, &res->std_abbr); p = ParseOffset(p, 0, 24, -1, &res->std_offset); if (p == nullptr) return false; if (*p == '\0') return true; p = ParseAbbr(p, &res->dst_abbr); if (p == nullptr) return false; res->dst_offset = res->std_offset + (60 * 60); // default if (*p != ',') p = ParseOffset(p, 0, 24, -1, &res->dst_offset); p = ParseDateTime(p, &res->dst_start); p = ParseDateTime(p, &res->dst_end); return p != nullptr && *p == '\0'; } } // namespace cctz timechange/src/cctz/src/time_zone_fixed.cc0000644000176200001440000001025214326763405020370 0ustar liggesusers// 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 "time_zone_fixed.h" #include #include #include #include #include namespace cctz { namespace { // The prefix used for the internal names of fixed-offset zones. const char kFixedZonePrefix[] = "Fixed/UTC"; const char kDigits[] = "0123456789"; char* Format02d(char* p, int v) { *p++ = kDigits[(v / 10) % 10]; *p++ = kDigits[v % 10]; return p; } int Parse02d(const char* p) { if (const char* ap = std::strchr(kDigits, *p)) { int v = static_cast(ap - kDigits); if (const char* bp = std::strchr(kDigits, *++p)) { return (v * 10) + static_cast(bp - kDigits); } } return -1; } } // namespace bool FixedOffsetFromName(const std::string& name, seconds* offset) { if (name == "UTC" || name == "UTC0") { *offset = seconds::zero(); return true; } const std::size_t prefix_len = sizeof(kFixedZonePrefix) - 1; const char* const ep = kFixedZonePrefix + prefix_len; if (name.size() != prefix_len + 9) // +99:99:99 return false; if (!std::equal(kFixedZonePrefix, ep, name.begin())) return false; const char* np = name.data() + prefix_len; if (np[0] != '+' && np[0] != '-') return false; if (np[3] != ':' || np[6] != ':') // see note below about large offsets return false; int hours = Parse02d(np + 1); if (hours == -1) return false; int mins = Parse02d(np + 4); if (mins == -1) return false; int secs = Parse02d(np + 7); if (secs == -1) return false; secs += ((hours * 60) + mins) * 60; if (secs > 24 * 60 * 60) return false; // outside supported offset range *offset = seconds(secs * (np[0] == '-' ? -1 : 1)); // "-" means west return true; } std::string FixedOffsetToName(const seconds& offset) { if (offset == seconds::zero()) return "UTC"; if (offset < std::chrono::hours(-24) || offset > std::chrono::hours(24)) { // We don't support fixed-offset zones more than 24 hours // away from UTC to avoid complications in rendering such // offsets and to (somewhat) limit the total number of zones. return "UTC"; } int offset_seconds = static_cast(offset.count()); const char sign = (offset_seconds < 0 ? '-' : '+'); int offset_minutes = offset_seconds / 60; offset_seconds %= 60; if (sign == '-') { if (offset_seconds > 0) { offset_seconds -= 60; offset_minutes += 1; } offset_seconds = -offset_seconds; offset_minutes = -offset_minutes; } int offset_hours = offset_minutes / 60; offset_minutes %= 60; const std::size_t prefix_len = sizeof(kFixedZonePrefix) - 1; char buf[prefix_len + sizeof("-24:00:00")]; char* ep = std::copy(kFixedZonePrefix, kFixedZonePrefix + prefix_len, buf); *ep++ = sign; ep = Format02d(ep, offset_hours); *ep++ = ':'; ep = Format02d(ep, offset_minutes); *ep++ = ':'; ep = Format02d(ep, offset_seconds); *ep++ = '\0'; assert(ep == buf + sizeof(buf)); return buf; } std::string FixedOffsetToAbbr(const seconds& offset) { std::string abbr = FixedOffsetToName(offset); const std::size_t prefix_len = sizeof(kFixedZonePrefix) - 1; if (abbr.size() == prefix_len + 9) { // +99:99:99 abbr.erase(0, prefix_len); // +99:99:99 abbr.erase(6, 1); // +99:9999 abbr.erase(3, 1); // +999999 if (abbr[5] == '0' && abbr[6] == '0') { // +999900 abbr.erase(5, 2); // +9999 if (abbr[3] == '0' && abbr[4] == '0') { // +9900 abbr.erase(3, 2); // +99 } } } return abbr; } } // namespace cctz timechange/src/cctz/src/time_zone_if.cc0000644000176200001440000000252413573274232017670 0ustar liggesusers// 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 "time_zone_if.h" #include "time_zone_info.h" #include "time_zone_libc.h" namespace cctz { std::unique_ptr TimeZoneIf::Load(const std::string& name) { // Support "libc:localtime" and "libc:*" to access the legacy // localtime and UTC support respectively from the C library. if (name.compare(0, 5, "libc:") == 0) { return std::unique_ptr(new TimeZoneLibC(name.substr(5))); } // Otherwise use the "zoneinfo" implementation by default. std::unique_ptr tz(new TimeZoneInfo); if (!tz->Load(name)) tz.reset(); return std::unique_ptr(tz.release()); } // Defined out-of-line to avoid emitting a weak vtable in all TUs. TimeZoneIf::~TimeZoneIf() {} } // namespace cctz timechange/src/cctz/src/time_zone_lookup.cc0000644000176200001440000001534614326763406020614 0ustar liggesusers// 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" #if defined(__ANDROID__) #include #if defined(__ANDROID_API__) && __ANDROID_API__ >= 21 #include #endif #endif #if defined(__APPLE__) #include #include #endif #if defined(__Fuchsia__) #include #include #include #include #endif #include #include #include #include "time_zone_fixed.h" #include "time_zone_impl.h" namespace cctz { #if defined(__ANDROID__) && defined(__ANDROID_API__) && __ANDROID_API__ >= 21 namespace { // Android 'L' removes __system_property_get() from the NDK, however // it is still a hidden symbol in libc so we use dlsym() to access it. // See Chromium's base/sys_info_android.cc for a similar example. using property_get_func = int (*)(const char*, char*); property_get_func LoadSystemPropertyGet() { int flag = RTLD_LAZY | RTLD_GLOBAL; #if defined(RTLD_NOLOAD) flag |= RTLD_NOLOAD; // libc.so should already be resident #endif if (void* handle = dlopen("libc.so", flag)) { void* sym = dlsym(handle, "__system_property_get"); dlclose(handle); return reinterpret_cast(sym); } return nullptr; } int __system_property_get(const char* name, char* value) { static property_get_func system_property_get = LoadSystemPropertyGet(); return system_property_get ? system_property_get(name, value) : -1; } } // namespace #endif std::string time_zone::name() const { return effective_impl().Name(); } time_zone::absolute_lookup time_zone::lookup( const time_point& tp) const { return effective_impl().BreakTime(tp); } time_zone::civil_lookup time_zone::lookup(const civil_second& cs) const { return effective_impl().MakeTime(cs); } bool time_zone::next_transition(const time_point& tp, civil_transition* trans) const { return effective_impl().NextTransition(tp, trans); } bool time_zone::prev_transition(const time_point& tp, civil_transition* trans) const { return effective_impl().PrevTransition(tp, trans); } std::string time_zone::version() const { return effective_impl().Version(); } std::string time_zone::description() const { return effective_impl().Description(); } const time_zone::Impl& time_zone::effective_impl() const { if (impl_ == nullptr) { // Dereferencing an implicit-UTC time_zone is expected to be // rare, so we don't mind paying a small synchronization cost. return *time_zone::Impl::UTC().impl_; } return *impl_; } bool load_time_zone(const std::string& name, time_zone* tz) { return time_zone::Impl::LoadTimeZone(name, tz); } time_zone utc_time_zone() { return time_zone::Impl::UTC(); // avoid name lookup } time_zone fixed_time_zone(const seconds& offset) { time_zone tz; load_time_zone(FixedOffsetToName(offset), &tz); return tz; } time_zone local_time_zone() { const char* zone = ":localtime"; #if defined(__ANDROID__) char sysprop[PROP_VALUE_MAX]; if (__system_property_get("persist.sys.timezone", sysprop) > 0) { zone = sysprop; } #endif #if defined(__APPLE__) std::vector buffer; CFTimeZoneRef tz_default = CFTimeZoneCopyDefault(); if (CFStringRef tz_name = CFTimeZoneGetName(tz_default)) { CFStringEncoding encoding = kCFStringEncodingUTF8; CFIndex length = CFStringGetLength(tz_name); CFIndex max_size = CFStringGetMaximumSizeForEncoding(length, encoding) + 1; buffer.resize(static_cast(max_size)); if (CFStringGetCString(tz_name, &buffer[0], max_size, encoding)) { zone = &buffer[0]; } } CFRelease(tz_default); #endif #if defined(__Fuchsia__) std::string primary_tz; [&]() { // Note: We can't use the synchronous FIDL API here because it doesn't // allow timeouts; if the FIDL call failed, local_time_zone() would never // return. const zx::duration kTimeout = zx::msec(500); // Don't attach to the thread because otherwise the thread's dispatcher // would be set to null when the loop is destroyed, causing any other FIDL // code running on the same thread to crash. async::Loop loop(&kAsyncLoopConfigNeverAttachToThread); fuchsia::intl::PropertyProviderHandle handle; zx_status_t status = fdio_service_connect_by_name( fuchsia::intl::PropertyProvider::Name_, handle.NewRequest().TakeChannel().release()); if (status != ZX_OK) { return; } fuchsia::intl::PropertyProviderPtr intl_provider; status = intl_provider.Bind(std::move(handle), loop.dispatcher()); if (status != ZX_OK) { return; } intl_provider->GetProfile( [&loop, &primary_tz](fuchsia::intl::Profile profile) { if (!profile.time_zones().empty()) { primary_tz = profile.time_zones()[0].id; } loop.Quit(); }); loop.Run(zx::deadline_after(kTimeout)); }(); if (!primary_tz.empty()) { zone = primary_tz.c_str(); } #endif // Allow ${TZ} to override to default zone. char* tz_env = nullptr; #if defined(_MSC_VER) _dupenv_s(&tz_env, nullptr, "TZ"); #else tz_env = std::getenv("TZ"); #endif if (tz_env) zone = tz_env; // We only support the "[:]" form. if (*zone == ':') ++zone; // Map "localtime" to a system-specific name, but // allow ${LOCALTIME} to override the default name. char* localtime_env = nullptr; if (strcmp(zone, "localtime") == 0) { #if defined(_MSC_VER) // System-specific default is just "localtime". _dupenv_s(&localtime_env, nullptr, "LOCALTIME"); #else zone = "/etc/localtime"; // System-specific default. localtime_env = std::getenv("LOCALTIME"); #endif if (localtime_env) zone = localtime_env; } const std::string name = zone; #if defined(_MSC_VER) free(localtime_env); free(tz_env); #endif time_zone tz; load_time_zone(name, &tz); // Falls back to UTC. // TODO: Follow the RFC3339 "Unknown Local Offset Convention" and // arrange for %z to generate "-0000" when we don't know the local // offset because the load_time_zone() failed and we're using UTC. return tz; } } // namespace cctz timechange/src/cctz/src/time_zone_fixed.h0000644000176200001440000000345613736317364020245 0ustar liggesusers// 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_FIXED_H_ #define CCTZ_TIME_ZONE_FIXED_H_ #include #include "cctz/time_zone.h" namespace cctz { // Helper functions for dealing with the names and abbreviations // of time zones that are a fixed offset (seconds east) from UTC. // FixedOffsetFromName() extracts the offset from a valid fixed-offset // name, while FixedOffsetToName() and FixedOffsetToAbbr() generate // the canonical zone name and abbreviation respectively for the given // offset. // // A fixed-offset name looks like "Fixed/UTC<+->::". // Its abbreviation is of the form "UTC(<+->H?H(MM(SS)?)?)?" where the // optional pieces are omitted when their values are zero. (Note that // the sign is the opposite of that used in a POSIX TZ specification.) // // Note: FixedOffsetFromName() fails on syntax errors or when the parsed // offset exceeds 24 hours. FixedOffsetToName() and FixedOffsetToAbbr() // both produce "UTC" when the argument offset exceeds 24 hours. bool FixedOffsetFromName(const std::string& name, seconds* offset); std::string FixedOffsetToName(const seconds& offset); std::string FixedOffsetToAbbr(const seconds& offset); } // namespace cctz #endif // CCTZ_TIME_ZONE_FIXED_H_ timechange/src/cctz/src/time_zone_info.cc0000644000176200001440000011724314326763406020235 0ustar liggesusers// 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. // This file implements the TimeZoneIf interface using the "zoneinfo" // data provided by the IANA Time Zone Database (i.e., the only real game // in town). // // TimeZoneInfo represents the history of UTC-offset changes within a time // zone. Most changes are due to daylight-saving rules, but occasionally // shifts are made to the time-zone's base offset. The database only attempts // to be definitive for times since 1970, so be wary of local-time conversions // before that. Also, rule and zone-boundary changes are made at the whim // of governments, so the conversion of future times needs to be taken with // a grain of salt. // // For more information see tzfile(5), http://www.iana.org/time-zones, or // https://en.wikipedia.org/wiki/Zoneinfo. // // Note that we assume the proleptic Gregorian calendar and 60-second // minutes throughout. #include "time_zone_info.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cctz/civil_time.h" #include "time_zone_fixed.h" #include "time_zone_posix.h" namespace cctz { namespace { inline bool IsLeap(year_t year) { return (year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0); } // The number of days in non-leap and leap years respectively. const std::int_least32_t kDaysPerYear[2] = {365, 366}; // The day offsets of the beginning of each (1-based) month in non-leap and // leap years respectively (e.g., 335 days before December in a leap year). const std::int_least16_t kMonthOffsets[2][1 + 12 + 1] = { {-1, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365}, {-1, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}, }; // We reject leap-second encoded zoneinfo and so assume 60-second minutes. const std::int_least32_t kSecsPerDay = 24 * 60 * 60; // 400-year chunks always have 146097 days (20871 weeks). const std::int_least64_t kSecsPer400Years = 146097LL * kSecsPerDay; // Like kDaysPerYear[] but scaled up by a factor of kSecsPerDay. const std::int_least32_t kSecsPerYear[2] = { 365 * kSecsPerDay, 366 * kSecsPerDay, }; // Convert a cctz::weekday to a POSIX TZ weekday number (0==Sun, ..., 6=Sat). inline int ToPosixWeekday(weekday wd) { switch (wd) { case weekday::sunday: return 0; case weekday::monday: return 1; case weekday::tuesday: return 2; case weekday::wednesday: return 3; case weekday::thursday: return 4; case weekday::friday: return 5; case weekday::saturday: return 6; } return 0; /*NOTREACHED*/ } // Single-byte, unsigned numeric values are encoded directly. inline std::uint_fast8_t Decode8(const char* cp) { return static_cast(*cp) & 0xff; } // Multi-byte, numeric values are encoded using a MSB first, // twos-complement representation. These helpers decode, from // the given address, 4-byte and 8-byte values respectively. // Note: If int_fastXX_t == intXX_t and this machine is not // twos complement, then there will be at least one input value // we cannot represent. std::int_fast32_t Decode32(const char* cp) { std::uint_fast32_t v = 0; for (int i = 0; i != (32 / 8); ++i) v = (v << 8) | Decode8(cp++); const std::int_fast32_t s32max = 0x7fffffff; const auto s32maxU = static_cast(s32max); if (v <= s32maxU) return static_cast(v); return static_cast(v - s32maxU - 1) - s32max - 1; } std::int_fast64_t Decode64(const char* cp) { std::uint_fast64_t v = 0; for (int i = 0; i != (64 / 8); ++i) v = (v << 8) | Decode8(cp++); const std::int_fast64_t s64max = 0x7fffffffffffffff; const auto s64maxU = static_cast(s64max); if (v <= s64maxU) return static_cast(v); return static_cast(v - s64maxU - 1) - s64max - 1; } // Does the rule for future transitions call for year-round daylight time? // See tz/zic.c:stringzone() for the details on how such rules are encoded. bool AllYearDST(const PosixTimeZone& posix) { if (posix.dst_start.date.fmt != PosixTransition::N) return false; if (posix.dst_start.date.n.day != 0) return false; if (posix.dst_start.time.offset != 0) return false; if (posix.dst_end.date.fmt != PosixTransition::J) return false; if (posix.dst_end.date.j.day != kDaysPerYear[0]) return false; const auto offset = posix.std_offset - posix.dst_offset; if (posix.dst_end.time.offset + offset != kSecsPerDay) return false; return true; } // Generate a year-relative offset for a PosixTransition. std::int_fast64_t TransOffset(bool leap_year, int jan1_weekday, const PosixTransition& pt) { std::int_fast64_t days = 0; switch (pt.date.fmt) { case PosixTransition::J: { days = pt.date.j.day; if (!leap_year || days < kMonthOffsets[1][3]) days -= 1; break; } case PosixTransition::N: { days = pt.date.n.day; break; } case PosixTransition::M: { const bool last_week = (pt.date.m.week == 5); days = kMonthOffsets[leap_year][pt.date.m.month + last_week]; const std::int_fast64_t weekday = (jan1_weekday + days) % 7; if (last_week) { days -= (weekday + 7 - 1 - pt.date.m.weekday) % 7 + 1; } else { days += (pt.date.m.weekday + 7 - weekday) % 7; days += (pt.date.m.week - 1) * 7; } break; } } return (days * kSecsPerDay) + pt.time.offset; } inline time_zone::civil_lookup MakeUnique(const time_point& tp) { time_zone::civil_lookup cl; cl.kind = time_zone::civil_lookup::UNIQUE; cl.pre = cl.trans = cl.post = tp; return cl; } inline time_zone::civil_lookup MakeUnique(std::int_fast64_t unix_time) { return MakeUnique(FromUnixSeconds(unix_time)); } inline time_zone::civil_lookup MakeSkipped(const Transition& tr, const civil_second& cs) { time_zone::civil_lookup cl; cl.kind = time_zone::civil_lookup::SKIPPED; cl.pre = FromUnixSeconds(tr.unix_time - 1 + (cs - tr.prev_civil_sec)); cl.trans = FromUnixSeconds(tr.unix_time); cl.post = FromUnixSeconds(tr.unix_time - (tr.civil_sec - cs)); return cl; } inline time_zone::civil_lookup MakeRepeated(const Transition& tr, const civil_second& cs) { time_zone::civil_lookup cl; cl.kind = time_zone::civil_lookup::REPEATED; cl.pre = FromUnixSeconds(tr.unix_time - 1 - (tr.prev_civil_sec - cs)); cl.trans = FromUnixSeconds(tr.unix_time); cl.post = FromUnixSeconds(tr.unix_time + (cs - tr.civil_sec)); return cl; } inline civil_second YearShift(const civil_second& cs, year_t shift) { return civil_second(cs.year() + shift, cs.month(), cs.day(), cs.hour(), cs.minute(), cs.second()); } } // namespace // What (no leap-seconds) UTC+seconds zoneinfo would look like. bool TimeZoneInfo::ResetToBuiltinUTC(const seconds& offset) { transition_types_.resize(1); TransitionType& tt(transition_types_.back()); tt.utc_offset = static_cast(offset.count()); tt.is_dst = false; tt.abbr_index = 0; // We temporarily add some redundant, contemporary (2015 through 2025) // transitions for performance reasons. See TimeZoneInfo::LocalTime(). // TODO: Fix the performance issue and remove the extra transitions. transitions_.clear(); transitions_.reserve(12); for (const std::int_fast64_t unix_time : { -(1LL << 59), // a "first half" transition 1420070400LL, // 2015-01-01T00:00:00+00:00 1451606400LL, // 2016-01-01T00:00:00+00:00 1483228800LL, // 2017-01-01T00:00:00+00:00 1514764800LL, // 2018-01-01T00:00:00+00:00 1546300800LL, // 2019-01-01T00:00:00+00:00 1577836800LL, // 2020-01-01T00:00:00+00:00 1609459200LL, // 2021-01-01T00:00:00+00:00 1640995200LL, // 2022-01-01T00:00:00+00:00 1672531200LL, // 2023-01-01T00:00:00+00:00 1704067200LL, // 2024-01-01T00:00:00+00:00 1735689600LL, // 2025-01-01T00:00:00+00:00 }) { Transition& tr(*transitions_.emplace(transitions_.end())); tr.unix_time = unix_time; tr.type_index = 0; tr.civil_sec = LocalTime(tr.unix_time, tt).cs; tr.prev_civil_sec = tr.civil_sec - 1; } default_transition_type_ = 0; abbreviations_ = FixedOffsetToAbbr(offset); abbreviations_.append(1, '\0'); future_spec_.clear(); // never needed for a fixed-offset zone extended_ = false; tt.civil_max = LocalTime(seconds::max().count(), tt).cs; tt.civil_min = LocalTime(seconds::min().count(), tt).cs; transitions_.shrink_to_fit(); return true; } // Builds the in-memory header using the raw bytes from the file. bool TimeZoneInfo::Header::Build(const tzhead& tzh) { std::int_fast32_t v; if ((v = Decode32(tzh.tzh_timecnt)) < 0) return false; timecnt = static_cast(v); if ((v = Decode32(tzh.tzh_typecnt)) < 0) return false; typecnt = static_cast(v); if ((v = Decode32(tzh.tzh_charcnt)) < 0) return false; charcnt = static_cast(v); if ((v = Decode32(tzh.tzh_leapcnt)) < 0) return false; leapcnt = static_cast(v); if ((v = Decode32(tzh.tzh_ttisstdcnt)) < 0) return false; ttisstdcnt = static_cast(v); if ((v = Decode32(tzh.tzh_ttisutcnt)) < 0) return false; ttisutcnt = static_cast(v); return true; } // How many bytes of data are associated with this header. The result // depends upon whether this is a section with 4-byte or 8-byte times. std::size_t TimeZoneInfo::Header::DataLength(std::size_t time_len) const { std::size_t len = 0; len += (time_len + 1) * timecnt; // unix_time + type_index len += (4 + 1 + 1) * typecnt; // utc_offset + is_dst + abbr_index len += 1 * charcnt; // abbreviations len += (time_len + 4) * leapcnt; // leap-time + TAI-UTC len += 1 * ttisstdcnt; // UTC/local indicators len += 1 * ttisutcnt; // standard/wall indicators return len; } // zic(8) can generate no-op transitions when a zone changes rules at an // instant when there is actually no discontinuity. So we check whether // two transitions have equivalent types (same offset/is_dst/abbr). bool TimeZoneInfo::EquivTransitions(std::uint_fast8_t tt1_index, std::uint_fast8_t tt2_index) const { if (tt1_index == tt2_index) return true; const TransitionType& tt1(transition_types_[tt1_index]); const TransitionType& tt2(transition_types_[tt2_index]); if (tt1.utc_offset != tt2.utc_offset) return false; if (tt1.is_dst != tt2.is_dst) return false; if (tt1.abbr_index != tt2.abbr_index) return false; return true; } // Find/make a transition type with these attributes. bool TimeZoneInfo::GetTransitionType(std::int_fast32_t utc_offset, bool is_dst, const std::string& abbr, std::uint_least8_t* index) { std::size_t type_index = 0; std::size_t abbr_index = abbreviations_.size(); for (; type_index != transition_types_.size(); ++type_index) { const TransitionType& tt(transition_types_[type_index]); const char* tt_abbr = &abbreviations_[tt.abbr_index]; if (tt_abbr == abbr) abbr_index = tt.abbr_index; if (tt.utc_offset == utc_offset && tt.is_dst == is_dst) { if (abbr_index == tt.abbr_index) break; // reuse } } if (type_index > 255 || abbr_index > 255) { // No index space (8 bits) available for a new type or abbreviation. return false; } if (type_index == transition_types_.size()) { TransitionType& tt(*transition_types_.emplace(transition_types_.end())); tt.utc_offset = static_cast(utc_offset); tt.is_dst = is_dst; if (abbr_index == abbreviations_.size()) { abbreviations_.append(abbr); abbreviations_.append(1, '\0'); } tt.abbr_index = static_cast(abbr_index); } *index = static_cast(type_index); return true; } // Use the POSIX-TZ-environment-variable-style string to handle times // in years after the last transition stored in the zoneinfo data. bool TimeZoneInfo::ExtendTransitions() { extended_ = false; if (future_spec_.empty()) return true; // last transition prevails PosixTimeZone posix; if (!ParsePosixSpec(future_spec_, &posix)) return false; // Find transition type for the future std specification. std::uint_least8_t std_ti; if (!GetTransitionType(posix.std_offset, false, posix.std_abbr, &std_ti)) return false; if (posix.dst_abbr.empty()) { // std only // The future specification should match the last transition, and // that means that handling the future will fall out naturally. return EquivTransitions(transitions_.back().type_index, std_ti); } // Find transition type for the future dst specification. std::uint_least8_t dst_ti; if (!GetTransitionType(posix.dst_offset, true, posix.dst_abbr, &dst_ti)) return false; if (AllYearDST(posix)) { // dst only // The future specification should match the last transition, and // that means that handling the future will fall out naturally. return EquivTransitions(transitions_.back().type_index, dst_ti); } // Extend the transitions for an additional 400 years using the // future specification. Years beyond those can be handled by // mapping back to a cycle-equivalent year within that range. // We may need two additional transitions for the current year. transitions_.reserve(transitions_.size() + 400 * 2 + 2); extended_ = true; const Transition& last(transitions_.back()); const std::int_fast64_t last_time = last.unix_time; const TransitionType& last_tt(transition_types_[last.type_index]); last_year_ = LocalTime(last_time, last_tt).cs.year(); bool leap_year = IsLeap(last_year_); const civil_second jan1(last_year_); std::int_fast64_t jan1_time = jan1 - civil_second(); int jan1_weekday = ToPosixWeekday(get_weekday(jan1)); Transition dst = {0, dst_ti, civil_second(), civil_second()}; Transition std = {0, std_ti, civil_second(), civil_second()}; for (const year_t limit = last_year_ + 400;; ++last_year_) { auto dst_trans_off = TransOffset(leap_year, jan1_weekday, posix.dst_start); auto std_trans_off = TransOffset(leap_year, jan1_weekday, posix.dst_end); dst.unix_time = jan1_time + dst_trans_off - posix.std_offset; std.unix_time = jan1_time + std_trans_off - posix.dst_offset; const auto* ta = dst.unix_time < std.unix_time ? &dst : &std; const auto* tb = dst.unix_time < std.unix_time ? &std : &dst; if (last_time < tb->unix_time) { if (last_time < ta->unix_time) transitions_.push_back(*ta); transitions_.push_back(*tb); } if (last_year_ == limit) break; jan1_time += kSecsPerYear[leap_year]; jan1_weekday = (jan1_weekday + kDaysPerYear[leap_year]) % 7; leap_year = !leap_year && IsLeap(last_year_ + 1); } return true; } bool TimeZoneInfo::Load(ZoneInfoSource* zip) { // Read and validate the header. tzhead tzh; if (zip->Read(&tzh, sizeof(tzh)) != sizeof(tzh)) return false; if (strncmp(tzh.tzh_magic, TZ_MAGIC, sizeof(tzh.tzh_magic)) != 0) return false; Header hdr; if (!hdr.Build(tzh)) return false; std::size_t time_len = 4; if (tzh.tzh_version[0] != '\0') { // Skip the 4-byte data. if (zip->Skip(hdr.DataLength(time_len)) != 0) return false; // Read and validate the header for the 8-byte data. if (zip->Read(&tzh, sizeof(tzh)) != sizeof(tzh)) return false; if (strncmp(tzh.tzh_magic, TZ_MAGIC, sizeof(tzh.tzh_magic)) != 0) return false; if (tzh.tzh_version[0] == '\0') return false; if (!hdr.Build(tzh)) return false; time_len = 8; } if (hdr.typecnt == 0) return false; if (hdr.leapcnt != 0) { // This code assumes 60-second minutes so we do not want // the leap-second encoded zoneinfo. We could reverse the // compensation, but the "right" encoding is rarely used // so currently we simply reject such data. return false; } if (hdr.ttisstdcnt != 0 && hdr.ttisstdcnt != hdr.typecnt) return false; if (hdr.ttisutcnt != 0 && hdr.ttisutcnt != hdr.typecnt) return false; // Read the data into a local buffer. std::size_t len = hdr.DataLength(time_len); std::vector tbuf(len); if (zip->Read(tbuf.data(), len) != len) return false; const char* bp = tbuf.data(); // Decode and validate the transitions. transitions_.reserve(hdr.timecnt + 2); transitions_.resize(hdr.timecnt); for (std::size_t i = 0; i != hdr.timecnt; ++i) { transitions_[i].unix_time = (time_len == 4) ? Decode32(bp) : Decode64(bp); bp += time_len; if (i != 0) { // Check that the transitions are ordered by time (as zic guarantees). if (!Transition::ByUnixTime()(transitions_[i - 1], transitions_[i])) return false; // out of order } } bool seen_type_0 = false; for (std::size_t i = 0; i != hdr.timecnt; ++i) { transitions_[i].type_index = Decode8(bp++); if (transitions_[i].type_index >= hdr.typecnt) return false; if (transitions_[i].type_index == 0) seen_type_0 = true; } // Decode and validate the transition types. transition_types_.reserve(hdr.typecnt + 2); transition_types_.resize(hdr.typecnt); for (std::size_t i = 0; i != hdr.typecnt; ++i) { transition_types_[i].utc_offset = static_cast(Decode32(bp)); if (transition_types_[i].utc_offset >= kSecsPerDay || transition_types_[i].utc_offset <= -kSecsPerDay) return false; bp += 4; transition_types_[i].is_dst = (Decode8(bp++) != 0); transition_types_[i].abbr_index = Decode8(bp++); if (transition_types_[i].abbr_index >= hdr.charcnt) return false; } // Determine the before-first-transition type. default_transition_type_ = 0; if (seen_type_0 && hdr.timecnt != 0) { std::uint_fast8_t index = 0; if (transition_types_[0].is_dst) { index = transitions_[0].type_index; while (index != 0 && transition_types_[index].is_dst) --index; } while (index != hdr.typecnt && transition_types_[index].is_dst) ++index; if (index != hdr.typecnt) default_transition_type_ = index; } // Copy all the abbreviations. abbreviations_.reserve(hdr.charcnt + 10); abbreviations_.assign(bp, hdr.charcnt); bp += hdr.charcnt; // Skip the unused portions. We've already dispensed with leap-second // encoded zoneinfo. The ttisstd/ttisgmt indicators only apply when // interpreting a POSIX spec that does not include start/end rules, and // that isn't the case here (see "zic -p"). bp += (8 + 4) * hdr.leapcnt; // leap-time + TAI-UTC bp += 1 * hdr.ttisstdcnt; // UTC/local indicators bp += 1 * hdr.ttisutcnt; // standard/wall indicators assert(bp == tbuf.data() + tbuf.size()); future_spec_.clear(); if (tzh.tzh_version[0] != '\0') { // Snarf up the NL-enclosed future POSIX spec. Note // that version '3' files utilize an extended format. auto get_char = [](ZoneInfoSource* azip) -> int { unsigned char ch; // all non-EOF results are positive return (azip->Read(&ch, 1) == 1) ? ch : EOF; }; if (get_char(zip) != '\n') return false; for (int c = get_char(zip); c != '\n'; c = get_char(zip)) { if (c == EOF) return false; future_spec_.push_back(static_cast(c)); } } // We don't check for EOF so that we're forwards compatible. // If we did not find version information during the standard loading // process (as of tzh_version '3' that is unsupported), then ask the // ZoneInfoSource for any out-of-bound version string it may be privy to. if (version_.empty()) { version_ = zip->Version(); } // Trim redundant transitions. zic may have added these to work around // differences between the glibc and reference implementations (see // zic.c:dontmerge) or to avoid bugs in old readers. For us, they just // get in the way when we do future_spec_ extension. while (hdr.timecnt > 1) { if (!EquivTransitions(transitions_[hdr.timecnt - 1].type_index, transitions_[hdr.timecnt - 2].type_index)) { break; } hdr.timecnt -= 1; } transitions_.resize(hdr.timecnt); // Ensure that there is always a transition in the first half of the // time line (the second half is handled below) so that the signed // difference between a civil_second and the civil_second of its // previous transition is always representable, without overflow. if (transitions_.empty() || transitions_.front().unix_time >= 0) { Transition& tr(*transitions_.emplace(transitions_.begin())); tr.unix_time = -(1LL << 59); // -18267312070-10-26T17:01:52+00:00 tr.type_index = default_transition_type_; } // Extend the transitions using the future specification. if (!ExtendTransitions()) return false; // Ensure that there is always a transition in the second half of the // time line (the first half is handled above) so that the signed // difference between a civil_second and the civil_second of its // previous transition is always representable, without overflow. const Transition& last(transitions_.back()); if (last.unix_time < 0) { const std::uint_fast8_t type_index = last.type_index; Transition& tr(*transitions_.emplace(transitions_.end())); tr.unix_time = 2147483647; // 2038-01-19T03:14:07+00:00 tr.type_index = type_index; } // Compute the local civil time for each transition and the preceding // second. These will be used for reverse conversions in MakeTime(). const TransitionType* ttp = &transition_types_[default_transition_type_]; for (std::size_t i = 0; i != transitions_.size(); ++i) { Transition& tr(transitions_[i]); tr.prev_civil_sec = LocalTime(tr.unix_time, *ttp).cs - 1; ttp = &transition_types_[tr.type_index]; tr.civil_sec = LocalTime(tr.unix_time, *ttp).cs; if (i != 0) { // Check that the transitions are ordered by civil time. Essentially // this means that an offset change cannot cross another such change. // No one does this in practice, and we depend on it in MakeTime(). if (!Transition::ByCivilTime()(transitions_[i - 1], tr)) return false; // out of order } } // Compute the maximum/minimum civil times that can be converted to a // time_point for each of the zone's transition types. for (auto& tt : transition_types_) { tt.civil_max = LocalTime(seconds::max().count(), tt).cs; tt.civil_min = LocalTime(seconds::min().count(), tt).cs; } transitions_.shrink_to_fit(); return true; } namespace { using FilePtr = std::unique_ptr; // fopen(3) adaptor. inline FilePtr FOpen(const char* path, const char* mode) { #if defined(_MSC_VER) FILE* fp; if (fopen_s(&fp, path, mode) != 0) fp = nullptr; return FilePtr(fp, fclose); #else // TODO: Enable the close-on-exec flag. return FilePtr(fopen(path, mode), fclose); #endif } // A stdio(3)-backed implementation of ZoneInfoSource. class FileZoneInfoSource : public ZoneInfoSource { public: static std::unique_ptr Open(const std::string& name); std::size_t Read(void* ptr, std::size_t size) override { size = std::min(size, len_); std::size_t nread = fread(ptr, 1, size, fp_.get()); len_ -= nread; return nread; } int Skip(std::size_t offset) override { offset = std::min(offset, len_); int rc = fseek(fp_.get(), static_cast(offset), SEEK_CUR); if (rc == 0) len_ -= offset; return rc; } std::string Version() const override { // TODO: It would nice if the zoneinfo data included the tzdb version. return std::string(); } protected: explicit FileZoneInfoSource( FilePtr fp, std::size_t len = std::numeric_limits::max()) : fp_(std::move(fp)), len_(len) {} private: FilePtr fp_; std::size_t len_; }; std::unique_ptr FileZoneInfoSource::Open( const std::string& name) { // Use of the "file:" prefix is intended for testing purposes only. const std::size_t pos = (name.compare(0, 5, "file:") == 0) ? 5 : 0; // Map the time-zone name to a path name. std::string path; if (pos == name.size() || name[pos] != '/') { const char* tzdir = "/usr/share/zoneinfo"; char* tzdir_env = nullptr; #if defined(_MSC_VER) _dupenv_s(&tzdir_env, nullptr, "TZDIR"); #else tzdir_env = std::getenv("TZDIR"); #endif if (tzdir_env && *tzdir_env) tzdir = tzdir_env; path += tzdir; path += '/'; #if defined(_MSC_VER) free(tzdir_env); #endif } path.append(name, pos, std::string::npos); // Open the zoneinfo file. auto fp = FOpen(path.c_str(), "rb"); if (fp == nullptr) return nullptr; return std::unique_ptr(new FileZoneInfoSource(std::move(fp))); } class AndroidZoneInfoSource : public FileZoneInfoSource { public: static std::unique_ptr Open(const std::string& name); std::string Version() const override { return version_; } private: explicit AndroidZoneInfoSource(FilePtr fp, std::size_t len, std::string version) : FileZoneInfoSource(std::move(fp), len), version_(std::move(version)) {} std::string version_; }; std::unique_ptr AndroidZoneInfoSource::Open( const std::string& name) { // Use of the "file:" prefix is intended for testing purposes only. const std::size_t pos = (name.compare(0, 5, "file:") == 0) ? 5 : 0; // See Android's libc/tzcode/bionic.cpp for additional information. for (const char* tzdata : {"/data/misc/zoneinfo/current/tzdata", "/system/usr/share/zoneinfo/tzdata"}) { auto fp = FOpen(tzdata, "rb"); if (fp == nullptr) continue; char hbuf[24]; // covers header.zonetab_offset too if (fread(hbuf, 1, sizeof(hbuf), fp.get()) != sizeof(hbuf)) continue; if (strncmp(hbuf, "tzdata", 6) != 0) continue; const char* vers = (hbuf[11] == '\0') ? hbuf + 6 : ""; const std::int_fast32_t index_offset = Decode32(hbuf + 12); const std::int_fast32_t data_offset = Decode32(hbuf + 16); if (index_offset < 0 || data_offset < index_offset) continue; if (fseek(fp.get(), static_cast(index_offset), SEEK_SET) != 0) continue; char ebuf[52]; // covers entry.unused too const std::size_t index_size = static_cast(data_offset - index_offset); const std::size_t zonecnt = index_size / sizeof(ebuf); if (zonecnt * sizeof(ebuf) != index_size) continue; for (std::size_t i = 0; i != zonecnt; ++i) { if (fread(ebuf, 1, sizeof(ebuf), fp.get()) != sizeof(ebuf)) break; const std::int_fast32_t start = data_offset + Decode32(ebuf + 40); const std::int_fast32_t length = Decode32(ebuf + 44); if (start < 0 || length < 0) break; ebuf[40] = '\0'; // ensure zone name is NUL terminated if (strcmp(name.c_str() + pos, ebuf) == 0) { if (fseek(fp.get(), static_cast(start), SEEK_SET) != 0) break; return std::unique_ptr(new AndroidZoneInfoSource( std::move(fp), static_cast(length), vers)); } } } return nullptr; } // A zoneinfo source for use inside Fuchsia components. This attempts to // read zoneinfo files from one of several known paths in a component's // incoming namespace. [Config data][1] is preferred, but package-specific // resources are also supported. // // Fuchsia's implementation supports `FileZoneInfoSource::Version()`. // // [1]: https://fuchsia.dev/fuchsia-src/development/components/data#using_config_data_in_your_component class FuchsiaZoneInfoSource : public FileZoneInfoSource { public: static std::unique_ptr Open(const std::string& name); std::string Version() const override { return version_; } private: explicit FuchsiaZoneInfoSource(FilePtr fp, std::string version) : FileZoneInfoSource(std::move(fp)), version_(std::move(version)) {} std::string version_; }; std::unique_ptr FuchsiaZoneInfoSource::Open( const std::string& name) { // Use of the "file:" prefix is intended for testing purposes only. const std::size_t pos = (name.compare(0, 5, "file:") == 0) ? 5 : 0; // Prefixes where a Fuchsia component might find zoneinfo files, // in descending order of preference. const auto kTzdataPrefixes = { "/config/data/tzdata/", "/pkg/data/tzdata/", "/data/tzdata/", }; const auto kEmptyPrefix = {""}; const bool name_absolute = (pos != name.size() && name[pos] == '/'); const auto prefixes = name_absolute ? kEmptyPrefix : kTzdataPrefixes; // Fuchsia builds place zoneinfo files at "". for (const std::string prefix : prefixes) { std::string path = prefix; if (!prefix.empty()) path += "zoneinfo/tzif2/"; // format path.append(name, pos, std::string::npos); auto fp = FOpen(path.c_str(), "rb"); if (fp == nullptr) continue; std::string version; if (!prefix.empty()) { // Fuchsia builds place the version in "revision.txt". std::ifstream version_stream(prefix + "revision.txt"); if (version_stream.is_open()) { // revision.txt should contain no newlines, but to be // defensive we read just the first line. std::getline(version_stream, version); } } return std::unique_ptr( new FuchsiaZoneInfoSource(std::move(fp), std::move(version))); } return nullptr; } } // namespace bool TimeZoneInfo::Load(const std::string& name) { // We can ensure that the loading of UTC or any other fixed-offset // zone never fails because the simple, fixed-offset state can be // internally generated. Note that this depends on our choice to not // accept leap-second encoded ("right") zoneinfo. auto offset = seconds::zero(); if (FixedOffsetFromName(name, &offset)) { return ResetToBuiltinUTC(offset); } // Find and use a ZoneInfoSource to load the named zone. auto zip = cctz_extension::zone_info_source_factory( name, [](const std::string& n) -> std::unique_ptr { if (auto z = FileZoneInfoSource::Open(n)) return z; if (auto z = AndroidZoneInfoSource::Open(n)) return z; if (auto z = FuchsiaZoneInfoSource::Open(n)) return z; return nullptr; }); return zip != nullptr && Load(zip.get()); } // BreakTime() translation for a particular transition type. time_zone::absolute_lookup TimeZoneInfo::LocalTime( std::int_fast64_t unix_time, const TransitionType& tt) const { // A civil time in "+offset" looks like (time+offset) in UTC. // Note: We perform two additions in the civil_second domain to // sidestep the chance of overflow in (unix_time + tt.utc_offset). return {(civil_second() + unix_time) + tt.utc_offset, tt.utc_offset, tt.is_dst, &abbreviations_[tt.abbr_index]}; } // BreakTime() translation for a particular transition. time_zone::absolute_lookup TimeZoneInfo::LocalTime( std::int_fast64_t unix_time, const Transition& tr) const { const TransitionType& tt = transition_types_[tr.type_index]; // Note: (unix_time - tr.unix_time) will never overflow as we // have ensured that there is always a "nearby" transition. return {tr.civil_sec + (unix_time - tr.unix_time), // TODO: Optimize. tt.utc_offset, tt.is_dst, &abbreviations_[tt.abbr_index]}; } // MakeTime() translation with a conversion-preserving +N * 400-year shift. time_zone::civil_lookup TimeZoneInfo::TimeLocal(const civil_second& cs, year_t c4_shift) const { assert(last_year_ - 400 < cs.year() && cs.year() <= last_year_); time_zone::civil_lookup cl = MakeTime(cs); if (c4_shift > seconds::max().count() / kSecsPer400Years) { cl.pre = cl.trans = cl.post = time_point::max(); } else { const auto offset = seconds(c4_shift * kSecsPer400Years); const auto limit = time_point::max() - offset; for (auto* tp : {&cl.pre, &cl.trans, &cl.post}) { if (*tp > limit) { *tp = time_point::max(); } else { *tp += offset; } } } return cl; } time_zone::absolute_lookup TimeZoneInfo::BreakTime( const time_point& tp) const { std::int_fast64_t unix_time = ToUnixSeconds(tp); const std::size_t timecnt = transitions_.size(); assert(timecnt != 0); // We always add a transition. if (unix_time < transitions_[0].unix_time) { return LocalTime(unix_time, transition_types_[default_transition_type_]); } if (unix_time >= transitions_[timecnt - 1].unix_time) { // After the last transition. If we extended the transitions using // future_spec_, shift back to a supported year using the 400-year // cycle of calendaric equivalence and then compensate accordingly. if (extended_) { const std::int_fast64_t diff = unix_time - transitions_[timecnt - 1].unix_time; const year_t shift = diff / kSecsPer400Years + 1; const auto d = seconds(shift * kSecsPer400Years); time_zone::absolute_lookup al = BreakTime(tp - d); al.cs = YearShift(al.cs, shift * 400); return al; } return LocalTime(unix_time, transitions_[timecnt - 1]); } const std::size_t hint = local_time_hint_.load(std::memory_order_relaxed); if (0 < hint && hint < timecnt) { if (transitions_[hint - 1].unix_time <= unix_time) { if (unix_time < transitions_[hint].unix_time) { return LocalTime(unix_time, transitions_[hint - 1]); } } } const Transition target = {unix_time, 0, civil_second(), civil_second()}; const Transition* begin = &transitions_[0]; const Transition* tr = std::upper_bound(begin, begin + timecnt, target, Transition::ByUnixTime()); local_time_hint_.store(static_cast(tr - begin), std::memory_order_relaxed); return LocalTime(unix_time, *--tr); } time_zone::civil_lookup TimeZoneInfo::MakeTime(const civil_second& cs) const { const std::size_t timecnt = transitions_.size(); assert(timecnt != 0); // We always add a transition. // Find the first transition after our target civil time. const Transition* tr = nullptr; const Transition* begin = &transitions_[0]; const Transition* end = begin + timecnt; if (cs < begin->civil_sec) { tr = begin; } else if (cs >= transitions_[timecnt - 1].civil_sec) { tr = end; } else { const std::size_t hint = time_local_hint_.load(std::memory_order_relaxed); if (0 < hint && hint < timecnt) { if (transitions_[hint - 1].civil_sec <= cs) { if (cs < transitions_[hint].civil_sec) { tr = begin + hint; } } } if (tr == nullptr) { const Transition target = {0, 0, cs, civil_second()}; tr = std::upper_bound(begin, end, target, Transition::ByCivilTime()); time_local_hint_.store(static_cast(tr - begin), std::memory_order_relaxed); } } if (tr == begin) { if (tr->prev_civil_sec >= cs) { // Before first transition, so use the default offset. const TransitionType& tt(transition_types_[default_transition_type_]); if (cs < tt.civil_min) return MakeUnique(time_point::min()); return MakeUnique(cs - (civil_second() + tt.utc_offset)); } // tr->prev_civil_sec < cs < tr->civil_sec return MakeSkipped(*tr, cs); } if (tr == end) { if (cs > (--tr)->prev_civil_sec) { // After the last transition. If we extended the transitions using // future_spec_, shift back to a supported year using the 400-year // cycle of calendaric equivalence and then compensate accordingly. if (extended_ && cs.year() > last_year_) { const year_t shift = (cs.year() - last_year_ - 1) / 400 + 1; return TimeLocal(YearShift(cs, shift * -400), shift); } const TransitionType& tt(transition_types_[tr->type_index]); if (cs > tt.civil_max) return MakeUnique(time_point::max()); return MakeUnique(tr->unix_time + (cs - tr->civil_sec)); } // tr->civil_sec <= cs <= tr->prev_civil_sec return MakeRepeated(*tr, cs); } if (tr->prev_civil_sec < cs) { // tr->prev_civil_sec < cs < tr->civil_sec return MakeSkipped(*tr, cs); } if (cs <= (--tr)->prev_civil_sec) { // tr->civil_sec <= cs <= tr->prev_civil_sec return MakeRepeated(*tr, cs); } // In between transitions. return MakeUnique(tr->unix_time + (cs - tr->civil_sec)); } std::string TimeZoneInfo::Version() const { return version_; } std::string TimeZoneInfo::Description() const { std::ostringstream oss; oss << "#trans=" << transitions_.size(); oss << " #types=" << transition_types_.size(); oss << " spec='" << future_spec_ << "'"; return oss.str(); } bool TimeZoneInfo::NextTransition(const time_point& tp, time_zone::civil_transition* trans) const { if (transitions_.empty()) return false; const Transition* begin = &transitions_[0]; const Transition* end = begin + transitions_.size(); if (begin->unix_time <= -(1LL << 59)) { // Do not report the BIG_BANG found in some zoneinfo data as it is // really a sentinel, not a transition. See pre-2018f tz/zic.c. ++begin; } std::int_fast64_t unix_time = ToUnixSeconds(tp); const Transition target = {unix_time, 0, civil_second(), civil_second()}; const Transition* tr = std::upper_bound(begin, end, target, Transition::ByUnixTime()); for (; tr != end; ++tr) { // skip no-op transitions std::uint_fast8_t prev_type_index = (tr == begin) ? default_transition_type_ : tr[-1].type_index; if (!EquivTransitions(prev_type_index, tr[0].type_index)) break; } // When tr == end we return false, ignoring future_spec_. if (tr == end) return false; trans->from = tr->prev_civil_sec + 1; trans->to = tr->civil_sec; return true; } bool TimeZoneInfo::PrevTransition(const time_point& tp, time_zone::civil_transition* trans) const { if (transitions_.empty()) return false; const Transition* begin = &transitions_[0]; const Transition* end = begin + transitions_.size(); if (begin->unix_time <= -(1LL << 59)) { // Do not report the BIG_BANG found in some zoneinfo data as it is // really a sentinel, not a transition. See pre-2018f tz/zic.c. ++begin; } std::int_fast64_t unix_time = ToUnixSeconds(tp); if (FromUnixSeconds(unix_time) != tp) { if (unix_time == std::numeric_limits::max()) { if (end == begin) return false; // Ignore future_spec_. trans->from = (--end)->prev_civil_sec + 1; trans->to = end->civil_sec; return true; } unix_time += 1; // ceils } const Transition target = {unix_time, 0, civil_second(), civil_second()}; const Transition* tr = std::lower_bound(begin, end, target, Transition::ByUnixTime()); for (; tr != begin; --tr) { // skip no-op transitions std::uint_fast8_t prev_type_index = (tr - 1 == begin) ? default_transition_type_ : tr[-2].type_index; if (!EquivTransitions(prev_type_index, tr[-1].type_index)) break; } // When tr == end we return the "last" transition, ignoring future_spec_. if (tr == begin) return false; trans->from = (--tr)->prev_civil_sec + 1; trans->to = tr->civil_sec; return true; } } // namespace cctz timechange/src/cctz/src/time_zone_posix.h0000644000176200001440000000733013573274232020276 0ustar liggesusers// 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. // Parsing of a POSIX zone spec as described in the TZ part of section 8.3 in // http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html. // // The current POSIX spec for America/Los_Angeles is "PST8PDT,M3.2.0,M11.1.0", // which would be broken down as ... // // PosixTimeZone { // std_abbr = "PST" // std_offset = -28800 // dst_abbr = "PDT" // dst_offset = -25200 // dst_start = PosixTransition { // date { // m { // month = 3 // week = 2 // weekday = 0 // } // } // time { // offset = 7200 // } // } // dst_end = PosixTransition { // date { // m { // month = 11 // week = 1 // weekday = 0 // } // } // time { // offset = 7200 // } // } // } #ifndef CCTZ_TIME_ZONE_POSIX_H_ #define CCTZ_TIME_ZONE_POSIX_H_ #include #include namespace cctz { // The date/time of the transition. The date is specified as either: // (J) the Nth day of the year (1 <= N <= 365), excluding leap days, or // (N) the Nth day of the year (0 <= N <= 365), including leap days, or // (M) the Nth weekday of a month (e.g., the 2nd Sunday in March). // The time, specified as a day offset, identifies the particular moment // of the transition, and may be negative or >= 24h, and in which case // it would take us to another day, and perhaps week, or even month. struct PosixTransition { enum DateFormat { J, N, M }; struct Date { struct NonLeapDay { std::int_fast16_t day; // day of non-leap year [1:365] }; struct Day { std::int_fast16_t day; // day of year [0:365] }; struct MonthWeekWeekday { std::int_fast8_t month; // month of year [1:12] std::int_fast8_t week; // week of month [1:5] (5==last) std::int_fast8_t weekday; // 0==Sun, ..., 6=Sat }; DateFormat fmt; union { NonLeapDay j; Day n; MonthWeekWeekday m; }; }; struct Time { std::int_fast32_t offset; // seconds before/after 00:00:00 }; Date date; Time time; }; // The entirety of a POSIX-string specified time-zone rule. The standard // abbreviation and offset are always given. If the time zone includes // daylight saving, then the daylight abbrevation is non-empty and the // remaining fields are also valid. Note that the start/end transitions // are not ordered---in the southern hemisphere the transition to end // daylight time occurs first in any particular year. struct PosixTimeZone { std::string std_abbr; std::int_fast32_t std_offset; std::string dst_abbr; std::int_fast32_t dst_offset; PosixTransition dst_start; PosixTransition dst_end; }; // Breaks down a POSIX time-zone specification into its constituent pieces, // filling in any missing values (DST offset, or start/end transition times) // with the standard-defined defaults. Returns false if the specification // could not be parsed (although some fields of *res may have been altered). bool ParsePosixSpec(const std::string& spec, PosixTimeZone* res); } // namespace cctz #endif // CCTZ_TIME_ZONE_POSIX_H_ timechange/src/cctz/src/time_zone_info.h0000644000176200001440000001214313736317364020072 0ustar liggesusers// 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_INFO_H_ #define CCTZ_TIME_ZONE_INFO_H_ #include #include #include #include #include #include "cctz/civil_time.h" #include "cctz/time_zone.h" #include "cctz/zone_info_source.h" #include "time_zone_if.h" #include "tzfile.h" namespace cctz { // A transition to a new UTC offset. struct Transition { std::int_least64_t unix_time; // the instant of this transition std::uint_least8_t type_index; // index of the transition type civil_second civil_sec; // local civil time of transition civil_second prev_civil_sec; // local civil time one second earlier struct ByUnixTime { inline bool operator()(const Transition& lhs, const Transition& rhs) const { return lhs.unix_time < rhs.unix_time; } }; struct ByCivilTime { inline bool operator()(const Transition& lhs, const Transition& rhs) const { return lhs.civil_sec < rhs.civil_sec; } }; }; // The characteristics of a particular transition. struct TransitionType { std::int_least32_t utc_offset; // the new prevailing UTC offset civil_second civil_max; // max convertible civil time for offset civil_second civil_min; // min convertible civil time for offset bool is_dst; // did we move into daylight-saving time std::uint_least8_t abbr_index; // index of the new abbreviation }; // A time zone backed by the IANA Time Zone Database (zoneinfo). class TimeZoneInfo : public TimeZoneIf { public: TimeZoneInfo() = default; TimeZoneInfo(const TimeZoneInfo&) = delete; TimeZoneInfo& operator=(const TimeZoneInfo&) = delete; // Loads the zoneinfo for the given name, returning true if successful. bool Load(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: struct Header { // counts of: std::size_t timecnt; // transition times std::size_t typecnt; // transition types std::size_t charcnt; // zone abbreviation characters std::size_t leapcnt; // leap seconds (we expect none) std::size_t ttisstdcnt; // UTC/local indicators (unused) std::size_t ttisutcnt; // standard/wall indicators (unused) bool Build(const tzhead& tzh); std::size_t DataLength(std::size_t time_len) const; }; bool GetTransitionType(std::int_fast32_t utc_offset, bool is_dst, const std::string& abbr, std::uint_least8_t* index); bool EquivTransitions(std::uint_fast8_t tt1_index, std::uint_fast8_t tt2_index) const; bool ExtendTransitions(); bool ResetToBuiltinUTC(const seconds& offset); bool Load(ZoneInfoSource* zip); // Helpers for BreakTime() and MakeTime(). time_zone::absolute_lookup LocalTime(std::int_fast64_t unix_time, const TransitionType& tt) const; time_zone::absolute_lookup LocalTime(std::int_fast64_t unix_time, const Transition& tr) const; time_zone::civil_lookup TimeLocal(const civil_second& cs, year_t c4_shift) const; std::vector transitions_; // ordered by unix_time and civil_sec std::vector transition_types_; // distinct transition types std::uint_fast8_t default_transition_type_; // for before first transition std::string abbreviations_; // all the NUL-terminated abbreviations std::string version_; // the tzdata version if available std::string future_spec_; // for after the last zic transition bool extended_; // future_spec_ was used to generate transitions year_t last_year_; // the final year of the generated transitions // We remember the transitions found during the last BreakTime() and // MakeTime() calls. If the next request is for the same transition we // will avoid re-searching. mutable std::atomic local_time_hint_ = {}; // BreakTime() hint mutable std::atomic time_local_hint_ = {}; // MakeTime() hint }; } // namespace cctz #endif // CCTZ_TIME_ZONE_INFO_H_ timechange/src/cctz/src/tzfile.h0000644000176200001440000000764014326763406016367 0ustar liggesusers/* Layout and location of TZif files. */ #ifndef TZFILE_H #define TZFILE_H /* ** This file is in the public domain, so clarified as of ** 1996-06-05 by Arthur David Olson. */ /* ** This header is for use ONLY with the time conversion code. ** There is no guarantee that it will remain unchanged, ** or that it will remain at all. ** Do NOT copy it to any system include directory. ** Thank you! */ /* ** Information about time zone files. */ #ifndef TZDIR # define TZDIR "/usr/share/zoneinfo" /* Time zone object file directory */ #endif /* !defined TZDIR */ #ifndef TZDEFAULT # define TZDEFAULT "/etc/localtime" #endif /* !defined TZDEFAULT */ #ifndef TZDEFRULES # define TZDEFRULES "posixrules" #endif /* !defined TZDEFRULES */ /* See Internet RFC 8536 for more details about the following format. */ /* ** Each file begins with. . . */ #define TZ_MAGIC "TZif" struct tzhead { char tzh_magic[4]; /* TZ_MAGIC */ char tzh_version[1]; /* '\0' or '2'-'4' as of 2021 */ char tzh_reserved[15]; /* reserved; must be zero */ char tzh_ttisutcnt[4]; /* coded number of trans. time flags */ char tzh_ttisstdcnt[4]; /* coded number of trans. time flags */ char tzh_leapcnt[4]; /* coded number of leap seconds */ char tzh_timecnt[4]; /* coded number of transition times */ char tzh_typecnt[4]; /* coded number of local time types */ char tzh_charcnt[4]; /* coded number of abbr. chars */ }; /* ** . . .followed by. . . ** ** tzh_timecnt (char [4])s coded transition times a la time(2) ** tzh_timecnt (unsigned char)s types of local time starting at above ** tzh_typecnt repetitions of ** one (char [4]) coded UT offset in seconds ** one (unsigned char) used to set tm_isdst ** one (unsigned char) that's an abbreviation list index ** tzh_charcnt (char)s '\0'-terminated zone abbreviations ** tzh_leapcnt repetitions of ** one (char [4]) coded leap second transition times ** one (char [4]) total correction after above ** tzh_ttisstdcnt (char)s indexed by type; if 1, transition ** time is standard time, if 0, ** transition time is local (wall clock) ** time; if absent, transition times are ** assumed to be local time ** tzh_ttisutcnt (char)s indexed by type; if 1, transition ** time is UT, if 0, transition time is ** local time; if absent, transition ** times are assumed to be local time. ** When this is 1, the corresponding ** std/wall indicator must also be 1. */ /* ** If tzh_version is '2' or greater, the above is followed by a second instance ** of tzhead and a second instance of the data in which each coded transition ** time uses 8 rather than 4 chars, ** then a POSIX-TZ-environment-variable-style string for use in handling ** instants after the last transition time stored in the file ** (with nothing between the newlines if there is no POSIX representation for ** such instants). ** ** If tz_version is '3' or greater, the above is extended as follows. ** First, the POSIX TZ string's hour offset may range from -167 ** through 167 as compared to the POSIX-required 0 through 24. ** Second, its DST start time may be January 1 at 00:00 and its stop ** time December 31 at 24:00 plus the difference between DST and ** standard time, indicating DST all year. */ /* ** In the current implementation, "tzset()" refuses to deal with files that ** exceed any of the limits below. */ #ifndef TZ_MAX_TIMES # define TZ_MAX_TIMES 2000 #endif /* !defined TZ_MAX_TIMES */ #ifndef TZ_MAX_TYPES /* This must be at least 17 for Europe/Samara and Europe/Vilnius. */ # define TZ_MAX_TYPES 256 /* Limited by what (unsigned char)'s can hold */ #endif /* !defined TZ_MAX_TYPES */ #ifndef TZ_MAX_CHARS # define TZ_MAX_CHARS 50 /* Maximum number of abbreviation characters */ /* (limited by what unsigned chars can hold) */ #endif /* !defined TZ_MAX_CHARS */ #ifndef TZ_MAX_LEAPS # define TZ_MAX_LEAPS 50 /* Maximum number of leap second corrections */ #endif /* !defined TZ_MAX_LEAPS */ #endif /* !defined TZFILE_H */ timechange/src/cctz/src/time_zone_impl.h0000644000176200001440000000576313736317364020112 0ustar liggesusers// 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_IMPL_H_ #define CCTZ_TIME_ZONE_IMPL_H_ #include #include #include "cctz/civil_time.h" #include "cctz/time_zone.h" #include "time_zone_if.h" #include "time_zone_info.h" namespace cctz { // time_zone::Impl is the internal object referenced by a cctz::time_zone. class time_zone::Impl { public: // The UTC time zone. Also used for other time zones that fail to load. static time_zone UTC(); // Load a named time zone. Returns false if the name is invalid, or if // some other kind of error occurs. Note that loading "UTC" never fails. static bool LoadTimeZone(const std::string& name, time_zone* tz); // Clears the map of cached time zones. Primarily for use in benchmarks // that gauge the performance of loading/parsing the time-zone data. static void ClearTimeZoneMapTestOnly(); // The primary key is the time-zone ID (e.g., "America/New_York"). const std::string& Name() const { // TODO: It would nice if the zoneinfo data included the zone name. return name_; } // Breaks a time_point down to civil-time components in this time zone. time_zone::absolute_lookup BreakTime(const time_point& tp) const { return zone_->BreakTime(tp); } // Converts the civil-time components in this time zone into a time_point. // That is, the opposite of BreakTime(). The requested civil time may be // ambiguous or illegal due to a change of UTC offset. time_zone::civil_lookup MakeTime(const civil_second& cs) const { return zone_->MakeTime(cs); } // Finds the time of the next/previous offset change in this time zone. bool NextTransition(const time_point& tp, time_zone::civil_transition* trans) const { return zone_->NextTransition(tp, trans); } bool PrevTransition(const time_point& tp, time_zone::civil_transition* trans) const { return zone_->PrevTransition(tp, trans); } // Returns an implementation-defined version string for this time zone. std::string Version() const { return zone_->Version(); } // Returns an implementation-defined description of this time zone. std::string Description() const { return zone_->Description(); } private: explicit Impl(const std::string& name); static const Impl* UTCImpl(); const std::string name_; std::unique_ptr zone_; }; } // namespace cctz #endif // CCTZ_TIME_ZONE_IMPL_H_ timechange/src/round.cpp0000644000176200001440000003334114552157367015023 0ustar liggesusers #include "R_ext/Arith.h" #include "R_ext/Print.h" #include "common.h" #include "cpp11/doubles.hpp" #include #define INT_FLOOR_MULTI_UNIT(x, n) ((x/n) * n) #define INT_FLOOR_MULTI_UNIT1(x, n) (((x - 1)/n) * n) inline double floor_multi_unit(double x, double n) noexcept { return std::floor(x/n) * n; } inline double floor_multi_unit1(double x, double n) noexcept { return std::floor((x-1)/n) * n; } #define INT_CEIL_MULTI_UNIT(x, n) ((x / n) * n + n) #define INT_CEIL_MULTI_UNIT1(x, n) (((x - 1) / n) * n + n) inline double ceil_multi_unit(double x, double n) noexcept { return std::floor(x/n) * n + n; } inline double ceil_multi_unit1(double x, double n) noexcept { return std::floor((x - 1)/n) * n + n; } enum class Unit {YEAR, HALFYEAR, QUARTER, SEASON, BIMONTH, MONTH, WEEK, DAY, HOUR, MINUTE, SECOND, AHOUR, AMINUTE, ASECOND}; void check_fractional_unit(const double N, const char * unit_str) { int_fast64_t fN = floor_to_int64(N); if (N < 1 || N - fN > 1e-14) Rf_error("Rounding with %s units (%.2f) is not supported", unit_str, N); } inline double month_unit(const Unit unit, double N) { if (unit == Unit::HALFYEAR) N *= 6; else if (unit == Unit::BIMONTH) N *= 2; else if (unit == Unit::SEASON) N *= 3; else if (unit == Unit::QUARTER) N *= 3; return N; } std::pair adjust_rounding_unit(const Unit unit, double N) { switch (unit) { case Unit::ASECOND: return std::make_pair(Unit::ASECOND, N); case Unit::AMINUTE: return std::make_pair(Unit::ASECOND, N*60); case Unit::AHOUR: return std::make_pair(Unit::ASECOND, N*3600); case Unit::SECOND: if (N > 60) Rf_error("Rounding unit for seconds larger than 60"); return std::make_pair(Unit::SECOND, N); case Unit::MINUTE: if (N < 1) return std::make_pair(Unit::SECOND, N * 60); if (N > 60) Rf_error("Rounding unit for minutes larger than 60"); check_fractional_unit(N, "fractional multi-minute"); return std::make_pair(Unit::MINUTE, N); case Unit::HOUR: if (N < 1) return std::make_pair(Unit::MINUTE, N * 60); if (N > 24) Rf_error("Rounding unit for hours larger than 24"); check_fractional_unit(N, "fractional multi-hour"); return std::make_pair(Unit::HOUR, N); case Unit::DAY: if (N < 1) return std::make_pair(Unit::HOUR, N * 24); if (N > 31) Rf_error("Rounding unit for days larger than 31"); check_fractional_unit(N, "fractional multi-day"); return std::make_pair(Unit::DAY, N); case Unit::SEASON: if (N != 1) Rf_error("Rounding with fractional or multi-unit seasons not supported"); /* [[fallthrough]]; // cpp++17 extension */ case Unit::HALFYEAR: case Unit::BIMONTH: case Unit::QUARTER: case Unit::MONTH: N = month_unit(unit, N); check_fractional_unit(N, "fractional months"); if (N > 12) Rf_error("Resulting rounding number of months (%.2f) larger than 12", N); break; case Unit::YEAR: check_fractional_unit(N, "fractional years"); break; case Unit::WEEK: if (N != 1) Rf_error("Rounding with multi-week or fractional weeks is not supported"); break; } return std::make_pair(unit, N); } Unit name2unit(std::string unit_name) { if (unit_name == "asecond") return Unit::ASECOND; if (unit_name == "aminute") return Unit::AMINUTE; if (unit_name == "ahour") return Unit::AHOUR; if (unit_name == "second") return Unit::SECOND; if (unit_name == "minute") return Unit::MINUTE; if (unit_name == "hour") return Unit::HOUR; if (unit_name == "day") return Unit::DAY; if (unit_name == "month") return Unit::MONTH; if (unit_name == "bimonth") return Unit::BIMONTH; if (unit_name == "season") return Unit::SEASON; if (unit_name == "quarter") return Unit::QUARTER; if (unit_name == "halfyear") return Unit::HALFYEAR; if (unit_name == "year") return Unit::YEAR; if (unit_name == "week") return Unit::WEEK; Rf_error("Invalid unit_name (%s)", unit_name.c_str()); } // TOTHINK: roll_dst is hard-coded. Should this be generalized? // used in time_floor exclusively template inline double ct2posix4floor(const T& ct, const cctz::time_zone& tz, const time_point& tp_orig, const cctz::civil_second& cs_orig, const double rem = 0.0) noexcept { cctz::time_zone::civil_lookup cl = tz.lookup(ct); return civil_lookup_to_posix( cl, tz, tp_orig, cs_orig, DST(RollDST::BOUNDARY, RollDST::POST), rem); } // used for time_ceiling exclusively template inline double ct2posix4ceiling(const T& ct, const cctz::time_zone& tz, const time_point& tp_orig, const cctz::civil_second& cs_orig, const int_fast64_t N, const bool check_boundary, const double rem = 0.0) noexcept { if (check_boundary && rem == 0 && cs_orig == ct - N) { time_point tpnew = cctz::convert(cs_orig, tz); return tpnew.time_since_epoch().count(); } else { cctz::time_zone::civil_lookup cl = tz.lookup(ct); return civil_lookup_to_posix( cl, tz, tp_orig, cs_orig, DST(RollDST::BOUNDARY, RollDST::PRE), rem); } } [[cpp11::register]] cpp11::writable::doubles C_time_ceiling(const cpp11::doubles dt, const std::string unit_name, const double nunits, const int week_start, const bool change_on_boundary, const cpp11::doubles origin) { Unit unit = name2unit(unit_name); 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\""); size_t n = dt.size(); cpp11::writable::doubles out(n); init_posixct(out, tz_name.c_str()); bool loop_origin = origin.size() != 1; if (loop_origin && origin.size() != dt.size()) { Rf_error("`origin` length (%lld) must be either 1 or the same as the length of the input date-time (%lld)\n", static_cast(origin.size()), static_cast(dt.size())); } auto UN = adjust_rounding_unit(unit, nunits); double N = UN.second; cctz::weekday wday = static_cast(week_start - 1); for (size_t i = 0; i < n; i++) { double dsecs = dt[i]; if (dsecs == R_PosInf || dsecs == R_NegInf) { out[i] = dsecs; continue; } int_fast64_t secs = floor_to_int64(dsecs); if (secs == NA_INT64) { out[i] = NA_REAL; continue; } double rem = dsecs - secs; bool check_boundary = rem == 0 && !change_on_boundary; sys_seconds ss(secs); time_point tp(ss); cctz::civil_second cs = cctz::convert(tp, tz); switch(UN.first) { case Unit::AHOUR: case Unit::AMINUTE: case Unit::ASECOND : { // Absolute seconds are duration in seconds. Rounding is performed with respect // with the origin. Thus, fractional nunits and units > 60 are supported. double orig = loop_origin ? origin[i] : origin[0]; double posix = orig + ceil_multi_unit(dsecs - orig, N); if (check_boundary && (posix - N) == dsecs) out[i] = dsecs; else out[i] = posix; break; } case Unit::SECOND : { double ds = ceil_multi_unit(cs.second() + rem, N); // double sec if (ds > 60) { int_fast64_t full = INT_FLOOR_MULTI_UNIT(static_cast(ds), 60); ds = ceil_multi_unit(ds - full, N); cs = cs + full; } int_fast64_t is = static_cast(ds); // int sec rem = ds - is; cctz::civil_second ct = cctz::civil_second(cctz::civil_minute(cs)) + is; out[i] = ct2posix4ceiling(ct, tz, tp, cs, N, check_boundary, rem); break; } case Unit::MINUTE : { double dm = ceil_multi_unit(cs.minute(), N); if (dm > 60) { int_fast64_t full = INT_FLOOR_MULTI_UNIT(static_cast(dm), 60); dm = ceil_multi_unit(dm - full, N); cs = cs + full*60; } cctz::civil_minute ct = cctz::civil_minute(cctz::civil_hour(cs)) + dm; out[i] = ct2posix4ceiling(ct, tz, tp, cs, N, check_boundary); break; } case Unit::HOUR : { cctz::civil_hour ct = cctz::civil_hour(cctz::civil_day(cs)) + ceil_multi_unit(cs.hour(), N); if (ct.hour() > 0 && (ct.day() != cs.day() || ct.month() != cs.month() || ct.year() != cs.year())) { ct = ct + (ceil_multi_unit(ct.hour(), N) - ct.hour()); } out[i] = ct2posix4ceiling(ct, tz, tp, cs, N, check_boundary); break; } case Unit::DAY : { cctz::civil_day ct = cctz::civil_day(cctz::civil_month(cs)) + ceil_multi_unit1(cs.day(), N); if (ct.day() > 1 && (ct.month() != cs.month() || ct.year() != cs.year())) { ct = ct + (ceil_multi_unit(ct.day(), N) - ct.day() + 1); } out[i] = ct2posix4ceiling(ct, tz, tp, cs, N, check_boundary); break; } case Unit::WEEK : { cctz::civil_day ct = cctz::next_weekday(cctz::civil_day(cs), wday); out[i] = ct2posix4ceiling(ct, tz, tp, cs, 7, check_boundary); break; } case Unit::SEASON : { cctz::civil_month ct = cctz::civil_month(cctz::civil_year(cs)) + (ceil_multi_unit(cs.month(), N) - 1); out[i] = ct2posix4ceiling(ct, tz, tp, cs, N, check_boundary); break; } case Unit::HALFYEAR : case Unit::QUARTER : case Unit::BIMONTH : case Unit::MONTH : { cctz::civil_month ct = cctz::civil_month(cctz::civil_year(cs)) + ceil_multi_unit1(cs.month(), N); if (ct.month() > 1 && ct.year() != cs.year()) { ct = ct + (ceil_multi_unit1(ct.month() , N) - ct.month() + 1); } out[i] = ct2posix4ceiling(ct, tz, tp, cs, N, check_boundary); break; } case Unit::YEAR : { cctz::civil_year ct = cctz::civil_year(ceil_multi_unit(cs.year(), N)); out[i] = ct2posix4ceiling(ct, tz, tp, cs, N, check_boundary); break; } } } return out; } [[cpp11::register]] cpp11::writable::doubles C_time_floor(const cpp11::doubles dt, const std::string unit_name, const double nunits, const int week_start, const cpp11::doubles origin) { Unit unit = name2unit(unit_name); 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\""); size_t n = dt.size(); cpp11::writable::doubles out(n); init_posixct(out, tz_name.c_str()); bool loop_origin = origin.size() != 1; if (loop_origin && origin.size() != dt.size()) { Rf_error("`origin` length (%lld) must be either 1 or the same as the length of the input date-time (%lld)\n", static_cast(origin.size()), static_cast(dt.size())); } auto UN = adjust_rounding_unit(unit, nunits); double N = UN.second; cctz::weekday wday = static_cast(week_start - 1); for (size_t i = 0; i < n; i++) { double dsecs = dt[i]; if (dsecs == R_PosInf || dsecs == R_NegInf) { out[i] = dsecs; continue; } int_fast64_t isecs = floor_to_int64(dsecs); if (isecs == NA_INT64) { out[i] = NA_REAL; continue; } sys_seconds ss(isecs); time_point tp(ss); cctz::civil_second cs = cctz::convert(tp, tz); switch(UN.first) { case Unit::AHOUR: case Unit::AMINUTE: case Unit::ASECOND: { // Absolute seconds rounding from the origin. No restrictions on units. double orig = loop_origin ? origin[i] : origin[0]; out[i] = orig + floor_multi_unit(dsecs - orig, N); break; } case Unit::SECOND : { double rem = dsecs - isecs; double ds = floor_multi_unit(cs.second() + rem, N); int_fast64_t is = static_cast(ds); rem = ds - is; /* Rprintf("dsec:%f isec%f rem:%f ds:%f is:%lld\n", dsecs, isecs, ds, is); */ cctz::civil_second ct = cctz::civil_second(cctz::civil_minute(cs)) + is; out[i] = ct2posix4floor(ct, tz, tp, cs, rem); break; } case Unit::MINUTE : { cctz::civil_minute ct = cctz::civil_minute(cctz::civil_hour(cs)) + floor_multi_unit(cs.minute(), N); out[i] = ct2posix4floor(ct, tz, tp, cs); break; } case Unit::HOUR : { cctz::civil_hour ct = cctz::civil_hour(cctz::civil_day(cs)) + floor_multi_unit(cs.hour(), N); out[i] = ct2posix4floor(ct, tz, tp, cs); break; } case Unit::DAY : { cctz::civil_day ct = cctz::civil_day(cctz::civil_month(cs)) + floor_multi_unit1(cs.day(), N); out[i] = ct2posix4floor(ct, tz, tp, cs); break; } case Unit::WEEK : { if (N != 1) Rf_warning("Multi-unit week rounding is not supported; ignoring"); // get the previous `wday` if `cs` is not already on `wday` cctz::civil_day ct = cctz::next_weekday(cctz::civil_day(cs), wday) - 7; out[i] = ct2posix4floor(ct, tz, tp, cs); break; } case Unit::SEASON : { cctz::civil_month ct = cctz::civil_month(cctz::civil_year(cs)) + floor_multi_unit(cs.month(), N) - 1; out[i] = ct2posix4floor(ct, tz, tp, cs); break; } case Unit::HALFYEAR : case Unit::QUARTER : case Unit::BIMONTH : case Unit::MONTH : { cctz::civil_month ct = cctz::civil_month(cctz::civil_year(cs)) + floor_multi_unit1(cs.month(), N); out[i] = ct2posix4floor(ct, tz, tp, cs); break; } case Unit::YEAR : { cctz::civil_year ct = cctz::civil_year(floor_multi_unit(cs.year(), N)); out[i] = ct2posix4floor(ct, tz, tp, cs); break; } } } return out; } timechange/src/Makevars0000644000176200001440000000072014551321720014640 0ustar liggesusersPKG_CPPFLAGS= -I. -I./cctz/src/ PKG_LIBS= -L. -lcctz LIBCCTZ=./cctz/src/time_zone_fixed.o \ ./cctz/src/time_zone_if.o \ ./cctz/src/time_zone_impl.o \ ./cctz/src/time_zone_info.o \ ./cctz/src/time_zone_libc.o \ ./cctz/src/time_zone_lookup.o \ ./cctz/src/time_zone_posix.o \ ./cctz/src/zone_info_source.o $(SHLIB): libcctz.a libcctz.a: $(LIBCCTZ) $(AR) rcs libcctz.a $(LIBCCTZ) clean: rm -f $(SHLIB) $(LIBCCTZ) $(OBJECTS) libcctz.a timechange/src/common.h0000644000176200001440000000774414345614444014633 0ustar liggesusers #ifndef TIMECHANGE_COMMON_H #define TIMECHANGE_COMMON_H #include #include #include #include #include #include "cctz/civil_time.h" #include "cctz/time_zone.h" #include "tzone.h" #include #include "Rinternals.h" /* using namespace std; */ namespace chrono = std::chrono; using sys_seconds = chrono::duration; using time_point = chrono::time_point; extern int_fast64_t NA_INT32; extern int_fast64_t NA_INT64; extern double fINT64_MAX; extern double fINT64_MIN; inline void init_posixct(cpp11::writable::doubles& x, const char* tz) { x.attr("class") = {"POSIXct", "POSIXt"}; x.attr("tzone") = tz; } inline cpp11::doubles posixct(const char* tz, R_xlen_t size = 0) { cpp11::writable::doubles out(size); init_posixct(out, tz); return out; } int_fast64_t floor_to_int64(double x); enum class RollMonth { FULL, PREDAY, BOUNDARY, POSTDAY, NA, NAym }; inline RollMonth parse_month_roll(const std::string& roll) { if (roll == "preday") return RollMonth::PREDAY; if (roll == "boundary") return RollMonth::BOUNDARY; if (roll == "postday") return RollMonth::POSTDAY; if (roll == "full") return RollMonth::FULL; if (roll == "NA") return RollMonth::NA; if (roll == "NAym") return RollMonth::NAym; // backward compatibility if (roll == "first") return RollMonth::POSTDAY; if (roll == "last") return RollMonth::PREDAY; if (roll == "skip") return RollMonth::FULL; Rf_error("Invalid roll_month type (%s)", roll.c_str()); } enum class RollDST { PRE, BOUNDARY, POST, NA, XFIRST, XLAST}; inline RollDST parse_dst_roll(const std::string& roll, bool allow_x = false) { if (roll == "boundary") return RollDST::BOUNDARY; if (roll == "post") return RollDST::POST; if (roll == "pre") return RollDST::PRE; if (roll == "NA") return RollDST::NA; if (roll == "xfirst") { if (allow_x) return RollDST::XFIRST; else Rf_error("'xfirst' dst_roll is not meaningful here"); } if (roll == "xlast") { if (allow_x) return RollDST::XLAST; else Rf_error("'xlast' dst_roll is not meaningful here"); } // backward compatibility if (roll == "first") return RollDST::POST; if (roll == "last") return RollDST::PRE; Rf_error("Invalid roll_dst type (%s)", roll.c_str()); } struct DST { RollDST skipped; RollDST repeated; DST(RollDST skipped, RollDST repeated): skipped(skipped), repeated(repeated) {} DST(const cpp11::strings roll_dst, bool allow_x = false) { if (roll_dst.empty() || roll_dst.size() > 2) Rf_error("roll_dst must be a character vector of length 1 or 2"); std::string dst_repeated(roll_dst[0]); skipped = parse_dst_roll(dst_repeated, allow_x); if (roll_dst.size() > 1) { std::string dst_skipped(roll_dst[1]); repeated = parse_dst_roll(dst_skipped, allow_x); } else { repeated = skipped; } } }; // 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, const double remainder) noexcept; double civil_lookup_to_posix(const cctz::time_zone::civil_lookup& cl, const DST& dst, const bool is_negative = false) noexcept; // Simplify these conversions when https://github.com/r-lib/cpp11/pull/265 is fixed inline bool is_convertable_without_loss_to_integer(double value) { double int_part; return std::modf(value, &int_part) == 0.0; } cpp11::integers to_integers(SEXP x); cpp11::doubles to_doubles(SEXP x); #endif // TIMECHANGE_COMMON_H timechange/src/cpp11.cpp0000644000176200001440000001430114552157265014610 0ustar liggesusers// Generated by cpp11: do not edit by hand // clang-format off #include "cpp11/declarations.hpp" #include // get.cpp cpp11::writable::list C_time_get(const cpp11::doubles& dt, const cpp11::strings& components, const int week_start); extern "C" SEXP _timechange_C_time_get(SEXP dt, SEXP components, SEXP week_start) { BEGIN_CPP11 return cpp11::as_sexp(C_time_get(cpp11::as_cpp>(dt), cpp11::as_cpp>(components), cpp11::as_cpp>(week_start))); END_CPP11 } // round.cpp cpp11::writable::doubles C_time_ceiling(const cpp11::doubles dt, const std::string unit_name, const double nunits, const int week_start, const bool change_on_boundary, const cpp11::doubles origin); extern "C" SEXP _timechange_C_time_ceiling(SEXP dt, SEXP unit_name, SEXP nunits, SEXP week_start, SEXP change_on_boundary, SEXP origin) { BEGIN_CPP11 return cpp11::as_sexp(C_time_ceiling(cpp11::as_cpp>(dt), cpp11::as_cpp>(unit_name), cpp11::as_cpp>(nunits), cpp11::as_cpp>(week_start), cpp11::as_cpp>(change_on_boundary), cpp11::as_cpp>(origin))); END_CPP11 } // round.cpp cpp11::writable::doubles C_time_floor(const cpp11::doubles dt, const std::string unit_name, const double nunits, const int week_start, const cpp11::doubles origin); extern "C" SEXP _timechange_C_time_floor(SEXP dt, SEXP unit_name, SEXP nunits, SEXP week_start, SEXP origin) { BEGIN_CPP11 return cpp11::as_sexp(C_time_floor(cpp11::as_cpp>(dt), cpp11::as_cpp>(unit_name), cpp11::as_cpp>(nunits), cpp11::as_cpp>(week_start), cpp11::as_cpp>(origin))); END_CPP11 } // tzone.cpp cpp11::strings C_local_tz(); extern "C" SEXP _timechange_C_local_tz() { BEGIN_CPP11 return cpp11::as_sexp(C_local_tz()); END_CPP11 } // tzone.cpp bool C_valid_tz(const cpp11::strings tz_name); extern "C" SEXP _timechange_C_valid_tz(SEXP tz_name) { BEGIN_CPP11 return cpp11::as_sexp(C_valid_tz(cpp11::as_cpp>(tz_name))); END_CPP11 } // update.cpp 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, const bool exact); extern "C" SEXP _timechange_C_time_update(SEXP dt, SEXP updates, SEXP tz, SEXP roll_month, SEXP roll_dst, SEXP week_start, SEXP exact) { BEGIN_CPP11 return cpp11::as_sexp(C_time_update(cpp11::as_cpp>(dt), cpp11::as_cpp>(updates), cpp11::as_cpp>(tz), cpp11::as_cpp>(roll_month), cpp11::as_cpp>(roll_dst), cpp11::as_cpp>(week_start), cpp11::as_cpp>(exact))); END_CPP11 } // update.cpp cpp11::writable::doubles C_time_add(const cpp11::doubles& dt, const cpp11::list periods, const std::string roll_month, const cpp11::strings roll_dst); extern "C" SEXP _timechange_C_time_add(SEXP dt, SEXP periods, SEXP roll_month, SEXP roll_dst) { BEGIN_CPP11 return cpp11::as_sexp(C_time_add(cpp11::as_cpp>(dt), cpp11::as_cpp>(periods), cpp11::as_cpp>(roll_month), cpp11::as_cpp>(roll_dst))); END_CPP11 } // update.cpp cpp11::writable::doubles C_force_tz(const cpp11::doubles dt, const cpp11::strings tz, const cpp11::strings roll_dst); extern "C" SEXP _timechange_C_force_tz(SEXP dt, SEXP tz, SEXP roll_dst) { BEGIN_CPP11 return cpp11::as_sexp(C_force_tz(cpp11::as_cpp>(dt), cpp11::as_cpp>(tz), cpp11::as_cpp>(roll_dst))); END_CPP11 } // update.cpp cpp11::writable::doubles C_force_tzs(const cpp11::doubles dt, const cpp11::strings tzs, const cpp11::strings tz_out, const cpp11::strings roll_dst); extern "C" SEXP _timechange_C_force_tzs(SEXP dt, SEXP tzs, SEXP tz_out, SEXP roll_dst) { BEGIN_CPP11 return cpp11::as_sexp(C_force_tzs(cpp11::as_cpp>(dt), cpp11::as_cpp>(tzs), cpp11::as_cpp>(tz_out), cpp11::as_cpp>(roll_dst))); END_CPP11 } // update.cpp cpp11::writable::doubles C_local_clock(const cpp11::doubles dt, const cpp11::strings tzs); extern "C" SEXP _timechange_C_local_clock(SEXP dt, SEXP tzs) { BEGIN_CPP11 return cpp11::as_sexp(C_local_clock(cpp11::as_cpp>(dt), cpp11::as_cpp>(tzs))); END_CPP11 } extern "C" { /* .Call calls */ extern SEXP C_parse_unit(SEXP); static const R_CallMethodDef CallEntries[] = { {"C_parse_unit", (DL_FUNC) &C_parse_unit, 1}, {"_timechange_C_force_tz", (DL_FUNC) &_timechange_C_force_tz, 3}, {"_timechange_C_force_tzs", (DL_FUNC) &_timechange_C_force_tzs, 4}, {"_timechange_C_local_clock", (DL_FUNC) &_timechange_C_local_clock, 2}, {"_timechange_C_local_tz", (DL_FUNC) &_timechange_C_local_tz, 0}, {"_timechange_C_time_add", (DL_FUNC) &_timechange_C_time_add, 4}, {"_timechange_C_time_ceiling", (DL_FUNC) &_timechange_C_time_ceiling, 6}, {"_timechange_C_time_floor", (DL_FUNC) &_timechange_C_time_floor, 5}, {"_timechange_C_time_get", (DL_FUNC) &_timechange_C_time_get, 3}, {"_timechange_C_time_update", (DL_FUNC) &_timechange_C_time_update, 7}, {"_timechange_C_valid_tz", (DL_FUNC) &_timechange_C_valid_tz, 1}, {NULL, NULL, 0} }; } extern "C" attribute_visible void R_init_timechange(DllInfo* dll){ R_registerRoutines(dll, NULL, CallEntries, NULL, NULL); R_useDynamicSymbols(dll, FALSE); R_forceSymbols(dll, TRUE); } timechange/R/0000755000176200001440000000000014552157265012573 5ustar liggesuserstimechange/R/update.R0000644000176200001440000001002514356562603014174 0ustar liggesusers#' Update components of a date-time object #' #' @param time a date-time object #' @param updates a named list of components #' @param year,month,yday,wday,mday,hour,minute,second components of the date-time to be #' updated. All components except `second` will be converted to integer. Components #' are replicated according to `vctrs` semantics, i.e. vectors must be either of #' length 1 or same length as `time` vector. #' @param tz time zone component (a singleton character vector) #' @param week_start first day of the week (default is 1, Monday). Set #' `timechange.week_start` option to change this globally. #' @param exact logical (TRUE), whether the update should be exact. If set to `FALSE` no #' rolling or unit-recycling is allowed and `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. `hour = 25`) are #' supplied and result in crossing boundaries of higher units. When `exact = TRUE`, #' `roll_month` and `roll_dst` arguments are ignored. #' @return A date-time with the requested elements updated. Retain its original class #' unless the original class is `Date` and at least one of the `hour`, `minute`, #' `second` or `tz` is supplied, in which case a `POSIXct` object is returned. #' @inheritParams time_add #' @seealso `[time_add()]` #' @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") #' @export time_update <- function(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) { if (length(time) == 0L) return(time) updts <- list(year = year, month = month, yday = yday, mday = mday, wday = wday, hour = hour, minute = minute, second = second) updates <- as.list(updates) for (nm in names(updts)) { if (!is.null(updts[[nm]])) if (is.null(updates[[nm]])) updates[[nm]] <- updts[[nm]] else updates[[nm]] <- updates[[nm]] + updts[[nm]] } updates <- updates[!vapply(updates, is.null, TRUE)] week_start <- as.integer(week_start) exact <- as.logical(exact) if (is.POSIXct(time)) { storage.mode(time) <- "double" C_time_update(time, updates, tz, roll_month, roll_dst, week_start, exact) } else if (is.Date(time)) { out <- date_to_posixct(time, tz(time)) out <- C_time_update(out, updates, tz, roll_month, roll_dst, week_start, exact) if (is.null(updates[["hour"]]) && is.null(updates[["minute"]]) && is.null(updates[["second"]]) && is.null(tz)) { out <- posixct_to_date(out) } out } else if (is.POSIXlt(time)) { as.POSIXlt.POSIXct( C_time_update(as.POSIXct.POSIXlt(time), updates, tz, roll_month, roll_dst, week_start, exact)) } else { unsupported_date_time(time) } } timechange/R/utils.R0000644000176200001440000000445414413246367014063 0ustar liggesusers is.POSIXt <- function(x) "POSIXt" %in% class(x) is.POSIXlt <- function(x) "POSIXlt" %in% class(x) is.POSIXct <- function(x) "POSIXct" %in% class(x) is.Date <- function(x) "Date" %in% class(x) is.instant <- function(x) any(c("POSIXt", "Date") %in% class(x)) unsupported_date_time <- function(x) { stop(sprintf("Unsupported date-time class '%s'", paste(class(x), sep = "/"))) } date_to_posixct <- function(date, tz = "UTC") { utc <- .POSIXct(unclass(date) * 86400, tz = "UTC") if (tz == "UTC") utc else C_force_tz(utc, tz, c("boundary", "post")) } posixct_to_date <- function(x) { tz <- tz(x) if (tz == "UTC") { structure(floor(unclass(x)/86400), class = "Date", tzone = NULL) } else { x <- C_force_tz(x, "UTC", c("boundary", "post")) structure(floor(unclass(x)/86400), class = "Date", tzone = tz) } } tz <- function(x) { tzone <- attr(x, "tzone")[[1]] if (is.null(tzone) && !is.POSIXt(x)) return("UTC") if (is.character(tzone) && nzchar(tzone)) return(tzone) tzone <- attr(as.POSIXlt(x[1]), "tzone")[[1]] if (is.null(tzone)) return("UTC") tzone } to_posixct <- function(time) { if (is.POSIXct(time)) { # Catch rare integer POSIXct, retaining attributes storage.mode(time) <- "double" time } else if (is.Date(time)) date_to_posixct(time, tz(time)) else if (is.POSIXlt(time)) { as.POSIXct.POSIXlt(time, tz = tz(time)) } else { unsupported_date_time(time) } } from_posixct <- function(ct, time, force_date = FALSE) { if (is.POSIXct(time)) ct else if (is.Date(time)) { if (force_date) { posixct_to_date(ct) } else { ct } } else if (is.POSIXlt(time)) { as.POSIXlt.POSIXct(ct) } else { ct } } ## as.POSIXct.POSIXlt does not treat Inf values correctly lt2ct <- function(lt) { out <- as.POSIXct.POSIXlt(lt) isinf <- is.infinite(lt$sec) out[isinf] <- lt$sec[isinf] out } from_posixlt <- function(new, old, force_date = FALSE) { if (is.POSIXlt(old)) new else if (is.Date(old)) { if (force_date) { as.Date.POSIXlt(new, tz = tz(old)) } else { lt2ct(new) } } else { lt2ct(new) } } standardise_unit_name <- function(x) { parse_unit(x)$unit } # @return list(n=nr_units, unit="unit-name") parse_unit <- function(unit) { .Call(C_parse_unit, as.character(unit)) } timechange/R/round.R0000644000176200001440000003137714551321620014043 0ustar liggesusers#' Round, floor and ceiling for date-time objects #' #' @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 `aseconds`, `aminutes` and `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 `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 `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 #' [base::round.POSIXt()] function but does not follow the convention of the base #' [base::round()] function which "rounds to the even digit" per IEC 60559. #' #' #' @section Ceiling of `Date` objects: #' #' By default rounding up `Date` objects follows 3 steps: #' #' 1. Convert to an instant representing lower bound of the Date: #' `2000-01-01` --> `2000-01-01 00:00:00` #' #' 2. Round up to the \strong{next} closest rounding unit boundary. For example, #' if the rounding unit is `month` then next closest boundary of `2000-01-01` #' is `2000-02-01 00:00:00`. #' #' The motivation for this is that the "partial" `2000-01-01` is conceptually #' an interval (`2000-01-01 00:00:00` -- `2000-01-02 00:00:00`) and the day #' hasn't started clocking yet at the exact boundary `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 #' `change_on_boundary` to a non-`NULL` value. #' #' 3. If rounding unit is smaller than a day, return the instant from step 2 #' (`POSIXct`), otherwise convert to and return a `Date` object. #' #' @name time_round #' @param time a date-time vector (`Date`, `POSIXct` or `POSIXlt`) #' @param unit a character string specifying a time unit or a multiple of a unit. Valid #' base periods for civil time rounding are `second`, `minute`, `hour`, `day`, `week`, #' `month`, `bimonth`, `quarter`, `season`, `halfyear` and `year`. The only units for #' absolute time rounding are `asecond`, `aminute` and `ahour`. Other absolute units #' can be achieved with multiples of `asecond` (e.g. "24ah"). See "Details" and #' examples. Arbitrary unique English abbreviations are allowed. One letter #' abbreviations follow `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. #' #' @param change_on_boundary If NULL (the default) don't change instants on the boundary #' (`time_ceiling(ymd_hms('2000-01-01 00:00:00'))` is `2000-01-01 00:00:00`), but #' round up `Date` objects to the next boundary (`time_ceiling(ymd("2000-01-01"), #' "month")` is `"2000-02-01"`). When `TRUE`, instants on the boundary are rounded up #' to the next boundary. When `FALSE`, date-time on the boundary are never rounded up #' (this was the default for \pkg{lubridate} prior to `v1.6.0`. See section `Rounding #' Up Date Objects` below for more details. #' @param week_start When unit is `weeks`, this is the first day of the week. Defaults #' to 1 (Monday). #' @param 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 `time` #' vector. Defaults to the Unix origin "1970-01-01 UTC". #' @return An object of the same class as the input object. When input is a `Date` #' object and unit is smaller than `day` a `POSIXct` object is returned. #' @seealso [base::round()] #' @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")) #' #' @export time_round <- function(time, unit = "second", week_start = getOption("timechange.week_start", 1), origin = unix_origin) { if (length(time) == 0L) return(time) nu <- parse_rounding_unit(unit) n <- nu$n unit <- nu$unit ct <- to_posixct(time) ## special case for fast absolute time rounding ## if (n == 1 && ( ## unit %in% c("day", "hour", "minute", "second") || ## (unit == "asecond" && identical(origin, unix_origin)) ## )) { ## out <- round.POSIXt(ct, units = base_units[[unit]]) ## return(from_posixlt(out, time, force_date = unit != "hour")) ## } ## Behavior here is different from the base rounding for repeated times because ## timechange operates on clock time but round.POSIXt on absolute time. Thus ## time_ceiling("2014-11-02 01:35:00 EDT", "hour") is "2014-11-02 02:00:00 EST", 1h25m ## difference. But round.POSIXt("2014-11-02 01:35:00 EDT", "hour") is "2014-11-02 ## 01:00:00 EST" which always results in clock hour <= than the original clock ## hour. Rounding on absolute time can be achieved with absolute units - asec, amin, ## ahour. above <- unclass(C_time_ceiling(ct, unit, n, week_start, TRUE, origin)) mid <- unclass(ct) below <- unclass(C_time_floor(ct, unit, n, week_start, origin)) wabove <- (above - mid) <= (mid - below) wabove <- !is.na(wabove) & wabove below[wabove] <- above[wabove] from_posixct(.POSIXct(below, tz = tz(time)), time, force_date = !unit %in% c("hour", "minute", "second", "asecond")) } #' @name time_round #' @export time_floor <- function(time, unit = "seconds", week_start = getOption("timechange.week_start", 1), origin = unix_origin) { if (length(time) == 0) return(time) nu <- parse_rounding_unit(unit) from_posixct(C_time_floor(to_posixct(time), nu$unit, nu$n, as.integer(week_start), origin), time, force_date = !nu$unit %in% c("asecond", "second", "minute", "hour")) } #' @name time_round #' @export time_ceiling <- function(time, unit = "seconds", change_on_boundary = inherits(time, "Date"), week_start = getOption("timechange.week_start", 1), origin = unix_origin) { if (length(time) == 0) return(time) nu <- parse_rounding_unit(unit) from_posixct(C_time_ceiling(to_posixct(time), nu$unit, nu$n, as.integer(week_start), as.logical(change_on_boundary), origin), time, force_date = !nu$unit %in% c("second", "minute", "hour")) } ## UTILS base_units <- list(second = "secs", minute = "mins", hour = "hours", day = "days") trunc_multi_limits <- c(asecond = Inf, aminute = Inf, ahour = Inf, second = 60, minute = 60, hour = 24, day = 31, year = Inf, week = 1, month = 12, bimonth = 6, quarter = 4, season = 4, halfyear = 2) parse_rounding_unit <- function(unit) { if (length(unit) > 1) { warning("'unit' argument has length larger than 1. Using first element.") unit <- unit[[1]] } validate_rounding_nunit(.Call(C_parse_unit, as.character(unit))) } # cOmpat: TODO: remove once lubridate no longer uses .normalize_multi_week_unit # https://github.com/tidyverse/lubridate/blob/8c67d9ceca5315ef636d4727348d8914aa5552ea/R/round.r#L206 parse_units <- parse_rounding_unit validate_rounding_nunit <- function(nunit) { if (nunit$n > trunc_multi_limits[[nunit$unit]]) stop(sprintf("Rounding with %s > %d is not supported. Use aseconds for arbitrary units.", nunit$unit, trunc_multi_limits[[nunit$unit]])) nunit } timechange/R/cpp11.R0000644000176200001440000000236614552157265013651 0ustar liggesusers# Generated by cpp11: do not edit by hand C_time_get <- function(dt, components, week_start) { .Call(`_timechange_C_time_get`, dt, components, week_start) } C_time_ceiling <- function(dt, unit_name, nunits, week_start, change_on_boundary, origin) { .Call(`_timechange_C_time_ceiling`, dt, unit_name, nunits, week_start, change_on_boundary, origin) } C_time_floor <- function(dt, unit_name, nunits, week_start, origin) { .Call(`_timechange_C_time_floor`, dt, unit_name, nunits, week_start, origin) } C_local_tz <- function() { .Call(`_timechange_C_local_tz`) } C_valid_tz <- function(tz_name) { .Call(`_timechange_C_valid_tz`, tz_name) } C_time_update <- function(dt, updates, tz, roll_month, roll_dst, week_start, exact) { .Call(`_timechange_C_time_update`, dt, updates, tz, roll_month, roll_dst, week_start, exact) } C_time_add <- function(dt, periods, roll_month, roll_dst) { .Call(`_timechange_C_time_add`, dt, periods, roll_month, roll_dst) } C_force_tz <- function(dt, tz, roll_dst) { .Call(`_timechange_C_force_tz`, dt, tz, roll_dst) } C_force_tzs <- function(dt, tzs, tz_out, roll_dst) { .Call(`_timechange_C_force_tzs`, dt, tzs, tz_out, roll_dst) } C_local_clock <- function(dt, tzs) { .Call(`_timechange_C_local_clock`, dt, tzs) } timechange/R/get.R0000644000176200001440000000554514356564510013503 0ustar liggesusers#' Get components of a date-time object #' #' @name time_get #' @param time a date-time object #' @param 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". #' @param week_start week starting day (Default is 1, Monday). Set #' `timechange.week_start` option to change this globally. #' @return A data.frame of the requested components #' @examples #' x <- as.POSIXct("2019-02-03") #' time_get(x) #' @export time_get <- function(time, components = c("year", "month", "yday", "mday", "wday", "hour", "minute", "second"), week_start = getOption("timechange.week_start", 1)) { if (length(diffs <- setdiff(components, names(components_template))) > 0) { stop(sprintf("Invalid components: %s", paste(diffs, collapse = ", "))) } if (length(time) == 0L) { return(components_template[components]) } if (length(components) == 0L) { return(components_empty_template(length(time))) } out <- if (is.POSIXct(time)) { C_time_get(time, components, week_start) } else if (is.Date(time)) { time <- date_to_posixct(time, "UTC") C_time_get(time, components, week_start) } else if (is.POSIXlt(time)) { unique_components <- unique(components) compslt <- timechange2posixlt[unique_components] out <- unclass(time)[compslt] if (!is.null(out$year)) out$year <- out$year + 1900L if (!is.null(out$yday)) out$yday <- out$yday + 1L if (!is.null(out$mon)) out$mon <- out$mon + 1L if (!is.null(out$wday)) out$wday <- 1L + (out$wday + (6L - week_start)) %% 7L out_names <- names(compslt) names(out) <- out_names out[components] } else { unsupported_date_time(time) } as.data.frame(out) } timechange2posixlt <- c("year" = "year", "month" = "mon", "yday" = "yday", "mday" = "mday", "day" = "mday", "wday" = "wday", "hour" = "hour", "minute" = "min", "second" = "sec") components_template <- data.frame(year = integer(), month = integer(), day = integer(), yday = integer(), mday = integer(), wday = integer(), hour = integer(), minute = integer(), second = double()) components_empty_template <- function(n) { structure( list(), names = character(), class = "data.frame", row.names = .set_row_names(n) ) } timechange/R/package.R0000644000176200001440000000267714345417165014323 0ustar liggesusers##' Package `timechange` ##' ##' 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 ##' `roll_month` and `roll_dst` arguments. ##' ##' @author Vitalie Spinu (\email{spinuvit@gmail.com}) ##' @useDynLib timechange, .registration=TRUE "_PACKAGE" unix_origin <- origin <- .POSIXct(0, tz = "UTC") .onLoad <- function(libname, pkgname) { ## CCTZ needs zoneinfo. On windows we set it to R's own zoneinfo. On unix-like ## it's in "/usr/share/zoneinfo" where CCTZ looks by default. On some systems ## (solaris, osx) it might be in a different location. So, help ourselves by ## setting the TZDIR env var, but only if it's not already set. if (Sys.getenv("TZDIR") == "") { ## adapted from OlsonNames function tzdir <- if (.Platform$OS.type == "windows") { file.path(R.home("share"), "zoneinfo") } else if (!file.exists("/usr/share/zoneinfo")) { tzdirs <- c(file.path(R.home("share"), "zoneinfo"), "/usr/share/lib/zoneinfo", "/usr/lib/zoneinfo", "/usr/local/etc/zoneinfo", "/etc/zoneinfo", "/usr/etc/zoneinfo") tzdirs <- tzdirs[file.exists(tzdirs)] if (length(tzdirs)) tzdirs[[1]] else NULL } if (!is.null(tzdir)) { Sys.setenv(TZDIR = tzdir) } } } timechange/R/zones.R0000644000176200001440000001345514356560563014065 0ustar liggesusers#' Time-zone manipulation #' #' @description `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. `time_at_tz` defaults to the #' Universal Coordinated time zone (UTC) when an unrecognized time zone is #' supplied. #' #' @param 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. #' @param 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 `time_force_tz` and `time_clock_at_tzs`, `tz` can be a vector #' of heterogeneous time-zones, in which case `time` and `tz` arguments are #' paired. If `time` and `tz` lengths differ, the smaller one is recycled #' according with usual R conventions. #' @return a POSIXct object with the updated time zone #' @name time-zones #' @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") #' @export time_at_tz <- function(time, tz = "UTC") { if (!C_valid_tz(tz)) warning(sprintf("Unrecognized time zone '%s'", tz)) if (is.list(time) && !is.POSIXlt(time)) { for (nm in names(time)) { if (is.instant(time[[nm]])) { time[[nm]] <- .at_tz(time[[nm]], tz) } } time } else { .at_tz(time, tz) } } .at_tz <- function(time, tz) { posixlt <- is.POSIXlt(time) new <- if (posixlt) as.POSIXct(time) else time attr(new, "tzone") <- tz if (posixlt) as.POSIXlt.POSIXct(new) else new } #' @description `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 `time` and `tz` arguments. #' #' @param roll_dst same as in `time_add` which see. #' @param tzout timezone of the output date-time vector. Meaningful only when #' `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. #' @name time-zones #' @export time_force_tz <- function(time, tz = "UTC", tzout = tz[[1]], roll_dst = c("boundary", "post")) { if (is.list(time) && !is.POSIXlt(time)) { for (nm in names(time)) { if (is.instant(time[[nm]])) { time[[nm]] <- .force_tz(time[[nm]], tz, tzout, roll_dst) } } time } else { .force_tz(time, tz, tzout, roll_dst) } } .force_tz <- function(time, tz, tzout, roll_dst) { if (length(tz) == 1L && tz == tzout) { from_posixct(C_force_tz(to_posixct(time), tz, roll_dst), time) } else { if (length(tz) < length(time)) tz <- rep_len(tz, length(time)) else if (length(tz) > length(time)) { attr <- attributes(time) time <- rep_len(time, length(tz)) attributes(time) <- attr } from_posixct(C_force_tzs(to_posixct(time), tz, tzout, roll_dst), time) } } #' @description `time_clock_at_tz` retrieves day clock time in specified time #' zones. Computation is vectorized over both `dt` and `tz` arguments, `tz` #' defaults to the timezone of `time`. #' #' @param units passed directly to [as.difftime()]. #' @name time-zones #' @export time_clock_at_tz <- function(time, tz = NULL, units = "secs") { if (is.list(time) && !is.POSIXlt(time)) { for (nm in names(time)) { if (is.instant(time[[nm]])) { time[[nm]] <- .clock_at_tz(time[[nm]], tz, units) } } time } else { time <- if (is.Date(time)) date_to_posixct(time, tz(time)) else as.POSIXct(time) .clock_at_tz(time, tz, units) } } .clock_at_tz <- function(time, tz, units) { if (is.null(tz)) tz <- tz(time) ## FIXME: don't replicate tz when len == 1; follow vctrs and lubridate replication ## convention if (length(tz) < length(time)) tz <- rep_len(tz, length(time)) else if (length(tz) > length(time)) { attr <- attributes(time) time <- rep_len(time, length(tz)) attributes(time) <- attr } # Catch rare integer POSIXct, retaining attributes storage.mode(time) <- "double" secs <- C_local_clock(time, tz) out <- structure(secs, units = "secs", class = "difftime") units(out) <- units out } timechange/R/addition.R0000644000176200001440000002563714356562716014531 0ustar liggesusers##' Arithmetics with periods ##' ##' @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. ##' ##' @param time date-time object ##' @param periods a named list of the form `list(year = 1, month = 2, ...)`. ##' @param year,month,week,day,hour,minute,second Units to be added to `time`. Units ##' except for seconds are converted to integer values. Components are replicated ##' according to `vctrs` semantics, i.e vectors must be either of length 1 or same ##' length as `time` vector. ##' @param 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 ##' `[(timechange::time_add())` for further details. ##' @param 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: ##' ##' * `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. ##' ##' 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 `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. ##' @param ... deprecated ##' ##' @details Arithmetic operations with multiple period units (years, months etc) are ##' applied in decreasing size order, from year to second. Thus `time_add(x, month = ##' 1, days = 3)` first adds 1 month to `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`1 + 1 = February` which results in ##' non-existent time of `2000-02-31 01:02:03`. Here the `roll_month` adjustment ##' kicks in. After the adjustment, the remaining 3 days are added. ##' ##' `roll_month` can be one of the following: ##' ##' * `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, `2000-01-31 01:02:03 + 1 month = 2000-03-01 00:00:00`. ##' ##' * `preday` - roll back to the last valid day of the previous month (pre-boundary ##' day) preserving the H, M, S units. For example, `2000-01-31 01:02:03 + 1 month = ##' 2000-02-28 01:02:03`. This is the default. ##' ##' * `postday` - roll to the first day post-boundary preserving the H, M, S units. For ##' example, `2000-01-31 01:02:03 + 1 month = 2000-03-01 01:02:03`. ##' ##' * `full` - full rolling. No adjustment is done to the simple arithmetic operations ##' (the gap is skipped as if it's not there). For example, `2000-01-31 01:02:03 + 1 ##' month + 3 days` is equivalent to `2000-01-01 01:02:03 + 1 month + 31 days + 3 days` ##' resulting in `2000-03-05 01:02:03`. ##' ##' * `NA` - if end result was rolled over the month boundary due to addition of units ##' smaller than month (day, hour, minute, second) produce NA. ##' ##' * `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") ##' ##' ##' @export time_add <- function(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"), ...) { ## temporary workaround to allow for plurals dots <- list(...) if (length(dots) > 0) { names(dots) <- sub("s$", "", names(dots)) periods <- c(periods, dots) } if (length(time) == 0L) return(time) if (is.null(periods)) { periods <- list() } else { if (!is.list(periods)) stop("character periods are not implemented yet") } prds <- list(year = year, month = month, week = week, day = day, hour = hour, minute = minute, second = second) periods <- as.list(periods) for (nm in names(prds)) { if (!is.null(prds[[nm]])) if (is.null(periods[[nm]])) periods[[nm]] <- prds[[nm]] else periods[[nm]] <- periods[[nm]] + prds[[nm]] } periods <- periods[!vapply(periods, is.null, TRUE)] if (is.POSIXct(time)) { storage.mode(time) <- "double" C_time_add(time, periods, roll_month, roll_dst) } else if (is.Date(time)) { out <- date_to_posixct(time, tz(time)) out <- C_time_add(out, periods, roll_month, roll_dst) if (is.null(periods[["hour"]]) && is.null(periods[["minute"]]) && is.null(periods[["second"]])) { out <- posixct_to_date(out) } out } else if (is.POSIXlt(time)) { as.POSIXlt.POSIXct(C_time_add(as.POSIXct.POSIXlt(time), periods, roll_month, roll_dst)) } else { unsupported_date_time(time) } } ##' @rdname time_add ##' @export time_subtract <- function(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"), ...) { if (length(time) == 0L) return(time) ## temporary workaround to allow for plurals dots <- list(...) if (length(dots) > 0) { names(dots) <- sub("s$", "", names(dots)) periods <- c(periods, dots) } if (is.null(periods)) { periods <- list() } else { if (!is.list(periods)) stop("character periods are not implemented yet") } for (nm in names(periods)) periods[[nm]] <- -periods[[nm]] prds <- list(year = year, month = month, week = week, day = day, hour = hour, minute = minute, second = second) for (nm in names(prds)) { if (!is.null(prds[[nm]])) if (is.null(periods[[nm]])) periods[[nm]] <- -prds[[nm]] else periods[[nm]] <- periods[[nm]] - prds[[nm]] } periods <- periods[!vapply(periods, is.null, TRUE)] time_add(time, periods, roll_month = roll_month, roll_dst = roll_dst) } timechange/NEWS.md0000644000176200001440000000434114552163750013466 0ustar liggesusersVersion 0.3.0 ============= ### Bug fixes - [lubridate#1122](https://github.com/tidyverse/lubridate/issues/1122) Implement force_tz fallback for GMT on system where GMT is missing in zoneinfo. - [lubridate#1114](https://github.com/tidyverse/lubridate/issues/1114) Fix rounding of infinite times - Fix %ld compile printf compiler warning ### Breaking Changes - For consistency reasons `time_round()` no longer fallbacks on `round.POSIXct()`. This affects only rounding on repeated DST transitions. By default `timechange` rounding operates on clock time, but `round.POSIXct()` operates on absolute time. For old behavior use absolute units (`asecs`, `amins`, `ahours`, etc). Version 0.2.0 ============= ## New Features - [#22](https://github.com/vspinu/timechange/issues/22) Implement absolute units `aminute` and `ahour` - [#23](https://github.com/vspinu/timechange/issues/23) Implement new `roll_dst` parameters `xfirst` and `xlast` to allow for directional crossing of the DST gap - Rounding gain new `origin` parameter with respect to which to round with "absolute" units. ## Bug Fixes - [#16](https://github.com/vspinu/timechange/issues/16) Rounding unit parser is now conformant to R numeric parser - [#23](https://github.com/vspinu/timechange/pull/24) Respect `tzone` attributes of Date objects. ## Internals - [#17](https://github.com/vspinu/timechange/issues/17) Simplified and refactored unit parser. Version 0.1.1 ============= ## Changes - Follow vctrs replication rules - Change arguments of `time_add()` and `time_subtract()` to singulars - Build on top of cpp11 instead of Rcpp Version 0.1.0 ============= ## New Features: - Refactor `roll_month` and `roll_dst` parameterisation + more intuitive names + full control over the behavior of repeated and skipped DST intervals - `time_update()` gains new argument `exact = FALSE` in order to enforce very strict updating rules Version 0.0.2 ============= ## New Features: - New function `time_get()` for extraction of date-time components ## Bug Fixes: - [#8](https://github.com/vspinu/timechange/issues/8) Correctly handle infinite date-times ## Changes: - Rename global option "week_start" -> "timechange.week_start" Version 0.0.1 ============= Initial Release timechange/MD50000644000176200001440000000601414552166502012675 0ustar liggesusers47dee2a7f33c18616284094f23146c61 *DESCRIPTION 016a72d7e6012ffa747f8f1571551f08 *NAMESPACE 74513e45f20108aef6f4e560e87aa234 *NEWS.md 12f02413fb92f6018321836deb3e727d *R/addition.R ea13e7da0782b8849b427f0e6d27fbc6 *R/cpp11.R c89bd0a8d66ab1c6215b2bdbf40d484c *R/get.R d991d209be62e6e7337d28afb1972e97 *R/package.R de6ab4eb42ef17ddd2e73264e654a3e6 *R/round.R d89b48ec3720d332fef4b0a6205fbb08 *R/update.R 9c13761a1912e54422b868fc0fa7d5fc *R/utils.R fcc9d6c6609c1157eec166fbea41c321 *R/zones.R bc9cebb7b864d905db1a32b45c1f25cb *README.md e33b45c88ddb15ea90353843aeeef2fa *man/time-zones.Rd efcb917e6a2b007422657ef47fac24e4 *man/time_add.Rd 79ef610c77a7acf9c5bc82f668cd1c02 *man/time_get.Rd 50c63a4c68bd936be29e198a25bf8019 *man/time_round.Rd 5f23cb9f06a604b9007161b969f44e0c *man/time_update.Rd be76b332aa778ca7fba909f16fe76d34 *man/timechange-package.Rd 807c4c52c229b26d9531f5151d200937 *src/Makevars 70cef418e3061411c75ffc0e29b44862 *src/cctz/civil_time.h 47439f0b52cafc3be42e3c0ced78b415 *src/cctz/civil_time_detail.h 19a5f0db02339633b061cd24b3cc2281 *src/cctz/src/time_zone_fixed.cc a71187d54c274cc9bfbb48d44cc89195 *src/cctz/src/time_zone_fixed.h 1b9d6bc859ad8d8ccb9e258a6ba0b98f *src/cctz/src/time_zone_if.cc b4b0e820dd19fe36313705820b70c75a *src/cctz/src/time_zone_if.h 7c8889f3d1777a0ed4b2e427f690e0d6 *src/cctz/src/time_zone_impl.cc b27b5e15323d9e86fb1e78ccc6cb2a23 *src/cctz/src/time_zone_impl.h 3dec73343ea5255d2049f593485238d6 *src/cctz/src/time_zone_info.cc 7c8b8f35a25aab53fb76c356eb1fd17c *src/cctz/src/time_zone_info.h 8a1ef8211946acad2fe5eb69659a431c *src/cctz/src/time_zone_libc.cc 290ef8776251333c8516b88f213db785 *src/cctz/src/time_zone_libc.h c4f3f9f4578df63c3bfbb870b87fc7c6 *src/cctz/src/time_zone_lookup.cc d768a51d281c7b9985ae73ee824d038c *src/cctz/src/time_zone_lookup_test.cc 46d9e073cfce772f8d0582134dfd4372 *src/cctz/src/time_zone_posix.cc 38903f4cf6c42636e9f42207b5e435d0 *src/cctz/src/time_zone_posix.h 20236303a3b8b322b5818d3289c6ed25 *src/cctz/src/tzfile.h b902b20040c55efdc292e307cf712491 *src/cctz/src/zone_info_source.cc 3dd0d7916d951a093f524dfdba965b14 *src/cctz/time_zone.h 407daaafb5b7e177d79f4e5a876de84a *src/cctz/zone_info_source.h 5cd68144dc06755d5c2c60b9791e094e *src/common.cpp 581ae0a6e2507027e6bcbc771028d438 *src/common.h caed851c6004d18db72ac0d135a34688 *src/cpp11.cpp 46206c71ae7b18fe4c2ca944b692bbcf *src/get.cpp 6ecba6e3f86e3b3d0824dbaf423b74b1 *src/parse.c 9b80e52931c553dc829eedf0fbdb4fcf *src/round.cpp 16b33db7921bf7ace1d1500e94a49d2f *src/tzone.cpp 2090cdf5479369746c502fd2a8837628 *src/tzone.h 5bac7e29e24d61a74863d6e7c4d499ca *src/update.cpp 55b63fd7907be950261b1219929dd794 *tests/testthat.R cde847696dfa18e9365778bcb16bcd6b *tests/testthat/helpers.R 879ee0be0d9f94609156e4da9ab339e4 *tests/testthat/test-addition.R 73a43410c383a845cbd58e398ff4dd21 *tests/testthat/test-get.R f9e8cc0b959a43aa9017dc0d5a3f5554 *tests/testthat/test-parse.R 340326fbffa19211efdebb69d309df45 *tests/testthat/test-round.R bc33b45341a85e21953d74686fe042a8 *tests/testthat/test-time-zones.R 76276d03b235e6fc468b21a54ce0dfc2 *tests/testthat/test-update.R