Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Non-strict localized datetimes #146

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,17 @@ TimeZones.jl
[![Stable Documentation](https://img.shields.io/badge/docs-stable-blue.svg)](http://timezonesjl.readthedocs.io/en/stable/)
[![Latest Documentation](https://img.shields.io/badge/docs-latest-blue.svg)](http://timezonesjl.readthedocs.io/en/latest/)

[IANA time zone database](http://www.iana.org/time-zones) access for the [Julia](http://julialang.org/) programming language. TimeZones.jl extends the Date/DateTime support for Julia to include a new time zone aware TimeType: ZonedDateTime.
[IANA time zone database](http://www.iana.org/time-zones) access for the [Julia](http://julialang.org/) programming language. TimeZones.jl extends the Date/DateTime support for Julia to include a new time zone aware TimeType: Localized.

## Features

* A new time zone aware TimeType: ZonedDateTime
* A new time zone aware TimeType: Localized
* Support for all time zones in the IANA time zone database (also known as the tz/zoneinfo/Olson database)
* ZonedDateTime-Period arithmetic [similar to that of DateTime](https://docs.julialang.org/en/stable/manual/dates/#TimeType-Period-Arithmetic-1)
* Localized-Period arithmetic [similar to that of DateTime](https://docs.julialang.org/en/stable/manual/dates/#TimeType-Period-Arithmetic-1)
* Local system time zone information as a TimeZone
* Current system time in any TimeZone
* Support for reading the [tzfile](http://man7.org/linux/man-pages/man5/tzfile.5.html) format
* String parsing of ZonedDateTime using [DateFormat](https://docs.julialang.org/en/stable/stdlib/dates/#Base.Dates.DateFormat)
* String parsing of Localized using [DateFormat](https://docs.julialang.org/en/stable/stdlib/dates/#Base.Dates.DateFormat)

## Installation

Expand Down
6 changes: 3 additions & 3 deletions benchmark/benchmarks.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ import TimeZones.TZData: parse_components
# https://github.com/JuliaTime/TimeZones.jl/issues/25
function parse_dates(n)
df = DateFormat("yyyymmddHH:MM:SS ZZZ")
arr = Array{ZonedDateTime}(n)
arr = Array{Localized}(n)
for (i, s) in enumerate(Iterators.repeated("2016060701:02:03 America/Toronto", n))
arr[i] = ZonedDateTime(s,df)
arr[i] = Localized(s,df)
end
return arr
end

@bench "ZonedDateTime" parse_dates(1000)
@bench "Localized" parse_dates(1000)
end
28 changes: 14 additions & 14 deletions docs/arithmetic.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
## ZonedDateTime-Period Arithmetic
## Localized-Period Arithmetic

`ZonedDateTime` uses calendrical arithmetic in a [similar manner to `DateTime`](https://docs.julialang.org/en/stable/manual/dates/#TimeType-Period-Arithmetic-1) but with some key differences. Lets look at these differences by adding a day to March 30th 2014 in Europe/Warsaw.
`Localized` uses calendrical arithmetic in a [similar manner to `DateTime`](https://docs.julialang.org/en/stable/manual/dates/#TimeType-Period-Arithmetic-1) but with some key differences. Lets look at these differences by adding a day to March 30th 2014 in Europe/Warsaw.

```julia
julia> using Base.Dates

julia> warsaw = tz"Europe/Warsaw"
Europe/Warsaw (UTC+1/UTC+2)

julia> spring = ZonedDateTime(2014, 3, 30, warsaw)
julia> spring = Localized(2014, 3, 30, warsaw)
2014-03-30T00:00:00+01:00

julia> spring + Day(1)
2014-03-31T00:00:00+02:00
```

Adding a day to the `ZonedDateTime` changed the date from the 30th to the 31st as expected. Looking closely however you'll notice that the time zone offset changed from +01:00 to +02:00. The reason for this change is because the time zone "Europe/Warsaw" switched from standard time (+01:00) to daylight saving time (+02:00) on the 30th. The change in the offset caused the local DateTime 2014-03-31T02:00:00 to be skipped effectively making the 30th a day which only contained 23 hours. Alternatively if we added hours we can see the difference:
Adding a day to the `Localized` changed the date from the 30th to the 31st as expected. Looking closely however you'll notice that the time zone offset changed from +01:00 to +02:00. The reason for this change is because the time zone "Europe/Warsaw" switched from standard time (+01:00) to daylight saving time (+02:00) on the 30th. The change in the offset caused the local DateTime 2014-03-31T02:00:00 to be skipped effectively making the 30th a day which only contained 23 hours. Alternatively if we added hours we can see the difference:

```julia
julia> spring + Hour(24)
Expand Down Expand Up @@ -48,17 +48,17 @@ julia> spring + Day(1) + Hour(24)
If using a version of Julia 0.5 or below you may want to force precedence when mixing `DatePeriod`s and `TimePeriod`s since the expression `Day(1) + Hour(24)` would be automatically canonicalized to `Day(2)`:

```julia
julia> ZonedDateTime(2014, 10, 25, warsaw) + Day(1) + Hour(24) # On Julia 0.5 or below
julia> Localized(2014, 10, 25, warsaw) + Day(1) + Hour(24) # On Julia 0.5 or below
2014-10-27T00:00:00+01:00

julia> ZonedDateTime(2014, 10, 25, warsaw) + Day(2)
julia> Localized(2014, 10, 25, warsaw) + Day(2)
2014-10-27T00:00:00+01:00
```

In Julia 0.6 period canonicalization no longer happens automatically:

```
julia> ZonedDateTime(2014, 10, 25, warsaw) + Day(1) + Hour(24) # Julia 0.6 and above
julia> Localized(2014, 10, 25, warsaw) + Day(1) + Hour(24) # Julia 0.6 and above
2014-10-26T23:00:00+01:00
```

Expand All @@ -70,18 +70,18 @@ Julia allows for the use of powerful [adjuster functions](https://docs.julialang
julia> warsaw = tz"Europe/Warsaw"
Europe/Warsaw (UTC+1/UTC+2)

julia> start = ZonedDateTime(2014, warsaw)
julia> start = Localized(2014, warsaw)
2014-01-01T00:00:00+01:00

julia> stop = ZonedDateTime(2015, warsaw)
julia> stop = Localized(2015, warsaw)
2015-01-01T00:00:00+01:00

julia> Dates.recur(start:Dates.Hour(1):stop) do d
Dates.dayofweek(d) == Dates.Wednesday &&
Dates.hour(d) == 9 &&
Dates.dayofweekofmonth(d) == 5
end
5-element Array{TimeZones.ZonedDateTime,1}:
5-element Array{TimeZones.Localized,1}:
2014-01-29T09:00:00+01:00
2014-04-30T09:00:00+02:00
2014-07-30T09:00:00+02:00
Expand All @@ -91,17 +91,17 @@ julia> Dates.recur(start:Dates.Hour(1):stop) do d

Note the transition from standard time to daylight saving time (and back again).

It is possible to define a range `start:step:stop` such that `start` and `stop` have different time zones. In this case the resulting `ZonedDateTime`s will all share a time zone with `start` but the range will stop at the instant that corresponds to `stop` in `start`'s time zone. For example:
It is possible to define a range `start:step:stop` such that `start` and `stop` have different time zones. In this case the resulting `Localized`s will all share a time zone with `start` but the range will stop at the instant that corresponds to `stop` in `start`'s time zone. For example:

```julia
julia> start = ZonedDateTime(2016, 1, 1, 12, tz"UTC")
julia> start = Localized(2016, 1, 1, 12, tz"UTC")
2016-01-01T12:00:00+00:00

julia> stop = ZonedDateTime(2016, 1, 1, 18, tz"Europe/Warsaw")
julia> stop = Localized(2016, 1, 1, 18, tz"Europe/Warsaw")
2016-01-01T18:00:00+01:00

julia> collect(start:Dates.Hour(1):stop)
6-element Array{TimeZones.ZonedDateTime,1}:
6-element Array{TimeZones.Localized,1}:
2016-01-01T12:00:00+00:00
2016-01-01T13:00:00+00:00
2016-01-01T14:00:00+00:00
Expand Down
30 changes: 15 additions & 15 deletions docs/conversions.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
## Switching Time Zones

Switching an existing `ZonedDateTime` from one `TimeZone` to another can be done with the function `astimezone`:
Switching an existing `Localized` from one `TimeZone` to another can be done with the function `astimezone`:

```julia
julia> zdt = ZonedDateTime(2014, 1, 1, tz"UTC")
julia> ldt = Localized(2014, 1, 1, tz"UTC")
2014-01-01T00:00:00+00:00

julia> astimezone(zdt, tz"Asia/Tokyo")
julia> astimezone(ldt, tz"Asia/Tokyo")
2014-01-01T09:00:00+09:00
```

## Parsing strings

`ZonedDateTime` parsing extends the functionality provided by `Dates`. If you haven't already it is recommended that you first read the official Julia manual on [Date and DateTime](https://docs.julialang.org/en/stable/manual/dates/#Constructors-1). The `TimeZones` package adds `z` and `Z` to the list of available [parsing character codes](https://docs.julialang.org/en/stable/stdlib/dates/#Base.Dates.DateFormat):
`Localized` parsing extends the functionality provided by `Dates`. If you haven't already it is recommended that you first read the official Julia manual on [Date and DateTime](https://docs.julialang.org/en/stable/manual/dates/#Constructors-1). The `TimeZones` package adds `z` and `Z` to the list of available [parsing character codes](https://docs.julialang.org/en/stable/stdlib/dates/#Base.Dates.DateFormat):

| Code | Matches | Comment |
|:-----|:---------------------|:-------------------------------------------------|
Expand All @@ -21,31 +21,31 @@ julia> astimezone(zdt, tz"Asia/Tokyo")

Note that with the exception of "UTC" and "GMT" time zone abbrevations cannot be parsed using the `Z` character code since most abbreviations are ambiguous. For example abbreviation "MST" could be interpreted as "Mountain Standard Time" (UTC-7) or "Moscow Summer Time" (UTC+3:31).

Parsing a `ZonedDateTime` just requires the text to parse and a format string:
Parsing a `Localized` just requires the text to parse and a format string:

```julia
julia> ZonedDateTime("20150101-0700", "yyyymmddzzzz")
julia> Localized("20150101-0700", "yyyymmddzzzz")
2015-01-01T00:00:00-07:00

julia> ZonedDateTime("2015-08-06T22:25:31+07:00", "yyyy-mm-ddTHH:MM:SSzzzz")
julia> Localized("2015-08-06T22:25:31+07:00", "yyyy-mm-ddTHH:MM:SSzzzz")
2015-08-06T22:25:31+07:00
```

When parsing several `ZonedDateTime` strings which use the same format you will see better performance if you first create a `Dates.DateFormat` instead of passing in a raw format string.
When parsing several `Localized` strings which use the same format you will see better performance if you first create a `Dates.DateFormat` instead of passing in a raw format string.

```julia
julia> df = Dates.DateFormat("yy-mm-ddz");

julia> ZonedDateTime("2015-03-29+01:00", df)
julia> Localized("2015-03-29+01:00", df)
2015-03-29T00:00:00+01:00

julia> ZonedDateTime("2015-03-30+02:00", df)
julia> Localized("2015-03-30+02:00", df)
2015-03-30T00:00:00+02:00
```

## Formatting strings

Formatting a `ZonedDateTime` as a string also extends the functionality provided by `Base.Dates`. The `TimeZones` package adds the new formatting character codes `z` and `Z` to the list of available [formatting character codes](https://docs.julialang.org/en/stable/stdlib/dates/#Base.Dates.format-Tuple{Base.Dates.TimeType,AbstractString}):
Formatting a `Localized` as a string also extends the functionality provided by `Base.Dates`. The `TimeZones` package adds the new formatting character codes `z` and `Z` to the list of available [formatting character codes](https://docs.julialang.org/en/stable/stdlib/dates/#Base.Dates.format-Tuple{Base.Dates.TimeType,AbstractString}):

| Code | Examples | Comment |
|:-----|:---------------------|:-------------------------------------------------|
Expand All @@ -54,15 +54,15 @@ Formatting a `ZonedDateTime` as a string also extends the functionality provided

It is recommended that you prefer the use of the `z` character code over `Z` time zone abbreviations can be interpreted in different ways.

Formatting uses the `Dates.format` function with a `ZonedDateTime` and a format string:
Formatting uses the `Dates.format` function with a `Localized` and a format string:

```julia
julia> zdt = ZonedDateTime(2015,8,6,22,25,tz"Europe/Warsaw")
julia> ldt = Localized(2015,8,6,22,25,tz"Europe/Warsaw")
2015-08-06T22:25:00+02:00

julia> Dates.format(zdt, "yyyymmddzzzz")
julia> Dates.format(ldt, "yyyymmddzzzz")
"20150806+02:00"

julia> Dates.format(zdt, "yyyy-mm-dd HH:MM ZZZ")
julia> Dates.format(ldt, "yyyy-mm-dd HH:MM ZZZ")
"2015-08-06 22:25 CEST"
```
12 changes: 6 additions & 6 deletions docs/current.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
## Current Time / System Time Zone

Julia provides the `now()` method to retrieve your current system's time as a `DateTime`. The TimeZones.jl package provides an additional `now(::TimeZone)` method providing the current time as a `ZonedDateTime`:
Julia provides the `now()` method to retrieve your current system's time as a `DateTime`. The TimeZones.jl package provides an additional `now(::TimeZone)` method providing the current time as a `Localized`:

```julia
now(tz"Europe/Warsaw")
Expand All @@ -27,22 +27,22 @@ julia> today(tz"Pacific/Midway"), today(tz"Pacific/Apia")
(2018-01-29, 2018-01-30)
```

You should be careful not to use `today()` when working with `ZonedDateTime`s as you may end up using the wrong day. For example:
You should be careful not to use `today()` when working with `Localized`s as you may end up using the wrong day. For example:

```julia
julia> midway, apia = tz"Pacific/Midway", tz"Pacific/Apia"
(Pacific/Midway (UTC-11), Pacific/Apia (UTC+13/UTC+14))

julia> ZonedDateTime(today() + Time(11), midway)
julia> Localized(today() + Time(11), midway)
2018-01-29T11:00:00-11:00

julia> ZonedDateTime(today() + Time(11), apia) # Should be 2018-01-30
julia> Localized(today() + Time(11), apia) # Should be 2018-01-30
2018-01-29T11:00:00+14:00

julia> ZonedDateTime(today(midway) + Time(11), midway)
julia> Localized(today(midway) + Time(11), midway)
2018-01-29T11:00:00-11:00

julia> ZonedDateTime(today(apia) + Time(11), apia)
julia> Localized(today(apia) + Time(11), apia)
2018-01-30T11:00:00+14:00
```

Expand Down
12 changes: 6 additions & 6 deletions docs/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ extract(active_archive(), TimeZones.TZ_SOURCE_DIR, "etcetera")
compile()
```

## Far-future ZonedDateTime with VariableTimeZone
## Far-future Localized with VariableTimeZone

Due to the internal representation of a `VariableTimeZone` it is infeasible to determine a time zones transitions to infinity. Since [2038-01-19T03:14:07](https://en.wikipedia.org/wiki/Year_2038_problem) is the last `DateTime` that can be represented by an `Int32` (`Dates.unix2datetime(typemax(Int32))`) it was decided that 2037 would be the last year in which all transition dates are computed. If additional transitions are known to exist after the last transition then a cutoff date is specified.

Expand All @@ -27,18 +27,18 @@ julia> last(warsaw.transitions)
julia> warsaw.cutoff # DateTime up until the last transition is effective
Nullable{DateTime}(2038-03-28T01:00:00)

julia> ZonedDateTime(DateTime(2039), warsaw)
julia> Localized(DateTime(2039), warsaw)
ERROR: TimeZone Europe/Warsaw does not handle dates on or after 2038-03-28T01:00:00 UTC
```

It is important to note that since we are taking about future time zone transitions and the rules dictating these transitions are subject to change and may not be accurate. If you still want to work with future `ZonedDateTime` past the default cutoff you can re-compile the `TimeZone` objects and specify the `max_year` keyword:
It is important to note that since we are taking about future time zone transitions and the rules dictating these transitions are subject to change and may not be accurate. If you still want to work with future `Localized` past the default cutoff you can re-compile the `TimeZone` objects and specify the `max_year` keyword:

```julia
julia> using TimeZones

julia> TimeZones.TZData.compile(max_year=2200)

julia> ZonedDateTime(DateTime(2100), tz"Europe/Warsaw")
julia> Localized(DateTime(2100), tz"Europe/Warsaw")
2100-01-01T00:00:00+01:00
```

Expand All @@ -47,13 +47,13 @@ Warning: since the `tz` string macro loads the `TimeZone` at compile time the ti
```julia
julia> begin
TimeZones.TZData.compile(max_year=2210)
ZonedDateTime(DateTime(2205), tz"Europe/Warsaw")
Localized(DateTime(2205), tz"Europe/Warsaw")
end
ERROR: UnhandledTimeError: TimeZone Europe/Warsaw does not handle dates on or after 2038-03-28T01:00:00 UTC

julia> begin
TimeZones.TZData.compile(max_year=2220)
ZonedDateTime(DateTime(2215), TimeZone("Europe/Warsaw"))
Localized(DateTime(2215), TimeZone("Europe/Warsaw"))
end
2215-01-01T00:00:00+01:00
```
32 changes: 16 additions & 16 deletions docs/rounding.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
## Rounding a ZonedDateTime
## Rounding a Localized

Rounding operations (`floor`, `ceil`, and `round`) on `ZonedDateTime`s are performed in a
Rounding operations (`floor`, `ceil`, and `round`) on `Localized`s are performed in a
[similar manner to `DateTime`](https://docs.julialang.org/en/stable/manual/dates/#Rounding-1)
and should generally behave as expected. When `VariableTimeZone` transitions are involved,
however, unexpected behaviour may be encountered.

Instead of performing rounding operations on a UTC representation of the `ZonedDateTime`,
Instead of performing rounding operations on a UTC representation of the `Localized`,
which would in some cases be computationally less expensive, rounding is done in the local
time zone. This ensures that rounding behaves as expected and is maximally meaningful.

Expand All @@ -18,16 +18,16 @@ wouldn't be on the hour in the local time zone.

When the target resolution is a `TimePeriod` the likelihood of encountering an ambiguous or
non-existent time (due to daylight saving time transitions) is increased. To resolve this
issue, rounding a `ZonedDateTime` with a `VariableTimeZone` to a `TimePeriod` uses the
`DateTime` value in the appropriate `FixedTimeZone`, then reconverts it to a `ZonedDateTime`
issue, rounding a `Localized` with a `VariableTimeZone` to a `TimePeriod` uses the
`DateTime` value in the appropriate `FixedTimeZone`, then reconverts it to a `Localized`
in the appropriate `VariableTimeZone` afterward. (See [Examples](#examples) below.)

### Rounding to a DatePeriod

When the target resolution is a `DatePeriod` rounding is done in the local time zone in a
straightforward fashion.

Rounding is not an entirely "safe" operation for `ZonedDateTime`s, as in some cases
Rounding is not an entirely "safe" operation for `Localized`s, as in some cases
historical transitions for some time zones (`Asia/Colombo`, for example) occur at midnight.
In such cases rounding to a `DatePeriod` may still result in an `AmbiguousTimeError` or a
`NonExistentTimeError`s. (But such occurrences should be relatively rare.)
Expand All @@ -40,38 +40,38 @@ The `America/Winnipeg` time zone transitioned from Central Standard Time (UTC-6:
Central Daylight Time (UTC-5:00) on 2016-03-13, moving directly from 01:59:59 to 03:00:00.

```julia
julia> zdt = ZonedDateTime(2016, 3, 13, 1, 45, tz"America/Winnipeg")
julia> ldt = Localized(2016, 3, 13, 1, 45, tz"America/Winnipeg")
2016-03-13T01:45:00-06:00

julia> floor(zdt, Dates.Day)
julia> floor(ldt, Dates.Day)
2016-03-13T00:00:00-06:00

julia> ceil(zdt, Dates.Day)
julia> ceil(ldt, Dates.Day)
2016-03-14T00:00:00-05:00

julia> round(zdt, Dates.Day)
julia> round(ldt, Dates.Day)
2016-03-13T00:00:00-06:00

julia> floor(zdt, Dates.Hour)
julia> floor(ldt, Dates.Hour)
2016-03-13T01:00:00-06:00

julia> ceil(zdt, Dates.Hour)
julia> ceil(ldt, Dates.Hour)
2016-03-13T03:00:00-05:00

julia> round(zdt, Dates.Hour)
julia> round(ldt, Dates.Hour)
2016-03-13T03:00:00-05:00
```

The `Asia/Colombo` time zone revised the definition of Lanka Time from UTC+6:30 to UTC+6:00
on 1996-10-26, moving from 00:29:59 back to 00:00:00.

```julia
julia> zdt = ZonedDateTime(1996, 10, 25, 23, 45, tz"Asia/Colombo")
julia> ldt = Localized(1996, 10, 25, 23, 45, tz"Asia/Colombo")
1996-10-25T23:45:00+06:30

julia> round(zdt, Dates.Hour)
julia> round(ldt, Dates.Hour)
1996-10-26T00:00:00+06:30

julia> round(zdt, Dates.Day)
julia> round(ldt, Dates.Day)
ERROR: Local DateTime 1996-10-26T00:00:00 is ambiguous
```
Loading