Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
e6095a2
Made tzcompile.jl compatible with Julia v0.4.
Jul 6, 2015
a5e626e
Removed trailing whitespace.
Jul 20, 2015
2715f33
Made use of joinpath.
Jul 20, 2015
1c26259
Simplified initial pass in tzparse.
Jul 20, 2015
d8885c4
Minor changes to improve clarity.
Jul 20, 2015
b27d89a
Until in zone is only split when needed.
Jul 20, 2015
2edb0a1
Revised parsedate function.
Jul 20, 2015
f5b64e2
Refactored parts of zoneparse.
Jul 24, 2015
fc4e591
Renamed src files to match module names.
Jul 27, 2015
9bc5dd4
Moved TZCompile.jl to timezones/Olsen.jl
Jul 27, 2015
191d0c4
Made TimeZone types and update Olsen parser.
Jul 27, 2015
3d088bf
Created ZonedDateTime type.
Jul 28, 2015
f09ab2f
Changed offset precision to seconds.
Jul 28, 2015
83c890a
Created local DateTime constructor for ZonedDateTime.
Jul 28, 2015
15a8f26
Fixed error with parsing zones.
Jul 28, 2015
51a2426
Refactored ZonedDateTime constructor.
Jul 28, 2015
b02b2cb
Broke up FixedTimeZone into two types.
Jul 28, 2015
2e89848
Added alternative ZonedDateTime constructor.
Jul 29, 2015
c1b86f8
Created initial testcases.
Jul 29, 2015
cc2dcd9
Improved TZ database parser initial pass error handling.
Jul 30, 2015
cc22cff
Major refactoring to the TZ database parser.
Jul 30, 2015
c81ff37
Fixed problems encoutered with negative Time.
Jul 31, 2015
b65d854
Switched Time to use seconds internally.
Jul 31, 2015
d44ccd8
Added testcases for Olsen.parsedate.
Jul 31, 2015
8daa60a
Simplified processing of Rule's at param.
Jul 31, 2015
53c74e0
Attempting to get accurate timezone parsing.
Jul 31, 2015
7321fbb
Corrected issues noticedd with resolving timezones.
Aug 3, 2015
619d10d
Reduced iterations by ordering rules.
Aug 4, 2015
117c969
Refactoring resolve to be as clear as possible.
Aug 4, 2015
7b7bb34
Update Time type to work comparing to Period types.
Aug 4, 2015
0368e9b
Made Time type a subtype of TimeType.
Aug 5, 2015
03de3b5
Updated parsing to deal with extremes of offsets/save.
Aug 5, 2015
8cac598
Ensured that Zone array is sorted during resolve.
Aug 5, 2015
fc8655b
Minor refactoring and comments for resolve.
Aug 5, 2015
874e8b1
Changed flag to type Char and silenced debugging messages.
Aug 5, 2015
8417f05
Various minor corrections.
Aug 5, 2015
7b6becc
Added function for loading all timezones.
Aug 5, 2015
3975635
Updated MINOFFSET.
Aug 5, 2015
e48f6c2
Build now serializes TimeZone objects.
Aug 5, 2015
8032710
Created function that lists timezone names.
Aug 5, 2015
77cf9aa
Refactoring build.jl to be more user friendly.
Aug 5, 2015
486d2f0
Added ZonedDateTime testcases and moved transition tests.
Aug 6, 2015
7cbd5db
Fixed undefined variable in error message.
Aug 6, 2015
e36d117
Added tests for obscure transition behaviours.
Aug 6, 2015
e1e78c8
Testcase for skipping an entire day.
Aug 6, 2015
0626077
Created tests for behaviours with strange timezones.
Aug 7, 2015
c06705d
Added support for TZ database Links.
Aug 7, 2015
75f9d86
Revised build process to handle download errors better.
Aug 7, 2015
d66aa99
Created ZonedDateTime constructor for switching a TimeZone.
Aug 7, 2015
11611aa
Added equality comparisons for ZonedDateTimes.
Aug 7, 2015
a17abc5
Implemented arithmetic for ZonedDateTime.
Aug 10, 2015
1102c15
Refactoring including switching FixedTimeZone to a concrete type.
Aug 10, 2015
2df4e98
Parsing TZ data can now return FixedTimeZone.
Aug 10, 2015
557b52b
Updated .gitignore file.
Aug 10, 2015
ae4b876
Added testcases that showcase ZonedDatetime associativity.
Aug 10, 2015
d0cbeac
Made FixedTimeZone constructor for strings.
Aug 11, 2015
94c0dc2
Implemented ZonedDateTime parsing.
Aug 11, 2015
e7e1b04
Updated DateFormat to dynamically lookup ids.
Aug 11, 2015
024ded2
Updated Travis file and added badges.
Aug 11, 2015
f84c4e4
Removed Compat import as we require 0.4 Base.Dates.
Aug 11, 2015
ddc6e87
Updated build to retry download upon failure.
Aug 11, 2015
568ae3f
Refactored TZ data and the compiled directory into constants.
Aug 11, 2015
3ee97be
Added additional testcases to increase code coverage.
Aug 11, 2015
ab24474
Added testcases for timezone_names.
Aug 11, 2015
4ca282b
Removed Transition from export list.
Aug 11, 2015
b98e8eb
Added testcase coverage for negative arithmetic.
Aug 11, 2015
8eb178f
Fixed bug with from_utc keyword.
Aug 12, 2015
892bc01
Moved Time type to separate file so that types could access extremes.
Aug 12, 2015
4a1c3b4
Improved testcase coverage.
Aug 12, 2015
ee90f37
Updated README.md to include basic usage.
Aug 12, 2015
5920d4e
Created trunc methods.
Aug 12, 2015
3dc2d26
Added firstdayof*/lastdayof* adjusters.
Aug 13, 2015
6ae92c5
Added several accessors.
Aug 13, 2015
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
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
# Ignore system files
*.swp
.DS_Store

