-
Notifications
You must be signed in to change notification settings - Fork 58
Implemented TimeZones Interface #3
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
Changes from all commits
e6095a2
a5e626e
2715f33
1c26259
d8885c4
b27d89a
2edb0a1
f5b64e2
fc4e591
9bc5dd4
191d0c4
3d088bf
f09ab2f
83c890a
15a8f26
51a2426
b02b2cb
2e89848
c1b86f8
cc2dcd9
cc22cff
c81ff37
b65d854
d44ccd8
8daa60a
53c74e0
7321fbb
619d10d
117c969
7b7bb34
0368e9b
03de3b5
8cac598
fc8655b
874e8b1
8417f05
7b6becc
3975635
e48f6c2
8032710
77cf9aa
486d2f0
7cbd5db
e36d117
e1e78c8
0626077
c06705d
75f9d86
d66aa99
11611aa
a17abc5
1102c15
2df4e98
557b52b
ae4b876
d0cbeac
94c0dc2
e7e1b04
024ded2
f84c4e4
ddc6e87
568ae3f
3ee97be
ab24474
4ca282b
b98e8eb
8eb178f
892bc01
4a1c3b4
ee90f37
5920d4e
3dc2d26
6ae92c5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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 |
| 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())' |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,102 @@ | ||
| Timezones.jl | ||
| TimeZones.jl | ||
| ============ | ||
|
|
||
| [](https://travis-ci.org/quinnj/TimeZones.jl) | ||
| [](https://coveralls.io/r/quinnj/TimeZones.jl?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. | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1 @@ | ||
| julia 0.3- | ||
| Dates | ||
|
|
||
| julia 0.4- | ||
| 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") |
| 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, | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't need
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok. I did create a new method for |
||
| # 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() | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe just call this
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was leaning towards calling it
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If we want to get really fancy, we could call it |
||
| 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 | ||
This file was deleted.
There was a problem hiding this comment.
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?There was a problem hiding this comment.
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.4means>=0.4.0. Not really the clearest thing in the world, is it :DThere was a problem hiding this comment.
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.