# Ignore dependencies that are build/retrieved
deps/tzdata
deps/compiled
26 changes: 11 additions & 15 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
language: cpp
compiler:
- clang
language: julia
sudo: false
os:
- osx
- linux
julia:
- nightly
notifications:
email: false
env:
matrix:
- JULIAVERSION="julianightlies"
before_install:
- sudo add-apt-repository ppa:staticfloat/julia-deps -y
- sudo add-apt-repository ppa:staticfloat/${JULIAVERSION} -y
- sudo apt-get update -qq -y
- sudo apt-get install libpcre3-dev julia -y
script:
- julia -e 'Pkg.init(); Pkg.clone(pwd()); Pkg.test("Timezones")'
- julia -e 'using Timezones; @assert isdefined(:Timezones); @assert typeof(Timezones) === Module'
- if [ $JULIAVERSION = "julianightlies" ]; then julia --code-coverage test/runtests.jl; fi
- if [[ -a .git/shallow ]]; then git fetch --unshallow; fi
- julia -e 'Pkg.clone(pwd()); Pkg.build("TimeZones"); Pkg.test("TimeZones"; coverage=true)';
after_success:
- if [ $JULIAVERSION = "julianightlies" ]; then julia -e 'cd(Pkg.dir("Timezones")); Pkg.add("Coverage"); using Coverage; Coveralls.submit(Coveralls.process_folder())'; fi
- julia -e 'cd(Pkg.dir("TimeZones")); Pkg.add("Coverage"); using Coverage; Coveralls.submit(Coveralls.process_folder())';
- julia -e 'cd(Pkg.dir("TimeZones")); Pkg.add("Coverage"); using Coverage; Codecov.submit(process_folder())'
100 changes: 99 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,102 @@
Timezones.jl
TimeZones.jl
============

[![Build Status](https://travis-ci.org/quinnj/TimeZones.jl.svg?branch=master)](https://travis-ci.org/quinnj/TimeZones.jl)
[![Coverage Status](https://coveralls.io/repos/quinnj/TimeZones.jl/badge.svg?branch=master)](https://coveralls.io/r/quinnj/TimeZones.jl?branch=master)
[![codecov.io](http://codecov.io/github/quinnj/TimeZones.jl/coverage.svg?branch=master)](http://codecov.io/github/quinnj/TimeZones.jl?branch=master)

Olsen Timezone Database Access for the Julia Programming Language

`ZonedDateTime` is *timezone-aware* (in Python parlance) of `DateTime`. All `ZonedDateTime` represented will always be in the correct zone without requiring manual normalization (required by Python's pytz module).

## Usage

To create a `ZonedDateTime` you simply pass in a `DateTime` and a `TimeZone`. Note that `using TimeZones` will generate a couple method definition overwritten warnings which can safely be ignored.

```julia
julia> using TimeZones

julia> warsaw = TimeZone("Europe/Warsaw")
Europe/Warsaw

julia> ZonedDateTime(DateTime(2014,1,1), warsaw)
2014-01-01T00:00:00+01:00
```

Working with DateTimes that occur around the "spring forward" transition can result in a `NonExistentTimeError`:

```julia
julia> ZonedDateTime(DateTime(2014,3,30,1), warsaw)
2014-03-30T01:00:00+01:00

julia> ZonedDateTime(DateTime(2014,3,30,2), warsaw)
ERROR: DateTime 2014-03-30T02:00:00 does not exist within Europe/Warsaw
in ZonedDateTime at ~/.julia/v0.4/TimeZones/src/timezones/types.jl:126
in ZonedDateTime at ~/.julia/v0.4/TimeZones/src/timezones/types.jl:119

julia> ZonedDateTime(DateTime(2014,3,30,3), warsaw)
2014-03-30T03:00:00+02:00
```

Working with DateTimes that occur around the "fall back" transition can result in a `AmbiguousTimeError`. Providing additional parameters can deal with the ambiguity:

```julia
julia> ZonedDateTime(DateTime(2014,10,26,2), warsaw)
ERROR: Local DateTime 2014-10-26T02:00:00 is ambiguious
in ZonedDateTime at ~/.julia/v0.4/TimeZones/src/timezones/types.jl:131
in ZonedDateTime at ~/.julia/v0.4/TimeZones/src/timezones/types.jl:119

julia> ZonedDateTime(DateTime(2014,10,26,2), warsaw, 1) # first occurrence of duplicate hour
2014-10-26T02:00:00+02:00

julia> ZonedDateTime(DateTime(2014,10,26,2), warsaw, 2) # second occurrence of duplicate hour
2014-10-26T02:00:00+01:00

julia> ZonedDateTime(DateTime(2014,10,26,2), warsaw, true) # in daylight saving time
2014-10-26T02:00:00+02:00

julia> ZonedDateTime(DateTime(2014,10,26,2), warsaw, false) # not in daylight saving time
2014-10-26T02:00:00+01:00
```

## TimeType-Period Arithmetic

`ZonedDateTime` use calendrical arithmetic in a [similar manner to `DateTime`](http://julia.readthedocs.org/en/latest/manual/dates/#timetype-period-arithmetic) but with some key differences. Lets look at these differences by adding a day to March 30th 2014 in Europe/Warsaw.


```julia
julia> warsaw = TimeZone("Europe/Warsaw")
Europe/Warsaw

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

julia> spring + Dates.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 more closely however you'll notice that the timezone offset changed from +01:00 to +02:00. The reason for this change is because Europe/Warsaw switched from standard time (+01:00) to daylight saving time (+02:00) causing the local date 2014-03-31T02:00:00 to be skipped effectively making the day only contain 23 hours. By adding Hours we can see the difference:

```julia
julia> spring + Dates.Hour(23)
2014-03-31T00:00:00+02:00

julia> spring + Dates.Hour(24)
2014-03-31T01:00:00+02:00
```

One potential cause of confusion regarding this behaviour is the loss in associativity. For example:

```julia
julia> (spring + Day(1)) + Hour(24)
2014-04-01T00:00:00+02:00

julia> (spring + Hour(24)) + Day(1)
2014-04-01T01:00:00+02:00

julia> spring + Hour(24) + Day(1)
2014-04-01T00:00:00+02:00
```

Take particular note of the last example which ends up merging the two periods into a single unit of 2 days.

4 changes: 1 addition & 3 deletions REQUIRE
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
julia 0.3-
Dates

julia 0.4-
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this just needs to be julia 0.4, right @IainNZ?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you'd like it to install it right now, Julia 0.4- is correct ("Julia 0.4 prerelease"). julia 0.4 means >=0.4.0. Not really the clearest thing in the world, is it :D

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, that makes sense. Thanks.

53 changes: 37 additions & 16 deletions deps/build.jl
Original file line number Diff line number Diff line change
@@ -1,24 +1,45 @@
import TimeZones: TZDATA_DIR, COMPILED_DIR
import TimeZones.Olsen: REGIONS, compile

dir = dirname(@__FILE__)
tz = joinpath(dir,"tzdata")
com = joinpath(dir,"compiled")
isdir(TZDATA_DIR) || mkdir(TZDATA_DIR)
isdir(COMPILED_DIR) || mkdir(COMPILED_DIR)

isdir(tz) || mkdir(tz)
isdir(com) || mkdir(com)
# TODO: Downloading fails regularly. Implement a retry system or file alternative
# sources.
info("Downloading TZ data")
@sync for region in REGIONS
@async begin
remote_file = "ftp://ftp.iana.org/tz/data/" * region
region_file = joinpath(TZDATA_DIR, region)
remaining = 3


for f in (tz,com)
for file in readdir(f)
rm(joinpath(f,file))
while remaining > 0
try
# Note the destination file will be overwritten upon success.
download(remote_file, region_file)
remaining = 0
catch e
if isa(e, ErrorException)
if remaining > 0
remaining -= 1
elseif isfile(region_file)
warn("Falling back to old region file $region. Unable to download: $remote_file")
else
error("Missing region file $region. Unable to download: $remote_file")
end
else
rethrow()
end
end
end
end
end

regions = ["africa","antarctica","asia","australasia",
"europe","northamerica","southamerica"]
for reg in regions
download("ftp://ftp.iana.org/tz/data/"*reg,joinpath(tz,reg))

info("Pre-processing TimeZone data")
for file in readdir(COMPILED_DIR)
rm(joinpath(COMPILED_DIR, file), recursive=true)
end
compile(TZDATA_DIR, COMPILED_DIR)

include("../src/TZCompile.jl")
TZCompile.generate_tzdata(tz,com)
println("=========Timezone Database Successfully Compiled=========")
info("Successfully processed TimeZone data")
62 changes: 62 additions & 0 deletions src/TimeZones.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
module TimeZones

using Base.Dates
import Base.Dates: days, hour, minute, second, millisecond

export TimeZone, FixedTimeZone, VariableTimeZone, ZonedDateTime,
AmbiguousTimeError, NonExistentTimeError, DateTime,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't need DateTime here

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. I did create a new method for DateTime.

# accessors.jl
hour, minute, second, millisecond,
# adjusters.jl
firstdayofweek, lastdayofweek,
firstdayofmonth, lastdayofmonth,
firstdayofyear, lastdayofyear,
firstdayofquarter, lastdayofquarter,
# Re-export from Base.Dates
yearmonthday, yearmonth, monthday, year, month, week, day, dayofmonth

const PKG_DIR = normpath(joinpath(dirname(@__FILE__), "..", "deps"))
const TZDATA_DIR = joinpath(PKG_DIR, "tzdata")
const COMPILED_DIR = joinpath(PKG_DIR, "compiled")

include("timezones/time.jl")
include("timezones/types.jl")
include("timezones/accessors.jl")
include("timezones/arithmetic.jl")
include("timezones/io.jl")
include("timezones/adjusters.jl")
include("timezones/Olsen.jl")

function TimeZone(name::String)
tz_path = joinpath(COMPILED_DIR, split(name, "/")...)

isfile(tz_path) || error("Unknown timezone $name")

open(tz_path, "r") do fp
return deserialize(fp)
end
end

function timezone_names()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe just call this timezones?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was leaning towards calling it names and not exporting it. Then the most of calls would be TimeZones.names(). Unfortunately names lists the functions exported by a module and may be confusing. Calling this function timezones could be misleading as someone may expect it to return all the TimeZone instances.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instances maybe? I also wonder if this should be a Symbol array that we return, not sure if it makes that big of a difference.

If we want to get really fancy, we could call it TimeZones.timezones() and have it return some kind of TimeZoneList type that you could getindex by numeric index or symbol/name that would deserialize the TimeZone.

names = String[]
check = Tuple{String,String}[(COMPILED_DIR, "")]

for (dir, partial) in check
for filename in readdir(dir)
startswith(filename, ".") && continue

path = joinpath(dir, filename)
name = partial == "" ? filename : join([partial, filename], "/")

if isdir(path)
push!(check, (path, name))
else
push!(names, name)
end
end
end

return sort(names)
end

end # module
26 changes: 0 additions & 26 deletions src/Timezones.jl

This file was deleted.

Loading